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.