@design-ai/cli 4.55.0 → 4.56.0

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 (52) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +39 -0
  3. package/README.ko.md +7 -7
  4. package/README.md +9 -9
  5. package/cli/lib/mcp-server.mjs +208 -10
  6. package/cli/lib/site-analysis.mjs +297 -0
  7. package/cli/lib/site-args.mjs +433 -0
  8. package/cli/lib/site-bundle-build.mjs +127 -0
  9. package/cli/lib/site-bundle-check.mjs +454 -0
  10. package/cli/lib/site-bundle-commands.mjs +95 -0
  11. package/cli/lib/site-bundle-compare.mjs +157 -0
  12. package/cli/lib/site-bundle-contract.mjs +79 -0
  13. package/cli/lib/site-bundle-files.mjs +87 -0
  14. package/cli/lib/site-bundle-handoff-runbook-actions.mjs +113 -0
  15. package/cli/lib/site-bundle-handoff-runbook-evidence-fields.mjs +164 -0
  16. package/cli/lib/site-bundle-handoff-runbook-evidence.mjs +334 -0
  17. package/cli/lib/site-bundle-handoff-runbook-format.mjs +31 -0
  18. package/cli/lib/site-bundle-handoff-runbook-stage-metadata.mjs +84 -0
  19. package/cli/lib/site-bundle-handoff-runbook.mjs +1331 -0
  20. package/cli/lib/site-bundle-handoff-summary.mjs +183 -0
  21. package/cli/lib/site-bundle-handoff.mjs +271 -0
  22. package/cli/lib/site-bundle-readme.mjs +98 -0
  23. package/cli/lib/site-bundle-repair-report.mjs +143 -0
  24. package/cli/lib/site-bundle-repair.mjs +68 -0
  25. package/cli/lib/site-content.mjs +399 -0
  26. package/cli/lib/site-evidence.mjs +35 -0
  27. package/cli/lib/site-mcp-commands.mjs +28 -0
  28. package/cli/lib/site-mcp-probes.mjs +159 -0
  29. package/cli/lib/site-mcp-readiness.mjs +157 -0
  30. package/cli/lib/site-mcp-report.mjs +324 -0
  31. package/cli/lib/site-next-actions.mjs +333 -0
  32. package/cli/lib/site-options.mjs +104 -0
  33. package/cli/lib/site-prompts.mjs +332 -0
  34. package/cli/lib/site-starter.mjs +153 -0
  35. package/cli/lib/site-strings.mjs +23 -0
  36. package/cli/lib/site-tasks.mjs +93 -0
  37. package/cli/lib/site-workflow-graph.mjs +309 -0
  38. package/cli/lib/site-workspace.mjs +492 -0
  39. package/cli/lib/site.mjs +108 -6617
  40. package/docs/DISTRIBUTION.ko.md +35 -6
  41. package/docs/DISTRIBUTION.md +35 -8
  42. package/docs/RELEASE-CHECKLIST.md +20 -3
  43. package/docs/ROADMAP.md +2179 -0
  44. package/docs/external-status.md +22 -7
  45. package/docs/integrations/design-ai-mcp-server.md +32 -0
  46. package/docs/integrations/vscode-walkthrough.ko.md +3 -3
  47. package/docs/integrations/vscode-walkthrough.md +3 -3
  48. package/docs/site-overrides/main.html +1 -1
  49. package/package.json +1 -1
  50. package/tools/audit/package-smoke.py +106 -0
  51. package/tools/audit/registry-smoke.py +378 -10
  52. package/tools/audit/smoke_assertions.py +83 -22
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "design-ai",
3
- "version": "4.55.0",
3
+ "version": "4.56.0",
4
4
  "description": "Senior product designer for any AI coding agent. 20 skills, 17 commands, 4 review agents covering UI/UX, website improvement, design systems, motion, illustration, print, video, game UI, conversational, and spatial design. Korean market depth.",
