diff --git a/.github/workflows/ci-mcp.yml b/.github/workflows/ci-mcp.yml
new file mode 100644
index 000000000..4091c82ad
--- /dev/null
+++ b/.github/workflows/ci-mcp.yml
@@ -0,0 +1,41 @@
+name: CI MCP
+
+permissions:
+ contents: read
+
+on:
+ pull_request:
+ paths:
+ - 'apps/mcp/**'
+ workflow_dispatch:
+
+concurrency:
+ group: ci-mcp-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+ validate:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: pnpm/action-setup@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: .nvmrc
+ cache: pnpm
+
+ - uses: oven-sh/setup-bun@v2
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build superdoc (dependency)
+ run: pnpm run build:superdoc
+
+ - name: Build
+ run: pnpm --prefix apps/mcp run build
+
+ - name: Test
+ run: pnpm --prefix apps/mcp run test
diff --git a/.github/workflows/release-mcp.yml b/.github/workflows/release-mcp.yml
new file mode 100644
index 000000000..e6ead9a52
--- /dev/null
+++ b/.github/workflows/release-mcp.yml
@@ -0,0 +1,64 @@
+# Auto-releases on push to main (@next channel)
+# For stable (@latest): cherry-pick commits to stable branch, then manually dispatch this workflow
+name: 📦 Release MCP
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - 'apps/mcp/**'
+ - '!**/*.md'
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ packages: write
+
+concurrency:
+ group: release-mcp-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ release:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Generate token
+ id: generate_token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ secrets.APP_ID }}
+ private-key: ${{ secrets.APP_PRIVATE_KEY }}
+
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ token: ${{ steps.generate_token.outputs.token }}
+
+ - uses: pnpm/action-setup@v4
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version-file: .nvmrc
+ cache: pnpm
+ registry-url: 'https://registry.npmjs.org'
+
+ - uses: oven-sh/setup-bun@v2
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Build superdoc (dependency)
+ run: pnpm run build:superdoc
+
+ - name: Build MCP server
+ run: pnpm --prefix apps/mcp run build
+
+ - name: Release
+ env:
+ GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+ LINEAR_TOKEN: ${{ secrets.LINEAR_TOKEN }}
+ working-directory: apps/mcp
+ run: pnpx semantic-release
diff --git a/apps/docs/docs.json b/apps/docs/docs.json
index 80e400fc6..b5e881d67 100644
--- a/apps/docs/docs.json
+++ b/apps/docs/docs.json
@@ -96,7 +96,11 @@
"document-engine/overview",
"document-api/overview",
"document-engine/sdks",
- "document-engine/cli"
+ "document-engine/cli",
+ {
+ "page": "document-engine/mcp",
+ "tag": "NEW"
+ }
]
},
{
diff --git a/apps/docs/document-engine/mcp.mdx b/apps/docs/document-engine/mcp.mdx
new file mode 100644
index 000000000..06f3a8b73
--- /dev/null
+++ b/apps/docs/document-engine/mcp.mdx
@@ -0,0 +1,193 @@
+---
+title: MCP Server
+sidebarTitle: MCP Server
+description: Give AI agents direct access to .docx files through the Model Context Protocol
+keywords: "mcp, model context protocol, ai agents, claude, cursor, windsurf, document automation, llm docx, track changes, comments"
+---
+
+The SuperDoc MCP server lets AI agents open, read, edit, and save `.docx` files. It exposes the same operations as the [Document API](/document-api/overview) through the [Model Context Protocol](https://modelcontextprotocol.io) — the open standard for connecting AI tools to agents.
+
+
+The MCP server is in alpha. Tools and output formats may change.
+
+
+## Setup
+
+Install once. Your MCP client spawns the server automatically on each conversation.
+
+
+
+ ```bash
+ claude mcp add superdoc -- npx @superdoc-dev/mcp
+ ```
+
+
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
+
+ ```json
+ {
+ "mcpServers": {
+ "superdoc": {
+ "command": "npx",
+ "args": ["@superdoc-dev/mcp"]
+ }
+ }
+ }
+ ```
+
+
+ Add to `~/.cursor/mcp.json`:
+
+ ```json
+ {
+ "mcpServers": {
+ "superdoc": {
+ "command": "npx",
+ "args": ["@superdoc-dev/mcp"]
+ }
+ }
+ }
+ ```
+
+
+ Add to `~/.codeium/windsurf/mcp_config.json`:
+
+ ```json
+ {
+ "mcpServers": {
+ "superdoc": {
+ "command": "npx",
+ "args": ["@superdoc-dev/mcp"]
+ }
+ }
+ }
+ ```
+
+
+
+## Workflow
+
+Every interaction follows the same pattern: open, read or edit, save, close.
+
+```
+superdoc_open → superdoc_find / superdoc_get_text → edit tools → superdoc_save → superdoc_close
+```
+
+1. `superdoc_open` loads a `.docx` file and returns a `session_id`
+2. `superdoc_find` locates content and returns target addresses
+3. Edit tools (`superdoc_insert`, `superdoc_replace`, `superdoc_delete`, `superdoc_format`) use those addresses
+4. `superdoc_save` writes changes to disk
+5. `superdoc_close` releases the session
+
+## Tools
+
+23 tools in eight groups. All tools take a `session_id` from `superdoc_open`.
+
+### Lifecycle
+
+| Tool | Input | Description |
+| --- | --- | --- |
+| `superdoc_open` | `path` | Open a `.docx` file. Returns `session_id` and file path |
+| `superdoc_save` | `session_id`, `out?` | Save to the original path, or to `out` if specified |
+| `superdoc_close` | `session_id` | Close the session. Unsaved changes are lost |
+
+### Query
+
+| Tool | Input | Description |
+| --- | --- | --- |
+| `superdoc_find` | `session_id`, `type?`, `pattern?`, `limit?`, `offset?` | Search by node type, text pattern, or both. Returns matches with addresses |
+| `superdoc_get_node` | `session_id`, `address` | Get details about a specific node by its address |
+| `superdoc_info` | `session_id` | Document metadata: structure summary, node counts, capabilities |
+| `superdoc_get_text` | `session_id` | Full plain-text content of the document |
+
+### Mutation
+
+All mutation tools accept `suggest?` — set to `true` to make the edit a tracked change instead of a direct edit.
+
+| Tool | Input | Description |
+| --- | --- | --- |
+| `superdoc_insert` | `session_id`, `text`, `target`, `suggest?` | Insert text at a target position |
+| `superdoc_replace` | `session_id`, `text`, `target`, `suggest?` | Replace content at a target range |
+| `superdoc_delete` | `session_id`, `target`, `suggest?` | Delete content at a target range |
+
+### Format
+
+| Tool | Input | Description |
+| --- | --- | --- |
+| `superdoc_format` | `session_id`, `style`, `target`, `suggest?` | Toggle formatting on a text range. Styles: `bold`, `italic`, `underline`, `strikethrough` |
+
+### Create
+
+| Tool | Input | Description |
+| --- | --- | --- |
+| `superdoc_create` | `session_id`, `type`, `text?`, `level?`, `at?`, `suggest?` | Create a block element. Types: `paragraph`, `heading` (headings require `level` 1-6) |
+
+### Track changes
+
+Review and resolve tracked changes (suggestions) in the document.
+
+| Tool | Input | Description |
+| --- | --- | --- |
+| `superdoc_list_changes` | `session_id`, `type?`, `limit?`, `offset?` | List tracked changes with type, author, date, and excerpt |
+| `superdoc_accept_change` | `session_id`, `id` | Accept a single change, applying it to the document |
+| `superdoc_reject_change` | `session_id`, `id` | Reject a single change, reverting it |
+| `superdoc_accept_all_changes` | `session_id` | Accept all tracked changes at once |
+| `superdoc_reject_all_changes` | `session_id` | Reject all tracked changes at once |
+
+### Comments
+
+Add and manage comments anchored to text ranges.
+
+| Tool | Input | Description |
+| --- | --- | --- |
+| `superdoc_add_comment` | `session_id`, `text`, `target` | Add a comment anchored to a text range |
+| `superdoc_list_comments` | `session_id`, `include_resolved?` | List all comments with author, status, and anchored text |
+| `superdoc_reply_comment` | `session_id`, `comment_id`, `text` | Reply to an existing comment thread |
+| `superdoc_resolve_comment` | `session_id`, `comment_id` | Mark a comment as resolved |
+
+### Lists
+
+| Tool | Input | Description |
+| --- | --- | --- |
+| `superdoc_insert_list` | `session_id`, `target`, `position`, `text?` | Insert a list item before or after an existing one |
+| `superdoc_list_set_type` | `session_id`, `target`, `kind` | Change a list between `ordered` (numbered) and `bullet` |
+
+## Suggesting mode
+
+Set `suggest=true` on any mutation, format, or create tool to make edits appear as tracked changes. The document stays unchanged until someone accepts the suggestions — in Word, in SuperDoc's browser editor, or programmatically via the track changes tools.
+
+## How it works
+
+The MCP server runs as a local subprocess, communicating over stdio. It manages document sessions in memory — each `superdoc_open` creates an Editor instance, and all subsequent operations run against that in-memory state until you `superdoc_save`.
+
+```
+AI Agent (Claude, Cursor, Windsurf)
+ │ MCP protocol (stdio)
+ â–¼
+@superdoc-dev/mcp
+ │ Document API
+ â–¼
+SuperDoc Editor (in-memory)
+ │ export
+ â–¼
+.docx file on disk
+```
+
+Your documents never leave your machine. The server runs locally, reads files from disk, and writes back to disk.
+
+## Debugging
+
+Test the server directly with the MCP Inspector:
+
+```bash
+npx @modelcontextprotocol/inspector -- npx @superdoc-dev/mcp
+```
+
+This opens a browser UI where you can call each tool manually and inspect the raw JSON-RPC messages.
+
+## Related
+
+- [CLI](/document-engine/cli) — edit documents from the terminal
+- [SDKs](/document-engine/sdks) — typed Node.js and Python wrappers
+- [Document API](/document-api/overview) — the in-browser API that defines the operation set
+- [AI Agents](/getting-started/ai-agents) — headless mode for server-side AI workflows
diff --git a/apps/docs/document-engine/overview.mdx b/apps/docs/document-engine/overview.mdx
index a426f5c41..0197f695e 100644
--- a/apps/docs/document-engine/overview.mdx
+++ b/apps/docs/document-engine/overview.mdx
@@ -9,24 +9,26 @@ keywords: "document engine, document api, sdk, cli, headless docx, document auto
Document Engine is in alpha and subject to breaking changes while the contract and adapters continue to evolve.
-Document Engine is the programmatic surface of SuperDoc. It gives you three ways to read and edit `.docx` files without a visible editor:
+Document Engine is the programmatic surface of SuperDoc. It gives you four ways to read and edit `.docx` files without a visible editor:
| Surface | Use case | Runtime |
| --- | --- | --- |
| [Document API](/document-api/overview) | In-browser editing via `editor.doc.*` methods | Browser (ProseMirror) |
| [SDKs](/document-engine/sdks) | Node.js and Python wrappers for backend automation | Node / Python |
| [CLI](/document-engine/cli) | Terminal commands for scripting and CI pipelines | Any shell |
+| [MCP Server](/document-engine/mcp) | AI agent access via the Model Context Protocol | Local subprocess |
-All three surfaces share the same operation set. An operation like `find` is available as `editor.doc.find()` in the browser, `superdoc.doc.find()` in the SDK, and `superdoc find` in the CLI.
+All four surfaces share the same operation set. An operation like `find` is available as `editor.doc.find()` in the browser, `superdoc.doc.find()` in the SDK, `superdoc find` in the CLI, and `superdoc_find` in MCP.
## How it works
-The Document API defines the canonical operations. The CLI wraps them in a stdio-based process. The SDKs manage the CLI process and expose typed methods for each operation.
+The Document API defines the canonical operations. The CLI and MCP server wrap them for different consumers. The SDKs manage the CLI process and expose typed methods.
```
Document API (source of truth)
- → CLI (stdio process)
- → Node SDK / Python SDK (typed wrappers)
+ → CLI (terminal interface)
+ → MCP Server (AI agent interface)
+ → Node SDK / Python SDK (typed wrappers over CLI)
```
## Where to start
@@ -34,3 +36,4 @@ Document API (source of truth)
- **Building a web editor?** Start with the [Document API](/document-api/overview).
- **Automating documents from a backend?** Start with the [SDKs](/document-engine/sdks).
- **Scripting from the terminal or CI?** Start with the [CLI](/document-engine/cli).
+- **Connecting AI agents (Claude, Cursor, Windsurf)?** Start with the [MCP Server](/document-engine/mcp).
diff --git a/apps/mcp/.releaserc.cjs b/apps/mcp/.releaserc.cjs
new file mode 100644
index 000000000..9b4f98cff
--- /dev/null
+++ b/apps/mcp/.releaserc.cjs
@@ -0,0 +1,45 @@
+/* eslint-env node */
+const branch = process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_BRANCH;
+
+const config = {
+ branches: [
+ { name: 'stable', channel: 'latest' },
+ { name: 'main', prerelease: 'next', channel: 'next' },
+ ],
+ tagFormat: 'mcp-v${version}',
+ plugins: [
+ 'semantic-release-commit-filter',
+ '@semantic-release/commit-analyzer',
+ '@semantic-release/release-notes-generator',
+ ['@semantic-release/npm'],
+ ],
+};
+
+const isPrerelease = config.branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease);
+
+if (!isPrerelease) {
+ config.plugins.push([
+ '@semantic-release/git',
+ {
+ assets: ['package.json'],
+ message: 'chore(mcp): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
+ },
+ ]);
+}
+
+// Linear integration - labels issues with version on release
+config.plugins.push(['semantic-release-linear-app', {
+ teamKeys: ['SD'],
+ addComment: true,
+ packageName: 'mcp',
+ commentTemplate: 'shipped in {package} {releaseLink} {channel}'
+}]);
+
+config.plugins.push([
+ '@semantic-release/github',
+ {
+ successComment: ':tada: This ${issue.pull_request ? "PR" : "issue"} is included in **@superdoc-dev/mcp** v${nextRelease.version}\n\nThe release is available on [GitHub release](${releases.find(release => release.pluginName === "@semantic-release/github").url})',
+ }
+]);
+
+module.exports = config;
diff --git a/apps/mcp/README.md b/apps/mcp/README.md
new file mode 100644
index 000000000..f6d30b0bc
--- /dev/null
+++ b/apps/mcp/README.md
@@ -0,0 +1,168 @@
+# @superdoc-dev/mcp
+
+MCP server for SuperDoc. Lets AI agents open, read, edit, and save `.docx` files through the [Model Context Protocol](https://modelcontextprotocol.io).
+
+Works with Claude Code, Claude Desktop, Cursor, Windsurf, OpenAI Codex, and any MCP-compatible client.
+
+## Quick start
+
+```bash
+npx @superdoc-dev/mcp
+```
+
+The server communicates over stdio. You don't run it directly — your MCP client spawns it as a subprocess.
+
+## Setup
+
+### Claude Code
+
+```bash
+claude mcp add superdoc -- npx @superdoc-dev/mcp
+```
+
+### Claude Desktop
+
+Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
+
+```json
+{
+ "mcpServers": {
+ "superdoc": {
+ "command": "npx",
+ "args": ["@superdoc-dev/mcp"]
+ }
+ }
+}
+```
+
+### Cursor
+
+Add to `~/.cursor/mcp.json`:
+
+```json
+{
+ "mcpServers": {
+ "superdoc": {
+ "command": "npx",
+ "args": ["@superdoc-dev/mcp"]
+ }
+ }
+}
+```
+
+### Windsurf
+
+Add to `~/.codeium/windsurf/mcp_config.json`:
+
+```json
+{
+ "mcpServers": {
+ "superdoc": {
+ "command": "npx",
+ "args": ["@superdoc-dev/mcp"]
+ }
+ }
+}
+```
+
+## Tools
+
+23 tools in eight groups. All tools take a `session_id` from `superdoc_open`.
+
+### Lifecycle
+
+| Tool | Description |
+| --- | --- |
+| `superdoc_open` | Open a `.docx` file and get a `session_id` |
+| `superdoc_save` | Save the document to disk (original path or custom `out` path) |
+| `superdoc_close` | Close the session and release memory |
+
+### Query
+
+| Tool | Description |
+| --- | --- |
+| `superdoc_find` | Search by text pattern, node type, or both. Returns addresses for mutations |
+| `superdoc_get_node` | Get details about a specific node |
+| `superdoc_info` | Document metadata and structure |
+| `superdoc_get_text` | Full plain text of the document |
+
+### Mutation
+
+| Tool | Description |
+| --- | --- |
+| `superdoc_insert` | Insert text at a position. Set `suggest=true` for tracked changes |
+| `superdoc_replace` | Replace content at a range. Set `suggest=true` for tracked changes |
+| `superdoc_delete` | Delete content at a range. Set `suggest=true` for tracked changes |
+
+### Format
+
+| Tool | Description |
+| --- | --- |
+| `superdoc_format` | Toggle formatting (`bold`, `italic`, `underline`, `strikethrough`). Set `suggest=true` for tracked changes |
+
+### Create
+
+| Tool | Description |
+| --- | --- |
+| `superdoc_create` | Create a block element (`paragraph`, `heading`). Set `suggest=true` for tracked changes |
+
+### Track changes
+
+| Tool | Description |
+| --- | --- |
+| `superdoc_list_changes` | List all tracked changes with type, author, and excerpt |
+| `superdoc_accept_change` | Accept a single tracked change |
+| `superdoc_reject_change` | Reject a single tracked change |
+| `superdoc_accept_all_changes` | Accept all tracked changes |
+| `superdoc_reject_all_changes` | Reject all tracked changes |
+
+### Comments
+
+| Tool | Description |
+| --- | --- |
+| `superdoc_add_comment` | Add a comment anchored to a text range |
+| `superdoc_list_comments` | List all comments with author, status, and anchored text |
+| `superdoc_reply_comment` | Reply to an existing comment thread |
+| `superdoc_resolve_comment` | Mark a comment as resolved |
+
+### Lists
+
+| Tool | Description |
+| --- | --- |
+| `superdoc_insert_list` | Insert a list item before or after an existing one |
+| `superdoc_list_set_type` | Change a list between ordered and bullet |
+
+## Workflow
+
+Every interaction follows the same pattern:
+
+```
+open → read/edit → save → close
+```
+
+1. `superdoc_open` loads a document and returns a `session_id`
+2. `superdoc_find` locates content and returns addresses
+3. Edit tools use those addresses to modify content
+4. `superdoc_save` writes changes to disk
+5. `superdoc_close` releases the session
+
+### Suggesting mode
+
+Set `suggest=true` on any mutation, format, or create tool to make edits appear as tracked changes (suggestions) instead of direct edits. Use `superdoc_list_changes` to review them, and `superdoc_accept_change` / `superdoc_reject_change` to resolve them.
+
+## Development
+
+```bash
+# Run locally
+bun run src/index.ts
+
+# Run tests
+bun test
+
+# Test with MCP Inspector
+npx @modelcontextprotocol/inspector -- bun run src/index.ts
+```
+
+## License
+
+See the [SuperDoc license](../../LICENSE).
diff --git a/apps/mcp/package.json b/apps/mcp/package.json
new file mode 100644
index 000000000..882d0cc5b
--- /dev/null
+++ b/apps/mcp/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@superdoc-dev/mcp",
+ "version": "0.0.0",
+ "type": "module",
+ "bin": {
+ "superdoc-mcp": "./dist/index.js"
+ },
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "dev": "bun run src/index.ts",
+ "build": "bun build src/index.ts --outdir dist --target node --format esm",
+ "test": "NODE_ENV=test bun test",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.26.0",
+ "zod": "^4.3.6"
+ },
+ "devDependencies": {
+ "@superdoc/document-api": "workspace:*",
+ "@superdoc/super-editor": "workspace:*",
+ "superdoc": "workspace:*",
+ "@types/bun": "catalog:",
+ "@types/node": "catalog:",
+ "typescript": "catalog:"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/apps/mcp/src/__tests__/protocol.test.ts b/apps/mcp/src/__tests__/protocol.test.ts
new file mode 100644
index 000000000..0d6100e72
--- /dev/null
+++ b/apps/mcp/src/__tests__/protocol.test.ts
@@ -0,0 +1,169 @@
+import { describe, it, expect, afterAll } from 'bun:test';
+import { resolve } from 'node:path';
+import { Client } from '@modelcontextprotocol/sdk/client/index.js';
+import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
+
+const BLANK_DOCX = resolve(import.meta.dir, '../../../../shared/common/data/blank.docx');
+const SERVER_ENTRY = resolve(import.meta.dir, '../index.ts');
+
+const EXPECTED_TOOLS = [
+ 'superdoc_open',
+ 'superdoc_save',
+ 'superdoc_close',
+ 'superdoc_find',
+ 'superdoc_get_node',
+ 'superdoc_info',
+ 'superdoc_get_text',
+ 'superdoc_insert',
+ 'superdoc_replace',
+ 'superdoc_delete',
+ 'superdoc_format',
+ 'superdoc_create',
+ 'superdoc_list_changes',
+ 'superdoc_accept_change',
+ 'superdoc_reject_change',
+ 'superdoc_accept_all_changes',
+ 'superdoc_reject_all_changes',
+ 'superdoc_add_comment',
+ 'superdoc_list_comments',
+ 'superdoc_reply_comment',
+ 'superdoc_resolve_comment',
+ 'superdoc_insert_list',
+ 'superdoc_list_set_type',
+];
+
+function textContent(result: Awaited>): string {
+ const content = 'content' in result ? result.content : [];
+ const first = (content as Array<{ type: string; text?: string }>)[0];
+ return first?.text ?? '';
+}
+
+function parseContent(result: Awaited>): unknown {
+ return JSON.parse(textContent(result));
+}
+
+describe('MCP protocol integration', () => {
+ let client: Client;
+ let transport: StdioClientTransport;
+
+ // Connect once for all tests — spawns the server subprocess
+ const ready = (async () => {
+ transport = new StdioClientTransport({
+ command: 'bun',
+ args: ['run', SERVER_ENTRY],
+ stderr: 'pipe',
+ });
+ client = new Client({ name: 'test-client', version: '1.0.0' });
+ await client.connect(transport);
+ })();
+
+ afterAll(async () => {
+ await transport?.close();
+ });
+
+ it('connects and lists all expected tools', async () => {
+ await ready;
+ const { tools } = await client.listTools();
+ const names = tools.map((t) => t.name).sort();
+
+ expect(names).toEqual([...EXPECTED_TOOLS].sort());
+ });
+
+ it('tools have required annotations', async () => {
+ await ready;
+ const { tools } = await client.listTools();
+
+ for (const tool of tools) {
+ expect(tool.annotations).toBeDefined();
+ expect(typeof tool.annotations!.readOnlyHint).toBe('boolean');
+ }
+ });
+
+ it('open → info → get_text → close workflow', async () => {
+ await ready;
+
+ // Open
+ const openResult = await client.callTool({ name: 'superdoc_open', arguments: { path: BLANK_DOCX } });
+ const opened = parseContent(openResult) as { session_id: string; filePath: string };
+ expect(opened.session_id).toBeString();
+ expect(opened.filePath).toBe(BLANK_DOCX);
+
+ const sid = opened.session_id;
+
+ // Info
+ const infoResult = await client.callTool({ name: 'superdoc_info', arguments: { session_id: sid } });
+ expect(textContent(infoResult)).toBeTruthy();
+
+ // Get text
+ const textResult = await client.callTool({ name: 'superdoc_get_text', arguments: { session_id: sid } });
+ expect(textContent(textResult)).toBeDefined();
+
+ // Close
+ const closeResult = await client.callTool({ name: 'superdoc_close', arguments: { session_id: sid } });
+ const closed = parseContent(closeResult) as { closed: boolean };
+ expect(closed.closed).toBe(true);
+ });
+
+ it('open → create → find → save → close workflow', async () => {
+ await ready;
+
+ // Open
+ const openResult = await client.callTool({ name: 'superdoc_open', arguments: { path: BLANK_DOCX } });
+ const { session_id: sid } = parseContent(openResult) as { session_id: string };
+
+ // Create a paragraph
+ const createResult = await client.callTool({
+ name: 'superdoc_create',
+ arguments: { session_id: sid, type: 'paragraph', text: 'MCP integration test' },
+ });
+ expect(textContent(createResult)).toContain('success');
+
+ // Find it
+ const findResult = await client.callTool({
+ name: 'superdoc_find',
+ arguments: { session_id: sid, pattern: 'MCP integration' },
+ });
+ const found = parseContent(findResult) as { matches: unknown[]; total: number };
+ expect(found.total).toBeGreaterThan(0);
+
+ // Save to temp path
+ const tmpPath = resolve(import.meta.dir, '../../../../tmp-protocol-test.docx');
+ const saveResult = await client.callTool({
+ name: 'superdoc_save',
+ arguments: { session_id: sid, out: tmpPath },
+ });
+ const saved = parseContent(saveResult) as { path: string; byteLength: number };
+ expect(saved.byteLength).toBeGreaterThan(0);
+
+ // Close
+ await client.callTool({ name: 'superdoc_close', arguments: { session_id: sid } });
+
+ // Clean up temp file
+ const { unlink } = await import('node:fs/promises');
+ await unlink(tmpPath).catch(() => {});
+ });
+
+ it('returns isError for invalid session', async () => {
+ await ready;
+
+ const result = await client.callTool({
+ name: 'superdoc_find',
+ arguments: { session_id: 'nonexistent', pattern: 'test' },
+ });
+
+ expect(result).toHaveProperty('isError', true);
+ expect(textContent(result)).toContain('No open session');
+ });
+
+ it('returns isError for invalid file path', async () => {
+ await ready;
+
+ const result = await client.callTool({
+ name: 'superdoc_open',
+ arguments: { path: '/nonexistent/file.docx' },
+ });
+
+ expect(result).toHaveProperty('isError', true);
+ expect(textContent(result)).toContain('Failed to open document');
+ });
+});
diff --git a/apps/mcp/src/__tests__/session-manager.test.ts b/apps/mcp/src/__tests__/session-manager.test.ts
new file mode 100644
index 000000000..bd5751dfb
--- /dev/null
+++ b/apps/mcp/src/__tests__/session-manager.test.ts
@@ -0,0 +1,77 @@
+import { describe, it, expect, afterEach } from 'bun:test';
+import { resolve } from 'node:path';
+import { SessionManager } from '../session-manager.js';
+
+const BLANK_DOCX = resolve(import.meta.dir, '../../../../shared/common/data/blank.docx');
+
+describe('SessionManager', () => {
+ const manager = new SessionManager();
+
+ afterEach(async () => {
+ await manager.closeAll();
+ });
+
+ it('opens a .docx file and returns a session', async () => {
+ const session = await manager.open(BLANK_DOCX);
+
+ expect(session.id).toBeString();
+ expect(session.filePath).toBe(BLANK_DOCX);
+ expect(session.editor).toBeDefined();
+ expect(session.api).toBeDefined();
+ expect(session.openedAt).toBeNumber();
+ });
+
+ it('retrieves an open session by id', async () => {
+ const session = await manager.open(BLANK_DOCX);
+ const retrieved = manager.get(session.id);
+
+ expect(retrieved).toBe(session);
+ });
+
+ it('throws when getting a non-existent session', () => {
+ expect(() => manager.get('nonexistent')).toThrow('No open session');
+ });
+
+ it('lists open sessions', async () => {
+ const session = await manager.open(BLANK_DOCX);
+ const list = manager.list();
+
+ expect(list).toHaveLength(1);
+ expect(list[0].id).toBe(session.id);
+ expect(list[0].filePath).toBe(BLANK_DOCX);
+ });
+
+ it('closes a session', async () => {
+ const session = await manager.open(BLANK_DOCX);
+ await manager.close(session.id);
+
+ expect(() => manager.get(session.id)).toThrow('No open session');
+ expect(manager.list()).toHaveLength(0);
+ });
+
+ it('close is idempotent for unknown ids', async () => {
+ await manager.close('nonexistent');
+ // should not throw
+ });
+
+ it('saves a document to a temp path', async () => {
+ const session = await manager.open(BLANK_DOCX);
+ const tmpPath = resolve(import.meta.dir, '../../../../tmp-test-output.docx');
+
+ const result = await manager.save(session.id, tmpPath);
+
+ expect(result.path).toBe(tmpPath);
+ expect(result.byteLength).toBeGreaterThan(0);
+
+ // Clean up
+ const { unlink } = await import('node:fs/promises');
+ await unlink(tmpPath).catch(() => {});
+ });
+
+ it('generates human-friendly session ids', async () => {
+ const session = await manager.open(BLANK_DOCX);
+
+ // Should contain part of the filename
+ expect(session.id).toMatch(/^blank-[a-f0-9]{6}$/);
+ });
+});
diff --git a/apps/mcp/src/__tests__/tools.test.ts b/apps/mcp/src/__tests__/tools.test.ts
new file mode 100644
index 000000000..ce36edc59
--- /dev/null
+++ b/apps/mcp/src/__tests__/tools.test.ts
@@ -0,0 +1,81 @@
+import { describe, it, expect, afterEach } from 'bun:test';
+import { resolve } from 'node:path';
+import { SessionManager } from '../session-manager.js';
+
+const BLANK_DOCX = resolve(import.meta.dir, '../../../../shared/common/data/blank.docx');
+
+describe('MCP tools integration', () => {
+ const sessions = new SessionManager();
+
+ afterEach(async () => {
+ await sessions.closeAll();
+ });
+
+ it('open → info → close lifecycle', async () => {
+ const session = await sessions.open(BLANK_DOCX);
+ const { api } = sessions.get(session.id);
+
+ const info = api.invoke({ operationId: 'info', input: {} });
+ expect(info).toBeDefined();
+
+ await sessions.close(session.id);
+ expect(() => sessions.get(session.id)).toThrow();
+ });
+
+ it('getText returns document text', async () => {
+ const session = await sessions.open(BLANK_DOCX);
+ const { api } = sessions.get(session.id);
+
+ const text = api.invoke({ operationId: 'getText', input: {} });
+ expect(text).toBeDefined();
+ // blank.docx may have empty or minimal text
+ expect(typeof text === 'string' || typeof text === 'object').toBe(true);
+ });
+
+ it('find returns results for paragraphs', async () => {
+ const session = await sessions.open(BLANK_DOCX);
+ const { api } = sessions.get(session.id);
+
+ const result = api.invoke({
+ operationId: 'find',
+ input: {
+ query: { select: { type: 'node', nodeType: 'paragraph' } },
+ },
+ });
+
+ expect(result).toBeDefined();
+ });
+
+ it('create.paragraph adds a paragraph', async () => {
+ const session = await sessions.open(BLANK_DOCX);
+ const { api } = sessions.get(session.id);
+
+ const result = api.invoke({
+ operationId: 'create.paragraph',
+ input: { text: 'Hello from MCP' },
+ });
+
+ expect(result).toBeDefined();
+
+ // Verify the text was inserted
+ const text = api.invoke({ operationId: 'getText', input: {} });
+ expect(String(text)).toContain('Hello from MCP');
+ });
+
+ it('insert + getText roundtrip', async () => {
+ const session = await sessions.open(BLANK_DOCX);
+ const { api } = sessions.get(session.id);
+
+ // Create a paragraph first to have a target
+ const created = api.invoke({
+ operationId: 'create.paragraph',
+ input: { text: 'Initial text' },
+ }) as { paragraph?: unknown; insertionPoint?: unknown };
+
+ expect(created).toBeDefined();
+
+ // Verify
+ const text = api.invoke({ operationId: 'getText', input: {} });
+ expect(String(text)).toContain('Initial text');
+ });
+});
diff --git a/apps/mcp/src/index.ts b/apps/mcp/src/index.ts
new file mode 100644
index 000000000..f811a3ac1
--- /dev/null
+++ b/apps/mcp/src/index.ts
@@ -0,0 +1,35 @@
+#!/usr/bin/env node
+import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import { SessionManager } from './session-manager.js';
+import { registerAllTools } from './tools/index.js';
+
+const server = new McpServer({
+ name: 'superdoc',
+ version: '1.0.0',
+});
+
+const sessions = new SessionManager();
+
+registerAllTools(server, sessions);
+
+const transport = new StdioServerTransport();
+
+async function main(): Promise {
+ await server.connect(transport);
+}
+
+main().catch((err) => {
+ console.error('SuperDoc MCP server failed to start:', err);
+ process.exit(1);
+});
+
+process.on('SIGINT', async () => {
+ await sessions.closeAll();
+ process.exit(0);
+});
+
+process.on('SIGTERM', async () => {
+ await sessions.closeAll();
+ process.exit(0);
+});
diff --git a/apps/mcp/src/session-manager.ts b/apps/mcp/src/session-manager.ts
new file mode 100644
index 000000000..f032ae2da
--- /dev/null
+++ b/apps/mcp/src/session-manager.ts
@@ -0,0 +1,109 @@
+import { readFile, writeFile } from 'node:fs/promises';
+import { randomBytes } from 'node:crypto';
+import { resolve, basename } from 'node:path';
+import { Editor } from 'superdoc/super-editor';
+import { getDocumentApiAdapters } from '@superdoc/super-editor/document-api-adapters';
+import { createDocumentApi, type DocumentApi } from '@superdoc/document-api';
+
+export interface Session {
+ id: string;
+ filePath: string;
+ editor: Editor;
+ api: DocumentApi;
+ openedAt: number;
+}
+
+export class SessionManager {
+ private sessions = new Map();
+
+ async open(filePath: string): Promise {
+ const absolutePath = resolve(filePath);
+
+ const bytes = await readFile(absolutePath);
+
+ const editor = await Editor.open(Buffer.from(bytes), {
+ documentId: absolutePath,
+ user: { id: 'mcp', name: 'MCP Server' },
+ });
+
+ const adapters = getDocumentApiAdapters(editor);
+ const api = createDocumentApi(adapters);
+
+ const id = generateSessionId(absolutePath);
+
+ const session: Session = {
+ id,
+ filePath: absolutePath,
+ editor,
+ api,
+ openedAt: Date.now(),
+ };
+
+ this.sessions.set(id, session);
+ return session;
+ }
+
+ get(sessionId: string): Session {
+ const session = this.sessions.get(sessionId);
+ if (!session) {
+ throw new Error(`No open session with id "${sessionId}". Use superdoc_open first.`);
+ }
+ return session;
+ }
+
+ async save(sessionId: string, outputPath?: string): Promise<{ path: string; byteLength: number }> {
+ const session = this.get(sessionId);
+ const targetPath = outputPath ? resolve(outputPath) : session.filePath;
+
+ const exported = await session.editor.exportDocument();
+ const bytes = toUint8Array(exported);
+
+ await writeFile(targetPath, bytes);
+
+ return { path: targetPath, byteLength: bytes.byteLength };
+ }
+
+ async close(sessionId: string): Promise {
+ const session = this.sessions.get(sessionId);
+ if (!session) return;
+
+ session.editor.destroy();
+ this.sessions.delete(sessionId);
+ }
+
+ async closeAll(): Promise {
+ for (const session of this.sessions.values()) {
+ session.editor.destroy();
+ }
+ this.sessions.clear();
+ }
+
+ list(): Array<{ id: string; filePath: string; openedAt: number }> {
+ return Array.from(this.sessions.values()).map((s) => ({
+ id: s.id,
+ filePath: s.filePath,
+ openedAt: s.openedAt,
+ }));
+ }
+}
+
+function generateSessionId(filePath: string): string {
+ const stem = basename(filePath).replace(/\.[^.]+$/, '');
+ const normalized =
+ stem
+ .toLowerCase()
+ .replace(/[^a-z0-9._-]+/g, '-')
+ .replace(/-{2,}/g, '-')
+ .replace(/^[._-]+|[._-]+$/g, '') || 'session';
+ const suffix = randomBytes(4).toString('hex').slice(0, 6);
+ return `${normalized.slice(0, 57)}-${suffix}`;
+}
+
+function toUint8Array(data: unknown): Uint8Array {
+ if (data instanceof Uint8Array) return data;
+ if (data instanceof ArrayBuffer) return new Uint8Array(data);
+ if (ArrayBuffer.isView(data)) {
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+ }
+ throw new Error('Exported document data is not binary.');
+}
diff --git a/apps/mcp/src/tools/comments.ts b/apps/mcp/src/tools/comments.ts
new file mode 100644
index 000000000..83bff4e1f
--- /dev/null
+++ b/apps/mcp/src/tools/comments.ts
@@ -0,0 +1,134 @@
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { SessionManager } from '../session-manager.js';
+
+export function registerCommentTools(server: McpServer, sessions: SessionManager): void {
+ server.registerTool(
+ 'superdoc_add_comment',
+ {
+ title: 'Add Comment',
+ description:
+ 'Add a comment anchored to a text range in the document. Use superdoc_find with a text pattern first, then pass a TextAddress from context[].textRanges as the target.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ text: z.string().describe('The comment text (question, concern, or feedback).'),
+ target: z
+ .string()
+ .describe(
+ 'JSON-encoded TextAddress: {"kind":"text","blockId":"...","range":{"start":N,"end":N}}. Get this from superdoc_find context[].textRanges, NOT from matches[].',
+ ),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, text, target }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const parsed = JSON.parse(target);
+ const result = api.invoke({
+ operationId: 'comments.add',
+ input: { text, target: parsed },
+ });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Add comment failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_list_comments',
+ {
+ title: 'List Comments',
+ description:
+ 'List all comments in the document. Returns comment text, author, status (open/resolved), and the text range each comment is anchored to.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ include_resolved: z.boolean().optional().describe('Include resolved comments. Defaults to true.'),
+ },
+ annotations: { readOnlyHint: true },
+ },
+ async ({ session_id, include_resolved }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const input: Record = {};
+ if (include_resolved != null) input.includeResolved = include_resolved;
+
+ const result = api.invoke({ operationId: 'comments.list', input });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `List comments failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_reply_comment',
+ {
+ title: 'Reply to Comment',
+ description: 'Reply to an existing comment thread. Use the comment ID from superdoc_list_comments.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ comment_id: z.string().describe('The parent comment ID to reply to (from superdoc_list_comments).'),
+ text: z.string().describe('The reply text.'),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, comment_id, text }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const result = api.invoke({
+ operationId: 'comments.reply',
+ input: { parentCommentId: comment_id, text },
+ });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Reply failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_resolve_comment',
+ {
+ title: 'Resolve Comment',
+ description: 'Mark a comment as resolved. Use the comment ID from superdoc_list_comments.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ comment_id: z.string().describe('The comment ID to resolve (from superdoc_list_comments).'),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, comment_id }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const result = api.invoke({
+ operationId: 'comments.resolve',
+ input: { commentId: comment_id },
+ });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Resolve failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+}
diff --git a/apps/mcp/src/tools/create.ts b/apps/mcp/src/tools/create.ts
new file mode 100644
index 000000000..f623d3f90
--- /dev/null
+++ b/apps/mcp/src/tools/create.ts
@@ -0,0 +1,56 @@
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { SessionManager } from '../session-manager.js';
+
+const TYPES = ['paragraph', 'heading'] as const;
+
+export function registerCreateTools(server: McpServer, sessions: SessionManager): void {
+ server.registerTool(
+ 'superdoc_create',
+ {
+ title: 'Create Block',
+ description:
+ 'Create a new block element in the document. Supports paragraphs and headings. Optionally specify text content and position. Set suggest=true to create as a tracked change (suggestion).',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ type: z.enum(TYPES).describe('The type of block to create.'),
+ text: z.string().optional().describe('Text content for the new block.'),
+ level: z.number().min(1).max(6).optional().describe('Heading level (1-6). Required when type is "heading".'),
+ at: z
+ .string()
+ .optional()
+ .describe('JSON-encoded position specifying where to create the block. If omitted, appends to the end.'),
+ suggest: z
+ .boolean()
+ .optional()
+ .describe(
+ 'If true, create as a tracked change (suggestion) that can be accepted or rejected later. Defaults to false (direct edit).',
+ ),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, type, text, level, at, suggest }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const input: Record = {};
+ if (text != null) input.text = text;
+ if (level != null) input.level = level;
+ if (at != null) input.at = JSON.parse(at);
+
+ const result = api.invoke({
+ operationId: `create.${type}`,
+ input,
+ options: suggest ? { changeMode: 'tracked' as const } : undefined,
+ });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Create failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+}
diff --git a/apps/mcp/src/tools/format.ts b/apps/mcp/src/tools/format.ts
new file mode 100644
index 000000000..8911e3694
--- /dev/null
+++ b/apps/mcp/src/tools/format.ts
@@ -0,0 +1,51 @@
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { SessionManager } from '../session-manager.js';
+
+const STYLES = ['bold', 'italic', 'underline', 'strikethrough'] as const;
+
+export function registerFormatTools(server: McpServer, sessions: SessionManager): void {
+ server.registerTool(
+ 'superdoc_format',
+ {
+ title: 'Format Text',
+ description:
+ "Toggle a formatting style on a text range. Use superdoc_find with a text pattern first, then pass a TextAddress from the result's context[].textRanges as the target. Set suggest=true to format as a tracked change (suggestion).",
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ style: z.enum(STYLES).describe('The formatting style to toggle.'),
+ target: z
+ .string()
+ .describe(
+ 'JSON-encoded TextAddress: {"kind":"text","blockId":"...","range":{"start":N,"end":N}}. Get this from superdoc_find context[].textRanges, NOT from matches[].',
+ ),
+ suggest: z
+ .boolean()
+ .optional()
+ .describe(
+ 'If true, format as a tracked change (suggestion) that can be accepted or rejected later. Defaults to false (direct edit).',
+ ),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, style, target, suggest }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const parsed = JSON.parse(target);
+ const result = api.invoke({
+ operationId: `format.${style}`,
+ input: { target: parsed },
+ options: suggest ? { changeMode: 'tracked' as const } : undefined,
+ });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Format failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+}
diff --git a/apps/mcp/src/tools/index.ts b/apps/mcp/src/tools/index.ts
new file mode 100644
index 000000000..7c1773678
--- /dev/null
+++ b/apps/mcp/src/tools/index.ts
@@ -0,0 +1,21 @@
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { SessionManager } from '../session-manager.js';
+import { registerLifecycleTools } from './lifecycle.js';
+import { registerQueryTools } from './query.js';
+import { registerMutationTools } from './mutation.js';
+import { registerFormatTools } from './format.js';
+import { registerCreateTools } from './create.js';
+import { registerTrackChangesTools } from './track-changes.js';
+import { registerCommentTools } from './comments.js';
+import { registerListTools } from './lists.js';
+
+export function registerAllTools(server: McpServer, sessions: SessionManager): void {
+ registerLifecycleTools(server, sessions);
+ registerQueryTools(server, sessions);
+ registerMutationTools(server, sessions);
+ registerFormatTools(server, sessions);
+ registerCreateTools(server, sessions);
+ registerTrackChangesTools(server, sessions);
+ registerCommentTools(server, sessions);
+ registerListTools(server, sessions);
+}
diff --git a/apps/mcp/src/tools/lifecycle.ts b/apps/mcp/src/tools/lifecycle.ts
new file mode 100644
index 000000000..c0e1530bd
--- /dev/null
+++ b/apps/mcp/src/tools/lifecycle.ts
@@ -0,0 +1,80 @@
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { SessionManager } from '../session-manager.js';
+
+export function registerLifecycleTools(server: McpServer, sessions: SessionManager): void {
+ server.registerTool(
+ 'superdoc_open',
+ {
+ title: 'Open Document',
+ description:
+ 'Open a Word document (.docx) for reading and editing. Must be called before any other operation. Returns a session_id to use in subsequent calls.',
+ inputSchema: {
+ path: z.string().describe('Absolute path to the .docx file.'),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ path }) => {
+ try {
+ const session = await sessions.open(path);
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: JSON.stringify({ session_id: session.id, filePath: session.filePath }),
+ },
+ ],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Failed to open document: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_save',
+ {
+ title: 'Save Document',
+ description: 'Save the document to disk. Writes to the original path unless "out" is specified.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ out: z.string().optional().describe('Save to a different file path instead of the original.'),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, out }) => {
+ try {
+ const result = await sessions.save(session_id, out);
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Failed to save: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_close',
+ {
+ title: 'Close Document',
+ description: 'Close a document session and release memory. Unsaved changes will be lost.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID to close.'),
+ },
+ annotations: { readOnlyHint: false, destructiveHint: true },
+ },
+ async ({ session_id }) => {
+ await sessions.close(session_id);
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify({ closed: true }) }],
+ };
+ },
+ );
+}
diff --git a/apps/mcp/src/tools/lists.ts b/apps/mcp/src/tools/lists.ts
new file mode 100644
index 000000000..3facd55f0
--- /dev/null
+++ b/apps/mcp/src/tools/lists.ts
@@ -0,0 +1,76 @@
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { SessionManager } from '../session-manager.js';
+
+export function registerListTools(server: McpServer, sessions: SessionManager): void {
+ server.registerTool(
+ 'superdoc_insert_list',
+ {
+ title: 'Insert List Item',
+ description:
+ 'Insert a new list item before or after an existing one. To start a new list, use superdoc_create with type "paragraph" first, then convert it. Or use superdoc_find to locate an existing list item.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ target: z
+ .string()
+ .describe('JSON-encoded list item address from superdoc_find or superdoc_list_items results.'),
+ position: z.enum(['before', 'after']).describe('Insert before or after the target item.'),
+ text: z.string().optional().describe('Text content for the new list item.'),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, target, position, text }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const parsed = JSON.parse(target);
+ const input: Record = { target: parsed, position };
+ if (text != null) input.text = text;
+
+ const result = api.invoke({
+ operationId: 'lists.insert',
+ input,
+ });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Insert list item failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_list_set_type',
+ {
+ title: 'Set List Type',
+ description: 'Change a list between ordered (numbered) and bullet (unordered).',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ target: z.string().describe('JSON-encoded list item address from superdoc_find results.'),
+ kind: z.enum(['ordered', 'bullet']).describe('The list type to set.'),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, target, kind }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const parsed = JSON.parse(target);
+ const result = api.invoke({
+ operationId: 'lists.setType',
+ input: { target: parsed, kind },
+ });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Set list type failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+}
diff --git a/apps/mcp/src/tools/mutation.ts b/apps/mcp/src/tools/mutation.ts
new file mode 100644
index 000000000..1ef415559
--- /dev/null
+++ b/apps/mcp/src/tools/mutation.ts
@@ -0,0 +1,140 @@
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { SessionManager } from '../session-manager.js';
+
+function mutationOptions(suggest?: boolean) {
+ return suggest ? { changeMode: 'tracked' as const } : undefined;
+}
+
+export function registerMutationTools(server: McpServer, sessions: SessionManager): void {
+ server.registerTool(
+ 'superdoc_insert',
+ {
+ title: 'Insert Text',
+ description:
+ "Insert text at a target position in the document. Use superdoc_find first, then pass a TextAddress from the result's context[].textRanges as the target. Set suggest=true to insert as a tracked change (suggestion) instead of a direct edit.",
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ text: z.string().describe('The text content to insert.'),
+ target: z
+ .string()
+ .describe(
+ 'JSON-encoded TextAddress: {"kind":"text","blockId":"...","range":{"start":N,"end":N}}. Get this from superdoc_find context[].textRanges, NOT from matches[].',
+ ),
+ suggest: z
+ .boolean()
+ .optional()
+ .describe(
+ 'If true, insert as a tracked change (suggestion) that can be accepted or rejected later. Defaults to false (direct edit).',
+ ),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, text, target, suggest }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const parsed = JSON.parse(target);
+ const result = api.invoke({
+ operationId: 'insert',
+ input: { text, target: parsed },
+ options: mutationOptions(suggest),
+ });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Insert failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_replace',
+ {
+ title: 'Replace Text',
+ description:
+ "Replace content at a target range with new text. Use superdoc_find with a text pattern first, then pass a TextAddress from the result's context[].textRanges as the target. Set suggest=true to make the replacement a tracked change (suggestion).",
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ text: z.string().describe('The replacement text.'),
+ target: z
+ .string()
+ .describe(
+ 'JSON-encoded TextAddress: {"kind":"text","blockId":"...","range":{"start":N,"end":N}}. Get this from superdoc_find context[].textRanges, NOT from matches[].',
+ ),
+ suggest: z
+ .boolean()
+ .optional()
+ .describe(
+ 'If true, replace as a tracked change (suggestion) that can be accepted or rejected later. Defaults to false (direct edit).',
+ ),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, text, target, suggest }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const parsed = JSON.parse(target);
+ const result = api.invoke({
+ operationId: 'replace',
+ input: { text, target: parsed },
+ options: mutationOptions(suggest),
+ });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Replace failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_delete',
+ {
+ title: 'Delete Content',
+ description:
+ "Delete content at a target range. Use superdoc_find with a text pattern first, then pass a TextAddress from the result's context[].textRanges as the target. Set suggest=true to delete as a tracked change (suggestion).",
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ target: z
+ .string()
+ .describe(
+ 'JSON-encoded TextAddress: {"kind":"text","blockId":"...","range":{"start":N,"end":N}}. Get this from superdoc_find context[].textRanges, NOT from matches[].',
+ ),
+ suggest: z
+ .boolean()
+ .optional()
+ .describe(
+ 'If true, delete as a tracked change (suggestion) that can be accepted or rejected later. Defaults to false (direct edit).',
+ ),
+ },
+ annotations: { readOnlyHint: false, destructiveHint: true },
+ },
+ async ({ session_id, target, suggest }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const parsed = JSON.parse(target);
+ const result = api.invoke({
+ operationId: 'delete',
+ input: { target: parsed },
+ options: mutationOptions(suggest),
+ });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Delete failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+}
diff --git a/apps/mcp/src/tools/query.ts b/apps/mcp/src/tools/query.ts
new file mode 100644
index 000000000..c7f8927b6
--- /dev/null
+++ b/apps/mcp/src/tools/query.ts
@@ -0,0 +1,133 @@
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { SessionManager } from '../session-manager.js';
+
+export function registerQueryTools(server: McpServer, sessions: SessionManager): void {
+ server.registerTool(
+ 'superdoc_find',
+ {
+ title: 'Find in Document',
+ description:
+ 'Search the document for nodes matching a type, text pattern, or both. For text searches, the result includes context[].textRanges — these are the TextAddress objects you pass as "target" to replace/insert/delete/format tools. Do NOT use matches[] as mutation targets (those are block addresses).',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ type: z.string().optional().describe('Node type to filter by (e.g. "heading", "paragraph", "table", "image").'),
+ pattern: z.string().optional().describe('Text pattern to search for (substring match).'),
+ limit: z.number().optional().describe('Maximum number of results.'),
+ offset: z.number().optional().describe('Skip this many results (for pagination).'),
+ },
+ annotations: { readOnlyHint: true },
+ },
+ async ({ session_id, type, pattern, limit, offset }) => {
+ try {
+ const { api } = sessions.get(session_id);
+
+ // Build a Selector or Query object directly — find accepts both.
+ // Selector: { type: 'text', pattern } or { type: 'node', nodeType }
+ // Query: { select: Selector, limit?, offset? }
+ let selector: Record;
+ if (pattern) {
+ selector = { type: 'text', pattern, mode: 'contains' };
+ } else if (type) {
+ selector = { type: 'node', nodeType: type };
+ } else {
+ selector = { type: 'node' };
+ }
+
+ const input: Record =
+ limit != null || offset != null ? { select: selector, limit, offset } : selector;
+
+ const result = api.invoke({ operationId: 'find', input });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Find failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_get_node',
+ {
+ title: 'Get Node',
+ description:
+ 'Get detailed information about a specific document node by its address (from superdoc_find results).',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ address: z.string().describe('JSON-encoded node address from superdoc_find results.'),
+ },
+ annotations: { readOnlyHint: true },
+ },
+ async ({ session_id, address }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const parsed = JSON.parse(address);
+ const result = api.invoke({ operationId: 'getNode', input: parsed });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Get node failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_info',
+ {
+ title: 'Document Info',
+ description: 'Return document metadata: structure summary, node counts, and capabilities.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ },
+ annotations: { readOnlyHint: true },
+ },
+ async ({ session_id }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const result = api.invoke({ operationId: 'info', input: {} });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Info failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_get_text',
+ {
+ title: 'Get Document Text',
+ description: 'Return the full plain-text content of the document.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ },
+ annotations: { readOnlyHint: true },
+ },
+ async ({ session_id }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const result = api.invoke({ operationId: 'getText', input: {} });
+ return {
+ content: [{ type: 'text' as const, text: typeof result === 'string' ? result : JSON.stringify(result) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Get text failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+}
diff --git a/apps/mcp/src/tools/track-changes.ts b/apps/mcp/src/tools/track-changes.ts
new file mode 100644
index 000000000..c4fb604fb
--- /dev/null
+++ b/apps/mcp/src/tools/track-changes.ts
@@ -0,0 +1,148 @@
+import { z } from 'zod';
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import type { SessionManager } from '../session-manager.js';
+
+export function registerTrackChangesTools(server: McpServer, sessions: SessionManager): void {
+ server.registerTool(
+ 'superdoc_list_changes',
+ {
+ title: 'List Tracked Changes',
+ description:
+ 'List all tracked changes (suggestions) in the document. Returns change type (insert/delete/format), author, date, and excerpt for each.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ type: z.enum(['insert', 'delete', 'format']).optional().describe('Filter by change type.'),
+ limit: z.number().optional().describe('Maximum number of results.'),
+ offset: z.number().optional().describe('Skip this many results (for pagination).'),
+ },
+ annotations: { readOnlyHint: true },
+ },
+ async ({ session_id, type, limit, offset }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const input: Record = {};
+ if (type != null) input.type = type;
+ if (limit != null) input.limit = limit;
+ if (offset != null) input.offset = offset;
+
+ const result = api.invoke({ operationId: 'trackChanges.list', input });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `List changes failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_accept_change',
+ {
+ title: 'Accept Tracked Change',
+ description:
+ 'Accept a single tracked change (suggestion), applying it to the document. Use the change ID from superdoc_list_changes.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ id: z.string().describe('The tracked change ID from superdoc_list_changes results.'),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, id }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const result = api.invoke({ operationId: 'trackChanges.accept', input: { id } });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Accept change failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_reject_change',
+ {
+ title: 'Reject Tracked Change',
+ description:
+ 'Reject a single tracked change (suggestion), reverting it from the document. Use the change ID from superdoc_list_changes.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ id: z.string().describe('The tracked change ID from superdoc_list_changes results.'),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id, id }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const result = api.invoke({ operationId: 'trackChanges.reject', input: { id } });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Reject change failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_accept_all_changes',
+ {
+ title: 'Accept All Tracked Changes',
+ description: 'Accept all tracked changes (suggestions) in the document, applying them all.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ },
+ annotations: { readOnlyHint: false },
+ },
+ async ({ session_id }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const result = api.invoke({ operationId: 'trackChanges.acceptAll', input: {} });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Accept all failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+
+ server.registerTool(
+ 'superdoc_reject_all_changes',
+ {
+ title: 'Reject All Tracked Changes',
+ description: 'Reject all tracked changes (suggestions) in the document, reverting them all.',
+ inputSchema: {
+ session_id: z.string().describe('Session ID from superdoc_open.'),
+ },
+ annotations: { readOnlyHint: false, destructiveHint: true },
+ },
+ async ({ session_id }) => {
+ try {
+ const { api } = sessions.get(session_id);
+ const result = api.invoke({ operationId: 'trackChanges.rejectAll', input: {} });
+ return {
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
+ };
+ } catch (err) {
+ return {
+ content: [{ type: 'text' as const, text: `Reject all failed: ${(err as Error).message}` }],
+ isError: true,
+ };
+ }
+ },
+ );
+}
diff --git a/apps/mcp/tsconfig.json b/apps/mcp/tsconfig.json
new file mode 100644
index 000000000..470e85813
--- /dev/null
+++ b/apps/mcp/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "skipLibCheck": true,
+ "types": ["bun"],
+ "paths": {
+ "@superdoc/super-editor/document-api-adapters": ["../../packages/super-editor/src/document-api-adapters/index.ts"]
+ }
+ },
+ "include": ["src"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a43af1678..bf97374ca 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -433,7 +433,7 @@ importers:
version: 14.0.3
mintlify:
specifier: ^4.2.331
- version: 4.2.374(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(typescript@5.9.3)
+ version: 4.2.374(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3))(@types/node@25.3.0)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(typescript@5.9.3)
remark-mdx:
specifier: ^3.1.1
version: 3.1.1
@@ -447,6 +447,34 @@ importers:
specifier: ^5.1.0
version: 5.1.0
+ apps/mcp:
+ dependencies:
+ '@modelcontextprotocol/sdk':
+ specifier: ^1.26.0
+ version: 1.26.0(zod@4.3.6)
+ zod:
+ specifier: ^4.3.6
+ version: 4.3.6
+ devDependencies:
+ '@superdoc/document-api':
+ specifier: workspace:*
+ version: link:../../packages/document-api
+ '@superdoc/super-editor':
+ specifier: workspace:*
+ version: link:../../packages/super-editor
+ '@types/bun':
+ specifier: 'catalog:'
+ version: 1.3.9
+ '@types/node':
+ specifier: 'catalog:'
+ version: 22.19.2
+ superdoc:
+ specifier: workspace:*
+ version: link:../../packages/superdoc
+ typescript:
+ specifier: 'catalog:'
+ version: 5.9.3
+
apps/vscode-ext:
dependencies:
superdoc:
@@ -2236,6 +2264,12 @@ packages:
y-protocols: ^1.0.6
yjs: ^13.6.8
+ '@hono/node-server@1.19.9':
+ resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==}
+ engines: {node: '>=18.14.1'}
+ peerDependencies:
+ hono: ^4
+
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@@ -2621,6 +2655,16 @@ packages:
'@mintlify/validation@0.1.604':
resolution: {integrity: sha512-UeT6ZwstePMwE8oGYasbNiPtBj304r6P9iN5BnlinQ8TePPVrTG/Y171/00pZUfcHEd0ySlFGj2saHdUtKyhFg==}
+ '@modelcontextprotocol/sdk@1.26.0':
+ resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@cfworker/json-schema': ^4.1.1
+ zod: ^3.25 || ^4.0
+ peerDependenciesMeta:
+ '@cfworker/json-schema':
+ optional: true
+
'@napi-rs/canvas-android-arm64@0.1.94':
resolution: {integrity: sha512-YQ6K83RWNMQOtgpk1aIML97QTE3zxPmVCHTi5eA8Nss4+B9JZi5J7LHQr7B5oD7VwSfWd++xsPdUiJ1+frqsMg==}
engines: {node: '>= 10'}
@@ -4288,6 +4332,10 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
+ accepts@2.0.0:
+ resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
+ engines: {node: '>= 0.6'}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -4691,6 +4739,10 @@ packages:
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ body-parser@2.2.2:
+ resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
+ engines: {node: '>=18'}
+
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -5129,6 +5181,10 @@ packages:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
+ content-disposition@1.0.1:
+ resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
+ engines: {node: '>=18'}
+
content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
@@ -5181,6 +5237,10 @@ packages:
cookie-signature@1.0.7:
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
+ cookie-signature@1.2.2:
+ resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
+ engines: {node: '>=6.6.0'}
+
cookie@0.4.2:
resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
engines: {node: '>= 0.6'}
@@ -5883,6 +5943,14 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
+ eventsource-parser@3.0.6:
+ resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
+ engines: {node: '>=18.0.0'}
+
+ eventsource@3.0.7:
+ resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
+ engines: {node: '>=18.0.0'}
+
evp_bytestokey@1.0.3:
resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==}
@@ -5912,6 +5980,12 @@ packages:
express-rate-limit@5.5.1:
resolution: {integrity: sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg==}
+ express-rate-limit@8.2.1:
+ resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==}
+ engines: {node: '>= 16'}
+ peerDependencies:
+ express: '>= 4.11'
+
express@4.18.2:
resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
engines: {node: '>= 0.10.0'}
@@ -5920,6 +5994,10 @@ packages:
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
engines: {node: '>= 0.10.0'}
+ express@5.2.1:
+ resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
+ engines: {node: '>= 18'}
+
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
@@ -6014,6 +6092,10 @@ packages:
resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
engines: {node: '>= 0.8'}
+ finalhandler@2.1.1:
+ resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
+ engines: {node: '>= 18.0.0'}
+
find-up-simple@1.0.1:
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
engines: {node: '>=18'}
@@ -6095,6 +6177,10 @@ packages:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
+ fresh@2.0.0:
+ resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
+ engines: {node: '>= 0.8'}
+
from2@2.3.0:
resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==}
@@ -6480,6 +6566,10 @@ packages:
hmac-drbg@1.0.1:
resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==}
+ hono@4.12.1:
+ resolution: {integrity: sha512-hi9afu8g0lfJVLolxElAZGANCTTl6bewIdsRNhaywfP9K8BPf++F2z6OLrYGIinUwpRKzbZHMhPwvc0ZEpAwGw==}
+ engines: {node: '>=16.9.0'}
+
hook-std@4.0.0:
resolution: {integrity: sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==}
engines: {node: '>=20'}
@@ -6685,6 +6775,10 @@ packages:
resolution: {integrity: sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==}
engines: {node: '>=12'}
+ ip-address@10.0.1:
+ resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==}
+ engines: {node: '>= 12'}
+
ip-address@10.1.0:
resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
engines: {node: '>= 12'}
@@ -6879,6 +6973,9 @@ packages:
is-promise@2.2.2:
resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
+ is-promise@4.0.0:
+ resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
+
is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
@@ -7022,6 +7119,9 @@ packages:
jju@1.4.0:
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
+ jose@6.1.3:
+ resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
+
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
@@ -7096,6 +7196,9 @@ packages:
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+ json-schema-typed@8.0.2:
+ resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
+
json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
@@ -7650,6 +7753,10 @@ packages:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
+ media-typer@1.1.0:
+ resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
+ engines: {node: '>= 0.8'}
+
meow@12.1.1:
resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==}
engines: {node: '>=16.10'}
@@ -7664,6 +7771,10 @@ packages:
merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
+ merge-descriptors@2.0.0:
+ resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
+ engines: {node: '>=18'}
+
merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -7901,6 +8012,10 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
+ mime-types@3.0.2:
+ resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
+ engines: {node: '>=18'}
+
mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
@@ -8063,6 +8178,10 @@ packages:
resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
engines: {node: '>= 0.6'}
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
@@ -8674,6 +8793,9 @@ packages:
path-to-regexp@3.3.0:
resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==}
+ path-to-regexp@8.3.0:
+ resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
+
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@@ -8751,6 +8873,10 @@ packages:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
+ pkce-challenge@5.0.1:
+ resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
+ engines: {node: '>=16.20.0'}
+
pkg-conf@2.1.0:
resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==}
engines: {node: '>=4'}
@@ -9057,6 +9183,10 @@ packages:
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
engines: {node: '>= 0.8'}
+ raw-body@3.0.2:
+ resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
+ engines: {node: '>= 0.10'}
+
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
@@ -9439,6 +9569,10 @@ packages:
rope-sequence@1.3.4:
resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
+ router@2.2.0:
+ resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
+ engines: {node: '>= 18'}
+
run-applescript@7.1.0:
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
engines: {node: '>=18'}
@@ -9567,6 +9701,10 @@ packages:
resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==}
engines: {node: '>= 0.8.0'}
+ send@1.2.1:
+ resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
+ engines: {node: '>= 18'}
+
serialize-error@13.0.1:
resolution: {integrity: sha512-bBZaRwLH9PN5HbLCjPId4dP5bNGEtumcErgOX952IsvOhVPrm3/AeK1y0UHA/QaPG701eg0yEnOKsCOC6X/kaA==}
engines: {node: '>=20'}
@@ -9582,6 +9720,10 @@ packages:
resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==}
engines: {node: '>= 0.8.0'}
+ serve-static@2.2.1:
+ resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
+ engines: {node: '>= 18'}
+
serve@14.2.5:
resolution: {integrity: sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==}
engines: {node: '>= 14'}
@@ -10243,6 +10385,10 @@ packages:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
+ type-is@2.0.1:
+ resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
+ engines: {node: '>= 0.6'}
+
typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -10967,6 +11113,11 @@ packages:
peerDependencies:
zod: ^3.20.0
+ zod-to-json-schema@3.25.1:
+ resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
+ peerDependencies:
+ zod: ^3.25 || ^4
+
zod-validation-error@4.0.2:
resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
engines: {node: '>=18.0.0'}
@@ -12208,6 +12359,10 @@ snapshots:
- bufferutil
- utf-8-validate
+ '@hono/node-server@1.19.9(hono@4.12.1)':
+ dependencies:
+ hono: 4.12.1
+
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.7':
@@ -12296,128 +12451,128 @@ snapshots:
'@inquirer/ansi@1.0.2': {}
- '@inquirer/checkbox@4.3.2(@types/node@22.19.2)':
+ '@inquirer/checkbox@4.3.2(@types/node@25.3.0)':
dependencies:
'@inquirer/ansi': 1.0.2
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
'@inquirer/figures': 1.0.15
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
yoctocolors-cjs: 2.1.3
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/confirm@5.1.21(@types/node@22.19.2)':
+ '@inquirer/confirm@5.1.21(@types/node@25.3.0)':
dependencies:
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/core@10.3.2(@types/node@22.19.2)':
+ '@inquirer/core@10.3.2(@types/node@25.3.0)':
dependencies:
'@inquirer/ansi': 1.0.2
'@inquirer/figures': 1.0.15
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
cli-width: 4.1.0
mute-stream: 2.0.0
signal-exit: 4.1.0
wrap-ansi: 6.2.0
yoctocolors-cjs: 2.1.3
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/editor@4.2.23(@types/node@22.19.2)':
+ '@inquirer/editor@4.2.23(@types/node@25.3.0)':
dependencies:
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
- '@inquirer/external-editor': 1.0.3(@types/node@22.19.2)
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
+ '@inquirer/external-editor': 1.0.3(@types/node@25.3.0)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/expand@4.0.23(@types/node@22.19.2)':
+ '@inquirer/expand@4.0.23(@types/node@25.3.0)':
dependencies:
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
yoctocolors-cjs: 2.1.3
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/external-editor@1.0.3(@types/node@22.19.2)':
+ '@inquirer/external-editor@1.0.3(@types/node@25.3.0)':
dependencies:
chardet: 2.1.1
iconv-lite: 0.7.2
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
'@inquirer/figures@1.0.15': {}
- '@inquirer/input@4.3.1(@types/node@22.19.2)':
+ '@inquirer/input@4.3.1(@types/node@25.3.0)':
dependencies:
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/number@3.0.23(@types/node@22.19.2)':
+ '@inquirer/number@3.0.23(@types/node@25.3.0)':
dependencies:
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/password@4.0.23(@types/node@22.19.2)':
+ '@inquirer/password@4.0.23(@types/node@25.3.0)':
dependencies:
'@inquirer/ansi': 1.0.2
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/prompts@7.9.0(@types/node@22.19.2)':
- dependencies:
- '@inquirer/checkbox': 4.3.2(@types/node@22.19.2)
- '@inquirer/confirm': 5.1.21(@types/node@22.19.2)
- '@inquirer/editor': 4.2.23(@types/node@22.19.2)
- '@inquirer/expand': 4.0.23(@types/node@22.19.2)
- '@inquirer/input': 4.3.1(@types/node@22.19.2)
- '@inquirer/number': 3.0.23(@types/node@22.19.2)
- '@inquirer/password': 4.0.23(@types/node@22.19.2)
- '@inquirer/rawlist': 4.1.11(@types/node@22.19.2)
- '@inquirer/search': 3.2.2(@types/node@22.19.2)
- '@inquirer/select': 4.4.2(@types/node@22.19.2)
+ '@inquirer/prompts@7.9.0(@types/node@25.3.0)':
+ dependencies:
+ '@inquirer/checkbox': 4.3.2(@types/node@25.3.0)
+ '@inquirer/confirm': 5.1.21(@types/node@25.3.0)
+ '@inquirer/editor': 4.2.23(@types/node@25.3.0)
+ '@inquirer/expand': 4.0.23(@types/node@25.3.0)
+ '@inquirer/input': 4.3.1(@types/node@25.3.0)
+ '@inquirer/number': 3.0.23(@types/node@25.3.0)
+ '@inquirer/password': 4.0.23(@types/node@25.3.0)
+ '@inquirer/rawlist': 4.1.11(@types/node@25.3.0)
+ '@inquirer/search': 3.2.2(@types/node@25.3.0)
+ '@inquirer/select': 4.4.2(@types/node@25.3.0)
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/rawlist@4.1.11(@types/node@22.19.2)':
+ '@inquirer/rawlist@4.1.11(@types/node@25.3.0)':
dependencies:
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
yoctocolors-cjs: 2.1.3
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/search@3.2.2(@types/node@22.19.2)':
+ '@inquirer/search@3.2.2(@types/node@25.3.0)':
dependencies:
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
'@inquirer/figures': 1.0.15
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
yoctocolors-cjs: 2.1.3
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/select@4.4.2(@types/node@22.19.2)':
+ '@inquirer/select@4.4.2(@types/node@25.3.0)':
dependencies:
'@inquirer/ansi': 1.0.2
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
'@inquirer/figures': 1.0.15
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
yoctocolors-cjs: 2.1.3
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
- '@inquirer/type@3.0.10(@types/node@22.19.2)':
+ '@inquirer/type@3.0.10(@types/node@25.3.0)':
optionalDependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
'@isaacs/cliui@8.0.2':
dependencies:
@@ -12566,9 +12721,9 @@ snapshots:
'@microsoft/tsdoc@0.16.0': {}
- '@mintlify/cli@4.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(typescript@5.9.3)':
+ '@mintlify/cli@4.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3))(@types/node@25.3.0)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(typescript@5.9.3)':
dependencies:
- '@inquirer/prompts': 7.9.0(@types/node@22.19.2)
+ '@inquirer/prompts': 7.9.0(@types/node@25.3.0)
'@mintlify/common': 1.0.748(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3)(typescript@5.9.3)
'@mintlify/link-rot': 3.0.912(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3)(typescript@5.9.3)
'@mintlify/models': 0.0.274
@@ -12583,7 +12738,7 @@ snapshots:
front-matter: 4.0.2
fs-extra: 11.2.0
ink: 6.3.0(@types/react@19.2.14)(react@19.2.3)
- inquirer: 12.3.0(@types/node@22.19.2)
+ inquirer: 12.3.0(@types/node@25.3.0)
js-yaml: 4.1.0
mdast-util-mdx-jsx: 3.2.0
react: 19.2.3
@@ -12987,6 +13142,28 @@ snapshots:
- supports-color
- typescript
+ '@modelcontextprotocol/sdk@1.26.0(zod@4.3.6)':
+ dependencies:
+ '@hono/node-server': 1.19.9(hono@4.12.1)
+ ajv: 8.18.0
+ ajv-formats: 3.0.1(ajv@8.18.0)
+ content-type: 1.0.5
+ cors: 2.8.6
+ cross-spawn: 7.0.6
+ eventsource: 3.0.7
+ eventsource-parser: 3.0.6
+ express: 5.2.1
+ express-rate-limit: 8.2.1(express@5.2.1)
+ hono: 4.12.1
+ jose: 6.1.3
+ json-schema-typed: 8.0.2
+ pkce-challenge: 5.0.1
+ raw-body: 3.0.2
+ zod: 4.3.6
+ zod-to-json-schema: 3.25.1(zod@4.3.6)
+ transitivePeerDependencies:
+ - supports-color
+
'@napi-rs/canvas-android-arm64@0.1.94':
optional: true
@@ -14093,7 +14270,7 @@ snapshots:
'@stoplight/json-ref-readers@1.2.2':
dependencies:
- node-fetch: 2.6.7
+ node-fetch: 2.7.0
tslib: 1.14.1
transitivePeerDependencies:
- encoding
@@ -14317,7 +14494,7 @@ snapshots:
'@types/conventional-commits-parser@5.0.2':
dependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
'@types/cookie@0.4.1': {}
@@ -14414,7 +14591,7 @@ snapshots:
'@types/responselike@1.0.0':
dependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
'@types/supports-color@8.1.3': {}
@@ -14430,7 +14607,7 @@ snapshots:
'@types/ws@8.18.1':
dependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
'@types/yauzl@2.10.3':
dependencies:
@@ -15045,6 +15222,11 @@ snapshots:
mime-types: 2.1.35
negotiator: 0.6.3
+ accepts@2.0.0:
+ dependencies:
+ mime-types: 3.0.2
+ negotiator: 1.0.0
+
acorn-jsx@5.3.2(acorn@8.11.2):
dependencies:
acorn: 8.11.2
@@ -15486,6 +15668,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ body-parser@2.2.2:
+ dependencies:
+ bytes: 3.1.2
+ content-type: 1.0.5
+ debug: 4.4.3(supports-color@5.5.0)
+ http-errors: 2.0.1
+ iconv-lite: 0.7.2
+ on-finished: 2.4.1
+ qs: 6.15.0
+ raw-body: 3.0.2
+ type-is: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
boolbase@1.0.0: {}
bottleneck@2.19.5: {}
@@ -15606,7 +15802,7 @@ snapshots:
bun-types@1.3.9:
dependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
bundle-name@4.1.0:
dependencies:
@@ -15971,6 +16167,8 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
+ content-disposition@1.0.1: {}
+
content-type@1.0.5: {}
conventional-changelog-angular@7.0.0:
@@ -16015,6 +16213,8 @@ snapshots:
cookie-signature@1.0.7: {}
+ cookie-signature@1.2.2: {}
+
cookie@0.4.2: {}
cookie@0.5.0: {}
@@ -16941,6 +17141,12 @@ snapshots:
events@3.3.0: {}
+ eventsource-parser@3.0.6: {}
+
+ eventsource@3.0.7:
+ dependencies:
+ eventsource-parser: 3.0.6
+
evp_bytestokey@1.0.3:
dependencies:
md5.js: 1.3.5
@@ -16993,6 +17199,11 @@ snapshots:
express-rate-limit@5.5.1: {}
+ express-rate-limit@8.2.1(express@5.2.1):
+ dependencies:
+ express: 5.2.1
+ ip-address: 10.0.1
+
express@4.18.2:
dependencies:
accepts: 1.3.8
@@ -17065,6 +17276,39 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ express@5.2.1:
+ dependencies:
+ accepts: 2.0.0
+ body-parser: 2.2.2
+ content-disposition: 1.0.1
+ content-type: 1.0.5
+ cookie: 0.7.2
+ cookie-signature: 1.2.2
+ debug: 4.4.3(supports-color@5.5.0)
+ depd: 2.0.0
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 2.1.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ merge-descriptors: 2.0.0
+ mime-types: 3.0.2
+ on-finished: 2.4.1
+ once: 1.4.0
+ parseurl: 1.3.3
+ proxy-addr: 2.0.7
+ qs: 6.15.0
+ range-parser: 1.2.1
+ router: 2.2.0
+ send: 1.2.1
+ serve-static: 2.2.1
+ statuses: 2.0.2
+ type-is: 2.0.1
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
exsolve@1.0.8: {}
extend@3.0.2: {}
@@ -17174,6 +17418,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ finalhandler@2.1.1:
+ dependencies:
+ debug: 4.4.3(supports-color@5.5.0)
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
find-up-simple@1.0.1: {}
find-up@2.1.0:
@@ -17249,6 +17504,8 @@ snapshots:
fresh@0.5.2: {}
+ fresh@2.0.0: {}
+
from2@2.3.0:
dependencies:
inherits: 2.0.4
@@ -17556,7 +17813,7 @@ snapshots:
happy-dom@20.4.0:
dependencies:
- '@types/node': 22.19.2
+ '@types/node': 25.3.0
'@types/whatwg-mimetype': 3.0.2
'@types/ws': 8.18.1
entities: 4.5.0
@@ -17888,6 +18145,8 @@ snapshots:
minimalistic-assert: 1.0.1
minimalistic-crypto-utils: 1.0.1
+ hono@4.12.1: {}
+
hook-std@4.0.0: {}
hosted-git-info@2.8.9: {}
@@ -18084,12 +18343,12 @@ snapshots:
inline-style-parser@0.2.7: {}
- inquirer@12.3.0(@types/node@22.19.2):
+ inquirer@12.3.0(@types/node@25.3.0):
dependencies:
- '@inquirer/core': 10.3.2(@types/node@22.19.2)
- '@inquirer/prompts': 7.9.0(@types/node@22.19.2)
- '@inquirer/type': 3.0.10(@types/node@22.19.2)
- '@types/node': 22.19.2
+ '@inquirer/core': 10.3.2(@types/node@25.3.0)
+ '@inquirer/prompts': 7.9.0(@types/node@25.3.0)
+ '@inquirer/type': 3.0.10(@types/node@25.3.0)
+ '@types/node': 25.3.0
ansi-escapes: 4.3.2
mute-stream: 2.0.0
run-async: 3.0.0
@@ -18106,6 +18365,8 @@ snapshots:
from2: 2.3.0
p-is-promise: 3.0.0
+ ip-address@10.0.1: {}
+
ip-address@10.1.0: {}
ip-regex@4.3.0: {}
@@ -18270,6 +18531,8 @@ snapshots:
is-promise@2.2.2: {}
+ is-promise@4.0.0: {}
+
is-regex@1.2.1:
dependencies:
call-bound: 1.0.4
@@ -18409,6 +18672,8 @@ snapshots:
jju@1.4.0: {}
+ jose@6.1.3: {}
+
joycon@3.1.1: {}
js-beautify@1.15.4:
@@ -18487,6 +18752,8 @@ snapshots:
json-schema-traverse@1.0.0: {}
+ json-schema-typed@8.0.2: {}
+
json-schema@0.4.0: {}
json-stable-stringify-without-jsonify@1.0.1: {}
@@ -19227,6 +19494,8 @@ snapshots:
media-typer@0.3.0: {}
+ media-typer@1.1.0: {}
+
meow@12.1.1: {}
meow@13.2.0: {}
@@ -19235,6 +19504,8 @@ snapshots:
merge-descriptors@1.0.3: {}
+ merge-descriptors@2.0.0: {}
+
merge-stream@2.0.0: {}
merge2@1.4.1: {}
@@ -19751,6 +20022,10 @@ snapshots:
dependencies:
mime-db: 1.52.0
+ mime-types@3.0.2:
+ dependencies:
+ mime-db: 1.54.0
+
mime@1.6.0: {}
mime@3.0.0: {}
@@ -19816,9 +20091,9 @@ snapshots:
minipass: 3.3.6
yallist: 4.0.0
- mintlify@4.2.374(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(typescript@5.9.3):
+ mintlify@4.2.374(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3))(@types/node@25.3.0)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(typescript@5.9.3):
dependencies:
- '@mintlify/cli': 4.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3))(@types/node@22.19.2)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(typescript@5.9.3)
+ '@mintlify/cli': 4.0.977(@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(react@19.2.3))(@types/node@25.3.0)(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.3))(typescript@5.9.3)
transitivePeerDependencies:
- '@radix-ui/react-popover'
- '@types/node'
@@ -19902,6 +20177,8 @@ snapshots:
negotiator@0.6.4: {}
+ negotiator@1.0.0: {}
+
neo-async@2.6.2: {}
neotraverse@0.6.18: {}
@@ -20414,6 +20691,8 @@ snapshots:
path-to-regexp@3.3.0: {}
+ path-to-regexp@8.3.0: {}
+
path-type@4.0.0: {}
pathe@2.0.3: {}
@@ -20493,6 +20772,8 @@ snapshots:
pirates@4.0.7: {}
+ pkce-challenge@5.0.1: {}
+
pkg-conf@2.1.0:
dependencies:
find-up: 2.1.0
@@ -20856,6 +21137,13 @@ snapshots:
iconv-lite: 0.4.24
unpipe: 1.0.0
+ raw-body@3.0.2:
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.1
+ iconv-lite: 0.7.2
+ unpipe: 1.0.0
+
rc@1.2.8:
dependencies:
deep-extend: 0.6.0
@@ -21453,6 +21741,16 @@ snapshots:
rope-sequence@1.3.4: {}
+ router@2.2.0:
+ dependencies:
+ debug: 4.4.3(supports-color@5.5.0)
+ depd: 2.0.0
+ is-promise: 4.0.0
+ parseurl: 1.3.3
+ path-to-regexp: 8.3.0
+ transitivePeerDependencies:
+ - supports-color
+
run-applescript@7.1.0: {}
run-async@3.0.0: {}
@@ -21626,6 +21924,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ send@1.2.1:
+ dependencies:
+ debug: 4.4.3(supports-color@5.5.0)
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ mime-types: 3.0.2
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
serialize-error@13.0.1:
dependencies:
non-error: 0.1.0
@@ -21659,6 +21973,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ serve-static@2.2.1:
+ dependencies:
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 1.2.1
+ transitivePeerDependencies:
+ - supports-color
+
serve@14.2.5:
dependencies:
'@zeit/schemas': 2.36.0
@@ -22483,6 +22806,12 @@ snapshots:
media-typer: 0.3.0
mime-types: 2.1.35
+ type-is@2.0.1:
+ dependencies:
+ content-type: 1.0.5
+ media-typer: 1.1.0
+ mime-types: 3.0.2
+
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4
@@ -23437,6 +23766,10 @@ snapshots:
dependencies:
zod: 3.24.0
+ zod-to-json-schema@3.25.1(zod@4.3.6):
+ dependencies:
+ zod: 4.3.6
+
zod-validation-error@4.0.2(zod@4.3.6):
dependencies:
zod: 4.3.6