MCP
With @cuple/mcp, AI agents can call your routes as MCP tools. Like OpenAPI, it reads your routes from the TypeScript source.
npm i -D @cuple/mcp typescript
Example
// mcp.ts
import { serveMcp } from "@cuple/mcp";
serveMcp("./src/routes.ts", "routes", { url: "http://localhost:8080/rpc" });
Then register it with your agent as a command. For example, in Claude Code:
claude mcp add my-api -- npx tsx mcp.ts
In this example:
-
"routes"is the name of the exported variable that holds your routes, as forgenerateOpenAPI. -
Every route becomes a tool, named after the route, e.g.
posts.getPost..meta({ description })becomes the tool's description. -
A tool's input is what the route declares:
params,query,bodyandheaders, the same asfetchCuple's. -
Tools call your running server at
url, through the RPC endpoint, so routes without.path()work as well. -
The response is given back as JSON. If it's not
success, the tool call is marked as an error. -
NOTE:
serveMcptalks to the agent over stdin and stdout, so don't write logs to stdout inmcp.ts. -
NOTE: It reads your TypeScript source, so run it where the source is available.
Signing in
The tools say what each route needs, headers included. With a login route and a route that requires authorization:
login: builder
.bodySchema(z.strictObject({ email: z.string(), password: z.string() }))
.post(async ({ data }) => success({ token: await signIn(data.body) })),
listPosts: builder
.chain(authLink) // .headersSchema(z.looseObject({ authorization: z.string() }))
.get(async () => success({ posts })),
listPosts's tool requires headers.authorization, so the agent calls login with the credentials you give it, and sends the token it gets back with the gated calls. There's nothing to configure.
- NOTE: The token goes through the conversation, like anything the agent reads. Give an agent an account with only the access it needs.
- TIP: For local testing, accept a dev token that only works in development mode, and give it to the agent. Then it can call gated routes without signing in itself.
Other transports
createMcpServer takes the same arguments and returns the server without connecting it, for a transport of your own:
import { createMcpServer } from "@cuple/mcp";
const server = createMcpServer("./src/routes.ts", "routes", { url: "http://localhost:8080/rpc" });
await server.connect(transport); // any transport from @modelcontextprotocol/sdk
MCP is here so AI agents can easily call Cuple endpoints. They're good with HTTP but don't know Cuple's /rpc protocol, and the tools do that part for them. It's early and not well tested yet.
Tools call your running server, so an agent gets the same validation, middlewares and auth as your app, and can reach nothing a normal client can't. The route's types are its instructions: an agent learns that listPosts needs authorization the same way it learns it needs params, with no extra setup to keep in sync.
Running as a separate process that reads the source is only where it started. An initRpc option that also serves MCP from the app would be simpler. Choosing which routes become tools isn't done yet either.