diff --git a/CHANGELOG.md b/CHANGELOG.md index 8965e47..6d637ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.3] - 2026-02-01 + +- Harden MCP request handling + ## [0.2.2] - 2026-02-01 - Fix MCP tool naming for Claude Desktop diff --git a/README.md b/README.md index c06602b..7716f3c 100644 --- a/README.md +++ b/README.md @@ -194,22 +194,22 @@ Your code takes those arguments, calls mermkit, and sends the result back. The L ### MCP compatibility -mermkit ships an MCP server over stdio. Any MCP-compatible host (Claude Desktop, Cursor, etc.) can use it directly. +mermkit ships an MCP server over stdio. Any MCP-compatible host can use it directly. -**Claude Desktop configuration:** +**MCP host configuration (example):** ```json { "mcpServers": { "mermkit": { "command": "npx", - "args": ["@mermkit/cli", "mcp"] + "args": ["-y", "@mermkit/cli@0.2.3", "mcp"] } } } ``` -Once connected, the host gains access to the same five tools listed above. In MCP hosts that restrict tool names, they are exposed as `mermkit_render`, `mermkit_renderBatch`, `mermkit_extract`, `mermkit_term`, and `mermkit_schema`. The MCP server reuses the same rendering and extraction logic as `serve` — it is a format translation layer (JSON-RPC 2.0) with no additional dependencies. +Once connected, the host gains access to the same five tools listed above. For hosts that restrict tool names, they are exposed as `mermkit_render`, `mermkit_renderBatch`, `mermkit_extract`, `mermkit_term`, and `mermkit_schema`. The MCP server reuses the same rendering and extraction logic as `serve` — it is a format translation layer (JSON-RPC 2.0) with no additional dependencies. ## Preview server diff --git a/bindings/python/pyproject.toml b/bindings/python/pyproject.toml index f7a792a..b30ea06 100644 --- a/bindings/python/pyproject.toml +++ b/bindings/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mermkit" -version = "0.2.2" +version = "0.2.3" description = "Python bindings for mermkit (Mermaid rendering toolkit)" readme = "README.md" requires-python = ">=3.8" diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index bcb6360..bb159eb 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mermkit" -version = "0.2.2" +version = "0.2.3" edition = "2021" description = "Rust bindings for mermkit (Mermaid rendering toolkit)" license = "MIT" diff --git a/package.json b/package.json index a9cf026..b170c1b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mermkit", "private": true, - "version": "0.2.2", + "version": "0.2.3", "repository": { "type": "git", "url": "https://github.com/MermaidKit/mermkit.git" diff --git a/packages/adapters/package.json b/packages/adapters/package.json index f7379b9..ee0d625 100644 --- a/packages/adapters/package.json +++ b/packages/adapters/package.json @@ -1,6 +1,6 @@ { "name": "@mermkit/adapters", - "version": "0.2.2", + "version": "0.2.3", "repository": { "type": "git", "url": "https://github.com/MermaidKit/mermkit.git" @@ -18,6 +18,6 @@ "build": "tsc -b" }, "dependencies": { - "@mermkit/core": "0.2.2" + "@mermkit/core": "0.2.3" } } diff --git a/packages/cli/package.json b/packages/cli/package.json index e7e4af3..b6d12a0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@mermkit/cli", - "version": "0.2.2", + "version": "0.2.3", "repository": { "type": "git", "url": "https://github.com/MermaidKit/mermkit.git" @@ -19,8 +19,8 @@ "build": "tsc -b" }, "dependencies": { - "@mermkit/core": "0.2.2", - "@mermkit/render": "0.2.2", - "@mermkit/adapters": "0.2.2" + "@mermkit/core": "0.2.3", + "@mermkit/render": "0.2.3", + "@mermkit/adapters": "0.2.3" } } diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index d97720b..53b10c9 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { watch } from "node:fs"; +import { readFileSync, watch } from "node:fs"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { spawn } from "node:child_process"; import { createServer } from "node:http"; @@ -12,6 +12,8 @@ import { createHash } from "node:crypto"; import { extractDiagrams, normalizeDiagram } from "@mermkit/core"; import { render, renderForTerminal } from "@mermkit/render"; +const PACKAGE_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version as string; + type Flags = Record; const [command, ...rest] = process.argv.slice(2); @@ -753,11 +755,15 @@ function writeMcpResponse(response: JsonRpcResponse): void { stdout.write(`${JSON.stringify(response)}\n`); } +function shouldRespondToId(id: unknown): id is string | number { + return typeof id === "string" || typeof id === "number"; +} + function maybeWriteMcpResponse( - id: string | number | undefined, + id: unknown, response: Omit ): void { - if (typeof id !== "string" && typeof id !== "number") return; + if (!shouldRespondToId(id)) return; writeMcpResponse({ ...response, id }); } @@ -776,7 +782,8 @@ function getMcpToolNameMap(): Map { return MCP_TOOL_NAME_MAP; } -function resolveMcpToolName(name: string): string { +function resolveMcpToolName(name: unknown): string | undefined { + if (typeof name !== "string") return undefined; if (name.includes(".")) return name; return getMcpToolNameMap().get(name) ?? name; } @@ -799,10 +806,18 @@ async function cmdMcp(): Promise { try { request = JSON.parse(trimmed) as JsonRpcRequest; } catch (error) { + stderr.write(`mcp parse error: ${errorMessage(error)}\n`); continue; } const id = request.id; + if (!request.method || typeof request.method !== "string") { + maybeWriteMcpResponse(id, { + jsonrpc: "2.0", + error: { code: -32600, message: "invalid request: missing method" } + }); + continue; + } if (request.method === "initialize") { maybeWriteMcpResponse(id, { @@ -810,7 +825,7 @@ async function cmdMcp(): Promise { result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, - serverInfo: { name: "mermkit", version: "0.1.0" } + serverInfo: { name: "mermkit", version: PACKAGE_VERSION } } }); continue; @@ -826,10 +841,13 @@ async function cmdMcp(): Promise { if (request.method === "tools/call") { const params = request.params ?? {}; - const toolName = resolveMcpToolName(params.name as string); + const toolName = resolveMcpToolName((params as { name?: unknown }).name); const toolInput = (params.input ?? {}) as Record; try { + if (!toolName) { + throw new Error("invalid request: tools/call requires a tool name"); + } const content = await executeMcpTool(toolName, toolInput); maybeWriteMcpResponse(id, { jsonrpc: "2.0", result: { content } }); } catch (error) { diff --git a/packages/core/package.json b/packages/core/package.json index a5dcbd7..195b4b5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@mermkit/core", - "version": "0.2.2", + "version": "0.2.3", "repository": { "type": "git", "url": "https://github.com/MermaidKit/mermkit.git" diff --git a/packages/render/package.json b/packages/render/package.json index 357d221..499fb55 100644 --- a/packages/render/package.json +++ b/packages/render/package.json @@ -1,6 +1,6 @@ { "name": "@mermkit/render", - "version": "0.2.2", + "version": "0.2.3", "repository": { "type": "git", "url": "https://github.com/MermaidKit/mermkit.git" @@ -18,7 +18,7 @@ "build": "tsc -b" }, "dependencies": { - "@mermkit/core": "0.2.2" + "@mermkit/core": "0.2.3" }, "optionalDependencies": { "dompurify": "^3.1.5", diff --git a/site/index.html b/site/index.html index 46e4153..1ca2843 100644 --- a/site/index.html +++ b/site/index.html @@ -493,19 +493,27 @@

MCP setup

command
-
npx @mermkit/cli mcp
+
npx -y @mermkit/cli@0.2.3 mcp
-
Claude Desktop config
+
MCP host config
{
   "mcpServers": {
     "mermkit": {
       "command": "npx",
-      "args": ["@mermkit/cli", "mcp"]
+      "args": ["-y", "@mermkit/cli@0.2.3", "mcp"]
     }
   }
 }
+
+
tool names
+
mermkit_render
+mermkit_renderBatch
+mermkit_extract
+mermkit_term
+mermkit_schema
+