From 3899315559735b245766febca436ad4f1101b956 Mon Sep 17 00:00:00 2001 From: Mgrdich Date: Tue, 17 Feb 2026 16:30:37 -0500 Subject: [PATCH 1/9] fix: correct tsconfig rootDir and add local MCP config Set rootDir to "src" so tsc outputs directly to dist/ instead of dist/src/, fixing the broken build/start scripts. Add .mcp.json for local MCP server discovery in Claude Code. Co-Authored-By: Claude Opus 4.6 --- .mcp.json | 8 ++++++++ tsconfig.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .mcp.json diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..f834812 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "claude-plugins": { + "command": "node", + "args": ["dist/index.js"] + } + } +} diff --git a/tsconfig.json b/tsconfig.json index 331f2ee..18afb18 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "module": "Node16", "moduleResolution": "Node16", "outDir": "dist", - "rootDir": ".", + "rootDir": "src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, From 0ab7262c1a9cda5ff2ef66647c0a745835b0d065 Mon Sep 17 00:00:00 2001 From: Mgrdich Date: Tue, 17 Feb 2026 17:19:50 -0500 Subject: [PATCH 2/9] feat: add HTTP transport for remote MCP access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support both stdio and HTTP transports, selected via PORT env var. Uses StreamableHTTPServerTransport from the MCP SDK with Node's built-in http module — no new dependencies required. Co-Authored-By: Claude Opus 4.6 --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 ++- src/index.ts | 47 +++++++++++++++++++++++++++++++--- 3 files changed, 116 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c2378f5..dc21e00 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ Other commands: ```bash pnpm run build # Compile TypeScript (only needed if modifying src/) -pnpm run start # Run the compiled MCP server +pnpm run start # Run the compiled MCP server (stdio) +pnpm run serve # Start HTTP server on port 3000 ``` If you are only adding or editing plugins (not modifying the MCP server source in `src/`), you do not need to run `pnpm run build`. @@ -73,6 +74,74 @@ These slash commands are available when working in this repo with Claude Code: | `kotlin-expert` | Agent | Kotlin 2.0+, coroutines, Spring Boot, domain modeling | | `react-expert` | Agent | React 19+, concurrent rendering, Tailwind, accessibility | +## MCP Server Integration + +This repository doubles as an MCP server for plugin discovery. Any agentic tooling that supports the [Model Context Protocol](https://modelcontextprotocol.io) can connect to it and programmatically list, search, and retrieve plugins — including their full markdown content. + +### Available Tools + +| Tool | Description | Parameters | +|------------------|--------------------------------------------------------------|------------------------------------------------------------| +| `list_plugins` | List all plugins with optional filters | `type?` (`skill`, `agent`, `prompt`), `tag?` (string) | +| `get_plugin` | Get a plugin's full details or a single component's markdown | `name` (string), `component?` (`skill`, `agent`, `prompt`) | +| `search_plugins` | Full-text search across name, description, and tags | `query` (string), `type?` (`skill`, `agent`, `prompt`) | + +### Connect from Claude Code Locally + +Add the server to your project's `.mcp.json` (or `~/.claude/claude_mcp_settings.json` for global access): + +```json +{ + "mcpServers": { + "claude-plugins": { + "command": "node", + "args": ["/path/to/claude-plugins/dist/index.js"] + } + } +} +``` + +Then in any Claude Code session the three tools above become available automatically. + +### Connect from other MCP clients + +The server supports both **stdio** and **HTTP** transports. + +**stdio** (default) — for local MCP clients: + +```bash +node /path/to/claude-plugins/dist/index.js +``` + +Or in dev mode (no build step required): + +```bash +npx tsx /path/to/claude-plugins/src/index.ts +``` + +**HTTP** — for remote access (defaults to port 3000): + +```bash +pnpm run serve # port 3000 +PORT=8080 pnpm run serve # custom port +``` + +This starts an HTTP server with the MCP Streamable HTTP transport at `POST /mcp`. Clients can connect using any MCP-compatible HTTP client: + +```bash +curl -X POST http://localhost:3000/mcp \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' +``` + +### Example workflow + +A typical agentic integration pattern: + +1. Call `list_plugins` or `search_plugins` to discover relevant plugins +2. Call `get_plugin` with `component: "agent"` to retrieve the full markdown prompt +3. Use the returned markdown as a system prompt, agent instruction, or context injection + ## Contributing ### Adding a Plugin diff --git a/package.json b/package.json index b222173..6f8d8e8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "build": "tsc", "start": "node dist/index.js", - "dev": "tsx src/index.ts" + "dev": "tsx src/index.ts", + "serve": "PORT=3000 node dist/index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", diff --git a/src/index.ts b/src/index.ts index c37bd58..abfc3ff 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { createServer as createHttpServer } from "http"; import { join } from "path"; import { loadPlugins } from "./loader.js"; import { createServer } from "./server.js"; @@ -8,6 +10,45 @@ const pluginsDir = join(process.cwd(), "plugins"); const plugins = loadPlugins(pluginsDir); console.error(`Loaded ${plugins.length} plugins`); -const server = createServer(plugins); -const transport = new StdioServerTransport(); -await server.connect(transport); +const port = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined; + +if (port) { + const httpServer = createHttpServer(async (req, res) => { + if (req.url === "/mcp" && req.method === "POST") { + const server = createServer(plugins); + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: undefined, + }); + await server.connect(transport); + + const body = await new Promise((resolve) => { + let data = ""; + req.on("data", (chunk: Buffer) => (data += chunk)); + req.on("end", () => resolve(data)); + }); + + await transport.handleRequest(req, res, JSON.parse(body)); + + res.on("close", () => { + transport.close(); + server.close(); + }); + } else { + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Not found" })); + } + }); + + httpServer.listen(port, () => { + console.error(`MCP HTTP server listening on port ${port}`); + }); + + process.on("SIGINT", () => { + httpServer.close(); + process.exit(0); + }); +} else { + const server = createServer(plugins); + const transport = new StdioServerTransport(); + await server.connect(transport); +} From 00e6921988fbdbc703b1ab3d97621a7256c2d4ba Mon Sep 17 00:00:00 2001 From: Mgrdich Date: Tue, 17 Feb 2026 17:24:05 -0500 Subject: [PATCH 3/9] refactor: replace local commit command with skill-based commit --- .claude/commands/commit.md | 9 --------- README.md | 5 ++--- 2 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 .claude/commands/commit.md diff --git a/.claude/commands/commit.md b/.claude/commands/commit.md deleted file mode 100644 index 61beecc..0000000 --- a/.claude/commands/commit.md +++ /dev/null @@ -1,9 +0,0 @@ -Look at the current git diff (staged and unstaged) and recent commit history. Generate a concise, well-structured commit message following this repository's conventions. - -Steps: -1. Run `git diff --cached` and `git diff` to see changes -2. Run `git log --oneline -10` to see recent commit style -3. Stage any unstaged changes if appropriate -4. Create the commit with a clear message - -Use conventional commits format (feat:, fix:, docs:, refactor:, etc.) unless the repo uses a different style. diff --git a/README.md b/README.md index dc21e00..f6bbc7b 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,9 @@ If you are only adding or editing plugins (not modifying the MCP server source i These slash commands are available when working in this repo with Claude Code: -| Command | Purpose | -|---------|---------| +| Command | Purpose | +|------------------|--------------------------------------------------------------------------| | `/update-readme` | Regenerate the Current Plugins table in the README from plugin manifests | -| `/commit` | Stage changes and create a commit with a conventional commit message | ## Current Plugins From 5832505beaadb2c0c182966c9e8c69a2572dfa0c Mon Sep 17 00:00:00 2001 From: Mgrdich Date: Wed, 18 Feb 2026 09:53:30 -0500 Subject: [PATCH 4/9] ci: add GitHub Actions workflow for build verification --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f59d5c9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - run: pnpm run build From d662dfcd50ab4de5e5f310f1eb52ee6791ca4ac3 Mon Sep 17 00:00:00 2001 From: Mgrdich Date: Wed, 18 Feb 2026 10:08:53 -0500 Subject: [PATCH 5/9] test: add MCP server tool tests with vitest --- .github/workflows/ci.yml | 2 + package.json | 6 +- pnpm-lock.yaml | 604 +++++++++++++++++++++++++++++++++++++++ src/server.test.ts | 172 +++++++++++ 4 files changed, 782 insertions(+), 2 deletions(-) create mode 100644 src/server.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f59d5c9..65537a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,3 +22,5 @@ jobs: - run: pnpm install --frozen-lockfile - run: pnpm run build + + - run: pnpm test diff --git a/package.json b/package.json index 6f8d8e8..553cafb 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "build": "tsc", "start": "node dist/index.js", "dev": "tsx src/index.ts", - "serve": "PORT=3000 node dist/index.js" + "serve": "PORT=3000 node dist/index.js", + "test": "vitest run" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", @@ -17,6 +18,7 @@ "devDependencies": { "@types/node": "^25.2.3", "tsx": "^4.19.3", - "typescript": "^5.7.3" + "typescript": "^5.7.3", + "vitest": "^4.0.18" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3620701..e68934b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,9 @@ importers: typescript: specifier: ^5.7.3 version: 5.9.3 + vitest: + specifier: ^4.0.18 + version: 4.0.18(@types/node@25.2.3)(tsx@4.21.0) packages: @@ -196,6 +199,9 @@ packages: resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} engines: {node: '>=18'} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@modelcontextprotocol/sdk@1.26.0': resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} engines: {node: '>=18'} @@ -206,9 +212,175 @@ packages: '@cfworker/json-schema': optional: true + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/node@25.2.3': resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -224,6 +396,10 @@ packages: ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + balanced-match@4.0.2: resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==} engines: {node: 20 || >=22} @@ -248,6 +424,10 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -304,6 +484,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -316,6 +499,9 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -328,6 +514,10 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + express-rate-limit@8.2.1: resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==} engines: {node: '>= 16'} @@ -344,6 +534,15 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + finalhandler@2.1.1: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} @@ -443,6 +642,9 @@ packages: resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -474,6 +676,11 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} @@ -486,6 +693,9 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -511,10 +721,24 @@ packages: path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pkce-challenge@5.0.1: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -538,6 +762,11 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -580,14 +809,42 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -617,11 +874,90 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -719,6 +1055,8 @@ snapshots: '@isaacs/cliui@9.0.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)': dependencies: '@hono/node-server': 1.19.9(hono@4.11.9) @@ -741,10 +1079,135 @@ snapshots: transitivePeerDependencies: - supports-color + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + + '@standard-schema/spec@1.1.0': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + '@types/node@25.2.3': dependencies: undici-types: 7.16.0 + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.2.3)(tsx@4.21.0))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.2.3)(tsx@4.21.0) + + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.18': {} + + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -761,6 +1224,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + assertion-error@2.0.1: {} + balanced-match@4.0.2: dependencies: jackspeak: 4.2.3 @@ -795,6 +1260,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + chai@6.2.2: {} + content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -834,6 +1301,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -869,6 +1338,10 @@ snapshots: escape-html@1.0.3: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + etag@1.8.1: {} eventsource-parser@3.0.6: {} @@ -877,6 +1350,8 @@ snapshots: dependencies: eventsource-parser: 3.0.6 + expect-type@1.3.0: {} + express-rate-limit@8.2.1(express@5.2.1): dependencies: express: 5.2.1 @@ -919,6 +1394,10 @@ snapshots: fast-uri@3.1.0: {} + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + finalhandler@2.1.1: dependencies: debug: 4.4.3 @@ -1019,6 +1498,10 @@ snapshots: lru-cache@11.2.6: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + math-intrinsics@1.1.0: {} media-typer@1.1.0: {} @@ -1039,12 +1522,16 @@ snapshots: ms@2.1.3: {} + nanoid@3.3.11: {} + negotiator@1.0.0: {} object-assign@4.1.1: {} object-inspect@1.13.4: {} + obug@2.1.1: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -1066,8 +1553,20 @@ snapshots: path-to-regexp@8.3.0: {} + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + pkce-challenge@5.0.1: {} + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -1090,6 +1589,37 @@ snapshots: resolve-pkg-maps@1.0.0: {} + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + router@2.2.0: dependencies: debug: 4.4.3 @@ -1163,10 +1693,29 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@4.1.0: {} + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@3.10.0: {} + + tinybench@2.9.0: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + toidentifier@1.0.1: {} tsx@4.21.0: @@ -1190,10 +1739,65 @@ snapshots: vary@1.1.2: {} + vite@7.3.1(@types/node@25.2.3)(tsx@4.21.0): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.2.3 + fsevents: 2.3.3 + tsx: 4.21.0 + + vitest@4.0.18(@types/node@25.2.3)(tsx@4.21.0): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.2.3)(tsx@4.21.0)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@25.2.3)(tsx@4.21.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.2.3 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wrappy@1.0.2: {} zod-to-json-schema@3.25.1(zod@3.25.76): diff --git a/src/server.test.ts b/src/server.test.ts new file mode 100644 index 0000000..83fadc0 --- /dev/null +++ b/src/server.test.ts @@ -0,0 +1,172 @@ +import { describe, it, expect, beforeAll } from "vitest"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; +import type { Plugin } from "./types.js"; +import { createServer } from "./server.js"; + +const plugins: Plugin[] = [ + { + name: "commit", + description: "Smart git commit helper", + version: "1.0.0", + tags: ["git", "workflow"], + components: ["skill"], + has_mcps: false, + skill: "# Commit\nRun a smart commit.", + }, + { + name: "review", + description: "Code review assistant", + version: "2.0.0", + tags: ["review", "quality"], + components: ["skill", "agent"], + has_mcps: true, + skill: "# Review\nReview code.", + agent: "# Review Agent\nAn agent for reviewing.", + mcps: { servers: {} }, + }, + { + name: "docs", + description: "Generate documentation", + version: "0.1.0", + tags: ["docs"], + components: ["prompt"], + has_mcps: false, + prompt: "# Docs Prompt\nGenerate docs.", + }, +]; + +async function callTool(client: Client, name: string, args: Record = {}) { + const result = await client.callTool({ name, arguments: args }); + const text = (result.content as Array<{ type: string; text: string }>)[0].text; + return { ...result, text, json: JSON.parse(text) }; +} + +describe("MCP server", () => { + let client: Client; + + beforeAll(async () => { + const server = createServer(plugins); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await server.connect(serverTransport); + client = new Client({ name: "test-client", version: "1.0.0" }); + await client.connect(clientTransport); + }); + + describe("list_plugins", () => { + it("returns all plugins when no filters", async () => { + const { json } = await callTool(client, "list_plugins"); + expect(json).toHaveLength(3); + expect(json.map((p: { name: string }) => p.name)).toEqual(["commit", "review", "docs"]); + }); + + it("does not include content fields in listing", async () => { + const { json } = await callTool(client, "list_plugins"); + for (const p of json) { + expect(p).not.toHaveProperty("skill"); + expect(p).not.toHaveProperty("agent"); + expect(p).not.toHaveProperty("prompt"); + } + }); + + it("filters by component type", async () => { + const { json } = await callTool(client, "list_plugins", { type: "agent" }); + expect(json).toHaveLength(1); + expect(json[0].name).toBe("review"); + }); + + it("filters by tag (case-insensitive)", async () => { + const { json } = await callTool(client, "list_plugins", { tag: "GIT" }); + expect(json).toHaveLength(1); + expect(json[0].name).toBe("commit"); + }); + + it("filters by both type and tag", async () => { + const { json } = await callTool(client, "list_plugins", { type: "skill", tag: "review" }); + expect(json).toHaveLength(1); + expect(json[0].name).toBe("review"); + }); + + it("returns empty array when no matches", async () => { + const { json } = await callTool(client, "list_plugins", { tag: "nonexistent" }); + expect(json).toEqual([]); + }); + }); + + describe("get_plugin", () => { + it("returns full plugin details", async () => { + const { json } = await callTool(client, "get_plugin", { name: "review" }); + expect(json.name).toBe("review"); + expect(json.skill).toBe("# Review\nReview code."); + expect(json.agent).toBe("# Review Agent\nAn agent for reviewing."); + expect(json.has_mcps).toBe(true); + }); + + it("returns specific component content", async () => { + const result = await client.callTool({ name: "get_plugin", arguments: { name: "commit", component: "skill" } }); + const text = (result.content as Array<{ type: string; text: string }>)[0].text; + expect(text).toBe("# Commit\nRun a smart commit."); + }); + + it("is case-insensitive on name", async () => { + const { json } = await callTool(client, "get_plugin", { name: "COMMIT" }); + expect(json.name).toBe("commit"); + }); + + it("returns error for unknown plugin", async () => { + const result = await client.callTool({ name: "get_plugin", arguments: { name: "nope" } }); + expect(result.isError).toBe(true); + const text = (result.content as Array<{ type: string; text: string }>)[0].text; + expect(text).toContain("not found"); + }); + + it("returns error for missing component", async () => { + const result = await client.callTool({ name: "get_plugin", arguments: { name: "commit", component: "agent" } }); + expect(result.isError).toBe(true); + const text = (result.content as Array<{ type: string; text: string }>)[0].text; + expect(text).toContain("no agent component"); + }); + }); + + describe("search_plugins", () => { + it("searches by name", async () => { + const { json } = await callTool(client, "search_plugins", { query: "commit" }); + expect(json).toHaveLength(1); + expect(json[0].name).toBe("commit"); + }); + + it("searches by description", async () => { + const { json } = await callTool(client, "search_plugins", { query: "documentation" }); + expect(json).toHaveLength(1); + expect(json[0].name).toBe("docs"); + }); + + it("searches by tag", async () => { + const { json } = await callTool(client, "search_plugins", { query: "quality" }); + expect(json).toHaveLength(1); + expect(json[0].name).toBe("review"); + }); + + it("is case-insensitive", async () => { + const { json } = await callTool(client, "search_plugins", { query: "REVIEW" }); + expect(json).toHaveLength(1); + expect(json[0].name).toBe("review"); + }); + + it("combines search with type filter", async () => { + const { json } = await callTool(client, "search_plugins", { query: "review", type: "agent" }); + expect(json).toHaveLength(1); + expect(json[0].name).toBe("review"); + }); + + it("returns empty when type filter excludes results", async () => { + const { json } = await callTool(client, "search_plugins", { query: "commit", type: "agent" }); + expect(json).toEqual([]); + }); + + it("returns empty for no matches", async () => { + const { json } = await callTool(client, "search_plugins", { query: "zzz_no_match" }); + expect(json).toEqual([]); + }); + }); +}); From 0cec713c3a62a24362f183bbc0d5326382ac00e4 Mon Sep 17 00:00:00 2001 From: Mgrdich Date: Wed, 18 Feb 2026 10:25:05 -0500 Subject: [PATCH 6/9] fix: specify packageManager for pnpm/action-setup --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 553cafb..a5f4d16 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "claude-plugins", + "packageManager": "pnpm@10.6.2", "version": "1.0.0", "type": "module", "main": "dist/index.js", From fe8fa5dd8b7dd74f8bec7a93d2894c2729fb0063 Mon Sep 17 00:00:00 2001 From: Mgrdich Date: Wed, 18 Feb 2026 10:44:54 -0500 Subject: [PATCH 7/9] ci: rename job from build to build-test --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65537a4..437327f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: branches: [main] jobs: - build: + build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 7604cf9eddbea14064f9ea7e7ddb855fc4b2fa5c Mon Sep 17 00:00:00 2001 From: Mgrdich <46796009+Mgrdich@users.noreply.github.com> Date: Wed, 18 Feb 2026 11:07:35 -0500 Subject: [PATCH 8/9] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6bbc7b..d605754 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,17 @@ npx tsx /path/to/claude-plugins/src/index.ts **HTTP** — for remote access (defaults to port 3000): ```bash -pnpm run serve # port 3000 -PORT=8080 pnpm run serve # custom port +pnpm run serve # port 3000 (all platforms) + +# Custom port: +# macOS/Linux (bash, zsh, etc.): +PORT=8080 pnpm run serve + +# Windows PowerShell: +$env:PORT=8080; pnpm run serve + +# Windows cmd.exe: +set PORT=8080&& pnpm run serve ``` This starts an HTTP server with the MCP Streamable HTTP transport at `POST /mcp`. Clients can connect using any MCP-compatible HTTP client: From f5338953eba45cf9199c342acda882bf47e6ea4b Mon Sep 17 00:00:00 2001 From: Mgrdich Date: Wed, 18 Feb 2026 11:11:47 -0500 Subject: [PATCH 9/9] fix: validate PORT env and harden HTTP body handling --- src/index.ts | 51 ++++++++++++++++++++++++++++++++++++++++----------- tsconfig.json | 3 ++- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index abfc3ff..fc2aa7b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,29 +10,58 @@ const pluginsDir = join(process.cwd(), "plugins"); const plugins = loadPlugins(pluginsDir); console.error(`Loaded ${plugins.length} plugins`); -const port = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined; +const MAX_BODY_SIZE = 1024 * 1024; // 1 MB -if (port) { +const portEnv = process.env.PORT; +let port: number | undefined; + +if (portEnv !== undefined) { + const parsed = Number(portEnv); + if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 0) { + console.error(`Invalid PORT value "${portEnv}". Expected a non-negative integer.`); + process.exit(1); + } + port = parsed; +} + +if (port !== undefined) { const httpServer = createHttpServer(async (req, res) => { if (req.url === "/mcp" && req.method === "POST") { const server = createServer(plugins); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); - await server.connect(transport); - const body = await new Promise((resolve) => { - let data = ""; - req.on("data", (chunk: Buffer) => (data += chunk)); - req.on("end", () => resolve(data)); - }); + try { + await server.connect(transport); - await transport.handleRequest(req, res, JSON.parse(body)); + const body = await new Promise((resolve, reject) => { + let data = ""; + let size = 0; + req.on("data", (chunk: Buffer) => { + size += chunk.length; + if (size > MAX_BODY_SIZE) { + req.destroy(); + reject(new Error("Request body too large")); + return; + } + data += chunk; + }); + req.on("end", () => resolve(data)); + req.on("error", reject); + }); - res.on("close", () => { + await transport.handleRequest(req, res, JSON.parse(body)); + } catch (err) { + if (!res.headersSent) { + const status = err instanceof Error && err.message === "Request body too large" ? 413 : 400; + res.writeHead(status, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: status === 413 ? "Request body too large" : "Bad request" })); + } + } finally { transport.close(); server.close(); - }); + } } else { res.writeHead(404, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: "Not found" })); diff --git a/tsconfig.json b/tsconfig.json index 18afb18..2fea8d7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,5 +11,6 @@ "resolveJsonModule": true, "declaration": true }, - "include": ["src/**/*", "scripts/**/*"] + "include": ["src/**/*", "scripts/**/*"], + "exclude": ["src/**/*.test.ts"] }