@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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +39 -0
- package/README.ko.md +7 -7
- package/README.md +9 -9
- package/cli/lib/mcp-server.mjs +208 -10
- package/cli/lib/site-analysis.mjs +297 -0
- package/cli/lib/site-args.mjs +433 -0
- package/cli/lib/site-bundle-build.mjs +127 -0
- package/cli/lib/site-bundle-check.mjs +454 -0
- package/cli/lib/site-bundle-commands.mjs +95 -0
- package/cli/lib/site-bundle-compare.mjs +157 -0
- package/cli/lib/site-bundle-contract.mjs +79 -0
- package/cli/lib/site-bundle-files.mjs +87 -0
- package/cli/lib/site-bundle-handoff-runbook-actions.mjs +113 -0
- package/cli/lib/site-bundle-handoff-runbook-evidence-fields.mjs +164 -0
- package/cli/lib/site-bundle-handoff-runbook-evidence.mjs +334 -0
- package/cli/lib/site-bundle-handoff-runbook-format.mjs +31 -0
- package/cli/lib/site-bundle-handoff-runbook-stage-metadata.mjs +84 -0
- package/cli/lib/site-bundle-handoff-runbook.mjs +1331 -0
- package/cli/lib/site-bundle-handoff-summary.mjs +183 -0
- package/cli/lib/site-bundle-handoff.mjs +271 -0
- package/cli/lib/site-bundle-readme.mjs +98 -0
- package/cli/lib/site-bundle-repair-report.mjs +143 -0
- package/cli/lib/site-bundle-repair.mjs +68 -0
- package/cli/lib/site-content.mjs +399 -0
- package/cli/lib/site-evidence.mjs +35 -0
- package/cli/lib/site-mcp-commands.mjs +28 -0
- package/cli/lib/site-mcp-probes.mjs +159 -0
- package/cli/lib/site-mcp-readiness.mjs +157 -0
- package/cli/lib/site-mcp-report.mjs +324 -0
- package/cli/lib/site-next-actions.mjs +333 -0
- package/cli/lib/site-options.mjs +104 -0
- package/cli/lib/site-prompts.mjs +332 -0
- package/cli/lib/site-starter.mjs +153 -0
- package/cli/lib/site-strings.mjs +23 -0
- package/cli/lib/site-tasks.mjs +93 -0
- package/cli/lib/site-workflow-graph.mjs +309 -0
- package/cli/lib/site-workspace.mjs +492 -0
- package/cli/lib/site.mjs +108 -6617
- package/docs/DISTRIBUTION.ko.md +35 -6
- package/docs/DISTRIBUTION.md +35 -8
- package/docs/RELEASE-CHECKLIST.md +20 -3
- package/docs/ROADMAP.md +2179 -0
- package/docs/external-status.md +22 -7
- package/docs/integrations/design-ai-mcp-server.md +32 -0
- package/docs/integrations/vscode-walkthrough.ko.md +3 -3
- package/docs/integrations/vscode-walkthrough.md +3 -3
- package/docs/site-overrides/main.html +1 -1
- package/package.json +1 -1
- package/tools/audit/package-smoke.py +106 -0
- package/tools/audit/registry-smoke.py +378 -10
- package/tools/audit/smoke_assertions.py +83 -22
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "design-ai",
|
|
3
|
-
"version": "4.
|
|
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
|
[](https://github.com/sungjin9288/design-ai/actions/workflows/audit.yml)
|
|
4
4
|
[](https://sungjin9288.github.io/design-ai/ko/)
|
|
5
5
|
[](knowledge/PRINCIPLES.md)
|
|
6
|
-
[](examples/README.md)
|
|
7
7
|
[](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-
|
|
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 합성) | ✓ |
|
|
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
|
|
45
|
+
### B. NPM
|
|
46
46
|
|
|
47
|
-
|
|
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/ #
|
|
123
|
+
├── examples/ # 221개 워크드 예제 ("good"이 어떻게 생겼는지)
|
|
124
124
|
│
|
|
125
125
|
├── skills/ # 20개 재사용 가능한 플레이북 (작업 중심)
|
|
126
126
|
├── agents/ # 4개 서브 에이전트 (병렬 리뷰)
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://github.com/sungjin9288/design-ai/actions/workflows/audit.yml)
|
|
4
4
|
[](https://sungjin9288.github.io/design-ai/)
|
|
5
5
|
[](knowledge/PRINCIPLES.md)
|
|
6
|
-
[](examples/README.md)
|
|
7
7
|
[](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-
|
|
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) | ✓ |
|
|
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
|
|
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
|
|
45
|
+
### Option B: NPM
|
|
46
46
|
|
|
47
|
-
Use this path
|
|
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
|
|
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** |
|
|
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/ #
|
|
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/
|
package/cli/lib/mcp-server.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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:
|
|
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
|
|
372
|
-
if (method === "ping")
|
|
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
|
-
|
|
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")
|
|
394
|
-
|
|
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 (
|
|
612
|
+
if (shouldWriteMcpResponse(message, response)) {
|
|
415
613
|
output.write(`${JSON.stringify(response)}\n`);
|
|
416
614
|
}
|
|
417
615
|
} catch (requestError) {
|
|
418
|
-
const id = message
|
|
616
|
+
const id = responseIdForMessage(message) ?? null;
|
|
419
617
|
output.write(`${JSON.stringify(errorResponse(id, -32603, requestError.message || String(requestError)))}\n`);
|
|
420
618
|
}
|
|
421
619
|
});
|