Skip to content

Commit 87968b7

Browse files
committed
feat(pluggable-widgets-mcp): refactor security guardrails into a single ts, update docs
1 parent 93e340d commit 87968b7

8 files changed

Lines changed: 270 additions & 138 deletions

File tree

packages/pluggable-widgets-mcp/AGENTS.md

Lines changed: 102 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,24 @@ This package implements an MCP server that enables AI assistants to scaffold and
1919
src/
2020
├── index.ts # Entry point - transport mode selection
2121
├── config.ts # Server configuration and constants
22+
├── security/
23+
│ ├── guardrails.ts # Security validation (path traversal, extension whitelist)
24+
│ └── index.ts # Security module exports
2225
├── server/
23-
│ ├── server.ts # MCP server factory and tool registration
26+
│ ├── server.ts # MCP server factory and tool/resource registration
2427
│ ├── http.ts # HTTP transport setup (Express)
2528
│ ├── stdio.ts # STDIO transport setup
2629
│ ├── routes.ts # Express route handlers
2730
│ └── session.ts # HTTP session management
31+
├── resources/
32+
│ ├── index.ts # Resource registration
33+
│ └── guidelines.ts # Widget development guidelines
2834
└── tools/
29-
├── index.ts # Tool aggregation
35+
├── index.ts # Tool registration aggregation
3036
├── types.ts # MCP tool type definitions
31-
├── scaffolding.tools.ts # Widget creation tool
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
3240
└── utils/
3341
├── generator.ts # Widget generator PTY wrapper
3442
├── progress-tracker.ts # Progress/logging helper
@@ -42,28 +50,44 @@ src/
4250

4351
The server supports two transport modes selected via CLI argument:
4452

45-
- **HTTP** (default): Multi-session Express server on port 3100
46-
- **STDIO**: Single-session stdin/stdout for CLI integration
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
4755

4856
### Tool Registration
4957

50-
Tools are defined using the `ToolDefinition<T>` interface:
58+
Tools are registered directly with the MCP server using the SDK's `server.tool()` method. The current architecture uses category-based registration functions:
5159

5260
```typescript
53-
interface ToolDefinition<T> {
54-
name: string; // Tool identifier
55-
title: string; // Human-readable name
56-
description: string; // LLM-facing description
57-
inputSchema: ZodType<T>; // Zod schema for validation
58-
handler: ToolHandler<T>; // Async handler function
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
5966
}
6067
```
6168

62-
New tools should be:
69+
**Available Tools**:
6370

64-
1. Created in `src/tools/` with a `*.tools.ts` suffix
65-
2. Export a `get*Tools()` function returning `ToolDefinition[]`
66-
3. Registered in `src/tools/index.ts`
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`.
6791

6892
### Widget Generator Integration
6993

@@ -78,53 +102,57 @@ The `create-widget` tool uses `node-pty` to interact with the Mendix widget gene
78102

79103
```bash
80104
pnpm dev # Development mode with hot reload (tsx watch)
81-
pnpm build # TypeScript compilation + path alias resolution
82-
pnpm start # Build and run (HTTP mode)
105+
pnpm build # TypeScript compilation + path alias resolution (preserves shebang)
106+
pnpm start # Build and run (HTTP mode on port 3100)
83107
pnpm start:stdio # Build and run (STDIO mode)
84-
pnpm lint # ESLint + Prettier check
108+
pnpm lint # ESLint check
85109
```
86110

87111
## Adding New Tools
88112

89113
1. **Create tool file**: `src/tools/my-feature.tools.ts`
90114

91115
```typescript
116+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
92117
import { z } from "zod";
93-
import type { ToolDefinition, ToolResponse } from "@/tools/types";
94-
import { createToolResponse, createErrorResponse } from "@/tools/utils/response";
95118

