@mandujs/mcp 0.38.6 → 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 (43) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -2
  3. package/src/executor/error-handler.ts +76 -2
  4. package/src/index.ts +5 -5
  5. package/src/prompts.ts +4 -4
  6. package/src/resources/handlers.ts +4 -6
  7. package/src/resources/skills/guides.ts +49 -49
  8. package/src/resources/skills/loader.ts +8 -8
  9. package/src/resources/skills/mandu-agent-workflow/SKILL.md +124 -124
  10. package/src/resources/skills/mandu-agent-workflow/metadata.json +7 -7
  11. package/src/resources/skills/mandu-composition/SKILL.md +47 -47
  12. package/src/resources/skills/mandu-deployment/SKILL.md +53 -53
  13. package/src/resources/skills/mandu-deployment/rules/db-provider-supabase.md +3 -3
  14. package/src/resources/skills/mandu-fs-routes/SKILL.md +47 -47
  15. package/src/resources/skills/mandu-guard/SKILL.md +58 -58
  16. package/src/resources/skills/mandu-hydration/SKILL.md +67 -67
  17. package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +54 -54
  18. package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +60 -60
  19. package/src/resources/skills/mandu-performance/SKILL.md +47 -47
  20. package/src/resources/skills/mandu-security/SKILL.md +47 -47
  21. package/src/resources/skills/mandu-slot/SKILL.md +48 -48
  22. package/src/resources/skills/mandu-styling/SKILL.md +52 -52
  23. package/src/resources/skills/mandu-testing/SKILL.md +55 -55
  24. package/src/resources/skills/mandu-ui/SKILL.md +52 -52
  25. package/src/resources/skills/recipes.ts +28 -28
  26. package/src/server.ts +2 -1
  27. package/src/tools/agent.ts +443 -443
  28. package/src/tools/ate.ts +37 -37
  29. package/src/tools/brain.ts +12 -11
  30. package/src/tools/composite.ts +13 -13
  31. package/src/tools/contract.ts +1 -1
  32. package/src/tools/deploy-plan.ts +1 -1
  33. package/src/tools/generate.ts +3 -1
  34. package/src/tools/guard.ts +36 -1
  35. package/src/tools/history.ts +1 -1
  36. package/src/tools/hydration.ts +1 -3
  37. package/src/tools/index.ts +14 -0
  38. package/src/tools/kitchen.ts +72 -72
  39. package/src/tools/runtime.ts +1 -1
  40. package/src/tools/slot-validation.ts +19 -19
  41. package/src/tools/spec.ts +59 -27
  42. package/src/tools/transaction.ts +19 -5
  43. package/src/utils/withWarnings.ts +1 -1
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
+ }
@@ -5,8 +5,8 @@ import {
5
5
  rollbackChange,
6
6
  getTransactionStatus,
7
7
  hasActiveTransaction,
8
- } from "@mandujs/core";
9
- import { acquireLock, releaseLock, checkLock } from "../tx-lock.js";
8
+ } from "@mandujs/core/change";
9
+ import { acquireLock, releaseLock, checkLock, requireLock } from "../tx-lock.js";
10
10
 
11
11
  export const transactionToolDefinitions: Tool[] = [
12
12
  {
@@ -99,9 +99,15 @@ export function transactionTools(projectRoot: string) {
99
99
  return { error: lock.error };
100
100
  }
101
101
 
102
- const change = await beginChange(projectRoot, {
103
- message: message || "MCP transaction",
104
- });
102
+ let change: Awaited<ReturnType<typeof beginChange>>;
103
+ try {
104
+ change = await beginChange(projectRoot, {
105
+ message: message || "MCP transaction",
106
+ });
107
+ } catch (error) {
108
+ if (lock.lockId) releaseLock(lock.lockId);
109
+ return { error: error instanceof Error ? error.message : String(error) };
110
+ }
105
111
 
106
112
  return {
107
113
  success: true,
@@ -122,6 +128,10 @@ export function transactionTools(projectRoot: string) {
122
128
  error: "No active transaction to commit",
123
129
  };
124
130
  }
131
+ const lockCheck = requireLock(lockId);
132
+ if (!lockCheck.allowed) {
133
+ return { error: lockCheck.error };
134
+ }
125
135
 
126
136
  const result = await commitChange(projectRoot);
127
137
  if (lockId) releaseLock(lockId);
@@ -142,6 +152,10 @@ export function transactionTools(projectRoot: string) {
142
152
  error: "No active transaction to rollback. Provide a changeId to rollback a specific change.",
143
153
  };
144
154
  }
155
+ const lockCheck = requireLock(lockId);
156
+ if (!lockCheck.allowed) {
157
+ return { error: lockCheck.error };
158
+ }
145
159
 
146
160
  const result = await rollbackChange(projectRoot, changeId);
147
161
  if (lockId) releaseLock(lockId);
@@ -7,7 +7,7 @@
7
7
  * MCP notification이 AI 에이전트에 전달되지 않는 문제를 해결.
8
8
  */
9
9
 
10
- import { getWatcher } from "../../../core/src/index.js";
10
+ import { getWatcher } from "@mandujs/core/watcher";
11
11
 
12
12
  const MUTATION_TOOLS = new Set([
13
13
  "mandu_write_slot",