@mandujs/mcp 0.38.7 → 0.38.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/tools/spec.ts +59 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/mcp",
3
- "version": "0.38.7",
3
+ "version": "0.38.8",
4
4
  "description": "Mandu MCP Server - Agent-native interface for Mandu framework operations",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@mandujs/core": "^0.54.13",
37
+ "@mandujs/core": "^0.54.16",
38
38
  "@mandujs/ate": "^0.26.1",
39
39
  "@mandujs/skills": "^0.20.1",
40
40
  "@modelcontextprotocol/sdk": "^1.25.3"
package/src/tools/spec.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
- import {
3
- loadManifest,
4
- generateManifest,
5
- } from "@mandujs/core";
6
- import { getProjectPaths } from "../utils/project.js";
7
- import path from "path";
8
- import fs from "fs/promises";
2
+ import {
3
+ loadManifest,
4
+ generateManifest,
5
+ } from "@mandujs/core";
6
+ import { getProjectPaths, isInsideProject } from "../utils/project.js";
7
+ import path from "path";
8
+ import fs from "fs/promises";
9
9
 
10
10
  export const specToolDefinitions: Tool[] = [
11
11
  {
@@ -151,26 +151,42 @@ export function specTools(projectRoot: string) {
151
151
  kind: "api" | "page";
152
152
  withSlot?: boolean;
153
153
  withContract?: boolean;
154
- };
155
-
156
- const createdFiles: string[] = [];
157
-
158
- // Scaffold app/ file
159
- const fileName = kind === "api" ? "route.ts" : "page.tsx";
160
- const appFilePath = path.join(paths.appDir, routePath, fileName);
161
- const appFileDir = path.dirname(appFilePath);
162
-
163
- await fs.mkdir(appFileDir, { recursive: true });
154
+ };
155
+
156
+ const createdFiles: string[] = [];
157
+ const normalizedRoutePath = normalizeRoutePath(routePath);
158
+ if (!normalizedRoutePath) {
159
+ return {
160
+ error: "Route path must be a non-empty relative path inside app/",
161
+ };
162
+ }
163
+
164
+ // Scaffold app/ file
165
+ const fileName = kind === "api" ? "route.ts" : "page.tsx";
166
+ const appFilePath = path.resolve(paths.appDir, normalizedRoutePath, fileName);
167
+ if (!isInsideProject(appFilePath, paths.appDir)) {
168
+ return {
169
+ error: "Route path resolves outside app/",
170
+ };
171
+ }
172
+ if (await Bun.file(appFilePath).exists()) {
173
+ return {
174
+ error: `Route source already exists: app/${normalizedRoutePath}/${fileName}`,
175
+ };
176
+ }
177
+ const appFileDir = path.dirname(appFilePath);
178
+
179
+ await fs.mkdir(appFileDir, { recursive: true });
164
180
 
165
181
  if (kind === "api") {
166
182
  await Bun.write(appFilePath, `export function GET(req: Request) {\n return Response.json({ message: "Hello" });\n}\n`);
167
- } else {
168
- await Bun.write(appFilePath, `export default function Page() {\n return <div>Page</div>;\n}\n`);
169
- }
170
- createdFiles.push(`app/${routePath}/${fileName}`);
171
-
172
- // Derive route ID from path
173
- const routeId = routePath.replace(/\//g, "-").replace(/[\[\]\.]/g, "");
183
+ } else {
184
+ await Bun.write(appFilePath, `export default function Page() {\n return <div>Page</div>;\n}\n`);
185
+ }
186
+ createdFiles.push(`app/${normalizedRoutePath}/${fileName}`);
187
+
188
+ // Derive route ID from path
189
+ const routeId = normalizedRoutePath.replace(/\//g, "-").replace(/[\[\]\.]/g, "");
174
190
 
175
191
  // Scaffold slot if requested
176
192
  if (withSlot) {
@@ -265,6 +281,22 @@ export function specTools(projectRoot: string) {
265
281
  handlers["mandu_add_route"] = handlers["mandu.route.add"];
266
282
  handlers["mandu_delete_route"] = handlers["mandu.route.delete"];
267
283
  handlers["mandu_validate_manifest"] = handlers["mandu.manifest.validate"];
268
-
269
- return handlers;
270
- }
284
+
285
+ return handlers;
286
+ }
287
+
288
+ function normalizeRoutePath(value: unknown): string | null {
289
+ if (typeof value !== "string") return null;
290
+ const trimmed = value.trim().replace(/\\/g, "/");
291
+ const withoutAppPrefix = trimmed.replace(/^app\//, "");
292
+ const normalized = withoutAppPrefix.replace(/^\/+|\/+$/g, "");
293
+ if (!normalized) return null;
294
+
295
+ const segments = normalized.split("/").filter(Boolean);
296
+ if (segments.length === 0) return null;
297
+ if (segments.some((segment) => segment === "." || segment === ".." || segment.includes(":") || segment.includes("\0"))) {
298
+ return null;
299
+ }
300
+
301
+ return segments.join("/");
302
+ }