96-
const mySchema = z.object({
119+
const myToolSchema = z.object({
97120
param: z.string().describe("Parameter description for LLM")
98121
});
99122

100-
type MyInput = z.infer<typeof mySchema>;
101-
102-
export function getMyTools(): ToolDefinition<MyInput>[] {
103-
return [
104-
{
105-
name: "my-tool",
106-
title: "My Tool",
107-
description: "What this tool does (shown to LLM)",
108-
inputSchema: mySchema,
109-
handler: async (args, context) => {
110-
// Implementation
111-
return createToolResponse("Success message");
112-
}
123+
export function registerMyTools(server: McpServer): void {
124+
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+
};
113139
}
114-
];
140+
);
141+
142+
console.error("[my-feature] Registered 1 tool");
115143
}
116144
```
117145

118146
2. **Register in index**: Update `src/tools/index.ts`
119147

120148
```typescript
121-
import { getMyTools } from "./my-feature.tools";
149+
import { registerMyTools } from "./my-feature.tools";
122150

123-
export function getAllTools(): AnyToolDefinition[] {
124-
return [
125-
...getScaffoldingTools(),
126-
...getMyTools() // Add here
127-
];
151+
export function registerAllTools(server: McpServer): void {
152+
registerScaffoldingTools(server);
153+
registerFileOperationTools(server);
154+
registerBuildTools(server);
155+
registerMyTools(server); // Add here
128156
}
129157
```
130158

@@ -145,8 +173,8 @@ export function getAllTools(): AnyToolDefinition[] {
145173
### Type Safety
146174

147175
- All tool inputs must have Zod schemas
148-
- Use `ToolContext` for MCP-provided context (notifications, progress)
149-
- Avoid `any` except in `AnyToolDefinition` (required for heterogeneous tool arrays)
176+
- Tool handlers receive fully typed arguments via Zod inference
177+
- Use `McpServer` methods directly for type-safe tool registration
150178

151179
## Testing
152180

@@ -162,14 +190,38 @@ npx @modelcontextprotocol/inspector
162190
# Connect to http://localhost:3100/mcp
163191
```
164192

193+
## Security
194+
195+
All security validation is centralized in `src/security/guardrails.ts` for easy auditing:
196+
197+
```typescript
198+
import { validateFilePath, ALLOWED_EXTENSIONS } from "@/security";
199+
200+
// Validates path traversal and extension whitelist
201+
validateFilePath(widgetPath, filePath, true); // true = check extension
202+
```
203+
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+
165214
## Key Files Reference
166215

167-
| File | Purpose |
168-
| -------------------------- | -------------------------------------- |
169-
| `config.ts` | All constants (ports, timeouts, paths) |
170-
| `tools/types.ts` | MCP tool type definitions |
171-
| `tools/utils/generator.ts` | Widget generator prompts and defaults |
172-
| `server/session.ts` | HTTP session lifecycle management |
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 |
173225

174226
## Common Patterns
175227

packages/pluggable-widgets-mcp/README.md

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ A Model Context Protocol (MCP) server that enables AI assistants to scaffold Men
99
```bash
1010
pnpm install
1111
pnpm build # Build the server
12-
pnpm start # HTTP mode (default)
13-
pnpm start:stdio # STDIO mode
12+
pnpm start # STDIO mode (default)
13+
pnpm start:stdio # HTTP mode
1414
```
1515

1616
## Global Installation
@@ -30,27 +30,27 @@ which pluggable-widgets-mcp
3030

3131
## Transport Modes
3232

33-
### HTTP Mode (default)
33+
### STDIO Mode (default)
3434

35-
Runs an HTTP server for web-based MCP clients.
35+
Runs via stdin/stdout for CLI-based MCP clients (Claude Desktop, etc.).
3636

3737
```bash
3838
pnpm start
39-
pnpm start:http
39+
pnpm start:stdio
4040
```
4141

42-
- Server runs on `http://localhost:3100` (override with `PORT` env var)
43-
- Health check: `GET /health`
44-
- MCP endpoint: `POST /mcp`
42+
### HTTP Mode
4543

46-
### STDIO Mode
47-
48-
Runs via stdin/stdout for CLI-based MCP clients (Claude Desktop, etc.).
44+
Runs an HTTP server for web-based MCP clients.
4945

5046
```bash
51-
pnpm start:stdio
47+
pnpm start:http
5248
```
5349

50+
- Server runs on `http://localhost:3100` (override with `PORT` env var)
51+
- Health check: `GET /health`
52+
- MCP endpoint: `POST /mcp`
53+
5454
## MCP Client Configuration
5555

5656
### HTTP
@@ -127,7 +127,10 @@ Generated widgets are placed in `generations/` directory within this package.
127127
| `write-widget-file` | Writes content to a file (creates parent dirs automatically) |
128128
| `batch-write-widget-files` | Writes multiple files atomically |
129129

130-
**Security:** Path traversal is blocked; only allowed extensions: `.tsx`, `.ts`, `.xml`, `.scss`, `.css`, `.json`, `.md`
130+
**Security:** All file operations are protected by `src/security/guardrails.ts`:
131+
132+
- Path traversal is blocked (no `..` escapes)
133+
- Extension whitelist: `.tsx`, `.ts`, `.xml`, `.scss`, `.css`, `.json`, `.md`
131134

132135
### build-widget
133136

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,5 @@ export const GENERATIONS_DIR = join(process.cwd(), "generations");
2424
// Path to local docs folder
2525
export const DOCS_DIR = join(PACKAGE_ROOT, "docs");
2626

27-
// Allowed file extensions for widget file operations
28-
export const ALLOWED_EXTENSIONS = [".tsx", ".ts", ".xml", ".scss", ".css", ".json", ".md", ".editorConfig.ts"];
29-
3027
// Timeouts
3128
export const SCAFFOLD_TIMEOUT_MS = 300000; // 5 minutes

0 commit comments

Comments
 (0)