|
1 | | -# Pluggable Widgets MCP Server - AI Agent Guide |
| 1 | +# Pluggable Widgets MCP Server |
2 | 2 |
|
3 | | -This document provides context for AI development assistants working on the MCP (Model Context Protocol) server for Mendix pluggable widgets. |
| 3 | +MCP server enabling AI assistants to scaffold and manage Mendix pluggable widgets via STDIO (default) or HTTP transport. |
4 | 4 |
|
5 | | -## Overview |
| 5 | +## Quick Reference |
6 | 6 |
|
7 | | -This package implements an MCP server that enables AI assistants to scaffold and manage Mendix pluggable widgets programmatically. It supports both HTTP and STDIO transports for flexible integration with various MCP clients. |
8 | | - |
9 | | -### Key Characteristics |
10 | | - |
11 | | -- **MCP SDK**: Built on `@modelcontextprotocol/sdk` for standardized AI tool integration |
12 | | -- **Dual Transport**: HTTP (Express) for web clients, STDIO for CLI clients (Claude Desktop, etc.) |
13 | | -- **TypeScript**: Fully typed with Zod schemas for runtime validation |
14 | | -- **Widget Generator**: Wraps `@mendix/generator-widget` via PTY for interactive scaffolding |
| 7 | +```bash |
| 8 | +pnpm dev # Development with hot reload |
| 9 | +pnpm build # TypeScript compilation + path alias resolution |
| 10 | +pnpm start # Build and run (STDIO mode, default) |
| 11 | +pnpm start:http # Build and run (HTTP mode, port 3100) |
| 12 | +pnpm lint # ESLint check |
| 13 | +``` |
15 | 14 |
|
16 | 15 | ## Project Structure |
17 | 16 |
|
18 | 17 | ``` |
19 | 18 | src/ |
20 | | -├── index.ts # Entry point - transport mode selection |
21 | | -├── config.ts # Server configuration and constants |
22 | | -├── security/ |
23 | | -│ ├── guardrails.ts # Security validation (path traversal, extension whitelist) |
24 | | -│ └── index.ts # Security module exports |
25 | | -├── server/ |
26 | | -│ ├── server.ts # MCP server factory and tool/resource registration |
27 | | -│ ├── http.ts # HTTP transport setup (Express) |
28 | | -│ ├── stdio.ts # STDIO transport setup |
29 | | -│ ├── routes.ts # Express route handlers |
30 | | -│ └── session.ts # HTTP session management |
31 | | -├── resources/ |
32 | | -│ ├── index.ts # Resource registration |
33 | | -│ └── guidelines.ts # Widget development guidelines |
34 | | -└── tools/ |
35 | | - ├── index.ts # Tool registration aggregation |
36 | | - ├── types.ts # MCP tool type definitions |
37 | | - ├── scaffolding.tools.ts # Widget creation (create-widget) |
38 | | - ├── file-operations.tools.ts # File read/write/list operations |
39 | | - ├── build.tools.ts # Widget building and validation |
40 | | - └── utils/ |
41 | | - ├── generator.ts # Widget generator PTY wrapper |
42 | | - ├── progress-tracker.ts # Progress/logging helper |
43 | | - ├── notifications.ts # MCP notification utilities |
44 | | - └── response.ts # Tool response helpers |
45 | | -``` |
46 | | - |
47 | | -## Architecture |
48 | | - |
49 | | -### Transport Layer |
50 | | - |
51 | | -The server supports two transport modes selected via CLI argument: |
52 | | - |
53 | | -- **STDIO** (default): Single-session stdin/stdout for CLI integration (Claude Code, Claude Desktop) |
54 | | -- **HTTP**: Multi-session Express server on port 3100 for web clients and testing |
55 | | - |
56 | | -### Tool Registration |
57 | | - |
58 | | -Tools are registered directly with the MCP server using the SDK's `server.tool()` method. The current architecture uses category-based registration functions: |
59 | | - |
60 | | -```typescript |
61 | | -// src/tools/index.ts |
62 | | -export function registerAllTools(server: McpServer): void { |
63 | | - registerScaffoldingTools(server); // Widget creation |
64 | | - registerFileOperationTools(server); // File operations |
65 | | - registerBuildTools(server); // Building & validation |
66 | | -} |
67 | | -``` |
68 | | - |
69 | | -**Available Tools**: |
70 | | - |
71 | | -- **Scaffolding**: `create-widget` - Scaffolds new widgets via PTY interaction |
72 | | -- **File Operations**: |
73 | | - - `list-widget-files` - Lists files in widget directory |
74 | | - - `read-widget-file` - Reads widget file contents |
75 | | - - `write-widget-file` - Writes single file |
76 | | - - `batch-write-widget-files` - Writes multiple files atomically |
77 | | -- **Build**: `build-widget` - Compiles widget and parses errors (TypeScript, XML, dependencies) |
78 | | - |
79 | | -### Resources |
80 | | - |
81 | | -MCP resources provide read-only documentation that clients can fetch on-demand: |
82 | | - |
83 | | -```typescript |
84 | | -// src/resources/index.ts |
85 | | -export function registerResources(server: McpServer): void { |
86 | | - registerGuidelineResources(server); // Widget development guidelines |
87 | | -} |
88 | | -``` |
89 | | - |
90 | | -Resources are loaded from `docs/` directory and exposed via URIs like `resource://guidelines/property-types`. |
91 | | - |
92 | | -### Widget Generator Integration |
93 | | - |
94 | | -The `create-widget` tool uses `node-pty` to interact with the Mendix widget generator CLI. Key implementation details: |
95 | | - |
96 | | -- **PTY Simulation**: Required because the generator uses interactive prompts |
97 | | -- **Prompt Detection**: Matches expected prompts in terminal output |
98 | | -- **Answer Automation**: Sends pre-configured answers based on user input |
99 | | -- **Progress Tracking**: Reports progress via MCP notifications |
100 | | - |
101 | | -## Development Commands |
102 | | - |
103 | | -```bash |
104 | | -pnpm dev # Development mode with hot reload (tsx watch) |
105 | | -pnpm build # TypeScript compilation + path alias resolution (preserves shebang) |
106 | | -pnpm start # Build and run (HTTP mode on port 3100) |
107 | | -pnpm start:stdio # Build and run (STDIO mode) |
108 | | -pnpm lint # ESLint check |
| 19 | +├── index.ts # Entry point - transport mode selection |
| 20 | +├── config.ts # Server configuration and constants |
| 21 | +├── security/ # Path traversal & extension validation |
| 22 | +├── server/ # HTTP and STDIO transport setup |
| 23 | +├── resources/ # MCP resources (guidelines) |
| 24 | +├── generators/ # XML and TSX code generators |
| 25 | +└── tools/ # MCP tool implementations |
109 | 26 | ``` |
110 | 27 |
|
111 | | -## Adding New Tools |
| 28 | +## Adding Tools |
112 | 29 |
|
113 | | -1. **Create tool file**: `src/tools/my-feature.tools.ts` |
| 30 | +1. Create `src/tools/my-feature.tools.ts`: |
114 | 31 |
|
115 | 32 | ```typescript |
116 | 33 | import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
117 | 34 | import { z } from "zod"; |
118 | 35 |
|
119 | | -const myToolSchema = z.object({ |
120 | | - param: z.string().describe("Parameter description for LLM") |
121 | | -}); |
122 | | - |
123 | 36 | export function registerMyTools(server: McpServer): void { |
124 | 37 | server.tool( |
125 | | - "my-tool", // Tool name |
126 | | - "Description shown to LLM", // Tool description |
127 | | - myToolSchema, // Input validation schema |
128 | | - async ({ param }) => { |
129 | | - // Handler with typed args |
130 | | - // Implementation |
131 | | - return { |
132 | | - content: [ |
133 | | - { |
134 | | - type: "text", |
135 | | - text: "Success message" |
136 | | - } |
137 | | - ] |
138 | | - }; |
139 | | - } |
| 38 | + "my-tool", |
| 39 | + "Description shown to LLM", |
| 40 | + z.object({ param: z.string().describe("Parameter description") }), |
| 41 | + async ({ param }) => ({ |
| 42 | + content: [{ type: "text", text: "Success" }] |
| 43 | + }) |
140 | 44 | ); |
141 | | - |
142 | | - console.error("[my-feature] Registered 1 tool"); |
143 | 45 | } |
144 | 46 | ``` |
145 | 47 |
|
146 | | -2. **Register in index**: Update `src/tools/index.ts` |
| 48 | +2. Register in `src/tools/index.ts`: |
147 | 49 |
|
148 | 50 | ```typescript |
149 | 51 | import { registerMyTools } from "./my-feature.tools"; |
150 | 52 |
|
151 | 53 | export function registerAllTools(server: McpServer): void { |
152 | | - registerScaffoldingTools(server); |
153 | | - registerFileOperationTools(server); |
154 | | - registerBuildTools(server); |
155 | | - registerMyTools(server); // Add here |
| 54 | + // ... existing registrations |
| 55 | + registerMyTools(server); |
156 | 56 | } |
157 | 57 | ``` |
158 | 58 |
|
159 | | -## Code Conventions |
| 59 | +## Code Patterns |
160 | 60 |
|
161 | | -### Imports |
| 61 | +- **Imports**: Use `@/` path alias for absolute imports from `src/` |
| 62 | +- **Schemas**: All tool inputs require Zod schemas |
| 63 | +- **Errors**: Use `createErrorResponse()` from `@/tools/utils/response` |
| 64 | +- **Long operations**: Use `ProgressTracker` from `@/tools/utils/progress-tracker` |
162 | 65 |
|
163 | | -- Use `@/` path alias for absolute imports from `src/` |
164 | | -- Prefer specific file imports over barrel exports when dealing with circular dependencies |
165 | | -- Group imports: node builtins → external packages → internal modules |
| 66 | +## Notification Behavior (Important for AI Agents) |
166 | 67 |
|
167 | | -### Error Handling |
| 68 | +When using this MCP server, understand where different types of output appear: |
168 | 69 |
|
169 | | -- Use `createErrorResponse()` for user-facing errors |
170 | | -- Log to `console.error` (not stdout) in STDIO mode |
171 | | -- Use `ProgressTracker` for long-running operations |
| 70 | +| Output Type | Visibility | Purpose | |
| 71 | +| -------------------------- | -------------------------- | ------------------------------------------------------- | |
| 72 | +| **Tool Results** | ✅ Visible in conversation | Final outcomes, structured data, success/error messages | |
| 73 | +| **Progress Notifications** | ❌ Not in conversation | Client UI indicators only (spinners, progress bars) | |
| 74 | +| **Log Messages** | ❌ Not in conversation | Debug console/MCP Inspector only | |
172 | 75 |
|
173 | | -### Type Safety |
| 76 | +**Key Implications for AI Agents:** |
174 | 77 |
|
175 | | -- All tool inputs must have Zod schemas |
176 | | -- Tool handlers receive fully typed arguments via Zod inference |
177 | | -- Use `McpServer` methods directly for type-safe tool registration |
| 78 | +1. **Don't expect intermediate progress in chat**: Long operations (scaffolding, building) will show results only when complete. The conversation won't contain step-by-step progress updates. |
178 | 79 |
|
179 | | -## Testing |
| 80 | +2. **Tool results are authoritative**: Only tool result content appears in the conversation history. Use this for: |
180 | 81 |
|
181 | | -Use MCP Inspector for interactive testing: |
| 82 | + - Success confirmations with file paths |
| 83 | + - Structured error messages with suggestions |
| 84 | + - Any information the AI needs to continue the workflow |
182 | 85 |
|
183 | | -```bash |
184 | | -# STDIO mode |
185 | | -npx @modelcontextprotocol/inspector node dist/index.js stdio |
| 86 | +3. **Progress tracking is for humans**: `sendProgress()` and `sendLogMessage()` are for human observers using MCP Inspector or UI indicators, not for AI decision-making. |
186 | 87 |
|
187 | | -# HTTP mode |
188 | | -pnpm start |
189 | | -npx @modelcontextprotocol/inspector |
190 | | -# Connect to http://localhost:3100/mcp |
191 | | -``` |
| 88 | +4. **When debugging**: |
| 89 | + - If operations seem to "hang", check MCP Inspector's Notifications/Logs panels |
| 90 | + - Progress notifications confirm the server is working, even if the chat is quiet |
| 91 | + - This is per MCP specification, not a bug |
192 | 92 |
|
193 | | -## Security |
194 | | - |
195 | | -All security validation is centralized in `src/security/guardrails.ts` for easy auditing: |
| 93 | +**Example Workflow:** |
196 | 94 |
|
197 | 95 | ```typescript |
198 | | -import { validateFilePath, ALLOWED_EXTENSIONS } from "@/security"; |
| 96 | +// ❌ This progress won't appear in AI's context |
| 97 | +await sendProgress(context, 50, "Scaffolding widget..."); |
199 | 98 |
|
200 | | -// Validates path traversal and extension whitelist |
201 | | -validateFilePath(widgetPath, filePath, true); // true = check extension |
| 99 | +// ✅ This result WILL appear in AI's context |
| 100 | +return createToolResponse(`Widget created at ${widgetPath}`); |
202 | 101 | ``` |
203 | 102 |
|
204 | | -### Security Measures |
205 | | - |
206 | | -| Protection | Function | Description | |
207 | | -| ------------------- | ------------------------- | ------------------------------------------------------------------- | |
208 | | -| Path Traversal | `validateFilePath()` | Blocks `..` sequences and resolved path escapes | |
209 | | -| Extension Whitelist | `isExtensionAllowed()` | Only allows: `.tsx`, `.ts`, `.xml`, `.scss`, `.css`, `.json`, `.md` | |
210 | | -| Directory Boundary | `isPathWithinDirectory()` | Ensures files stay within widget directory | |
211 | | - |
212 | | -When adding file operation tools, always use `validateFilePath()` from the security module. |
213 | | - |
214 | | -## Key Files Reference |
215 | | - |
216 | | -| File | Purpose | |
217 | | -| -------------------------- | ------------------------------------------------ | |
218 | | -| `config.ts` | Server constants (ports, timeouts, paths) | |
219 | | -| `security/guardrails.ts` | Security validation (path traversal, extensions) | |
220 | | -| `tools/index.ts` | Tool registration aggregation | |
221 | | -| `tools/utils/generator.ts` | Widget generator PTY prompts and defaults | |
222 | | -| `resources/guidelines.ts` | Widget development guideline resources | |
223 | | -| `server/session.ts` | HTTP session lifecycle management | |
224 | | -| `server/server.ts` | MCP server factory and registration entry point | |
225 | | - |
226 | | -## Common Patterns |
227 | | - |
228 | | -### Progress Notifications |
229 | | - |
230 | | -```typescript |
231 | | -const tracker = new ProgressTracker({ |
232 | | - context, |
233 | | - logger: "my-tool", |
234 | | - totalSteps: 5 |
235 | | -}); |
| 103 | +## Testing |
236 | 104 |
|
237 | | -tracker.start("initializing"); |
238 | | -await tracker.progress(25, "Step 1 complete"); |
239 | | -await tracker.info("Detailed log message", { key: "value" }); |
240 | | -tracker.stop(); |
| 105 | +```bash |
| 106 | +npx @modelcontextprotocol/inspector node dist/index.js |
241 | 107 | ``` |
242 | 108 |
|
243 | | -### Long-Running Operations |
244 | | - |
245 | | -- Use `ProgressTracker` for heartbeat and stuck detection |
246 | | -- Set appropriate timeouts (see `SCAFFOLD_TIMEOUT_MS`) |
247 | | -- Call `tracker.markComplete()` before expected long waits (e.g., npm install) |
248 | | - |
249 | | -## Roadmap Context |
250 | | - |
251 | | -Current focus is widget scaffolding. Planned additions: |
| 109 | +## Security |
252 | 110 |
|
253 | | -- Widget property editing |
254 | | -- XML configuration management |
255 | | -- Build and deployment automation |
| 111 | +**Read before implementing file operations**: [docs/agent/security.md](docs/agent/security.md) |
256 | 112 |
|
257 | | -When adding features, maintain the existing patterns for tool registration, progress tracking, and transport-agnostic design. |
| 113 | +All file operation tools must use `validateFilePath()` from `@/security` to prevent path traversal attacks. |
0 commit comments