Skip to content

MCP Integration

The Model Context Protocol (MCP) is a standard for exposing tools and data sources to AI assistants like Claude. When you configure Ontogen’s MCP server generator, your entity CRUD operations become MCP tools that an AI assistant can call — listing, creating, updating, and deleting your domain objects through natural language.

This recipe shows you how to generate MCP tool definitions from your Ontogen entity pipeline.

An MCP tool has three things: a name, a description, and a JSON Schema for its parameters. Ontogen generates all three from your entity definitions and API metadata.

For a Workout entity, you’d get tools like:

Tool nameDescriptionParameters
workout_listList all workouts (One page of workouts when paginated){ "sort": ["string"]? }, plus limit? and offset? when the module is paginated
workout_get_by_idGet a single workout by ID{ "id": "string" }
workout_createCreate a new workout{ "id": "string", "name": "string?", "date": "string", ... }
workout_updateUpdate an existing workout{ "id": "string", "name": "string?", ... }
workout_deleteDelete a workout by ID{ "id": "string" }

Tool names are the IPC command names, {module}_{fn}. The descriptions come from your API function doc comments. The parameter schemas come from your entity field types and DTOs.

Arguments and results are flat JSON, the same values the API functions take and return — the HTTP server’s JSON:API documents do not apply to MCP. A tool returns the entity itself, a list returns an array, and a paginated list returns { "items": [...], "total": N, "limit": N, "offset": N }. A tool error is the error’s Display text.

  1. Add the MCP generator to your server config

    Section titled “Add the MCP generator to your server config”

    In your build.rs, add ServerGenerator::Mcp to the generators list on ServersConfig:

    use ontogen::servers::ServerGenerator;
    // Inside your ServersConfig:
    generators: vec![
    ServerGenerator::HttpAxum {
    output: "src/api/transport/http/generated.rs".into(),
    },
    // Add MCP alongside your other transports
    ServerGenerator::Mcp {
    output: "src/api/transport/mcp/generated.rs".into(),
    },
    ],

    The MCP generator reads the same ApiOutput as the HTTP and IPC generators. You don’t need any additional configuration for MCP-specific behavior.

  2. Create the directory and module file for the generated MCP code:

    src/api/transport/mcp/
    mod.rs
    generated.rs # will be written by the generator

    In src/api/transport/mcp/mod.rs:

    pub mod generated;

    And register it in your transport module tree.

  3. Terminal window
    cargo build

    The generator produces src/api/transport/mcp/generated.rs with tool definitions for every entity in your API surface.

  4. The generated code gives you tool definitions (names, descriptions, parameter schemas). You need to serve them via an MCP-compatible transport — typically stdio for local tools or HTTP with SSE for remote tools.

    The generated module provides tool_definitions() for a tools/list response and handle_tool_call(state, name, args) to dispatch a tools/call:

    use crate::api::transport::mcp::generated;
    // tools/list: each tool has a name, a description and an input_schema (a serde_json::Value)
    for tool in generated::tool_definitions() {
    mcp_server.register_tool(tool.name, tool.description, tool.input_schema);
    }
    // tools/call: runs the matching handler and returns its flat JSON result
    let result = generated::handle_tool_call(&state, tool_name, &args)?;

    The exact integration depends on which MCP server library you use. The generated code provides the tool metadata — you wire it into your server framework.

  5. MCP tool descriptions come directly from the /// doc comments on your API functions. The generated CRUD functions have default descriptions like “List all workouts” and “Create a new workout”.

    For custom endpoints, your doc comments become the tool descriptions that AI assistants see:

    /// Search workouts by date range. Returns workouts between
    /// the start and end dates (inclusive). Dates are ISO8601 format.
    pub async fn search_by_date(
    store: &Store,
    start: &str,
    end: &str,
    ) -> Result<Vec<Workout>, AppError> { ... }

    This becomes an MCP tool with the full description available to the AI for deciding when and how to call it.

The MCP generator converts Rust types to JSON Schema:

Rust typeJSON Schema
String, &str{ "type": "string" }
i32, i64{ "type": "integer" }
bool{ "type": "boolean" }
Option<T>Schema for T, not required
Vec<T>{ "type": "array", "items": <schema for T> }
CreateEntityInputObject schema with all fields from the DTO

Required vs optional is determined by the field types in your DTOs. Option<T> fields become optional properties in the JSON Schema.

Custom API endpoints you write in your scan directories automatically become MCP tools. The tool is named like its IPC command ({module}_{fn}), the doc comment becomes the description, and the parameters become the schema.

/// Get training volume (total sets x reps x weight) for an exercise
/// over the last N days. Useful for tracking progressive overload.
pub async fn training_volume(
store: &Store,
exercise_id: &str,
days: i32,
) -> Result<f64, AppError> { ... }

In a workout module this produces an MCP tool:

  • Name: workout_training_volume
  • Description: “Get training volume (total sets x reps x weight) for an exercise over the last N days. Useful for tracking progressive overload.”
  • Parameters: { "exercise_id": { "type": "string" }, "days": { "type": "integer" } }

MCP generation is independent of HTTP and IPC generation. You can run all three, or just MCP, or any combination. They all read from the same ApiOutput and produce separate output files.

A common pattern is to generate all three transports:

use ontogen::servers::ServerGenerator;
generators: vec![
ServerGenerator::HttpAxum {
output: "src/api/transport/http/generated.rs".into(),
},
ServerGenerator::TauriIpc {
output: "src/api/transport/ipc/generated.rs".into(),
},
ServerGenerator::Mcp {
output: "src/api/transport/mcp/generated.rs".into(),
},
],

This gives you: a web API, a desktop app API, and an AI-accessible API — all from the same entity definitions, all type-safe, all kept in sync automatically.