Extending the bundle

← Extending the bundle

Ashterix

docs: rewrite JsonRpcBundle docs, add SDK docs, move them under docs/api

en/docs/api/mcp-bundle/developers/extensions.md new file +75 −0

Extending the bundle

Everything below is a regular Symfony service override or a tagged service; the bundle itself does not need changes.

What How For
Availability\McpAvailabilityCheckerInterface service alias Whether a group answers at all (otherwise 404)
Security\McpAccessPolicyInterface service alias CORS origins and the host allowlist for DNS rebinding protection
ClientManifest\McpClientManifestGeneratorInterface tag ufo_mcp.client_manifest_generator with a key A manifest for another client
translations/rpc/<group>.<locale>.yaml core translations Texts for the agent without code changes
#[McpTool] attribute on a method ToolAnnotations hints

Group availability

McpAvailabilityCheckerInterface::isEnabled(string $group): bool decides whether a group answers at all (otherwise 404). The default DefaultMcpAvailabilityChecker reads enabled (ufo_mcp.groups.<group>.enabled falling back to ufo_mcp.enabled). Replace it for a feature flag, a per-tenant switch or a maintenance window:

use Ufo\McpBundle\Availability\McpAvailabilityCheckerInterface;

final readonly class FeatureFlagAvailability implements McpAvailabilityCheckerInterface
{
    public function __construct(private FeatureFlags $flags) {}

    public function isEnabled(string $group): bool
    {
        return $this->flags->isOn('mcp.'.$group);
    }
}
# config/services.yaml
services:
    Ufo\McpBundle\Availability\McpAvailabilityCheckerInterface: '@App\Mcp\FeatureFlagAvailability'

Your implementation fully replaces the configuration check; if you need both, call the default one yourself.

Hosts and origins

McpAccessPolicyInterface: see CORS and DNS rebinding.

A manifest for another client

use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
use Ufo\McpBundle\ClientManifest\McpClientManifestGeneratorHolder;
use Ufo\McpBundle\ClientManifest\McpClientManifestGeneratorInterface;
use Ufo\McpBundle\ConfigService\McpGroupConfig;

#[AutoconfigureTag(McpClientManifestGeneratorHolder::TAG, ['key' => 'acme'])]
final class AcmeAgentManifestGenerator implements McpClientManifestGeneratorInterface
{
    public function generate(McpGroupConfig $group, string $endpointUrl, string $tokenHeaderName): array
    {
        return [
            'servers' => [
                $group->name => ['url' => $endpointUrl, 'headers' => [$tokenHeaderName => '<UFO_MCP_TOKEN>']],
            ],
        ];
    }
}

The manifest is served at <prefix>/manifest/acme.json in the format that client's config file expects. The key must match [\w-]+ and must not clash with the built-in ones (claude, cursor, gemini, openai). $endpointUrl is the absolute endpoint URL from the request, $tokenHeaderName is ufo_json_rpc.security.token_name. Do not put a real token into a manifest.

Texts for the agent

Group translations (translations/rpc/<group>.<locale>.yaml) describe a method for an AI agent differently from the OpenRPC documentation, without code changes; see Title and description. An extension bundle can ship its own translations through the core ufo_json_rpc.translations.paths.

Everything the core offers

A tool call is a core RPC request, so the JsonRpcBundle extension points work unchanged: event listeners (rpc.request, rpc.pre_execute, rpc.post_execute, rpc.pre_response, rpc.post_response, rpc.error), parameter converters, token validators and per-group token providers, the response cache pool.

Other ecosystem packages are on the JsonRpcBundle Extensions page.