Skip to content

Multi-Protocol

Crate supports four API protocols out of the box:

ProtocolModuleRouting StyleDefault Path
RestApicrate.protocols.restApi.policyPath-based/models, /models/:id
JsonApicrate.protocols.jsonApi.policyPath-based (JSON:API format)/models, /models/:id
Mcpcrate.protocols.mcp.policyBody-matched JSON-RPC/mcp
GraphQLcrate.protocols.graphql.policyBody-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"}}).

By default, crateSetup uses RestApi:

// These are equivalent:
auto crateRouter = router.crateSetup;
auto crateRouter = router.crateSetup!RestApi;

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 /mcp with tool calls like list_users, get_user, create_user, etc.

All the middleware added via .and() applies to all protocol endpoints.

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 MCP
crateRouter.prepare(spaceCrate);
// Users only get REST (no MCP tools for user management)
crateRouter.prepareFor!(RestApi)(userCrate);
// Preferences only get REST with middleware
crateRouter.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.

  • Single selected policy: Returns a TypeRouter for direct method chaining (.expose!, .itemOperation!, .and())
  • Multiple selected policies: Returns a BlankRouter[] for UFCS chaining (.and(), .withCustomOperation())
// Single policy - returns TypeRouter
auto 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);

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).

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.

Protocols can define initialization hooks:

  • onRouterInit — Called once when the first model is added. MCP uses this to register the /mcp initialize 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.

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 /products with Accept: application/vnd.api+json (JSON:API)
  • POST /mcp with {"method": "tools/call", "params": {"name": "list_products"}} (MCP)
  • POST /graphql with {"query": "{ products { _id name } }"} (GraphQL)