Skip to content

Commit 106995f

Browse files
committed
feat(pluggable-widgets-mcp): wip
1 parent f878d6c commit 106995f

11 files changed

Lines changed: 518 additions & 32 deletions

File tree

packages/pluggable-widgets-mcp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
},
3030
"devDependencies": {
3131
"@types/cors": "^2.8.19",
32-
"@types/express": "^5.0.2",
32+
"@types/express": "^5.0.6",
3333
"@types/node": "^24.10.1",
3434
"tsc-alias": "^1.8.16",
3535
"typescript": "^5.9.3"

packages/pluggable-widgets-mcp/src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,11 @@ const __dirname = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url))
2121
export const PACKAGE_ROOT = join(__dirname, "../");
2222
export const GENERATIONS_DIR = join(PACKAGE_ROOT, "generations");
2323

24+
// Path to docs/requirements (relative to monorepo root)
25+
export const DOCS_DIR = join(PACKAGE_ROOT, "../../docs/requirements");
26+
27+
// Allowed file extensions for widget file operations
28+
export const ALLOWED_EXTENSIONS = [".tsx", ".ts", ".xml", ".scss", ".css", ".json", ".md", ".editorConfig.ts"];
29+
2430
// Timeouts
2531
export const SCAFFOLD_TIMEOUT_MS = 300000; // 5 minutes

packages/pluggable-widgets-mcp/src/index.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,15 @@ type TransportMode = "http" | "stdio";
55

66
const mode = (process.argv[2] as TransportMode) || "http";
77

