Multi-Protocol
Available Protocols
Section titled “Available Protocols”Crate supports four API protocols out of the box:
| Protocol | Module | Routing Style | Default Path |
|---|---|---|---|
| RestApi | crate.protocols.restApi.policy | Path-based | /models, /models/:id |
| JsonApi | crate.protocols.jsonApi.policy | Path-based (JSON:API format) | /models, /models/:id |
| Mcp | crate.protocols.mcp.policy | Body-matched JSON-RPC | /mcp |
| GraphQL | crate.protocols.graphql.policy | Body-matched | /graphql |
Path-based protocols use different URL paths for different operations (e.g., GET /users vs GET /users/:id).
Body-matched protocols use a single endpoint and route based on the JSON request body content (e.g., POST /mcp with {"method": "tools/call", "params": {"name": "list_users"}}).
Single Protocol (Default)
Section titled “Single Protocol (Default)”By default, crateSetup uses RestApi:
// These are equivalent:auto crateRouter = router.crateSetup;auto crateRouter = router.crateSetup!RestApi;Multiple Protocols
Section titled “Multiple Protocols”Pass multiple policies as template parameters to crateSetup:
import crate.protocols.restApi.policy;import crate.protocols.mcp.policy;
auto crateRouter = router.crateSetup!(RestApi, Mcp);crateRouter.add(userCrate);This single add() call creates endpoints for both protocols:
- REST:
GET /users,GET /users/:id,POST /users, etc. - MCP:
POST /mcpwith tool calls likelist_users,get_user,create_user, etc.
All the middleware added via .and() applies to all protocol endpoints.
Per-Model Policy Selection
Section titled “Per-Model Policy Selection”Not every model needs every protocol. Use prepareFor!() or addFor!() to select which policies apply to a specific model:
auto crateRouter = router.crateSetup!(RestApi, Mcp);
// Spaces get both REST and MCPcrateRouter.prepare(spaceCrate);
// Users only get REST (no MCP tools for user management)crateRouter.prepareFor!(RestApi)(userCrate);
// Preferences only get REST with middlewarecrateRouter.addFor!(RestApi)(preferenceCrate, authMiddleware);The compile-time static assert ensures you only select policies that are configured on the router. Trying prepareFor!(GraphQL) on a RestApi+Mcp router is a compile error.
Return Types
Section titled “Return Types”- Single selected policy: Returns a
TypeRouterfor direct method chaining (.expose!,.itemOperation!,.and()) - Multiple selected policies: Returns a
BlankRouter[]for UFCS chaining (.and(),.withCustomOperation())
// Single policy - returns TypeRouterauto typeRouter = crateRouter.prepareFor!(RestApi)(userCrate);typeRouter.expose!"getProfile";typeRouter.and(authMiddleware);
// Multiple policies - returns BlankRouter[]BlankRouter[] routers = crateRouter.prepareFor!(RestApi, Mcp)(spaceCrate);routers .withCustomOperation(historyOp) .and(authMiddleware);How UFCS Chaining Works
Section titled “How UFCS Chaining Works”When prepare() returns a BlankRouter[] (multiple policies), the .and() and .withCustomOperation() methods are free functions that forward to each router:
// This call:routers.and(middleware);
// Is equivalent to:foreach (router; routers) { router.and(middleware);}This is D’s Uniform Function Call Syntax (UFCS) — a free function and(BlankRouter[], T) can be called as routers.and(T).
Custom Base URLs
Section titled “Custom Base URLs”Each policy can be configured with a custom base URL:
auto crateRouter = router.crateSetup!( RestApiPolicy!(PolicyConfig("/api/v1")), McpPolicy!(PolicyConfig("/mcp")));This puts REST endpoints at /api/v1/users, /api/v1/users/:id, etc., and MCP at /mcp.
Protocol Lifecycle Hooks
Section titled “Protocol Lifecycle Hooks”Protocols can define initialization hooks:
onRouterInit— Called once when the first model is added. MCP uses this to register the/mcpinitialize and tools/list endpoints.onRouterReady— Called lazily on the first HTTP request. MCP uses this to populate the tools list with all registered models.
These hooks are called for all configured policies regardless of per-model selection.
Combining All Four Protocols
Section titled “Combining All Four Protocols”import crate.protocols.restApi.policy;import crate.protocols.jsonApi.policy;import crate.protocols.mcp.policy;import crate.protocols.graphql.policy;
auto crateRouter = router.crateSetup!(RestApi, JsonApi, Mcp, GraphQL);crateRouter.add(productCrate);The product model is now accessible via:
GET /products(REST)GET /productswithAccept: application/vnd.api+json(JSON:API)POST /mcpwith{"method": "tools/call", "params": {"name": "list_products"}}(MCP)POST /graphqlwith{"query": "{ products { _id name } }"}(GraphQL)