MCP Base Classes
The MCP subsystem provides base classes for building domain-specific MCP (Model Context Protocol) servers that integrate cleanly with the platform’s Gateway, caching, and provider routing. Domain repos extend BaseMCPServer rather than building MCP servers from scratch.
Architecture guide: Tools, MCP & Gateway
Key Classes and Functions
| Class / Function | Module | Purpose |
|---|---|---|
BaseMCPServer | agent_core.mcp.base_server | Shared server skeleton — tool registration, transport selection, health endpoint, $defs flattening |
cache_get() / cache_set() | agent_core.mcp.cache | Module-level cache functions backed by Redis or in-process dict |
resolve_provider() | agent_core.mcp.provider_routing | Resolves the correct provider class from EXECUTION_MODE env var |
BaseMCPServer
BaseMCPServer wraps mcp.server.Server directly (no FastMCP dependency). It handles transport selection, tool registration, error wrapping, health endpoints, and background tasks.
Critical:
BaseMCPServerflattens JSON Schema$defson tool registration. AgentCore Gateway rejects tool schemas that contain$defsreferences — the flattening is applied automatically to every registered tool schema so domain authors do not need to handle it manually.
from agent_core.mcp.base_server import BaseMCPServer
from mcp.types import Tool
mcp = BaseMCPServer("document-server", default_port=8080)
@mcp.tool(Tool(
name="get_document",
description="Retrieve a document by ID",
inputSchema={
"type": "object",
"properties": {"document_id": {"type": "string"}},
"required": ["document_id"],
},
))
async def get_document(arguments: dict) -> dict:
doc_id = arguments["document_id"]
doc = await store.get(doc_id)
return {"id": doc.id, "content": doc.content}
if __name__ == "__main__":
mcp.run()
BaseMCPServer API
class BaseMCPServer:
def __init__(
self,
name: str,
default_port: int = 8080,
) -> None: ...
def tool(self, definition: Tool) -> Callable:
"""Decorator. Registers an async function as an MCP tool.
Automatically flattens $defs in the inputSchema before registration."""
...
def add_background_task(self, coro_factory: Callable[[], Coroutine]) -> None:
"""Register a background coroutine factory. Runs alongside the server."""
...
def run(self) -> None:
"""Start the server. Transport selected via MCP_TRANSPORT env var."""
...
Transport Selection (MCP_TRANSPORT env var)
MCP_TRANSPORT value | Transport | Endpoints |
|---|---|---|
stdio (default) | Stdio JSON-RPC | — |
http | Streamable HTTP (Starlette/uvicorn) | GET /ping, GET /health, POST /mcp, POST / |
sse | Legacy SSE | GET /health, GET /sse, POST /messages |
cache_get() / cache_set()
Module-level cache functions backed by Redis (when REDIS_URL is set) or an in-process dict:
from agent_core.mcp.cache import cache_get, cache_set
# Namespace + kwargs form a cache key
cached = cache_get("documents", prefix="v2", doc_id="abc-123")
if cached is None:
result = await fetch_document("abc-123")
cache_set("documents", result, 300, prefix="v2", doc_id="abc-123")
def cache_get(
namespace: str,
*,
prefix: str = "",
**kwargs, # Remaining kwargs form the deterministic SHA-256 cache key
) -> Any | None: ...
def cache_set(
namespace: str,
value: Any,
ttl_seconds: int = 300, # Positional arg — default 300 s
*,
prefix: str = "",
**kwargs,
) -> None: ...
No class instantiation or configuration YAML is needed. Redis connection is read from REDIS_URL at first use. If Redis is unavailable at first use, caching is silently disabled for the process lifetime — tool calls proceed normally.
resolve_provider()
Routes to the correct provider class based on EXECUTION_MODE env var. Each MCP server declares a registry dict mapping ExecutionMode to a provider class, then calls resolve_provider():
from agent_core.mcp.provider_routing import resolve_provider
from agent_core.execution.mode import ExecutionMode
PROVIDERS = {
ExecutionMode.SIMULATION: MockDataProvider,
ExecutionMode.STAGING: StagingAPIProvider,
ExecutionMode.PRODUCTION: ProductionAPIProvider,
}
# Reads EXECUTION_MODE, picks the class, instantiates it
provider = resolve_provider(PROVIDERS)
def resolve_provider(
registry: dict[ExecutionMode, type[T]],
*,
aliases: dict[str, str] | None = None, # Optional mode name aliases
) -> T: ...
The selected provider class is always instantiated with no arguments. Provider configuration should be read inside __init__ from environment variables.
There is no MCPProviderRouter class. Provider routing is a single function call.
Gateway Target Registration
Gateway target registration for an MCP server is performed by TargetRegistry at deploy time, not by BaseMCPServer at startup. There is no gateway.auto_register feature. See Gateway SDK reference for target registration details.
Building a Domain MCP Server
Recommended pattern for domain repos:
- Create a Python package (e.g.,
analytics-mcp) - Instantiate
BaseMCPServerand register tools with@mcp.tool() - Use
cache_get/cache_setfor expensive lookups - Use
resolve_provider()if different backends are needed per environment - Package as a Docker container; declare as a
mcp_servertarget ingateway-targets.yaml
The platform’s Terraform infrastructure provisions the ECR repository, ECS service, and Gateway target entry. Domain repos only write Python.
See Also
- Gateway SDK reference — Target types, registration
- Tools, MCP & Gateway guide — Architecture, deployment patterns