{"_id":"@emeryld/rrroutes-mcp","name":"@emeryld/rrroutes-mcp","dist-tags":{"latest":"1.1.0"},"versions":{"1.1.0":{"name":"@emeryld/rrroutes-mcp","description":"Expose finalized RRRoutes contract endpoints as MCP tools","version":"1.1.0","private":false,"type":"module","main":"dist/index.cjs","module":"dist/index.mjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./stdio":{"types":"./dist/stdio.d.ts","import":"./dist/stdio.mjs","require":"./dist/stdio.cjs"}},"dependencies":{"@modelcontextprotocol/sdk":"^1.30.0","@emeryld/rrroutes-contract":"^2.11.0"},"peerDependencies":{"zod":"^4.0.0"},"devDependencies":{"@jest/globals":"^30.4.1","zod":"4.3.6","@emeryld/rrroutes-docs-tool":"0.1.0"},"repository":{"type":"git","url":"git+https://github.com/EmeryK-1/RRRoutes.git"},"scripts":{"clean":"rimraf dist","build":"pnpm run clean && pnpm run build:js && pnpm run build:types","build:js":"tsup --config tsup.config.ts","build:types":"tsc -p tsconfig.build.json","typecheck":"tsc -p tsconfig.json --noEmit","test":"NODE_OPTIONS=--experimental-vm-modules jest --config ../../jest.base.config.js --watchman=false --runInBand --runTestsByPath src/server.test.ts"},"_id":"@emeryld/rrroutes-mcp@1.1.0","bugs":{"url":"https://github.com/EmeryK-1/RRRoutes/issues"},"homepage":"https://github.com/EmeryK-1/RRRoutes#readme","_integrity":"sha512-BkCcufnStDfH4Myvr2de08oyqnyQ8qELiMrJb4iDDN9sf14gofHdWBFH5HXlhxhE/M1TgKPY+1+Mczn/CTau7Q==","_resolved":"/private/var/folders/5x/zf5c1y3x3fb758ncq40308m80000gn/T/f2d4bb50da9329463bc7c653ad49a6c6/emeryld-rrroutes-mcp-1.1.0.tgz","_from":"file:emeryld-rrroutes-mcp-1.1.0.tgz","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-BkCcufnStDfH4Myvr2de08oyqnyQ8qELiMrJb4iDDN9sf14gofHdWBFH5HXlhxhE/M1TgKPY+1+Mczn/CTau7Q==","shasum":"de2189cb24b5f2a0aa0095b8314ff6c9bc06fd64","tarball":"https://registry.npmjs.org/@emeryld/rrroutes-mcp/-/rrroutes-mcp-1.1.0.tgz","fileCount":18,"unpackedSize":120975,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCqg2yE1wrm6vOP2PJRYk8Dr1lW5jHZXSa4+XSDMp24JAIhAO6xhMZyc9X7m4wW+dX73Wj16kpXAomgKVRbKBnZ0XKT"}]},"_npmUser":{"name":"emeryld","email":"karambiri.emery@gmail.com"},"directories":{},"maintainers":[{"name":"emeryld","email":"karambiri.emery@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/rrroutes-mcp_1.1.0_1786380802605_0.8888844725301916"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-10T16:53:22.412Z","1.1.0":"2026-08-10T16:53:22.762Z","modified":"2026-08-10T16:53:23.150Z"},"maintainers":[{"name":"emeryld","email":"karambiri.emery@gmail.com"}],"description":"Expose finalized RRRoutes contract endpoints as MCP tools","homepage":"https://github.com/EmeryK-1/RRRoutes#readme","repository":{"type":"git","url":"git+https://github.com/EmeryK-1/RRRoutes.git"},"bugs":{"url":"https://github.com/EmeryK-1/RRRoutes/issues"},"readme":"# `@emeryld/rrroutes-mcp`\n\nTurn a finalized RRRoutes registry into an MCP server. Every HTTP endpoint is\nregistered as one MCP tool, using the route contract for its name, description,\ninput validation, output validation, documentation metadata, and safety hints.\n\n## Installation\n\n```sh\npnpm add @emeryld/rrroutes-mcp @emeryld/rrroutes-contract zod\n```\n\nNode 20 or newer is required.\n\n## Getting started\n\nDefine and finalize the contract that should supply the MCP tools:\n\n```ts\n// src/routes.ts\nimport { finalize, resource } from '@emeryld/rrroutes-contract'\nimport { z } from 'zod'\n\nconst leaves = resource('/v1')\n  .sub(\n    resource('users')\n      .get({\n        id: 'users.list',\n        summary: 'List users',\n        description: 'Returns users visible to the authenticated caller.',\n        querySchema: z.object({\n          search: z.string().optional().describe('Name or email to match.'),\n          limit: z.coerce.number().min(1).max(100).default(20),\n        }),\n        outputSchema: z.array(\n          z.object({\n            id: z.string().uuid(),\n            name: z.string(),\n            email: z.string().email(),\n          }),\n        ),\n      })\n      .post({\n        id: 'users.create',\n        summary: 'Create a user',\n        bodySchema: z.object({\n          name: z.string().min(1).describe('Display name.'),\n          email: z.string().email().describe('Unique email address.'),\n        }),\n        outputSchema: z.object({\n          id: z.string().uuid(),\n          name: z.string(),\n          email: z.string().email(),\n        }),\n      })\n      .done(),\n  )\n  .done()\n\nexport const registry = finalize(leaves)\n```\n\nCreate the executable stdio server. The example starts with every tool disabled\nand explicitly exposes two routes:\n\n```ts\n// src/mcp-server.ts\nimport { createRRRoutesMcpServer, defineMcpConfig } from '@emeryld/rrroutes-mcp'\nimport { serveRRRoutesMcpStdio } from '@emeryld/rrroutes-mcp/stdio'\nimport { registry } from './routes.js'\n\nconst baseUrl = process.env.RRROUTES_BASE_URL\nif (!baseUrl) throw new Error('RRROUTES_BASE_URL is required')\n\nconst config = defineMcpConfig(registry, {\n  tools: {\n    defaultEnabled: false,\n    routes: {\n      'GET /v1/users': true,\n      'POST /v1/users': {\n        enabled: true,\n        name: 'create_user',\n      },\n    },\n  },\n})\n\nconst mcp = createRRRoutesMcpServer({\n  registry,\n  baseUrl,\n  headers: process.env.RRROUTES_API_TOKEN\n    ? { authorization: `Bearer ${process.env.RRROUTES_API_TOKEN}` }\n    : undefined,\n  config,\n  server: { name: 'my-rrroutes-api', version: '1.0.0' },\n})\n\nawait serveRRRoutesMcpStdio(mcp)\n```\n\nCompile the application, then configure an MCP host to launch the emitted\nJavaScript. A typical stdio definition looks like this; use the exact file and\ntop-level key required by the chosen host:\n\n```json\n{\n  \"mcpServers\": {\n    \"rrroutes\": {\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/dist/mcp-server.js\"],\n      \"env\": {\n        \"RRROUTES_BASE_URL\": \"https://api.example.com\",\n        \"RRROUTES_API_TOKEN\": \"replace-me\"\n      }\n    }\n  }\n}\n```\n\nSmoke-test the compiled server with the MCP Inspector:\n\n```sh\nnpx @modelcontextprotocol/inspector node dist/mcp-server.js\n```\n\n## Generated tool schemas\n\nAn endpoint only exposes the input namespaces it declares:\n\n- `params` from `paramsSchema`\n- `query` from `querySchema`\n- `body` from `bodySchema`\n- `files` from `bodyFiles`\n\nThe original Zod descriptions are retained. The response uses the contract's\nnormal `{ out, meta }` envelope as MCP structured content.\n\n## Configure and toggle tools\n\n`defineMcpConfig` checks canonical `METHOD /path` route keys against the\nregistry. A boolean only changes initial exposure; the object form can also\noverride MCP presentation:\n\n```ts\nconst config = defineMcpConfig(registry, {\n  tools: {\n    defaultEnabled: true,\n    routes: {\n      'POST /v1/users': {\n        enabled: false,\n        title: 'Create user',\n        annotations: { destructiveHint: false },\n      },\n    },\n  },\n})\n```\n\nTools can be changed after startup. Connected MCP clients receive the SDK's\ntool-list change notification:\n\n```ts\nmcp.tools.disable('POST /v1/users')\nmcp.tools.enable('POST /v1/users')\nmcp.tools.setEnabled('POST /v1/users', featureFlags.userCreation)\n```\n\n## Endpoint client, HTTP, or custom execution\n\n`baseUrl` selects the built-in fetch executor. It compiles path params,\nserializes query values, builds JSON or multipart bodies, forwards cancellation,\nand validates output.\n\nIf the application already has its central endpoint client, pass it directly.\nThe MCP adapter uses `endpointClient.dispatchEndpoint(...)`, preserving the\nclient's base URL, custom fetcher, authentication, validation, debug events, and\nmultipart behavior:\n\n```ts\nimport { QueryClient } from '@tanstack/react-query'\nimport { createRouteClient } from '@emeryld/rrroutes-client'\n\nconst endpointClient = createRouteClient({\n  baseUrl: 'https://api.example.com',\n  queryClient: new QueryClient(),\n})\n\nconst mcp = createRRRoutesMcpServer({\n  registry,\n  endpointClient,\n})\n```\n\nThe dispatcher is also a public imperative API:\n\n```ts\nawait endpointClient.dispatchEndpoint({\n  leaf: registry.byKey['POST /v1/users'],\n  input: { body: { name: 'Ada', email: 'ada@example.com' } },\n  signal: abortController.signal,\n})\n```\n\nUse `execute` only for a different application-specific execution path. A\ncustom executor must return the output envelope described by the leaf.\n\n## Authenticating as the caller\n\n`headers` is fixed for the life of the server, which is only correct when the\nserver has one caller. `auth` is resolved on **every** execution instead, so one\nserver can answer several sessions as themselves:\n\n```ts\nconst mcp = createRRRoutesMcpServer({\n  registry,\n  baseUrl,\n  // Called per tool call. Back it with whatever carries the current session —\n  // an AsyncLocalStorage the transport populates, a session lookup.\n  auth: () => {\n    const bearerToken = currentSession()?.token\n    return bearerToken ? { bearerToken } : undefined\n  },\n})\n```\n\n`bearerToken` becomes `Authorization: Bearer <token>`, and any `headers` on the\nreturned object are merged over the static ones. A per-call credential wins over\nan `Authorization` set in `headers`.\n\nReturning `undefined` sends the request unauthenticated. That is the right\nanswer for a public endpoint and for a background run with no session — the\nendpoint's own authorization decides the rest, which is what keeps a tool's view\nidentical to the caller's rather than to the server's.\n\n## Multipart input\n\nFor a `bodyFiles` field named `avatar`, call the generated tool with:\n\n```json\n{\n  \"body\": { \"caption\": \"Profile photo\" },\n  \"files\": {\n    \"avatar\": {\n      \"data\": \"iVBORw0KGgoAAA...\",\n      \"filename\": \"avatar.png\",\n      \"mediaType\": \"image/png\"\n    }\n  }\n}\n```\n\nThe built-in HTTP executor and endpoint-client adapter convert this JSON-safe\nrepresentation into `FormData`.\n\nThe complete generated package reference lives at\n[`docs/generated/packages/emeryld-rrroutes-mcp.md`](../../docs/generated/packages/emeryld-rrroutes-mcp.md).\n","readmeFilename":"README.md","_rev":"1-eb927563c006765a4c5e8d82ac6162f3"}