Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Model Context Protocol (MCP) servers for the HeroUI design system. Access HeroUI
| Package | Description | Status | Docs |
| --- | --- | --- | --- |
| `@heroui/react-mcp` | MCP server for web (`@heroui/react`) component docs, examples, and theme data | ✅ Available on npm | [README](packages/react-mcp/README.md) |
| `@heroui/native-mcp` | MCP server for React Native (`@heroui/native`) component docs and tooling | 🚧 In development | [README](packages/native-mcp/README.md) |
| `@heroui/native-mcp` | MCP server for React Native (`@heroui/native`) component docs and tooling | ✅ Available on npm | [README](packages/native-mcp/README.md) |

## Quick Start

Expand All @@ -23,7 +23,6 @@ npx -y @heroui/react-mcp@latest

**For React Native:**
```bash
# Coming soon
npx -y @heroui/native-mcp@latest
```

Expand Down
2 changes: 1 addition & 1 deletion packages/native-mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"clean": "rm -rf dist dist-api",
"prepublishOnly": "pnpm build",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .",
"release": "bumpp --execute='pnpm run changelog' --all --tag='native-mcp-v%s'",
"release": "bumpp --execute='pnpm run changelog' --all --tag='native-mcp-v%s' --commit='chore(native-mcp): release v%s'",
"release:check": "pnpm run lint && pnpm run typecheck && pnpm run build"
},
"dependencies": {
Expand Down
136 changes: 0 additions & 136 deletions packages/native-mcp/src/api/routes/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,140 +394,4 @@ components.post("/examples", async (c) => {
}
});

// Get component source code (bulk)
components.post("/source", async (c) => {
const startTime = Date.now();

initAnalytics(c.env);
const analytics = getAnalytics();

try {
const body = await c.req.json();
const componentNames = body.components as string[];

if (!componentNames || !Array.isArray(componentNames)) {
return c.json(
{
error: "Invalid request",
details: "components array is required",
},
400,
);
}

analytics?.trackMcpRequest("api-user", {
method: "POST",
toolName: "get-component-source",
requestSize: componentNames.length,
});

const service = await getDataService(c.env);
const results = await service.getComponents(componentNames);
const latestVersion = await service.getLatestVersion();

const branch = "alpha";
const baseUrl = `https://raw.githubusercontent.com/heroui-inc/heroui-native/refs/heads/${branch}`;

// Fetch source code from GitHub
const sourceResults = await Promise.all(
results.map(async (result) => {
if (!result.data) {
return {
component: result.component,
error: result.error || "Component not found",
};
}

// Construct source file path
// Assuming components are in src/components/{component-name}/{component-name}.tsx
const componentPath = result.component
.replace(/([A-Z])/g, "-$1")
.toLowerCase()
.replace(/^-/, "");
const sourceUrl = `${baseUrl}/src/components/${componentPath}/${componentPath}.tsx`;

try {
const response = await fetch(sourceUrl);

if (!response.ok) {
// Try alternate path structure
const altUrl = `${baseUrl}/src/components/${componentPath}/index.tsx`;
const altResponse = await fetch(altUrl);

if (!altResponse.ok) {
return {
component: result.component,
error: "Source code not available",
};
}

const sourceCode = await altResponse.text();

return {
component: result.component,
filePath: `src/components/${componentPath}/index.tsx`,
sourceCode,
githubUrl: altUrl
.replace("raw.githubusercontent.com", "github.com")
.replace("/refs/heads/", "/blob/"),
};
}

const sourceCode = await response.text();

return {
component: result.component,
filePath: `src/components/${componentPath}/${componentPath}.tsx`,
sourceCode,
githubUrl: sourceUrl
.replace("raw.githubusercontent.com", "github.com")
.replace("/refs/heads/", "/blob/"),
};
} catch (error) {
return {
component: result.component,
error: error instanceof Error ? error.message : "Failed to fetch source code",
};
}
}),
);

const responseTime = Date.now() - startTime;

analytics?.trackMcpSuccess("api-user", {
method: "POST",
toolName: "get-component-source",
responseTime,
responseSize: JSON.stringify(sourceResults).length,
});

// Set cache headers
c.header("Cache-Control", CACHE_CONTROL.LATEST);

return c.json({
results: sourceResults,
version: latestVersion || "unknown",
latestVersion: latestVersion || "unknown",
});
} catch (error) {
const responseTime = Date.now() - startTime;
console.error("Error getting component source:", error);

analytics?.trackMcpError("api-user", {
method: "POST",
toolName: "get-component-source",
error: error instanceof Error ? error.message : "Unknown error",
responseTime,
});

return c.json(
{
error: "Failed to get component source code",
details: error instanceof Error ? error.message : "Unknown error",
},
500,
);
}
});

