AI-native APIs · the signature feature
Write a route once. Get REST and an MCP tool.
Because every Apex route carries a strict, typed schema, exposing it to an AI is automatic. The framework carries the MCP transport — you install nothing extra. Next.js, Nuxt, Rails, and Laravel don't do this. It's the reason Apex exists.
One definition, two surfaces #
defineApexRoute takes a Zod input shape, a handler, and a description. That single
definition drives REST validation and the MCP tool's
inputSchema — no duplication, no drift.
server/api/add.ts
import { defineApexRoute } from '@apex-stack/core'
import { z } from 'zod'
export default defineApexRoute({
method: 'POST',
description: 'Add two numbers and return their sum', // → MCP tool description
input: { a: z.number(), b: z.number() }, // → REST validation + MCP inputSchema
mcp: true, // opt in to MCP exposure
handler: ({ input }) => ({ sum: input.a + input.b }),
})
You now have both surfaces from that one file:
server/api/add.ts
POST /api/add {a,b} → { "sum": 5 }
tools/call add {a,b} → { "sum": 5 }
Invalid input returns 400 with the Zod issues. Only mcp: true routes are
exposed as tools — everything else stays REST-only.
defineApexRoute options #
| Option | Type | Purpose |
|---|---|---|
method | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | HTTP method. Defaults to GET. |
description | string | Human + AI-readable. Becomes the MCP tool description. |
input | Zod raw shape | An object of Zod validators. Drives REST validation and the MCP inputSchema. |
mcp | boolean | Opt in to MCP exposure. Defaults to false. |
mcpName | string | Override the tool name (must match ^[a-zA-Z0-9_-]{1,64}$). Defaults to a slug from the file path. |
handler | (ctx) => Output | The implementation. ctx.input is validated; ctx.url is the raw request URL. |
A handler is an adapter — business logic belongs in a service so it's reusable beyond the route. The route's job is the schema-carrying contract that makes REST + MCP automatic.
For a GET route, input is read from the query string; for others, from the JSON
body. Use z.coerce.* when a query value should become a number/boolean:
server/api/greet.ts
export default defineApexRoute({
method: 'GET',
description: 'Greet a person by name',
input: { name: z.string() },
mcp: true,
handler: ({ input }) => ({ message: `Hello, ${input.name}!` }),
})
The /mcp endpoint #
Apex serves all mcp: true routes at a single /mcp endpoint using the
official MCP SDK's Web-standard
Streamable HTTP transport, in stateless JSON mode — a fresh server per request,
no session bookkeeping. It's available in apex dev and, in production, from
apex build --server + apex start.
tools/listreturns every exposed route as a tool, with its description andinputSchema.tools/call <name>runs the same handler as the REST endpoint, over the same data.
The apex mcp inspector #
A built-in inspector connects to a running dev server's /mcp, lists tools with their
signatures, and calls one — the local test loop for AI-callable routes, right in your terminal
(it reuses the bundled MCP SDK, so there's nothing to install).
# list every tool and its input signature
apex mcp
# call one with JSON arguments
apex mcp --call add --args '{"a":2,"b":3}'
# point at a non-default endpoint
apex mcp --url http://localhost:4000/mcp
Full flags on the CLI reference. Smoke-testing tools with the inspector before shipping is the recommended rhythm.
Point Claude at your API #
Because /mcp speaks standard MCP over Streamable HTTP, any MCP client — Claude
included — can connect to it and call your tools directly. Run your app, expose
/mcp, and an assistant can read and write through the very same handlers your UI
uses. No adapter, no second implementation, no drift between "the API" and "what the AI can do".
Resources are AI-native too #
The same principle scales to data. defineResource turns
one table into list/get/create/update/delete over REST and as MCP tools
(<name>_list, <name>_create, …) on one database — so an AI
creating a row and a browser listing rows see the same data.
Design notes #
- The schema is the enabler. The Zod shape is used for REST validation and the MCP
inputSchema— one source of truth. - Opt-in & safe. MCP exposure is per-route (
mcp: true). Free-form handlers without a schema aren't auto-exposed, by design. - Stateless. The
/mcptransport runs in stateless JSON mode — a fresh server per request.
Route-level auth/permission scopes for tools, outputSchema → MCP structured
content, and extracting the MCP server into its own package to keep core lean.