MCP Bundle configuration

MCP Bundle configuration

Configuration has two parts: routes define which endpoints exist and which API group and version each one serves; the ufo_mcp configuration only tunes the group's server. The configuration never decides which methods become tools or which version is served.

Routes: one import per endpoint

# config/routes/ufo_mcp.yaml
ufo_mcp:
    resource: ../../vendor/ufo-tech/mcp-bundle/config/router.yaml
    prefix: /mcp
    defaults: { api_group: mcp }

ufo_mcp_internal:
    resource: ../../vendor/ufo-tech/mcp-bundle/config/router.yaml
    prefix: /mcp/internal
    defaults: { api_group: internal }
  • api_group is the core API group the endpoint serves. Without it, the core default group general is used.
  • Every import gets its own route names (mcp_ufo_mcp_endpoint, internal_ufo_mcp_endpoint, …), so name_prefix is not needed. The core route loader does this for YAML imports; in PHP or XML route configuration set name_prefix by hand.

Every import serves:

Route Name HTTP methods
<prefix>, <prefix>/{ver} <group>_ufo_mcp_endpoint POST, DELETE, OPTIONS; GET answers 405
<prefix>/server.json, <prefix>/{ver}/server.json <group>_ufo_mcp_server_json, <group>_ufo_mcp_server_json_ver GET
<prefix>/manifest/{aiName}.json, <prefix>/{ver}/manifest/{aiName}.json <group>_ufo_mcp_manifest, <group>_ufo_mcp_manifest_ver GET

{ver} is v1, v2, …; without it, v1.

Configuration reference

File config/packages/ufo_mcp.yaml, root key ufo_mcp.

ufo_mcp:
    enabled: true
    instructions: ~
    max_body_bytes: 4194304
    session_ttl: 3600
    mcp_server_info:
        name: 'io.github.your-org/your-app'
        title: 'Your App MCP Server'
        description: 'Exposes Your App RPC services as MCP tools.'
        icons:
            - src: 'https://example.com/icon.png'
              mime_type: 'image/png'
              sizes: ['128x128']
              theme: ~
    groups:
        internal:
            instructions: 'Internal tools.'
            mcp_server_info:
                name: 'io.github.your-org/your-app-internal'
Parameter Default Description
enabled true Whether the endpoint answers; otherwise 404. Supports '%env(bool:MCP_ENABLED)%'
instructions null The instructions field of the initialize response: a hint to the agent on how to use the server
max_body_bytes 4194304 Request body limit in bytes, >= 1; a larger body gets 413
session_ttl 3600 How many seconds a session of the session-based protocol lives, >= 1
mcp_server_info.name null Server id in reverse-DNS format for the MCP Registry; serverInfo.name in initialize. If unset: ufo-rpc in initialize, and the field is absent from server.json
mcp_server_info.title null serverInfo.title in initialize and title in server.json
mcp_server_info.description null server.json only
mcp_server_info.icons[] [] server.json only: src (required), mime_type, sizes, theme (light | dark)
groups.<group> — Optional per-group overrides: the same fields enabled, instructions, max_body_bytes, session_ttl, mcp_server_info
  • groups is optional. The route defines the group; a group without its own entry runs on the bundle-wide settings. An entry only overrides them field by field: an unset (or ~) field inherits the bundle-wide value, mcp_server_info too (a group may set only name).
  • There is no version setting. serverInfo.version in initialize and version in server.json are always the request's {ver}.
  • Options of earlier builds (session_directory, server_name, server_version, methods) were removed; with them the configuration fails with "Unrecognized option".

For a group, max_body_bytes and session_ttl are not validated by the configuration the way the bundle-wide values are. Set integers >= 1; see Open issues.

API groups

The core #[RPC\APIGroups] attribute on a class or method decides which methods become tools:

use Ufo\RpcObject\RPC;

#[RPC\Info('Catalog')]
#[RPC\APIGroups(['mcp', 'internal'], withDefault: false)]
final class CatalogProcedure implements IRpcService { /* /mcp and /mcp/internal, not /rpc */ }

#[RPC\Info('Admin')]
#[RPC\APIGroups(['internal'], withDefault: false)]
final class AdminProcedure implements IRpcService { /* only /mcp/internal */ }

The same group can be served by /rpc/... (JsonRpcBundle) and /mcp/... (this bundle) at the same time. Token checks for the group (ufo_json_rpc.security.providers) apply to MCP as well.

API versions

/mcp and /mcp/v1 serve version 1, /mcp/v2 serves version 2 of the group in the core (#[RPC\Info('Catalog', version: 2, removedMethods: [...])]; a version inherits the previous one, see API versions). A version the group does not have answers 404. A group without any method answers 404 too.

Sessions and a shared cache

Clients of the session-based protocol (before 2026-07-28) get an Mcp-Session-Id. The session is stored in the core cache pool under the key ufo_mcp_<group>_<session id> with the group's session_ttl. By default this is a local filesystem pool; when several hosts serve MCP, point it to a shared store, otherwise a session created on one host is unknown on another:

# config/packages/ufo_json_rpc.yaml
ufo_json_rpc:
    cache:
        pool: 'cache.app'   # e.g. a shared Redis

Stateless clients of the 2026-07-28 protocol do not need session storage.