QUESTPIE

Overview

Your collections, routes and access rules already describe the app. These pages project that description into an MCP server an agent can call and an OpenAPI document a person can read.

View markdown

There is no second model to maintain here. You add a module, name what an agent may reach, and run codegen. Both surfaces read the same files the admin and the typed client read.

PageWhat it covers
MCPThe Model Context Protocol server from @questpie/mcp, and what becomes a tool.
MCP over OAuth 2.1How an outside agent signs in as a real person, and what its scopes reach.
OpenAPI and ScalarThe OpenAPI 3.1 document and the Scalar reference UI from @questpie/openapi.

Two surfaces, two jobs

OpenAPI describes. It emits a document covering your collections, globals, routes, auth and search. The document decides nothing. Listing a path grants no access to it.

MCP executes. An agent calls a tool and the app performs the operation. That call runs the same .access() rules your REST traffic runs.

Turn both on

The starters already register openApiModule. MCP is opt-in, so add it yourself.

src/questpie/server/modules.ts
import { adminModule } from "@questpie/admin/modules/admin";
import { mcpModule } from "@questpie/mcp/modules/mcp";
import { openApiModule } from "@questpie/openapi";

export default [adminModule, mcpModule, openApiModule] as const;

Then say what an agent may reach. Name the collection and name each operation.

src/questpie/server/config/mcp.ts
import { mcpConfig } from "@questpie/mcp";

export default mcpConfig({
	crud: {
		collections: {
			posts: { operations: { list: true, get: true } },
		},
	},
});
questpie generate

What you now serve

Three endpoints, under your handler's base path, /api in the starters.

EndpointWhat it is
POST /api/mcpThe MCP endpoint. It answers 401 without a signed-in caller.
GET /api/openapi.jsonThe OpenAPI 3.1 document, cached and served with an ETag.
GET /api/docsThe Scalar reference, built from that document.

An agent connecting as a user who may read posts sees two tools, collections.posts.list and collections.posts.get. It sees no other collection, and it cannot create, update or delete a post.

Exposure and permission are separate

config/mcp.ts decides which tools exist. .access() decides whether a call succeeds. Both have to pass.

Nothing is exposed until you list it

The tool catalog starts empty. A collection you never named contributes no tools, whatever its access rules say. Naming the collection is not enough either. Each operation has to be switched on under operations. That is why posts: true on its own still yields nothing.

The rules you already wrote still run

Every tool call re-checks the collection's .access() rules for the calling session. So does the tool list. A user who may not read posts never sees collections.posts.list in the first place.

OAuth can only subtract

An external agent authenticates over OAuth 2.1 and carries consented scopes. The scope gate runs beside the access rules, never instead of them. The effective permission is scopes ∩ RBAC, so a scope cannot widen what that user is allowed to do.

stdio is the exception

The stdio transport demands explicit authority. Set stdio.trustedMaintenance and the process runs as system, which skips the access checks entirely. See MCP.

What QUESTPIE does not ship

There is no agent runtime here. Provider choice, approvals and what an agent is allowed to decide belong to the app that owns those concepts. QUESTPIE gives you the pieces underneath: MCP tools, jobs, a queue and a sandbox for untrusted code.

Next

MCP covers the surface itself. Every config key, custom tools, and the transports. Access control sits under all of it, because every tool call ends there.

On this page