QUESTPIE
CodeRoutes

Metadata

One .meta() call names a route for the OpenAPI spec and can hand the same route to an agent as an MCP tool. It changes nothing about how the route runs.

View markdown

How does a route get a decent name in the API reference, and how does an agent find it? Both answers live in the same object.

One call, both consumers

src/questpie/server/routes/revenue-stats.post.ts
import { route } from "questpie/services";
import { z } from "zod";

export default route()
	.post()
	.schema(
		z.object({
			startDate: z.string().datetime(),
			endDate: z.string().datetime(),
		}),
	)
	.meta({
		title: "Get revenue stats",
		description: "Revenue for a date range, completed appointments only.",
		tags: ["reports"],
		mcp: {
			expose: true,
			name: "reports.revenue",
			annotations: { readOnlyHint: true },
		},
	})
	.handler(async ({ input, collections }) => {
		const { docs } = await collections.appointments.find({
			where: {
				status: "completed",
				scheduledAt: {
					gte: new Date(input.startDate),
					lte: new Date(input.endDate),
				},
			},
			limit: 10_000,
		});
		return { count: docs.length };
	});

What OpenAPI reads

The spec comes from @questpie/openapi. It reads three keys and ignores the rest.

You writeIt becomes
titleThe operation summary.
descriptionThe operation description.
tagsThe operation's tags, registered on the spec too.
nothingThe route key, tagged Routes: <first>.

Your .schema() and .outputSchema() become the request and response schemas without any help from .meta(). A raw route gets a permissive request body and a note saying it returns whatever it likes.

`.access(true)` shows up in the spec

An explicit true emits security: [] on the operation, which opts it out of the spec's security scheme. A function rule, false, or no rule at all inherits the scheme instead.

What MCP reads

The tool surface comes from @questpie/mcp. A route becomes a tool only when all of this holds.

ConditionWhy
mcp.expose === trueOpt in, one route at a time. Nothing is automatic.
The route is JSON modeA raw route has no input schema to advertise.
The MCP module allows itIts own policy still gates the tool.

The tool's input schema is your .schema(). For a route with URL params, the tool takes { params, input } instead, so an agent can fill in :id.

mcp keyEffect
exposeSet true to make the tool exist.
nameThe tool name. Defaults to routes.<key>.
titleA human label. Falls back to the outer title.
descriptionWhat the tool does. Falls back to the outer one.
annotationsThe standard MCP hints, see below.

annotations carries readOnlyHint, destructiveHint, idempotentHint and openWorldHint. All four ride out to the agent as standard MCP tool hints. None of them changes how QUESTPIE runs the route.

Your access rule still runs

Exposing a route as a tool does not bypass .access(). The MCP module checks its own policy, then evaluates your route's rule against the agent's session before the handler runs.

Anything else you put there

RouteMeta has an open index signature, so extra keys are allowed. They ride through introspection untouched. Keep them serializable. Nothing in the framework reads them, which makes them a good seam for a module of your own.

  • OpenAPI, the whole generated spec and the reference UI it ships with.
  • MCP, the tool surface, its policy, and how an agent connects.

On this page