export {components};
30 changes: 16 additions & 14 deletions packages/native-mcp/src/services/component-data-service-r2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,28 +276,28 @@
/**
* Get version information
*/
async getVersionInfo(): Promise<VersionInfo | null> {
async getVersionInfo(): Promise<Record<string, VersionInfo>> {
try {
const data = await this.getFromR2<VersionInfo>("native/versions.json");
const data = await this.getFromR2<Record<string, VersionInfo>>("native/versions.json");

return data || null;
return data || {};
} catch (error) {
console.error("Error getting version info:", error);

return null;
return {};
}
}

/**
* Get the latest version
* Get the latest version for a specific package
*/
async getLatestVersion(): Promise<string | null> {
async getLatestVersion(packageName: string = "heroui-native"): Promise<string | null> {
try {
const versionInfo = await this.getVersionInfo();

return versionInfo?.current || null;
return versionInfo[packageName]?.current || null;
} catch (error) {
console.error(`Error getting latest version:`, error);
console.error(`Error getting latest version for ${packageName}:`, error);

return null;
}
Expand All @@ -320,9 +320,10 @@
if (!response.Contents || response.Contents.length === 0) {
// Fallback to metadata if no version files found
const versionInfo = await this.getVersionInfo();
const nativeVersion = versionInfo["heroui-native"];

if (versionInfo && versionInfo.current) {
return [versionInfo.current, "latest"];
if (nativeVersion?.current) {
return [nativeVersion.current, "latest"];
}

return ["latest"];
Expand Down Expand Up @@ -409,15 +410,16 @@
/**
* Check version status
*/
async checkVersion(currentVersion?: string): Promise<{message: string}> {
async checkVersion(currentVersion?: string, packageName: string = "heroui-native"): Promise<{message: string}> {

Check warning on line 413 in packages/native-mcp/src/services/component-data-service-r2.ts

View workflow job for this annotation

GitHub Actions / pnpm (lint)

Replace `currentVersion?:·string,·packageName:·string·=·"heroui-native"` with `⏎····currentVersion?:·string,⏎····packageName:·string·=·"heroui-native",⏎··`

Check warning on line 413 in packages/native-mcp/src/services/component-data-service-r2.ts

View workflow job for this annotation

GitHub Actions / pnpm (lint)

Replace `currentVersion?:·string,·packageName:·string·=·"heroui-native"` with `⏎····currentVersion?:·string,⏎····packageName:·string·=·"heroui-native",⏎··`
try {
const versionInfo = await this.getVersionInfo();
const packageVersion = versionInfo[packageName];

if (!versionInfo) {
return {message: `Unable to get version information`};
if (!packageVersion) {
return {message: `Unable to get version information for ${packageName}`};
}

const latestVersion = versionInfo.current;
const latestVersion = packageVersion.current;

if (!currentVersion) {
return {message: `Latest version: ${latestVersion}`};
Expand Down
27 changes: 21 additions & 6 deletions packages/native-mcp/src/services/theme-service-r2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,29 @@ export class ThemeServiceR2 {
}

/**
* Get the latest version
* Get the latest version from R2 versions.json
*/
async getLatestVersion(): Promise<string | null> {
const themeSystem = await this.getThemeSystem();
if (!themeSystem) {
async getLatestVersion(packageName: string = "heroui-native-theme"): Promise<string | null> {
try {
const response = await this.client.send(
new GetObjectCommand({
Bucket: this.bucketName,
Key: "native/versions.json",
}),
);

if (response.Body) {
const bodyString = await response.Body.transformToString();
const versionInfo = JSON.parse(bodyString);

return versionInfo?.[packageName]?.current || null;
}

return null;
}
} catch (error) {
console.error(`Error fetching latest version for ${packageName}:`, error);

return themeSystem.version;
return null;
}
}
}
2 changes: 1 addition & 1 deletion packages/react-mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"clean": "rm -rf dist",
"prepublishOnly": "pnpm build",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s --commit-path .",
"release": "bumpp --execute='pnpm run changelog' --all --tag='react-mcp-v%s'",
"release": "bumpp --execute='pnpm run changelog' --all --tag='react-mcp-v%s' --commit='chore(react-mcp): release v%s'",
"release:check": "pnpm run lint && pnpm run typecheck && pnpm run build"
},
"dependencies": {
Expand Down
Loading