From c661b07aed4ace88a893dcc7311cd524c75ed48c Mon Sep 17 00:00:00 2001 From: Abhishek Chauhan Date: Tue, 17 Mar 2026 19:14:51 +0530 Subject: [PATCH] feat: add MCP server for docs, policies, and source code Add an MCP (Model Context Protocol) server that exposes NemoClaw documentation, blueprint config, network policies, NIM model catalog, and source code as 12 searchable tools for AI assistants. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Abhishek Chauhan --- .github/workflows/pr.yaml | 4 + .gitignore | 1 + mcp-docs-server/README.md | 62 ++ mcp-docs-server/index.js | 650 ++++++++++++++++ mcp-docs-server/package-lock.json | 1143 +++++++++++++++++++++++++++++ mcp-docs-server/package.json | 23 + test/mcp-docs-server.test.cjs | 215 ++++++ 7 files changed, 2098 insertions(+) create mode 100644 mcp-docs-server/README.md create mode 100644 mcp-docs-server/index.js create mode 100644 mcp-docs-server/package-lock.json create mode 100644 mcp-docs-server/package.json create mode 100644 test/mcp-docs-server.test.cjs diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 372aa074f8..23e39a7d74 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -68,6 +68,10 @@ jobs: npm install npm run build + - name: Install MCP docs server dependencies + working-directory: mcp-docs-server + run: npm install + - name: Run all unit tests with coverage run: npx vitest run --coverage diff --git a/.gitignore b/.gitignore index 6561d6a91b..0ef22352ba 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ secrets.json secrets.yaml service-account*.json token.json +.mcp.json diff --git a/mcp-docs-server/README.md b/mcp-docs-server/README.md new file mode 100644 index 0000000000..4fb805e2cd --- /dev/null +++ b/mcp-docs-server/README.md @@ -0,0 +1,62 @@ + + +# NemoClaw Docs MCP Server + +An [MCP](https://modelcontextprotocol.io/) server that exposes NemoClaw documentation, source code, blueprint configuration, network policies, and NIM model catalog as tools for AI assistants. + +## Tools + +| Tool | Description | +|---|---| +| `list_docs` | List all documentation pages | +| `read_doc` | Read a specific doc page by path | +| `search` | Full-text search across docs, source code, policies, scripts, and configs | +| `get_blueprint_config` | Blueprint YAML with inference profiles and sandbox config | +| `get_baseline_policy` | Baseline sandbox network and filesystem policy | +| `list_policy_presets` | Available network policy presets with endpoints | +| `get_policy_preset` | Full YAML for a specific policy preset | +| `get_nim_models` | NIM container image catalog with GPU memory requirements | +| `read_source_file` | Read any source file from the repo | +| `list_source_files` | Browse indexed source files by category | +| `get_architecture_overview` | Architecture summary with components, flows, and models | +| `get_dockerfile` | Sandbox container Dockerfile | + +## Usage with Claude Code + +Create a `.mcp.json` file in the project root: + +```json +{ + "mcpServers": { + "nemoclaw-docs": { + "command": "node", + "args": ["mcp-docs-server/index.js"] + } + } +} +``` + +Then restart Claude Code. The server starts automatically. + +## Usage with Other MCP Clients + +Run the server over stdio: + +```console +$ cd /path/to/NemoClaw +$ node mcp-docs-server/index.js +``` + +The server communicates over stdin/stdout using the MCP JSON-RPC protocol. + +## Install Dependencies + +```console +$ cd mcp-docs-server +$ npm install +``` + +Requires Node.js 20 or later. diff --git a/mcp-docs-server/index.js b/mcp-docs-server/index.js new file mode 100644 index 0000000000..774d4f401a --- /dev/null +++ b/mcp-docs-server/index.js @@ -0,0 +1,650 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { createRequire } from "module"; + +const require = createRequire(import.meta.url); +const { version } = require("./package.json"); + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, ".."); +const DOCS_DIR = path.join(ROOT, "docs"); +const BLUEPRINT_DIR = path.join(ROOT, "nemoclaw-blueprint"); +const POLICIES_DIR = path.join(BLUEPRINT_DIR, "policies"); +const PRESETS_DIR = path.join(POLICIES_DIR, "presets"); +const PLUGIN_SRC_DIR = path.join(ROOT, "nemoclaw", "src"); +const SCRIPTS_DIR = path.join(ROOT, "scripts"); +const BIN_DIR = path.join(ROOT, "bin"); + +// ── Indexing helpers ──────────────────────────────────────────────────── + +function readIfExists(filePath) { + try { + return fs.readFileSync(filePath, "utf-8"); + } catch { + return null; + } +} + +function parseJsonSafe(text) { + try { + return JSON.parse(text); + } catch { + return null; + } +} + +function stripFrontmatter(content) { + return content + .replace(/^---\n[\s\S]*?---\n/, "") + .replace(/\n*/g, "") + .trim(); +} + +function extractTitle(content) { + const fmMatch = content.match(/^---\n[\s\S]*?title:\s*\n\s*page:\s*"([^"]+)"/); + if (fmMatch) return fmMatch[1]; + const h1Match = content.match(/^#\s+(.+)$/m); + return h1Match ? h1Match[1] : null; +} + +function extractDescription(content) { + const match = content.match(/^description:\s*"([^"]+)"/m); + return match ? match[1] : ""; +} + +// ── Index: Documentation ──────────────────────────────────────────────── + +const docs = []; + +function indexDocs(dir) { + if (!fs.existsSync(dir)) return; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory() && !entry.name.startsWith("_")) { + indexDocs(fullPath); + } else if (entry.name.endsWith(".md")) { + const relPath = path.relative(DOCS_DIR, fullPath); + const raw = fs.readFileSync(fullPath, "utf-8"); + docs.push({ + path: relPath, + title: extractTitle(raw) || relPath, + description: extractDescription(raw), + content: stripFrontmatter(raw), + }); + } + } +} + +indexDocs(DOCS_DIR); + +// ── Index: Policy presets ─────────────────────────────────────────────── + +const presets = []; + +if (fs.existsSync(PRESETS_DIR)) { + for (const f of fs.readdirSync(PRESETS_DIR)) { + if (!f.endsWith(".yaml")) continue; + const content = fs.readFileSync(path.join(PRESETS_DIR, f), "utf-8"); + const nameMatch = content.match(/^\s*name:\s*(.+)$/m); + const descMatch = content.match(/^\s*description:\s*"?([^"\n]*)"?$/m); + const hosts = []; + const hostRe = /host:\s*([^\s,}]+)/g; + let m; + while ((m = hostRe.exec(content)) !== null) hosts.push(m[1]); + presets.push({ + file: f, + name: nameMatch ? nameMatch[1].trim() : f.replace(".yaml", ""), + description: descMatch ? descMatch[1].trim() : "", + endpoints: hosts, + content, + }); + } +} + +// ── Index: Source code files ──────────────────────────────────────────── + +const sourceFiles = []; + +function indexSource(dir, category) { + if (!fs.existsSync(dir)) return; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + indexSource(fullPath, category); + } else if (/\.(ts|js|py|sh|yaml|json)$/.test(entry.name) && !entry.name.startsWith(".")) { + const relPath = path.relative(ROOT, fullPath); + const content = fs.readFileSync(fullPath, "utf-8"); + let desc = ""; + const jsDocMatch = content.match(/^\/\/\s*(.*?)(?:\n|$)/m); + const pyDocMatch = content.match(/"""([\s\S]*?)"""/); + const shDescMatch = content.match(/^#\s*(.*?)(?:\n|$)/m); + if (jsDocMatch) desc = jsDocMatch[1].replace(/^SPDX.*$/, "").trim(); + if (!desc && pyDocMatch) desc = pyDocMatch[1].split("\n")[0].trim(); + if (!desc && shDescMatch) desc = shDescMatch[1].replace(/^!.*$/, "").trim(); + sourceFiles.push({ path: relPath, category, description: desc, content }); + } + } +} + +indexSource(PLUGIN_SRC_DIR, "plugin"); +indexSource(path.join(BLUEPRINT_DIR, "orchestrator"), "blueprint"); +indexSource(path.join(BLUEPRINT_DIR, "migrations"), "blueprint"); +indexSource(SCRIPTS_DIR, "scripts"); +indexSource(BIN_DIR, "cli"); + +// Add key root config files +for (const [relPath, fullPath] of [ + ["nemoclaw-blueprint/blueprint.yaml", path.join(BLUEPRINT_DIR, "blueprint.yaml")], + ["Dockerfile", path.join(ROOT, "Dockerfile")], + ["README.md", path.join(ROOT, "README.md")], + ["install.sh", path.join(ROOT, "install.sh")], + ["uninstall.sh", path.join(ROOT, "uninstall.sh")], +]) { + const content = readIfExists(fullPath); + if (content) { + sourceFiles.push({ + path: relPath, + category: relPath === "nemoclaw-blueprint/blueprint.yaml" ? "blueprint" : "config", + description: relPath, + content, + }); + } +} + +// ── Unified search ────────────────────────────────────────────────────── + +function search(items, query, maxResults = 5) { + const terms = query.toLowerCase().split(/\s+/).filter(Boolean); + if (terms.length === 0) return []; + return items + .map((item) => { + let score = 0; + for (const term of terms) { + if ((item.path || "").toLowerCase().includes(term)) score += 8; + if ((item.title || "").toLowerCase().includes(term)) score += 10; + if ((item.description || "").toLowerCase().includes(term)) score += 5; + let idx = 0; + const content = (item.content || "").toLowerCase(); + while ((idx = content.indexOf(term, idx)) !== -1) { + score += 1; + idx += term.length; + } + } + if (score === 0) return null; + + const lines = (item.content || "").split("\n"); + const snippets = []; + for (let i = 0; i < lines.length && snippets.length < 3; i++) { + if (terms.some((t) => lines[i].toLowerCase().includes(t))) { + const start = Math.max(0, i - 1); + const end = Math.min(lines.length, i + 3); + snippets.push(lines.slice(start, end).join("\n")); + } + } + return { item, score, snippets }; + }) + .filter(Boolean) + .sort((a, b) => b.score - a.score) + .slice(0, maxResults); +} + +// ── MCP Server ────────────────────────────────────────────────────────── + +const server = new McpServer({ + name: "nemoclaw-docs", + version, +}); + +// ── Tool: list_docs ───────────────────────────────────────────────────── + +server.tool("list_docs", "List all NemoClaw documentation pages", {}, async () => { + const list = docs.map( + (d) => `- **${d.title}** (\`${d.path}\`)\n ${d.description}` + ); + return { + content: [{ type: "text", text: `# NemoClaw Documentation\n\n${list.join("\n\n")}` }], + }; +}); + +// ── Tool: read_doc ────────────────────────────────────────────────────── + +server.tool( + "read_doc", + "Read a specific NemoClaw documentation page by path", + { + doc_path: z + .string() + .describe("Path relative to docs/, e.g. 'about/overview.md' or 'reference/commands.md'"), + }, + async ({ doc_path }) => { + const doc = docs.find((d) => d.path === doc_path); + if (!doc) { + return { + content: [ + { + type: "text", + text: `Document not found: ${doc_path}\n\nAvailable:\n${docs.map((d) => `- ${d.path}`).join("\n")}`, + }, + ], + isError: true, + }; + } + return { content: [{ type: "text", text: `# ${doc.title}\n\n${doc.content}` }] }; + } +); + +// ── Tool: search ──────────────────────────────────────────────────────── + +server.tool( + "search", + "Search across ALL NemoClaw knowledge — docs, source code, policies, configs, scripts. Returns ranked results with snippets.", + { + query: z.string().describe("Search query (keywords or phrase)"), + scope: z + .enum(["all", "docs", "code", "policies", "blueprint", "scripts"]) + .optional() + .default("all") + .describe("Limit search scope: all, docs, code (plugin+cli), policies, blueprint, scripts"), + max_results: z.number().optional().default(8).describe("Max results (default: 8)"), + }, + async ({ query, scope, max_results }) => { + let pool = []; + if (scope === "all" || scope === "docs") pool.push(...docs); + if (scope === "all" || scope === "code") { + pool.push(...sourceFiles.filter((s) => s.category === "plugin" || s.category === "cli")); + } + if (scope === "all" || scope === "scripts") { + pool.push(...sourceFiles.filter((s) => s.category === "scripts")); + } + if (scope === "all" || scope === "policies") { + pool.push( + ...presets.map((p) => ({ + path: `presets/${p.file}`, + title: `Policy preset: ${p.name}`, + description: p.description, + content: p.content, + })) + ); + const basePolicy = readIfExists(path.join(POLICIES_DIR, "openclaw-sandbox.yaml")); + if (basePolicy) { + pool.push({ + path: "policies/openclaw-sandbox.yaml", + title: "Baseline sandbox policy", + description: "Strict deny-by-default network and filesystem policy", + content: basePolicy, + }); + } + } + if (scope === "all" || scope === "blueprint") { + pool.push(...sourceFiles.filter((s) => s.category === "blueprint")); + } + if (scope === "all") { + pool.push(...sourceFiles.filter((s) => s.category === "config")); + } + + const results = search(pool, query, max_results); + if (results.length === 0) { + return { content: [{ type: "text", text: `No results for "${query}".` }] }; + } + + const text = results + .map((r) => { + const snippetText = + r.snippets.length > 0 + ? r.snippets.map((s) => "```\n" + s + "\n```").join("\n") + : ""; + return `## ${r.item.title || r.item.path}\n**Path:** \`${r.item.path}\` | **Score:** ${r.score}\n${r.item.description ? `> ${r.item.description}\n` : ""}\n${snippetText}`; + }) + .join("\n\n---\n\n"); + + return { + content: [{ type: "text", text: `# Search: "${query}" (scope: ${scope})\n\n${text}` }], + }; + } +); + +// ── Tool: get_blueprint_config ────────────────────────────────────────── + +server.tool( + "get_blueprint_config", + "Get the NemoClaw blueprint configuration — profiles, sandbox image, inference providers, policy setup", + {}, + async () => { + const content = readIfExists(path.join(BLUEPRINT_DIR, "blueprint.yaml")); + if (!content) { + return { content: [{ type: "text", text: "blueprint.yaml not found" }], isError: true }; + } + return { + content: [ + { + type: "text", + text: `# NemoClaw Blueprint Configuration\n\n\`\`\`yaml\n${content}\n\`\`\``, + }, + ], + }; + } +); + +// ── Tool: get_baseline_policy ─────────────────────────────────────────── + +server.tool( + "get_baseline_policy", + "Get the full baseline sandbox network and filesystem policy (openclaw-sandbox.yaml)", + {}, + async () => { + const content = readIfExists(path.join(POLICIES_DIR, "openclaw-sandbox.yaml")); + if (!content) { + return { content: [{ type: "text", text: "Baseline policy not found" }], isError: true }; + } + return { + content: [ + { + type: "text", + text: `# Baseline Sandbox Policy\n\n\`\`\`yaml\n${content}\n\`\`\``, + }, + ], + }; + } +); + +// ── Tool: list_policy_presets ──────────────────────────────────────────── + +server.tool( + "list_policy_presets", + "List all available network policy presets (Discord, Slack, Docker, npm, PyPI, etc.) with their allowed endpoints", + {}, + async () => { + if (presets.length === 0) { + return { content: [{ type: "text", text: "No policy presets found." }] }; + } + const rows = presets + .map( + (p) => + `### ${p.name}\n${p.description}\n- **Endpoints:** ${p.endpoints.join(", ")}\n- **File:** \`presets/${p.file}\`` + ) + .join("\n\n"); + + return { + content: [ + { + type: "text", + text: `# Network Policy Presets\n\n${presets.length} presets available. Use \`nemoclaw policy-add\` to apply.\n\n${rows}`, + }, + ], + }; + } +); + +// ── Tool: get_policy_preset ───────────────────────────────────────────── + +server.tool( + "get_policy_preset", + "Get the full YAML content of a specific network policy preset", + { + preset_name: z + .string() + .describe( + "Preset name: discord, slack, docker, npm, pypi, jira, huggingface, outlook, telegram" + ), + }, + async ({ preset_name }) => { + const preset = presets.find( + (p) => p.name === preset_name || p.file === `${preset_name}.yaml` + ); + if (!preset) { + return { + content: [ + { + type: "text", + text: `Preset "${preset_name}" not found.\n\nAvailable: ${presets.map((p) => p.name).join(", ")}`, + }, + ], + isError: true, + }; + } + return { + content: [ + { + type: "text", + text: `# Policy Preset: ${preset.name}\n\n${preset.description}\n\n**Endpoints:** ${preset.endpoints.join(", ")}\n\n\`\`\`yaml\n${preset.content}\n\`\`\``, + }, + ], + }; + } +); + +// ── Tool: get_nim_models ──────────────────────────────────────────────── + +server.tool( + "get_nim_models", + "Get the catalog of NIM container images and models with GPU memory requirements", + {}, + async () => { + const nimImagesPath = path.join(BIN_DIR, "lib", "nim-images.json"); + const content = readIfExists(nimImagesPath); + if (!content) { + return { content: [{ type: "text", text: "NIM images catalog not found" }], isError: true }; + } + const catalog = parseJsonSafe(content); + if (!catalog || !Array.isArray(catalog.models)) { + return { + content: [{ type: "text", text: "NIM images catalog has invalid format" }], + isError: true, + }; + } + const rows = catalog.models + .map( + (m) => + `| \`${m.name || "unknown"}\` | \`${m.image || "unknown"}\` | ${m.minGpuMemoryMB ? (m.minGpuMemoryMB / 1024).toFixed(0) : "?"} GB |` + ) + .join("\n"); + + return { + content: [ + { + type: "text", + text: `# NIM Model Catalog\n\n| Model | Container Image | Min GPU Memory |\n|---|---|---|\n${rows}\n\n## Raw JSON\n\n\`\`\`json\n${content}\n\`\`\``, + }, + ], + }; + } +); + +// ── Tool: read_source_file ────────────────────────────────────────────── + +server.tool( + "read_source_file", + "Read any NemoClaw source file — plugin code, scripts, blueprint runner, Dockerfile, configs", + { + file_path: z + .string() + .describe( + "Path relative to project root, e.g. 'nemoclaw/src/commands/launch.ts', 'scripts/setup.sh', 'bin/nemoclaw.js', 'Dockerfile'" + ), + }, + async ({ file_path }) => { + const src = sourceFiles.find((s) => s.path === file_path); + if (src) { + return { + content: [ + { + type: "text", + text: `# ${file_path}\n\n${src.description ? `> ${src.description}\n\n` : ""}\`\`\`\n${src.content}\n\`\`\``, + }, + ], + }; + } + // Fallback: try reading from disk (validate path stays within project root) + const full = path.resolve(ROOT, file_path); + if (!full.startsWith(ROOT + path.sep) && full !== ROOT) { + return { + content: [{ type: "text", text: `Access denied: path outside project root` }], + isError: true, + }; + } + const content = readIfExists(full); + if (!content) { + const available = sourceFiles.map((s) => s.path).join("\n- "); + return { + content: [ + { type: "text", text: `File not found: ${file_path}\n\nIndexed files:\n- ${available}` }, + ], + isError: true, + }; + } + return { content: [{ type: "text", text: `# ${file_path}\n\n\`\`\`\n${content}\n\`\`\`` }] }; + } +); + +// ── Tool: list_source_files ───────────────────────────────────────────── + +server.tool( + "list_source_files", + "List all indexed NemoClaw source files by category (plugin, blueprint, scripts, cli, config)", + { + category: z + .enum(["all", "plugin", "blueprint", "scripts", "cli", "config"]) + .optional() + .default("all") + .describe("Filter by category (default: all)"), + }, + async ({ category }) => { + const filtered = + category === "all" ? sourceFiles : sourceFiles.filter((s) => s.category === category); + + const grouped = {}; + for (const f of filtered) { + const cat = f.category; + if (!grouped[cat]) grouped[cat] = []; + grouped[cat].push(f); + } + + const text = Object.entries(grouped) + .map( + ([cat, files]) => + `## ${cat}\n${files.map((f) => `- \`${f.path}\` — ${f.description || "(no description)"}`).join("\n")}` + ) + .join("\n\n"); + + return { + content: [ + { + type: "text", + text: `# NemoClaw Source Files (${filtered.length} files)\n\n${text}`, + }, + ], + }; + } +); + +// ── Tool: get_architecture_overview ───────────────────────────────────── + +server.tool( + "get_architecture_overview", + "Get a comprehensive overview of NemoClaw architecture — plugin, blueprint, sandbox, CLI, policies, inference, and deployment", + {}, + async () => { + // Build dynamic sections from indexed data + const presetNames = presets.map((p) => p.name).join(", "); + + const nimImagesPath = path.join(BIN_DIR, "lib", "nim-images.json"); + const nimContent = readIfExists(nimImagesPath); + const nimCatalog = nimContent ? parseJsonSafe(nimContent) : null; + const nimRows = nimCatalog?.models + ? nimCatalog.models + .map((m) => `| \`${m.name}\` | ${(m.minGpuMemoryMB / 1024).toFixed(0)} GB |`) + .join("\n") + : "| (catalog unavailable) | |"; + + const bpContent = readIfExists(path.join(BLUEPRINT_DIR, "blueprint.yaml")); + const profileNames = bpContent + ? (bpContent.match(/profiles:\n([\s\S]*?)(?:\n\S|\n$)/)?.[1] || "") + .split("\n") + .map((l) => l.replace(/^\s*-\s*/, "").trim()) + .filter(Boolean) + .join(", ") + : "default, ncp, nim-local, vllm"; + + const overview = `# NemoClaw Architecture Overview + +## Components + +### 1. CLI Entrypoint (\`bin/nemoclaw.js\`) +Host-side CLI dispatcher with global commands (onboard, deploy, start/stop/status, list) +and sandbox-scoped commands ( connect/status/logs/destroy/policy-add/policy-list). + +### 2. Plugin (\`nemoclaw/src/\`) +TypeScript OpenClaw plugin registered under \`openclaw nemoclaw\`. Commands: launch, connect, +status, logs, migrate, eject, onboard. Registers NVIDIA NIM provider with model catalog. + +### 3. Blueprint (\`nemoclaw-blueprint/\`) +Versioned Python artifact that orchestrates sandbox lifecycle via OpenShell CLI. +- **blueprint.yaml** — Profiles: ${profileNames} +- **orchestrator/runner.py** — Plan/apply/status/rollback actions +- **migrations/snapshot.py** — Snapshot/restore for host-to-sandbox migration + +### 4. Policies (\`nemoclaw-blueprint/policies/\`) +- **openclaw-sandbox.yaml** — Strict baseline: deny-by-default network, filesystem isolation +- **presets/** — ${presets.length} composable policy presets: ${presetNames} + +### 5. Sandbox Container (\`Dockerfile\`) +Node 22 + OpenClaw CLI + NemoClaw plugin + blueprint. Runs as \`sandbox\` user. +Inference routed through \`inference.local\` (OpenShell gateway proxy). + +### 6. Scripts (\`scripts/\`) +- **setup.sh** — Gateway creation, provider config, sandbox build +- **brev-setup.sh** — Remote GPU VM bootstrap +- **nemoclaw-start.sh** — Sandbox entrypoint (config, auth, gateway startup) +- **start-services.sh** — Telegram bridge, cloudflared tunnel +- **walkthrough.sh** — Interactive demo with split tmux + +## NIM Container Models + +| Model | Min GPU | +|---|---| +${nimRows} + +## Key Flows +1. **Onboard**: nemoclaw onboard → prompt for API key → create gateway → register provider → build sandbox → apply policy +2. **Deploy**: nemoclaw deploy → provision Brev VM → install Docker/OpenShell → run setup → connect +3. **Migrate**: openclaw nemoclaw migrate → snapshot host config → create sandbox → restore into sandbox → cutover +4. **Policy**: nemoclaw policy-add → select preset → merge into running policy via openshell policy set +`; + + return { content: [{ type: "text", text: overview }] }; + } +); + +// ── Tool: get_dockerfile ──────────────────────────────────────────────── + +server.tool( + "get_dockerfile", + "Get the NemoClaw sandbox Dockerfile with build steps explanation", + {}, + async () => { + const content = readIfExists(path.join(ROOT, "Dockerfile")); + if (!content) { + return { content: [{ type: "text", text: "Dockerfile not found" }], isError: true }; + } + return { + content: [ + { + type: "text", + text: `# NemoClaw Sandbox Dockerfile\n\n\`\`\`dockerfile\n${content}\n\`\`\``, + }, + ], + }; + } +); + +// ── Start ─────────────────────────────────────────────────────────────── + +const transport = new StdioServerTransport(); +await server.connect(transport); diff --git a/mcp-docs-server/package-lock.json b/mcp-docs-server/package-lock.json new file mode 100644 index 0000000000..b93f524600 --- /dev/null +++ b/mcp-docs-server/package-lock.json @@ -0,0 +1,1143 @@ +{ + "name": "nemoclaw-docs-mcp", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nemoclaw-docs-mcp", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1", + "zod": "^3.24.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", + "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.8", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.8.tgz", + "integrity": "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", + "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "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" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/mcp-docs-server/package.json b/mcp-docs-server/package.json new file mode 100644 index 0000000000..09300c8ba9 --- /dev/null +++ b/mcp-docs-server/package.json @@ -0,0 +1,23 @@ +{ + "name": "nemoclaw-docs-mcp", + "version": "0.1.0", + "description": "MCP server for NemoClaw documentation, policies, and source code", + "license": "Apache-2.0", + "type": "module", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1", + "zod": "^3.24.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/NVIDIA/NemoClaw.git", + "directory": "mcp-docs-server" + } +} diff --git a/test/mcp-docs-server.test.cjs b/test/mcp-docs-server.test.cjs new file mode 100644 index 0000000000..eff2f07f50 --- /dev/null +++ b/test/mcp-docs-server.test.cjs @@ -0,0 +1,215 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); + +const ROOT = path.join(__dirname, ".."); +const SERVER = path.join(ROOT, "mcp-docs-server", "index.js"); + +function mcpCall(method, params = {}) { + const init = JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "initialize", + params: { + protocolVersion: "2024-11-05", + capabilities: {}, + clientInfo: { name: "test", version: "1.0" }, + }, + }); + const initialized = JSON.stringify({ + jsonrpc: "2.0", + method: "notifications/initialized", + }); + const call = JSON.stringify({ + jsonrpc: "2.0", + id: 2, + method, + params, + }); + + const input = `${init}\n${initialized}\n${call}\n`; + const output = execSync(`node "${SERVER}"`, { + input, + encoding: "utf-8", + timeout: 15000, + cwd: ROOT, + }); + + // Parse the last JSON line (the response to our call) + const lines = output.trim().split("\n").filter(Boolean); + const responses = lines.map((l) => JSON.parse(l)); + const response = responses.find((r) => r.id === 2); + assert.ok(response, "Expected response with id 2"); + return response; +} + +describe("MCP docs server", () => { + it("lists tools", () => { + const res = mcpCall("tools/list"); + const names = res.result.tools.map((t) => t.name); + assert.ok(names.includes("list_docs"), "missing list_docs tool"); + assert.ok(names.includes("read_doc"), "missing read_doc tool"); + assert.ok(names.includes("search"), "missing search tool"); + assert.ok(names.includes("get_blueprint_config"), "missing get_blueprint_config tool"); + assert.ok(names.includes("get_baseline_policy"), "missing get_baseline_policy tool"); + assert.ok(names.includes("list_policy_presets"), "missing list_policy_presets tool"); + assert.ok(names.includes("get_policy_preset"), "missing get_policy_preset tool"); + assert.ok(names.includes("get_nim_models"), "missing get_nim_models tool"); + assert.ok(names.includes("read_source_file"), "missing read_source_file tool"); + assert.ok(names.includes("list_source_files"), "missing list_source_files tool"); + assert.ok(names.includes("get_architecture_overview"), "missing get_architecture_overview tool"); + assert.ok(names.includes("get_dockerfile"), "missing get_dockerfile tool"); + assert.equal(names.length, 12, "expected 12 tools"); + }); + + it("list_docs returns doc pages", () => { + const res = mcpCall("tools/call", { name: "list_docs", arguments: {} }); + const text = res.result.content[0].text; + assert.ok(text.includes("NemoClaw Documentation"), "missing heading"); + assert.ok(text.includes("overview.md"), "missing overview doc"); + assert.ok(text.includes("commands.md"), "missing commands doc"); + }); + + it("read_doc returns content for valid path", () => { + const res = mcpCall("tools/call", { + name: "read_doc", + arguments: { doc_path: "about/overview.md" }, + }); + const text = res.result.content[0].text; + assert.ok(text.includes("Overview"), "missing title"); + assert.ok(text.includes("NemoClaw"), "missing NemoClaw mention"); + }); + + it("read_doc returns error for invalid path", () => { + const res = mcpCall("tools/call", { + name: "read_doc", + arguments: { doc_path: "nonexistent.md" }, + }); + assert.equal(res.result.isError, true, "expected isError flag"); + assert.ok(res.result.content[0].text.includes("not found"), "missing error message"); + }); + + it("search finds results for 'inference'", () => { + const res = mcpCall("tools/call", { + name: "search", + arguments: { query: "inference", max_results: 3 }, + }); + const text = res.result.content[0].text; + assert.ok(text.includes("Search:"), "missing search heading"); + assert.ok(text.includes("Score:"), "missing score"); + }); + + it("search returns empty for gibberish", () => { + const res = mcpCall("tools/call", { + name: "search", + arguments: { query: "xyzzyplughtwisty" }, + }); + assert.ok(res.result.content[0].text.includes("No results"), "expected no results"); + }); + + it("search respects scope filtering", () => { + const res = mcpCall("tools/call", { + name: "search", + arguments: { query: "sandbox", scope: "policies" }, + }); + const text = res.result.content[0].text; + assert.ok(text.includes("scope: policies"), "scope not reflected"); + }); + + it("get_blueprint_config returns YAML", () => { + const res = mcpCall("tools/call", { name: "get_blueprint_config", arguments: {} }); + const text = res.result.content[0].text; + assert.ok(text.includes("version:"), "missing version field"); + assert.ok(text.includes("profiles:"), "missing profiles"); + }); + + it("get_baseline_policy returns policy YAML", () => { + const res = mcpCall("tools/call", { name: "get_baseline_policy", arguments: {} }); + const text = res.result.content[0].text; + assert.ok(text.includes("network_policies:"), "missing network_policies"); + assert.ok(text.includes("filesystem_policy:"), "missing filesystem_policy"); + }); + + it("list_policy_presets returns presets", () => { + const res = mcpCall("tools/call", { name: "list_policy_presets", arguments: {} }); + const text = res.result.content[0].text; + assert.ok(text.includes("discord"), "missing discord preset"); + assert.ok(text.includes("slack"), "missing slack preset"); + assert.ok(text.includes("docker"), "missing docker preset"); + }); + + it("get_policy_preset returns preset YAML", () => { + const res = mcpCall("tools/call", { + name: "get_policy_preset", + arguments: { preset_name: "slack" }, + }); + const text = res.result.content[0].text; + assert.ok(text.includes("slack"), "missing preset name"); + assert.ok(text.includes("api.slack.com"), "missing slack endpoint"); + }); + + it("get_policy_preset returns error for unknown preset", () => { + const res = mcpCall("tools/call", { + name: "get_policy_preset", + arguments: { preset_name: "nonexistent" }, + }); + assert.equal(res.result.isError, true, "expected isError flag"); + }); + + it("get_nim_models returns model catalog", () => { + const res = mcpCall("tools/call", { name: "get_nim_models", arguments: {} }); + const text = res.result.content[0].text; + assert.ok(text.includes("NIM Model Catalog"), "missing heading"); + assert.ok(text.includes("nemotron"), "missing nemotron model"); + }); + + it("list_source_files returns files", () => { + const res = mcpCall("tools/call", { name: "list_source_files", arguments: {} }); + const text = res.result.content[0].text; + assert.ok(text.includes("plugin"), "missing plugin category"); + assert.ok(text.includes("scripts"), "missing scripts category"); + }); + + it("list_source_files filters by category", () => { + const res = mcpCall("tools/call", { + name: "list_source_files", + arguments: { category: "blueprint" }, + }); + const text = res.result.content[0].text; + assert.ok(text.includes("blueprint"), "missing blueprint category"); + assert.ok(!text.includes("## scripts"), "should not include scripts category"); + }); + + it("get_architecture_overview returns overview", () => { + const res = mcpCall("tools/call", { name: "get_architecture_overview", arguments: {} }); + const text = res.result.content[0].text; + assert.ok(text.includes("Architecture Overview"), "missing heading"); + assert.ok(text.includes("Key Flows"), "missing key flows"); + }); + + it("get_dockerfile returns Dockerfile", () => { + const res = mcpCall("tools/call", { name: "get_dockerfile", arguments: {} }); + const text = res.result.content[0].text; + assert.ok(text.includes("FROM"), "missing FROM instruction"); + assert.ok(text.includes("sandbox"), "missing sandbox user"); + }); + + it("server version matches package.json", () => { + const res = mcpCall("initialize", { + protocolVersion: "2024-11-05", + capabilities: {}, + clientInfo: { name: "test", version: "1.0" }, + }); + // The init response is id 1, but we sent it as id 2 in mcpCall wrapper + // Use a direct check instead + const pkg = JSON.parse( + fs.readFileSync(path.join(ROOT, "mcp-docs-server", "package.json"), "utf-8") + ); + assert.equal(pkg.version, "0.1.0", "package.json version mismatch"); + }); +});