@mandujs/mcp 0.18.8 → 0.18.10

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.
package/src/index.ts CHANGED
@@ -83,6 +83,14 @@ export {
83
83
  getToolsSummary,
84
84
  } from "./tools/index.js";
85
85
 
86
+ // Profile exports
87
+ export {
88
+ type McpProfile,
89
+ PROFILE_CATEGORIES,
90
+ getProfileCategories,
91
+ isValidProfile,
92
+ } from "./profiles.js";
93
+
86
94
  // CLI entry point
87
95
  import { startServer } from "./server.js";
88
96
  import path from "path";
@@ -0,0 +1,119 @@
1
+ /**
2
+ * MCP Resources for Mandu Framework
3
+ *
4
+ * Project state data exposed via the MCP resource protocol.
5
+ */
6
+
7
+ import type { Resource } from "@modelcontextprotocol/sdk/types.js";
8
+ import path from "path";
9
+ import { readConfig, readJsonFile } from "./utils/project.js";
10
+ import { loadManduConfig, loadManifest } from "@mandujs/core";
11
+ import { getDevServerState } from "./tools/project.js";
12
+
13
+ export const manduResourceDefinitions: Resource[] = [
14
+ {
15
+ uri: "mandu://routes",
16
+ name: "Route Manifest",
17
+ description: "Current project route manifest (JSON)",
18
+ mimeType: "application/json",
19
+ },
20
+ {
21
+ uri: "mandu://config",
22
+ name: "Mandu Config",
23
+ description: "Parsed mandu.config.ts settings",
24
+ mimeType: "application/json",
25
+ },
26
+ {
27
+ uri: "mandu://errors",
28
+ name: "Recent Errors",
29
+ description: "Recent build and runtime errors",
30
+ mimeType: "application/json",
31
+ },
32
+ ];
33
+
34
+ type ResourceReadResult = { uri: string; mimeType: string; text: string };
35
+ type ResourceHandler = () => Promise<ResourceReadResult>;
36
+
37
+ function jsonResult(uri: string, data: unknown): ResourceReadResult {
38
+ return { uri, mimeType: "application/json", text: JSON.stringify(data, null, 2) };
39
+ }
40
+
41
+ export function manduResourceHandlers(projectRoot: string): Record<string, ResourceHandler> {
42
+ const manifestPath = path.join(projectRoot, ".mandu", "routes.manifest.json");
43
+
44
+ return {
45
+ "mandu://routes": async () => {
46
+ const result = await loadManifest(manifestPath);
47
+ if (!result.success || !result.data) {
48
+ return jsonResult("mandu://routes", {
49
+ error: "Failed to load route manifest",
50
+ details: result.errors,
51
+ hint: "Run 'mandu generate' or 'mandu dev' to create the manifest.",
52
+ });
53
+ }
54
+ return jsonResult("mandu://routes", {
55
+ version: result.data.version,
56
+ routeCount: result.data.routes.length,
57
+ routes: result.data.routes.map((r) => ({
58
+ id: r.id, pattern: r.pattern, kind: r.kind, module: r.module,
59
+ slotModule: r.slotModule ?? null, clientModule: r.clientModule ?? null,
60
+ })),
61
+ });
62
+ },
63
+
64
+ "mandu://config": async () => {
65
+ try {
66
+ const config = await loadManduConfig(projectRoot);
67
+ return jsonResult("mandu://config", {
68
+ server: config.server ?? {},
69
+ guard: config.guard ?? {},
70
+ build: config.build ?? {},
71
+ dev: config.dev ?? {},
72
+ fsRoutes: config.fsRoutes ?? {},
73
+ seo: config.seo ?? {},
74
+ });
75
+ } catch {
76
+ const raw = await readConfig(projectRoot);
77
+ return jsonResult("mandu://config", raw ?? {
78
+ error: "No mandu.config.ts/js/json found",
79
+ hint: "Create a mandu.config.ts in the project root.",
80
+ });
81
+ }
82
+ },
83
+
84
+ "mandu://errors": async () => {
85
+ const errors: unknown[] = [];
86
+
87
+ // Try Kitchen DevTools error log from running dev server
88
+ let port: number | undefined;
89
+ const serverState = getDevServerState();
90
+ if (serverState) {
91
+ for (const line of serverState.output) {
92
+ const m = line.match(/https?:\/\/localhost:(\d+)/);
93
+ if (m) port = parseInt(m[1], 10);
94
+ }
95
+ }
96
+ if (port) {
97
+ try {
98
+ const res = await fetch(`http://localhost:${port}/__kitchen/api/errors`);
99
+ if (res.ok) {
100
+ const body = (await res.json()) as { errors: unknown[] };
101
+ if (body.errors?.length) errors.push(...body.errors);
102
+ }
103
+ } catch { /* dev server not reachable */ }
104
+ }
105
+
106
+ // Read local error log file
107
+ const loggedErrors = await readJsonFile<unknown[]>(
108
+ path.join(projectRoot, ".mandu", "errors.json"),
109
+ );
110
+ if (Array.isArray(loggedErrors)) errors.push(...loggedErrors);
111
+
112
+ return jsonResult("mandu://errors", {
113
+ count: errors.length,
114
+ errors,
115
+ source: port ? "kitchen+log" : "log",
116
+ });
117
+ },
118
+ };
119
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * MCP Tool Profiles
3
+ *
4
+ * Controls how many tool categories are exposed to AI agents.
5
+ * - minimal: Core scaffolding tools only (~15 tools)
6
+ * - standard: Common development workflow (~40 tools)
7
+ * - full: All categories, no filtering (default)
8
+ */
9
+
10
+ export type McpProfile = "minimal" | "standard" | "full";
11
+
12
+ export const PROFILE_CATEGORIES: Record<McpProfile, string[] | null> = {
13
+ minimal: ["spec", "project", "guard", "generate"],
14
+ standard: [
15
+ "spec", "project", "guard", "generate",
16
+ "contract", "slot", "hydration", "seo",
17
+ "component", "kitchen", "composite",
18
+ ],
19
+ full: null,
20
+ };
21
+
22
+ /**
23
+ * Returns allowed category names for a profile, or null if all categories are allowed.
24
+ */
25
+ export function getProfileCategories(profile: McpProfile): string[] | null {
26
+ return PROFILE_CATEGORIES[profile] ?? null;
27
+ }
28
+
29
+ /**
30
+ * Type guard for valid profile strings.
31
+ */
32
+ export function isValidProfile(value: string): value is McpProfile {
33
+ return value === "minimal" || value === "standard" || value === "full";
34
+ }
package/src/prompts.ts ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * MCP Prompts for Mandu Framework
3
+ *
4
+ * Conversation templates that guide agents through common Mandu workflows.
5
+ */
6
+
7
+ import type { Prompt, GetPromptResult } from "@modelcontextprotocol/sdk/types.js";
8
+
9
+ export const manduPrompts: Prompt[] = [
10
+ {
11
+ name: "new-feature",
12
+ description: "Guide creating a new feature with routes, contracts, and islands",
13
+ arguments: [
14
+ { name: "description", description: "Feature description", required: true },
15
+ ],
16
+ },
17
+ {
18
+ name: "debug",
19
+ description: "Diagnose and fix errors in the Mandu project",
20
+ arguments: [
21
+ { name: "symptom", description: "Error message or symptom", required: false },
22
+ ],
23
+ },
24
+ {
25
+ name: "add-crud",
26
+ description: "Create a complete CRUD API with contracts and tests",
27
+ arguments: [
28
+ { name: "resource", description: "Resource name (e.g., 'products')", required: true },
29
+ ],
30
+ },
31
+ ];
32
+
33
+ function msg(text: string): GetPromptResult {
34
+ return { messages: [{ role: "user", content: { type: "text", text } }] };
35
+ }
36
+
37
+ function capitalize(s: string): string {
38
+ return s.charAt(0).toUpperCase() + s.slice(1);
39
+ }
40
+
41
+ type PromptHandler = (args: Record<string, string>) => GetPromptResult;
42
+
43
+ const promptHandlers: Record<string, PromptHandler> = {
44
+ "new-feature": (args) => msg(`Create a new feature: ${args.description}
45
+
46
+ Follow these steps using Mandu MCP tools:
47
+
48
+ 1. Read current route manifest: Resource mandu://routes
49
+ 2. Read project config: Resource mandu://config
50
+ 3. Negotiate the feature spec: Tool mandu.negotiate
51
+ 4. Generate scaffold: Tool mandu.negotiate.scaffold
52
+ 5. If client interactivity needed, create island: Tool mandu_create_island
53
+ Use the declarative pattern: island('visible', Component)
54
+ 6. If data requirements exist, create slot: Tool mandu_create_slot
55
+ Slots are server-side data loaders that run before render
56
+ 7. If API is exposed, define contract: Tool mandu_create_contract
57
+ Contracts are Zod schemas for validation and OpenAPI generation
58
+ 8. Validate architecture: Tool mandu_guard_check
59
+ 9. Run brain doctor: Tool mandu_brain_diagnose`),
60
+
61
+ "debug": (args) => msg(`${args.symptom ? `Diagnose this error: ${args.symptom}` : "Diagnose errors in the Mandu project"}
62
+
63
+ Follow these diagnostic steps using Mandu MCP tools:
64
+
65
+ 1. Check client-side errors: Tool mandu.kitchen.errors
66
+ 2. Check recent build/runtime errors: Resource mandu://errors
67
+ 3. Check architecture rule violations: Resource mandu://watch/warnings
68
+ 4. Run brain doctor for structural analysis: Tool mandu_brain_diagnose
69
+ 5. Verify route manifest: Resource mandu://routes
70
+ 6. Inspect specific route slot: Resource mandu://slots/{routeId}
71
+ 7. Run guard check: Tool mandu_guard_check
72
+
73
+ After identifying root cause, fix and re-run checks to confirm.`),
74
+
75
+ "add-crud": (args) => {
76
+ const r = args.resource;
77
+ const R = capitalize(r);
78
+ return msg(`Create a complete CRUD API for the '${r}' resource.
79
+
80
+ Follow these steps using Mandu MCP tools:
81
+
82
+ 1. Read project config: Resource mandu://config
83
+ 2. Create API routes: Tool mandu.negotiate
84
+ - GET/POST for /api/${r}, GET/PUT/DELETE for /api/${r}/[id]
85
+ 3. Define contracts: Tool mandu_create_contract
86
+ - Create${R}Schema, Update${R}Schema, ${R}ResponseSchema
87
+ 4. Generate scaffold: Tool mandu.negotiate.scaffold
88
+ 5. Create data slot for list page: Tool mandu_create_slot
89
+ 6. Validate: Tool mandu_guard_check + Tool mandu_brain_diagnose
90
+ 7. Verify routes: Resource mandu://routes`);
91
+ },
92
+ };
93
+
94
+ /**
95
+ * Get the prompt handler result for a given prompt name and arguments.
96
+ */
97
+ export function getPromptResult(
98
+ name: string,
99
+ args: Record<string, string>,
100
+ ): GetPromptResult | null {
101
+ const handler = promptHandlers[name];
102
+ if (!handler) return null;
103
+ return handler(args);
104
+ }
@@ -4,7 +4,6 @@ import {
4
4
  getTransactionStatus,
5
5
  getWatcher,
6
6
  type GeneratedMap,
7
- type SpecLock,
8
7
  } from "@mandujs/core";
9
8
  import { getProjectPaths, readJsonFile } from "../utils/project.js";
10
9
  import {
@@ -26,12 +25,6 @@ export const resourceDefinitions: Resource[] = [
26
25
  description: "Current routes.manifest.json content",
27
26
  mimeType: "application/json",
28
27
  },
29
- {
30
- uri: "mandu://spec/lock",
31
- name: "Spec Lock",
32
- description: "Current spec.lock.json with hash verification",
33
- mimeType: "application/json",
34
- },
35
28
  {
36
29
  uri: "mandu://generated/map",
37
30
  name: "Generated Map",
@@ -181,22 +174,6 @@ export function resourceHandlers(
181
174
  };
182
175
  },
183
176
 
184
- "mandu://spec/lock": async () => {
185
- const lock = await readJsonFile<SpecLock>(paths.lockPath);
186
- if (!lock) {
187
- return {
188
- exists: false,
189
- message: "spec.lock.json not found",
190
- };
191
- }
192
-
193
- return {
194
- exists: true,
195
- routesHash: lock.routesHash,
196
- updatedAt: lock.updatedAt,
197
- };
198
- },
199
-
200
177
  "mandu://generated/map": async () => {
201
178
  const generatedMap = await readJsonFile<GeneratedMap>(paths.generatedMapPath);
202
179
  if (!generatedMap) {
package/src/server.ts CHANGED
@@ -16,6 +16,8 @@ import {
16
16
  ListToolsRequestSchema,
17
17
  ListResourcesRequestSchema,
18
18
  ReadResourceRequestSchema,
19
+ ListPromptsRequestSchema,
20
+ GetPromptRequestSchema,
19
21
  type CallToolResult,
20
22
  } from "@modelcontextprotocol/sdk/types.js";
21
23
 
@@ -38,11 +40,18 @@ import { mcpHookRegistry, registerDefaultMcpHooks, type McpToolContext } from ".
38
40
  // DNA-006: 설정 핫 리로드
39
41
  import { startMcpConfigWatcher, type McpConfigWatcher } from "./hooks/config-watcher.js";
40
42
 
43
+ // Prompts
44
+ import { manduPrompts, getPromptResult } from "./prompts.js";
45
+
46
+ // New top-level resources
47
+ import { manduResourceDefinitions, manduResourceHandlers } from "./new-resources.js";
48
+
41
49
  // 기존 컴포넌트
42
50
  import { resourceHandlers, resourceDefinitions } from "./resources/handlers.js";
43
51
  import { findProjectRoot } from "./utils/project.js";
44
52
  import { applyWarningInjection } from "./utils/withWarnings.js";
45
53
  import { ActivityMonitor } from "./activity-monitor.js";
54
+ import { type McpProfile, isValidProfile } from "./profiles.js";
46
55
 
47
56
  /**
48
57
  * MCP 서버 버전
@@ -61,11 +70,16 @@ export class ManduMcpServer {
61
70
  private config?: ManduConfig;
62
71
  private configWatcher?: McpConfigWatcher;
63
72
  private toolExecutor: ToolExecutor;
73
+ private profile: McpProfile;
64
74
 
65
75
  constructor(projectRoot: string) {
66
76
  this.projectRoot = projectRoot;
67
77
  this.monitor = new ActivityMonitor(projectRoot);
68
78
 
79
+ // Resolve profile from environment variable (default: "full")
80
+ const envProfile = process.env.MANDU_MCP_PROFILE ?? "full";
81
+ this.profile = isValidProfile(envProfile) ? envProfile : "full";
82
+
69
83
  // MCP Server 초기화
70
84
  this.server = new Server(
71
85
  {
@@ -76,13 +90,16 @@ export class ManduMcpServer {
76
90
  capabilities: {
77
91
  tools: {},
78
92
  resources: {},
93
+ prompts: {},
79
94
  logging: {},
80
95
  },
81
96
  }
82
97
  );
83
98
 
84
99
  // DNA-001: 플러그인 기반 도구 등록
85
- registerBuiltinTools(projectRoot, this.server, this.monitor);
100
+ registerBuiltinTools(projectRoot, this.server, this.monitor, {
101
+ profile: this.profile,
102
+ });
86
103
 
87
104
  // DNA-008: 로깅 통합
88
105
  setupMcpLogging({ consoleOutput: false });
@@ -100,6 +117,7 @@ export class ManduMcpServer {
100
117
  // 핸들러 등록
101
118
  this.registerToolHandlers();
102
119
  this.registerResourceHandlers();
120
+ this.registerPromptHandlers();
103
121
  }
104
122
 
105
123
  /**
@@ -124,21 +142,75 @@ export class ManduMcpServer {
124
142
  });
125
143
  }
126
144
 
145
+ /**
146
+ * Prompt handler registration
147
+ */
148
+ private registerPromptHandlers(): void {
149
+ this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
150
+ return { prompts: manduPrompts };
151
+ });
152
+
153
+ this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
154
+ const { name, arguments: args } = request.params;
155
+ const result = getPromptResult(name, args ?? {});
156
+
157
+ if (!result) {
158
+ return {
159
+ messages: [
160
+ {
161
+ role: "user" as const,
162
+ content: {
163
+ type: "text" as const,
164
+ text: `Unknown prompt: ${name}. Available prompts: ${manduPrompts.map((p) => p.name).join(", ")}`,
165
+ },
166
+ },
167
+ ],
168
+ };
169
+ }
170
+
171
+ return result;
172
+ });
173
+ }
174
+
127
175
  /**
128
176
  * 리소스 핸들러 등록 (기존 유지)
129
177
  */