8-
async function main(): Promise<void> {
9-
switch (mode) {
10-
case "stdio":
11-
await startStdioServer();
12-
break;
13-
case "http":
14-
default:
15-
await startHttpServer();
16-
break;
17-
}
8+
switch (mode) {
9+
case "stdio":
10+
startStdioServer().catch(err => {
11+
console.error("Fatal error:", err);
12+
process.exit(1);
13+
});
14+
break;
15+
case "http":
16+
default:
17+
startHttpServer();
18+
break;
1819
}
19-
20-
main().catch(err => {
21-
console.error("Fatal error:", err);
22-
process.exit(1);
23-
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { readFile } from "node:fs/promises";
2+
import { join } from "node:path";
3+
import { DOCS_DIR } from "@/config";
4+
5+
/**
6+
* Definition for a guideline resource.
7+
*/
8+
export interface GuidelineResource {
9+
/** Unique resource name */
10+
name: string;
11+
/** Resource URI (e.g., mendix://guidelines/frontend) */
12+
uri: string;
13+
/** Human-readable title */
14+
title: string;
15+
/** Description of what this guideline covers */
16+
description: string;
17+
/** Source markdown file name */
18+
filename: string;
19+
}
20+
21+
/**
22+
* All available guideline resources.
23+
*/
24+
export const GUIDELINE_RESOURCES: GuidelineResource[] = [
25+
{
26+
name: "frontend-guidelines",
27+
uri: "mendix://guidelines/frontend",
28+
title: "Frontend Guidelines",
29+
description: "CSS/SCSS styling, naming conventions, component best practices, and Atlas UI integration",
30+
filename: "frontend-guidelines.md"
31+
},
32+
{
33+
name: "implementation-plan",
34+
uri: "mendix://guidelines/implementation",
35+
title: "Implementation Plan",
36+
description: "Step-by-step guide for creating new widgets, including PR templates and testing requirements",
37+
filename: "implementation-plan.md"
38+
},
39+
{
40+
name: "app-flow",
41+
uri: "mendix://guidelines/app-flow",
42+
title: "Application Flow",
43+
description: "Complete widget development lifecycle from scaffolding to Studio Pro integration",
44+
filename: "app-flow.md"
45+
},
46+
{
47+
name: "backend-structure",
48+
uri: "mendix://guidelines/backend-structure",
49+
title: "Backend Structure",
50+
description:
51+
"Widget-to-Mendix runtime integration, data handling with EditableValue/ActionValue, and event management",
52+
filename: "backend-structure.md"
53+
},
54+
{
55+
name: "tech-stack",
56+
uri: "mendix://guidelines/tech-stack",
57+
title: "Technology Stack",
58+
description: "Core technologies (TypeScript, React, SCSS), monorepo structure, and development tools",
59+
filename: "tech-stack.md"
60+
}
61+
];
62+
63+
/**
64+
* Cache for loaded guideline content to avoid repeated file reads.
65+
*/
66+
const guidelineCache = new Map<string, string>();
67+
68+
/**
69+
* Loads the content of a guideline file.
70+
* Caches content after first load for performance.
71+
*
72+
* @param filename - The markdown filename to load
73+
* @returns The file content as a string
74+
*/
75+
export async function loadGuidelineContent(filename: string): Promise<string> {
76+
// Check cache first
77+
if (guidelineCache.has(filename)) {
78+
return guidelineCache.get(filename)!;
79+
}
80+
81+
const filePath = join(DOCS_DIR, filename);
82+
83+
try {
84+
const content = await readFile(filePath, "utf-8");
85+
guidelineCache.set(filename, content);
86+
return content;
87+
} catch (error) {
88+
const message = error instanceof Error ? error.message : String(error);
89+
throw new Error(`Failed to load guideline ${filename}: ${message}`);
90+
}
91+
}
92+
93+
/**
94+
* Clears the guideline cache. Useful for testing or hot-reloading.
95+
*/
96+
export function clearGuidelineCache(): void {
97+
guidelineCache.clear();
98+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import { GUIDELINE_RESOURCES, loadGuidelineContent } from "./guidelines";
3+
4+
/**
5+
* Registers all MCP resources with the server.
6+
*
7+
* Resources are read-only data sources that clients can fetch on-demand.
8+
* We expose the Mendix widget development guidelines as resources so LLMs
9+
* can access them when implementing widget functionality.
10+
*/
11+
export function registerResources(server: McpServer): void {
12+
registerGuidelineResources(server);
13+
}
14+
15+
/**
16+
* Registers guideline documentation as MCP resources.
17+
*/
18+
function registerGuidelineResources(server: McpServer): void {
19+
for (const resource of GUIDELINE_RESOURCES) {
20+
server.registerResource(
21+
resource.name,
22+
resource.uri,
23+
{
24+
title: resource.title,
25+
description: resource.description,
26+
mimeType: "text/markdown"
27+
},
28+
async uri => {
29+
const content = await loadGuidelineContent(resource.filename);
30+
return {
31+
contents: [
32+
{
33+
uri: uri.href,
34+
mimeType: "text/markdown",
35+
text: content
36+
}
37+
]
38+
};
39+
}
40+
);
41+
}
42+
43+
console.error(`[resources] Registered ${GUIDELINE_RESOURCES.length} guideline resources`);
44+
}

packages/pluggable-widgets-mcp/src/server/http.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,22 @@ import { sessionManager } from "./session";
88
* Starts the MCP server with HTTP/Streamable transport.
99
* Supports multiple concurrent sessions via Express.
1010
*/
11-
export async function startHttpServer(): Promise<void> {
11+
export function startHttpServer(): void {
1212
const app = createMcpExpressApp();
1313
app.use(cors());
1414

1515
setupRoutes(app);
1616

17-
app.listen(PORT, () => {
17+
const server = app.listen(PORT, () => {
1818
console.log(`[HTTP] MCP Server started on port ${PORT}`);
1919
console.log(`[HTTP] Health check: http://localhost:${PORT}/health`);
2020
console.log(`[HTTP] MCP endpoint: http://localhost:${PORT}/mcp`);
2121
});
2222

23-
setupGracefulShutdown();
24-
}
25-
26-
function setupGracefulShutdown(): void {
2723
const shutdown = async (): Promise<void> => {
2824
console.log("\n[HTTP] Shutting down server...");
2925
await sessionManager.closeAll();
30-
process.exit(0);
26+
server.close(() => process.exit(0));
3127
};
3228

3329
process.on("SIGINT", shutdown);

packages/pluggable-widgets-mcp/src/server/server.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { SERVER_ICON, SERVER_INSTRUCTIONS, SERVER_NAME, SERVER_VERSION, SERVER_WEBSITE_URL } from "@/config";
2+
import { registerResources } from "@/resources";
23
import { getAllTools } from "@/tools";
34
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
45

56
/**
6-
* Creates and configures a new MCP server instance with all registered tools.
7+
* Creates and configures a new MCP server instance with all registered tools and resources.
78
*/
89
export function createMcpServer(): McpServer {
910
const server = new McpServer(
@@ -25,6 +26,7 @@ export function createMcpServer(): McpServer {
2526
);
2627

2728
registerTools(server);
29+
registerResources(server);
2830

2931
return server;
3032
}

0 commit comments

Comments
 (0)