Skip to content

API

pico_mcp.decorators

Marker decorators: store the MCP registration on the method; the server factory picks them up when the container builds MCPServer.

tool(*, read_only=None, destructive=None, idempotent=None, open_world=None, **options)

Expose a component method as an MCP tool.

The flags become the standard MCP tool annotations (readOnlyHint...), which clients and approval gateways read to decide how much to trust a call. options go to MCPServer.add_tool (name, description, title...).

Source code in src/pico_mcp/decorators.py
def tool(
    *,
    read_only: bool | None = None,
    destructive: bool | None = None,
    idempotent: bool | None = None,
    open_world: bool | None = None,
    **options,
):
    """Expose a component method as an MCP tool.

    The flags become the standard MCP tool annotations (``readOnlyHint``...),
    which clients and approval gateways read to decide how much to trust a call.
    ``options`` go to ``MCPServer.add_tool`` (``name``, ``description``, ``title``...).
    """
    hints = {
        "read_only_hint": read_only,
        "destructive_hint": destructive,
        "idempotent_hint": idempotent,
        "open_world_hint": open_world,
    }
    hints = {k: v for k, v in hints.items() if v is not None}
    if hints:
        if "annotations" in options:
            raise TypeError("@tool: pass the semantic flags or annotations=, not both")
        if read_only and destructive:
            raise ValueError("@tool: a read-only tool cannot be destructive")
        if read_only:
            # destructiveHint defaults to true; say it explicitly for consumers that skip readOnlyHint.
            hints.setdefault("destructive_hint", False)
        options["annotations"] = ToolAnnotations(**hints)
    return _marker("tool", **options)

resource(uri, **options)

Expose a component method as an MCP resource; {param} in uri makes a template.

Source code in src/pico_mcp/decorators.py
def resource(uri: str, **options):
    """Expose a component method as an MCP resource; ``{param}`` in ``uri`` makes a template."""
    return _marker("resource", uri=uri, **options)

prompt(**options)

Expose a component method as an MCP prompt.

Source code in src/pico_mcp/decorators.py
def prompt(**options):
    """Expose a component method as an MCP prompt."""
    return _marker("prompt", **options)

pico_mcp.server

Builds the MCPServer from the @tool/@resource/@prompt methods of every registered component.

Each call resolves the component through the container, so scopes and interceptors apply exactly as for any other caller.

McpServerFactory

Provides the SDK's MCPServer with every marked component method registered.

Source code in src/pico_mcp/server.py
@factory
class McpServerFactory:
    """Provides the SDK's ``MCPServer`` with every marked component method registered."""

    @provides(MCPServer, scope="singleton")
    def create_mcp_server(self, container: PicoContainer, settings: McpSettings) -> MCPServer:
        server = MCPServer(settings.name, instructions=settings.instructions)
        seen: dict[tuple[str, str], str] = {}
        for cls, fn, (kind, options) in _discover(container):
            handler = _bind(container, cls, fn)
            key = (kind, options.get("uri") or options.get("name") or fn.__name__)
            origin = f"{cls.__qualname__}.{fn.__name__}"
            if key in seen:
                # The SDK only warns and keeps the first one: a silently shadowed tool is a bug.
                raise ValueError(f"duplicate MCP {kind} {key[1]!r}: {seen[key]} and {origin}")
            seen[key] = origin
            getattr(server, kind)(**options)(handler)
        return server

pico_mcp.config

Settings for pico-mcp (prefix mcp, zero-config).

McpSettings dataclass

Identity the server announces to MCP clients.

Source code in src/pico_mcp/config.py
@configured(target="self", prefix="mcp", mapping="tree")
@dataclass
class McpSettings:
    """Identity the server announces to MCP clients."""

    name: str = "pico-mcp"
    instructions: str | None = None