130
178
  private registerResourceHandlers(): void {
131
179
  const handlers = resourceHandlers(this.projectRoot);
180
+ const newHandlers = manduResourceHandlers(this.projectRoot);
181
+
182
+ // Merge resource definitions (new top-level + existing)
183
+ const allResources = [...manduResourceDefinitions, ...resourceDefinitions];
132
184
 
133
185
  this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
134
- return {
135
- resources: resourceDefinitions,
136
- };
186
+ return { resources: allResources };
137
187
  });
138
188
 
139
189
  this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
140
190
  const { uri } = request.params;
141
191
 
192
+ // Check new top-level resource handlers first
193
+ const newHandler = newHandlers[uri];
194
+ if (newHandler) {
195
+ try {
196
+ const result = await newHandler();
197
+ return { contents: [result] };
198
+ } catch (error) {
199
+ return {
200
+ contents: [
201
+ {
202
+ uri,
203
+ mimeType: "application/json",
204
+ text: JSON.stringify({
205
+ error: error instanceof Error ? error.message : String(error),
206
+ }),
207
+ },
208
+ ],
209
+ };
210
+ }
211
+ }
212
+
213
+ // Fall through to existing resource handlers
142
214
  const handler = handlers[uri];
143
215
  if (!handler) {
144
216
  // 동적 리소스 패턴 매칭
@@ -244,7 +316,7 @@ export class ManduMcpServer {
244
316
  warning.message
245
317
  );
246
318
 
247
- // Claude Code에 알림
319
+ // MCP 클라이언트에 알림
248
320
  this.server.sendLoggingMessage({
249
321
  level: "warning",
250
322
  logger: "mandu-watch",
@@ -270,6 +342,7 @@ export class ManduMcpServer {
270
342
  const summary = getToolsSummary();
271
343
  console.error(`Mandu MCP Server v${MCP_VERSION} running`);
272
344
  console.error(` Project: ${this.projectRoot}`);
345
+ console.error(` Profile: ${this.profile}`);
273
346
  console.error(` Tools: ${summary.total} (${summary.categories.join(", ")})`);
274
347
  }
275
348
 
package/src/tools/ate.ts CHANGED
@@ -15,6 +15,9 @@ import type { OracleLevel } from "@mandujs/ate";
15
15
  export const ateToolDefinitions: Tool[] = [
16
16
  {
17
17
  name: "mandu.ate.extract",
18
+ annotations: {
19
+ readOnlyHint: false,
20
+ },
18
21
  description:
19
22
  "ATE Step 1 — Extract: Statically analyze the Mandu project's AST to build an interaction graph of routes, slots, contracts, and data flow. " +
20
23
  "Identifies all testable interactions without running the server. " +
@@ -39,6 +42,9 @@ export const ateToolDefinitions: Tool[] = [
39
42
  },
40
43
  {
41
44
  name: "mandu.ate.generate",
45
+ annotations: {
46
+ readOnlyHint: false,
47
+ },
42
48
  description:
43
49
  "ATE Step 2 — Generate: Create Playwright test scenarios from the interaction graph produced by mandu.ate.extract. " +
44
50
  "Oracle level controls assertion depth: " +
@@ -67,6 +73,9 @@ export const ateToolDefinitions: Tool[] = [
67
73
  },
68
74
  {
69
75
  name: "mandu.ate.run",
76
+ annotations: {
77
+ readOnlyHint: false,
78
+ },
70
79
  description:
71
80
  "ATE Step 3 — Run: Execute the generated Playwright specs against a running Mandu dev server. " +
72
81
  "Collects test artifacts (screenshots, traces, results) in .mandu/ate/runs/{runId}/. " +
@@ -93,6 +102,9 @@ export const ateToolDefinitions: Tool[] = [
93
102
  },
94
103
  {
95
104
  name: "mandu.ate.report",
105
+ annotations: {
106
+ readOnlyHint: true,
107
+ },
96
108
  description:
97
109
  "ATE Step 4 — Report: Generate a test report from run artifacts. " +
98
110
  "Produces pass/fail summary, coverage by route, and failure details. " +
@@ -132,6 +144,9 @@ export const ateToolDefinitions: Tool[] = [
132
144
  },
133
145
  {
134
146
  name: "mandu.ate.heal",
147
+ annotations: {
148
+ readOnlyHint: true,
149
+ },
135
150
  description:
136
151
  "ATE Step 5 — Heal: Analyze test failures from a run and generate safe diff suggestions for fixing the code. " +
137
152
  "Classifies failures by root cause (schema mismatch, missing handler, wrong status, selector stale, etc.) " +
@@ -149,6 +164,9 @@ export const ateToolDefinitions: Tool[] = [
149
164
  },
150
165
  {
151
166
  name: "mandu.ate.impact",
167
+ annotations: {
168
+ readOnlyHint: true,
169
+ },
152
170
  description:
153
171
  "ATE Optimization — Impact Analysis: Calculate the minimal subset of routes affected by changed files using git diff. " +
154
172
  "Avoids running the full test suite when only part of the codebase changed. " +
@@ -166,6 +184,9 @@ export const ateToolDefinitions: Tool[] = [
166
184
  },
167
185
  {
168
186
  name: "mandu.ate.auto_pipeline",
187
+ annotations: {
188
+ readOnlyHint: false,
189
+ },
169
190
  description:
170
191
  "ATE Full Pipeline — Run the complete ATE cycle in one call: " +
171
192
  "Extract AST → Generate Playwright specs → Run tests → Create report → Suggest heals. " +
@@ -202,6 +223,9 @@ export const ateToolDefinitions: Tool[] = [
202
223
  },
203
224
  {
204
225
  name: "mandu.ate.feedback",
226
+ annotations: {
227
+ readOnlyHint: true,
228
+ },
205
229
  description:
206
230
  "ATE Feedback — Evaluate heal suggestions from a failed run and classify which fixes are safe to auto-apply. " +
207
231
  "Safe-to-auto-apply: selector-map updates (CSS selector changes). " +
@@ -222,6 +246,10 @@ export const ateToolDefinitions: Tool[] = [
222
246
  },
223
247
  {
224
248
  name: "mandu.ate.apply_heal",
249
+ annotations: {
250
+ destructiveHint: true,
251
+ readOnlyHint: false,
252
+ },
225
253
  description:
226
254
  "ATE Apply — Apply a specific heal suggestion diff to the codebase. " +
227
255
  "Always creates a backup snapshot first (use mandu_rollback to undo). " +