5
5
  "author": {
6
6
  "name": "Sungjin Park",
package/CHANGELOG.md CHANGED
@@ -2,11 +2,50 @@
2
2
 
3
3
  User-facing release notes for design-ai. Versions follow semver.
4
4
 
5
+ ## v4.56.0 — MCP Protocol Hardening and Published Client Evidence (2026-07)
6
+
7
+ Hardened the local stdio MCP server that Claude Code, Codex, and other MCP clients use to call design-ai. This release turns malformed JSON-RPC envelopes, request ids, notifications, initialize params, and tool arguments into deterministic protocol errors before CLI execution, then refreshes evidence that the published npm MCP entrypoint and local Claude/Codex registrations work.
8
+
9
+ ### Added
10
+ - MCP tool argument validation now rejects unsupported fields, wrong JSON types, non-integer integer fields, out-of-range values, and blank required strings before invoking CLI commands.
11
+ - Public npm `design-ai-mcp` evidence now records initialize and tools/list protocol smoke from a clean one-shot `npm exec --package @design-ai/cli@4.55.0 -- design-ai-mcp` path.
12
+ - Local Claude Code and Codex client evidence now records the clone-backed `design-ai` MCP server as configured and connected.
13
+
14
+ ### Changed
15
+ - Website Improvement runbook helpers were split into clearer, smaller modules and named predicates while preserving the existing handoff JSON and Markdown contracts.
16
+ - MCP subprocess tests now use a shared stdio response helper, making negative protocol scenarios easier to read and extend.
17
+ - MCP setup docs now explain why one-shot public npm verification should run outside the source checkout.
18
+
19
+ ### Fixed
20
+ - MCP initialize now rejects malformed `protocolVersion` values, including non-string and blank-only protocol versions.
21
+ - MCP notifications now reject request ids across the whole `notifications/` namespace while preserving silent handling for valid id-less notifications.
22
+ - MCP response-producing methods now require ids, and malformed JSON-RPC request ids, methods, params, and tool-call containers return deterministic JSON-RPC errors.
23
+
24
+ ### Verified
25
+ - All 8 audits passed.
26
+ - `npm run release:check`.
27
+ - `npm run release:metadata`.
28
+ - `npm run audit:strict`.
29
+ - `git diff --check`.
30
+ - Public npm MCP protocol smoke returned server version `4.55.0` and 10 tools before the release bump.
31
+ - Main-branch GitHub Actions passed for `Design-AI audit` and `Deploy doc site` after the MCP client evidence refresh.
32
+
33
+ ### Versions
34
+ - `package.json` + `.claude-plugin/plugin.json`: 4.55.0 → 4.56.0.
35
+ - `vscode-extension/package.json`: remains 0.4.1.
36
+
37
+ ### What this enables
38
+ - Claude Code and Codex users get stricter, more predictable MCP error behavior without changing the design-ai tool surface.
39
+ - Maintainers can verify the published MCP entrypoint and local client registrations from concrete evidence before and after publishing.
40
+
5
41
  ## Unreleased — Agent Eval, Learning Signals, Skill Proposals, MCP Probes, Workflow Graphs, and Handoff Evidence
6
42
 
7
43
  Added deterministic route, prompt-plan, prompt-pack eval surfaces, a read-only learning signal registry with deterministic agent development backlog actions and structured readiness summaries, focused backlog readiness pass-through for local AI/agent handoffs with packed-tarball smoke coverage and release metadata guard coverage, preview-only skill evolution proposals, optional Website Console MCP probes with output-file smoke persistence and executable embedded follow-up commands, shared MCP probe output-file smoke assertions, release metadata guard coverage for shared MCP probe output-file smoke assertions, embedded MCP check probe next-step commands, executable embedded MCP check probe command smoke coverage, human-readable MCP check probe command guidance, packed/public smoke coverage for that human guidance, human report output-file persistence smoke coverage, embedded human report output commands, release metadata guard coverage for embedded human report output commands, MCP action plan JSON parity for embedded human report output commands, release metadata guard coverage for that action-plan parity, and packed/public smoke execution for action-plan emitted human report commands, structured Website Console MCP action plan JSON export with output-file persistence and embedded probe output-file commands, release metadata guard coverage for embedded MCP action plan probe output-file commands, next-action operator checklist output-file smoke persistence, portable Website Console workflow graph export, static Website Console graph rendering, browser-local Website Console handoff evidence tracking, CLI/bundle handoff evidence export, verified bundle evidence metadata, generated bundle contract verification with per-file diagnostics, repair guidance, repair preview/apply, repair report output-file persistence, repair report command guidance, executable repair guidance smoke coverage, shared repair guidance smoke helpers, shared repair report assertion helpers, release metadata guard coverage for shared repair guidance smoke helpers and shared repair report assertion helpers, public registry Website Console MCP probe smoke coverage, MCP probe action plan smoke coverage, shared Website Console site help topic example smoke assertions, release metadata guard coverage for shared Website Console site help topic example smoke assertions, release metadata guard coverage and packed-tarball smoke coverage for optional refresh-only agent backlog runbook selection semantics, post-commit release evidence for that refresh-only backlog smoke contract, full `release:check` evidence after the refresh-only backlog closeout, and packed-tarball evidence preservation smoke coverage for local AI/agent development drift review.
8
44
 
9
45
  ### Added
46
+ - Added MCP tool argument validation, so Claude Code and Codex receive clear typed errors for malformed tool calls before `design-ai` invokes any CLI command.
47
+ - Added packed-tarball smoke coverage for the standalone `design-ai-mcp` binary, covering both installed `node_modules/.bin/design-ai-mcp` and one-shot `npm exec --package <tarball> -- design-ai-mcp` setup paths.
48
+ - Added public registry smoke coverage for `npm exec --package @design-ai/cli@<version> -- design-ai-mcp`, so post-publish verification checks the same MCP stdio entrypoint that users install from npm.
10
49
  - Improved Website Console revalidation gate action labels, so compact gate copy/export buttons identify their JSON artifact type directly.
11
50
  - Improved Website Console Source Bundle action labels, so Markdown and provenance JSON copy/export buttons identify their artifact type directly.
12
51
  - Added Website Console Source Bundle detail source copy action, so operators can copy the current runbook source marker directly from the provenance table.
package/README.ko.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Audit](https://img.shields.io/badge/audit-passing-brightgreen)](https://github.com/sungjin9288/design-ai/actions/workflows/audit.yml)
4
4
  [![Docs](https://img.shields.io/badge/docs-live-indigo)](https://sungjin9288.github.io/design-ai/ko/)
5
5
  [![Knowledge files](https://img.shields.io/badge/knowledge-92-blue)](knowledge/PRINCIPLES.md)
6
- [![Examples](https://img.shields.io/badge/examples-223-blue)](examples/README.md)
6
+ [![Examples](https://img.shields.io/badge/examples-221-blue)](examples/README.md)
7
7
  [![Skills](https://img.shields.io/badge/skills-20-blue)](skills/README.md)
8
8
 
9
9
  > 🇰🇷 한국어 / [🇺🇸 English](https://github.com/sungjin9288/design-ai/blob/main/README.md)
@@ -12,14 +12,14 @@
12
12
 
13
13
  > **모델이 아니에요. 파인튜닝도 아니에요.** 디자인 전문 지식을 구조화한 코퍼스 + 에이전트가 바로 실행할 수 있는 지시문이에요. 범용 LLM을 이번 세션에서만큼은 시니어 디자이너로 바꿔주는 셈이에요.
14
14
 
15
- > **배포 상태, 2026-06-23 확인:** 로컬 `npm run release:check`는 통과했고 GitHub Pages 문서는 공개 URL에서 확인됐어요. public npm VS Code Marketplace 배포는 아직 확인되지 않았으므로 package publish가 끝나기 전까지는 Git clone/local install 경로를 사용하세요. 자세한 내용은 [`docs/external-status.md`](docs/external-status.md)를 확인하세요.
15
+ > **배포 상태, 2026-06-30 확인:** 로컬 `npm run release:check`는 통과했고 GitHub Pages 문서는 공개 URL에서 확인됐으며, GitHub Release `v4.55.0`, npm `@design-ai/cli@4.55.0`, VS Code Marketplace `sungjin.design-ai-vscode` publish가 확인됐어요. 자세한 내용은 [`docs/external-status.md`](docs/external-status.md)를 확인하세요.
16
16
 
17
17
  ## 한눈에 보는 커버리지
18
18
 
19
19
  | 영역 | 지식 | 워크드 예제 | 스킬 |
20
20
  |---|---|---|---|
21
21
  | 디자인 토큰 (W3C DTCG, OKLCH) | ✓ | ✓ | `color-palette` |
22
- | 컴포넌트 (Ant + MUI + shadcn 합성) | ✓ | 72 specs | `component-spec-writer` |
22
+ | 컴포넌트 (Ant + MUI + shadcn 합성) | ✓ | 210 component specs | `component-spec-writer` |
23
23
  | UX 패턴 (인증, 가격, 히어로, 폼 등) | ✓ | ✓ | `ux-audit`, `design-critique` |
24
24
  | 웹사이트 개선 컨트롤 타워 | ✓ | ✓ | `website-improvement` |
25
25
  | 한국어 i18n (한글, 결제, 앱스토어, 핀테크) | ✓ | ✓ | (전 영역 적용) |
@@ -34,7 +34,7 @@
34
34
 
35
35
  ## 설치 (Claude Code)
36
36
 
37
- ### A. Git clone / local install (현재 추천)
37
+ ### A. Git clone / local install
38
38
 
39
39
  ```bash
40
40
  git clone https://github.com/sungjin9288/design-ai.git
@@ -42,9 +42,9 @@ cd design-ai
42
42
  ./install.sh
43
43
  ```
44
44
 
45
- ### B. NPM (public publish 이후)
45
+ ### B. NPM
46
46
 
47
- `@design-ai/cli`가 publish되고 `npm run registry:smoke`가 통과한 뒤에만 이 경로를 사용하세요.
47
+ public npm package 설치 경로예요.
48
48
 
49
49
  ```bash
50
50
  npx @design-ai/cli install
@@ -120,7 +120,7 @@ design-ai/
120
120
  │ ├── spatial/ # VR / AR / 패널 / 편안함
121
121
  │ └── i18n/ # 한국어 타이포그래피, 결제, 앱스토어 등
122
122
 
123
- ├── examples/ # 223개 워크드 예제 ("good"이 어떻게 생겼는지)
123
+ ├── examples/ # 221개 워크드 예제 ("good"이 어떻게 생겼는지)
124
124
 
125
125
  ├── skills/ # 20개 재사용 가능한 플레이북 (작업 중심)
126
126
  ├── agents/ # 4개 서브 에이전트 (병렬 리뷰)
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![Audit](https://img.shields.io/badge/audit-passing-brightgreen)](https://github.com/sungjin9288/design-ai/actions/workflows/audit.yml)
4
4
  [![Docs](https://img.shields.io/badge/docs-live-indigo)](https://sungjin9288.github.io/design-ai/)
5
5
  [![Knowledge files](https://img.shields.io/badge/knowledge-92-blue)](knowledge/PRINCIPLES.md)
6
- [![Examples](https://img.shields.io/badge/examples-223-blue)](examples/README.md)
6
+ [![Examples](https://img.shields.io/badge/examples-221-blue)](examples/README.md)
7
7
  [![Skills](https://img.shields.io/badge/skills-20-blue)](skills/README.md)
8
8
 
9
9
  > 🇺🇸 English / [🇰🇷 한국어](https://github.com/sungjin9288/design-ai/blob/main/README.ko.md)
@@ -12,14 +12,14 @@ A model-agnostic design knowledge base + skill system. Drop it in front of any A
12
12
 
13
13
  > **Not a model. Not a fine-tune.** A structured corpus of design expertise + agent-ready instructions that turn a general-purpose LLM into an expert.
14
14
 
15
- > **Distribution status, checked 2026-06-23:** local `npm run release:check` passes and GitHub Pages docs are live. Public npm and VS Code Marketplace distribution are not currently confirmed, so use the Git clone/local install path until package publishing is completed. See [`docs/external-status.md`](docs/external-status.md).
15
+ > **Distribution status, checked 2026-06-30:** local `npm run release:check` passes, GitHub Pages docs are live, GitHub Release `v4.55.0` is published, `@design-ai/cli@4.55.0` is public on npm with registry smoke coverage, and `sungjin.design-ai-vscode` is public on the VS Code Marketplace. See [`docs/external-status.md`](docs/external-status.md).
16
16
 
17
17
  ## Coverage at a glance
18
18
 
19
19
  | Domain | Knowledge | Worked examples | Skill |
20
20
  |---|---|---|---|
21
21
  | Design tokens (W3C DTCG, OKLCH) | ✓ | ✓ | `color-palette` |
22
- | Components (Ant + MUI + shadcn synthesis) | ✓ | 47 specs | `component-spec-writer` |
22
+ | Components (Ant + MUI + shadcn synthesis) | ✓ | 210 component specs | `component-spec-writer` |
23
23
  | UX patterns (auth, pricing, hero, forms, etc.) | ✓ | ✓ | `ux-audit`, `design-critique` |
24
24
  | Website improvement control tower | ✓ | ✓ | `website-improvement` |
25
25
  | Korean i18n (Hangul, payments, app store, fintech) | ✓ | ✓ | (cross-cutting) |
@@ -34,7 +34,7 @@ A model-agnostic design knowledge base + skill system. Drop it in front of any A
34
34
 
35
35
  ## Install (Claude Code)
36
36
 
37
- ### Option A: Git clone / local install (currently recommended)
37
+ ### Option A: Git clone / local install
38
38
 
39
39
  ```bash
40
40
  git clone https://github.com/sungjin9288/design-ai.git
@@ -42,9 +42,9 @@ cd design-ai
42
42
  ./install.sh
43
43
  ```
44
44
 
45
- ### Option B: NPM (after public publish)
45
+ ### Option B: NPM
46
46
 
47
- Use this path only after `@design-ai/cli` is published and `npm run registry:smoke` passes.
47
+ Use this path for the public npm package.
48
48
 
49
49
  ```bash
50
50
  npx @design-ai/cli install
@@ -57,7 +57,7 @@ npm install -g @design-ai/cli
57
57
  design-ai install
58
58
  ```
59
59
 
60
- ### Option C: Homebrew (after release tap verification)
60
+ ### Option C: Homebrew tap
61
61
 
62
62
  ```bash
63
63
  brew tap sungjin9288/design-ai https://github.com/sungjin9288/design-ai.git
@@ -88,7 +88,7 @@ See [`docs/DISTRIBUTION.md`](docs/DISTRIBUTION.md) for the full distribution gui
88
88
  | **Cursor** | Open this dir; symlink or copy `AGENTS.md` to `.cursorrules`. [Walkthrough](docs/integrations/cursor-walkthrough.md). |
89
89
  | **Aider** | Pass `AGENTS.md` as system prompt. [Walkthrough](docs/integrations/aider-walkthrough.md). |
90
90
  | **Anthropic / OpenAI SDK** | Embed relevant skill `PLAYBOOK.md` files in your prompt. [Walkthrough](docs/integrations/sdk-walkthrough.md). |
91
- | **VS Code** | Use the local extension workspace for sidebar tree + quick-pick commands until Marketplace publish is complete. [Walkthrough](docs/integrations/vscode-walkthrough.md). |
91
+ | **VS Code** | Install the public Marketplace extension for sidebar trees + quick-pick commands. [Walkthrough](docs/integrations/vscode-walkthrough.md). |
92
92
  | **Plain prompt** | Paste any `skills/*/PLAYBOOK.md` body — each is self-contained. |
93
93
 
94
94
  See [`docs/USING.md`](docs/USING.md) for per-agent setup details, or the linked walkthroughs for concrete example sessions.
@@ -125,7 +125,7 @@ design-ai/
125
125
  │ ├── spatial/ # VR / AR / panels / comfort
126
126
  │ └── i18n/ # Korean typography, payments, app store, ...
127
127
 
128
- ├── examples/ # 223 worked outputs (what "good" looks like)
128
+ ├── examples/ # 221 worked outputs (what "good" looks like)
129
129
 
130
130
  ├── skills/ # 20 reusable playbooks (task-focused)
131
131
  │ ├── design-system-builder/ illustration-designer/
@@ -10,6 +10,14 @@ import { DESIGN_AI_HOME, PACKAGE_ROOT } from "./paths.mjs";
10
10
  const PROTOCOL_VERSION = "2025-11-25";
11
11
  const MAX_TOOL_OUTPUT_BYTES = 220_000;
12
12
  const DESIGN_AI_BIN = path.join(PACKAGE_ROOT, "cli", "bin", "design-ai.mjs");
13
+ const MCP_RESPONSE_METHODS = new Set([
14
+ "initialize",
15
+ "ping",
16
+ "tools/list",
17
+ "tools/call",
18
+ "resources/list",
19
+ "prompts/list",
20
+ ]);
13
21
 
14
22
  function optionalString(description = "") {
15
23
  return { type: "string", description };
@@ -196,6 +204,51 @@ function assertString(value, name) {
196
204
  return value;
197
205
  }
198
206
 
207
+ function assertMcpInputValue(name, value, schema) {
208
+ if (schema.type === "string") {
209
+ if (typeof value !== "string") throw new Error(`${name} must be a string`);
210
+ if (schema.minLength && value.trim() === "") {
211
+ throw new Error(`${name} must be a non-empty string`);
212
+ }
213
+ if (schema.minLength && value.length < schema.minLength) {
214
+ throw new Error(`${name} must be at least ${schema.minLength} characters`);
215
+ }
216
+ return;
217
+ }
218
+
219
+ if (schema.type === "boolean") {
220
+ if (typeof value !== "boolean") throw new Error(`${name} must be a boolean`);
221
+ return;
222
+ }
223
+
224
+ if (schema.type === "integer") {
225
+ if (!Number.isInteger(value)) throw new Error(`${name} must be an integer`);
226
+ if (schema.minimum !== undefined && value < schema.minimum) {
227
+ throw new Error(`${name} must be at least ${schema.minimum}`);
228
+ }
229
+ if (schema.maximum !== undefined && value > schema.maximum) {
230
+ throw new Error(`${name} must be at most ${schema.maximum}`);
231
+ }
232
+ }
233
+ }
234
+
235
+ function assertMcpToolInput(tool, input) {
236
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
237
+ throw new Error(`${tool.name} arguments must be an object`);
238
+ }
239
+
240
+ const properties = tool.inputSchema.properties || {};
241
+ for (const name of tool.inputSchema.required || []) {
242
+ if (!Object.hasOwn(input, name)) throw new Error(`${tool.name}.${name} is required`);
243
+ }
244
+
245
+ for (const [name, value] of Object.entries(input)) {
246
+ const schema = properties[name];
247
+ if (!schema) throw new Error(`Unknown argument for ${tool.name}: ${name}`);
248
+ assertMcpInputValue(`${tool.name}.${name}`, value, schema);
249
+ }
250
+ }
251
+
199
252
  function maybePush(args, flag, value) {
200
253
  if (value !== undefined && value !== null && value !== "") args.push(flag, String(value));
201
254
  }
@@ -328,6 +381,9 @@ export function runDesignAiCli(args, { stdin = "" } = {}) {
328
381
  }
329
382
 
330
383
  export async function callMcpTool(name, input = {}, runCli = runDesignAiCli) {
384
+ const tool = MCP_TOOLS.find((item) => item.name === name);
385
+ if (tool) assertMcpToolInput(tool, input || {});
386
+
331
387
  const invocation = buildCliInvocation(name, input || {});
332
388
  const result = await runCli(invocation.args, { stdin: invocation.stdin });
333
389
  const text = [
@@ -349,14 +405,131 @@ function errorResponse(id, code, message) {
349
405
  return { jsonrpc: "2.0", id: id ?? null, error: { code, message } };
350
406
  }
351
407
 
408
+ function chooseProtocolVersion(requestedVersion) {
409
+ return requestedVersion === PROTOCOL_VERSION ? requestedVersion : PROTOCOL_VERSION;
410
+ }
411
+
412
+ function isObjectRecord(value) {
413
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
414
+ }
415
+
416
+ function validateMcpRequestEnvelope(message) {
417
+ if (!isObjectRecord(message)) {
418
+ return "Invalid MCP request: request must be an object";
419
+ }
420
+ if (message.jsonrpc !== "2.0") {
421
+ return 'Invalid MCP request: jsonrpc must be "2.0"';
422
+ }
423
+ if (hasRequestId(message) && !isValidMcpRequestId(message.id)) {
424
+ return "Invalid MCP request: id must be a string, number, or null";
425
+ }
426
+ return "";
427
+ }
428
+
429
+ function validateMcpRequestMethod(message) {
430
+ if (!Object.hasOwn(message, "method")) {
431
+ return "Invalid MCP request: missing method";
432
+ }
433
+ if (typeof message.method !== "string" || message.method.trim() === "") {
434
+ return "Invalid MCP request: method must be a non-empty string";
435
+ }
436
+ return "";
437
+ }
438
+
439
+ function isMcpNotificationMethod(method) {
440
+ return typeof method === "string" && method.startsWith("notifications/");
441
+ }
442
+
443
+ function validateMcpNotificationRequest(message) {
444
+ if (isMcpNotificationMethod(message.method) && hasRequestId(message)) {
445
+ return `Invalid MCP request: ${message.method} must not include id`;
446
+ }
447
+ return "";
448
+ }
449
+
450
+ function validateMcpRequestIdRequirement(message) {
451
+ if (MCP_RESPONSE_METHODS.has(message.method) && !hasRequestId(message)) {
452
+ return `Invalid MCP request: ${message.method} must include id`;
453
+ }
454
+ return "";
455
+ }
456
+
457
+ function validateOptionalObjectParams(method, params) {
458
+ if (params === undefined || isObjectRecord(params)) return "";
459
+ return `${method} params must be an object when provided`;
460
+ }
461
+
462
+ function validateToolCallParams(params) {
463
+ if (!isObjectRecord(params)) {
464
+ return "tools/call params must be an object";
465
+ }
466
+ if (typeof params.name !== "string" || params.name.trim() === "") {
467
+ return "tools/call params.name must be a non-empty string";
468
+ }
469
+ if (Object.hasOwn(params, "arguments") && params.arguments !== undefined && !isObjectRecord(params.arguments)) {
470
+ return "tools/call params.arguments must be an object when provided";
471
+ }
472
+ return "";
473
+ }
474
+
475
+ function validateInitializeParams(params) {
476
+ if (!isObjectRecord(params)) {
477
+ return "initialize params must be an object";
478
+ }
479
+ if (Object.hasOwn(params, "protocolVersion") && typeof params.protocolVersion !== "string") {
480
+ return "initialize params.protocolVersion must be a string";
481
+ }
482
+ if (typeof params.protocolVersion === "string" && params.protocolVersion.trim() === "") {
483
+ return "initialize params.protocolVersion must be a non-empty string";
484
+ }
485
+ return "";
486
+ }
487
+
488
+ function hasRequestId(message) {
489
+ return isObjectRecord(message) && Object.hasOwn(message, "id");
490
+ }
491
+
492
+ function isValidMcpRequestId(id) {
493
+ return id === null || typeof id === "string" || (typeof id === "number" && Number.isFinite(id));
494
+ }
495
+
496
+ function responseIdForMessage(message) {
497
+ if (!hasRequestId(message)) return undefined;
498
+ return isValidMcpRequestId(message.id) ? message.id : undefined;
499
+ }
500
+
501
+ function isInvalidRequestResponse(response) {
502
+ return response?.error?.code === -32600;
503
+ }
504
+
505
+ function shouldWriteMcpResponse(message, response) {
506
+ if (!response) return false;
507
+ return hasRequestId(message) || isInvalidRequestResponse(response);
508
+ }
509
+
352
510
  export async function handleMcpRequest(message, { runCli = runDesignAiCli } = {}) {
353
- const { id, method, params = {} } = message || {};
511
+ const id = responseIdForMessage(message);
512
+ const envelopeError = validateMcpRequestEnvelope(message);
513
+ if (envelopeError) return errorResponse(id, -32600, envelopeError);
514
+
515
+ const methodError = validateMcpRequestMethod(message);
516
+ if (methodError) return errorResponse(id, -32600, methodError);
517
+
518
+ const notificationError = validateMcpNotificationRequest(message);
519
+ if (notificationError) return errorResponse(id, -32600, notificationError);
520
+
521
+ const requestIdError = validateMcpRequestIdRequirement(message);
522
+ if (requestIdError) return errorResponse(id, -32600, requestIdError);
354
523
 
355
- if (!method) return errorResponse(id, -32600, "Invalid MCP request: missing method");
524
+ const { method, params } = message;
356
525
 
357
526
  if (method === "initialize") {
527
+ const initializeParams = params === undefined ? {} : params;
528
+ const paramsError = validateInitializeParams(initializeParams);
529
+ if (paramsError) return errorResponse(id, -32602, paramsError);
530
+
358
531
  return successResponse(id, {
359
- protocolVersion: params.protocolVersion || PROTOCOL_VERSION,
532
+ protocolVersion: chooseProtocolVersion(initializeParams.protocolVersion),
360
533
  capabilities: {
361
534
  tools: { listChanged: false },
362
535
  },
@@ -368,19 +541,36 @@ export async function handleMcpRequest(message, { runCli = runDesignAiCli } = {}
368
541
  });
369
542
  }
370
543
 
371
- if (method === "notifications/initialized") return null;
372
- if (method === "ping") return successResponse(id, {});
544
+ if (isMcpNotificationMethod(method)) return null;
545
+ if (method === "ping") {
546
+ const paramsError = validateOptionalObjectParams(method, params);
547
+ if (paramsError) return errorResponse(id, -32602, paramsError);
548
+ return successResponse(id, {});
549
+ }
373
550
 
374
551
  if (method === "tools/list") {
552
+ const paramsError = validateOptionalObjectParams(method, params);
553
+ if (paramsError) return errorResponse(id, -32602, paramsError);
375
554
  return successResponse(id, { tools: MCP_TOOLS });
376
555
  }
377
556
 
378
557
  if (method === "tools/call") {
558
+ const paramsError = validateToolCallParams(params);
559
+ if (paramsError) return errorResponse(id, -32602, paramsError);
560
+
379
561
  const name = params.name;
380
562
  const tool = MCP_TOOLS.find((item) => item.name === name);
381
563
  if (!tool) return errorResponse(id, -32602, `Unknown tool: ${name}`);
564
+
565
+ const toolArguments = params.arguments === undefined ? {} : params.arguments;
382
566
  try {
383
- const result = await callMcpTool(name, params.arguments || {}, runCli);
567
+ assertMcpToolInput(tool, toolArguments);
568
+ } catch (validationError) {
569
+ return errorResponse(id, -32602, validationError.message || String(validationError));
570
+ }
571
+
572
+ try {
573
+ const result = await callMcpTool(name, toolArguments, runCli);
384
574
  return successResponse(id, result);
385
575
  } catch (error) {
386
576
  return successResponse(id, {
@@ -390,8 +580,16 @@ export async function handleMcpRequest(message, { runCli = runDesignAiCli } = {}
390
580
  }
391
581
  }
392
582
 
393
- if (method === "resources/list") return successResponse(id, { resources: [] });
394
- if (method === "prompts/list") return successResponse(id, { prompts: [] });
583
+ if (method === "resources/list") {
584
+ const paramsError = validateOptionalObjectParams(method, params);
585
+ if (paramsError) return errorResponse(id, -32602, paramsError);
586
+ return successResponse(id, { resources: [] });
587
+ }
588
+ if (method === "prompts/list") {
589
+ const paramsError = validateOptionalObjectParams(method, params);
590
+ if (paramsError) return errorResponse(id, -32602, paramsError);
591
+ return successResponse(id, { prompts: [] });
592
+ }
395
593
 
396
594
  return errorResponse(id, -32601, `Method not found: ${method}`);
397
595
  }
@@ -411,11 +609,11 @@ export function startMcpStdioServer({ input = process.stdin, output = process.st
411
609
 
412
610
  try {
413
611
  const response = await handleMcpRequest(message);
414
- if (response && message.id !== undefined) {
612
+ if (shouldWriteMcpResponse(message, response)) {
415
613
  output.write(`${JSON.stringify(response)}\n`);
416
614
  }
417
615
  } catch (requestError) {
418
- const id = message && message.id !== undefined ? message.id : null;
616
+ const id = responseIdForMessage(message) ?? null;
419
617
  output.write(`${JSON.stringify(errorResponse(id, -32603, requestError.message || String(requestError)))}\n`);
420
618
  }
421
619
  });