# llms-full.txt — lauren-mcp complete API reference > This file documents every public symbol in `lauren_mcp.__all__`. > It is intended for AI assistants and LLM-based tooling. > See llms.txt for a shorter overview. Package: lauren-mcp Version: 1.0.0 Status: Beta (Development Status :: 4 - Beta) Source: https://github.com/lauren-framework/lauren-mcp Docs: https://lauren-framework.github.io/lauren-mcp/ PyPI: https://pypi.org/project/lauren-mcp/ --- ## Version constants ### LATEST ```python LATEST: str ``` The latest MCP protocol version string supported by this library. Example value: `"2024-11-05"`. Use this when constructing `InitializeParams` if you want to negotiate the newest protocol version. ### STABLE ```python STABLE: str ``` The stable MCP protocol version string recommended for production use. May lag behind `LATEST` during transition periods to give server implementations time to adopt new protocol features. Example value: `"2024-11-05"`. ### SUPPORTED ```python SUPPORTED: list[str] ``` A list of all MCP protocol version strings this library can handle. Example value: `["2024-11-05"]`. The library refuses `initialize` handshakes that propose a version not in this list. --- ## Server decorators ### mcp_server ```python def mcp_server( path: str, *, transport: str = "ws", ) -> Callable[[type], type]: ``` Class decorator that registers a class as an MCP server mounted at `path`. Also applies `@injectable(scope=Scope.SINGLETON)` so the class participates in Lauren's DI graph. **Parameters** - `path` (`str`, required): URL path prefix for this server (e.g. `"/mcp"`). - `transport` (`str`, default `"ws"`): Default transport when `McpServerModule.for_root()` is called without an explicit `transport=` argument. One of `"ws"`, `"sse"`, `"streamable"`, `"both"` (WS + SSE), or `"all"` (WS + Streamable HTTP). **Returns**: The decorated class, unmodified except for attached MCP metadata. **Example** ```python from lauren_mcp import mcp_server, mcp_tool @mcp_server("/mcp") class ShopServer: @mcp_tool() async def search(self, query: str) -> list[dict]: """Search items. Args: query: Search terms.""" ... ``` --- ### mcp_tool ```python def mcp_tool( *, name: str | None = None, description: str | None = None, annotations: ToolAnnotations | None = None, timeout: float | None = None, tags: frozenset[str] | set[str] | None = None, meta: dict[str, Any] | None = None, output_schema: type | dict | None = None, ) -> Callable[[Callable], Callable]: ``` Method decorator that marks an async method on an `@mcp_server` class as an MCP tool. The tool's JSON Schema is derived automatically from Python type annotations. Parameters annotated `McpToolContext` are **excluded** from the schema and injected at call time. Google/Sphinx/NumPy docstring `Args:` sections populate per-parameter descriptions. **Parameters** - `name`: Override tool name (defaults to method name). - `description`: Override tool description (defaults to docstring first paragraph). - `annotations`: `ToolAnnotations` hints for AI clients (`readOnlyHint`, etc.). - `timeout`: Per-call execution deadline in seconds; raises `INTERNAL_ERROR` if exceeded. - `tags`: Categorical tags included in `tools/list` under `"tags"`. - `meta`: Opaque metadata forwarded to clients under `"_meta"`. - `output_schema`: JSON Schema dict, Pydantic model, dataclass, or TypedDict describing the structured output; advertised as `outputSchema` in `tools/list`. **Schema generation — supported annotations** | Python annotation | JSON Schema | |---|---| | `str`, `int`, `float`, `bool` | primitive types | | `list[X]` | `{"type": "array", "items": }` | | `dict[str, X]` | `{"type": "object", "additionalProperties": }` | | `X \| None` / `Optional[X]` | optional (omitted from `required`) | | `Literal["a", "b"]` | `{"type": "string", "enum": ["a", "b"]}` | | `Annotated[int, Field(ge=0)]` | `{"type": "integer", "minimum": 0}` | | Pydantic `BaseModel` | object with `$defs` (requires `[pydantic]` extra) | | `msgspec.Struct` | object with `$defs` (requires `[msgspec]` extra) | | `@dataclass` class | object schema inlined | | `TypedDict` | object schema with `required` from `__required_keys__` | | `UUID`, `datetime`, `date`, `Path` | string with format | | `McpToolContext` | **excluded** from schema, injected by framework | **Per-method Lauren decorators** `@use_guards`, `@use_interceptors`, `@use_exception_handlers`, and `@set_metadata` from the Lauren framework can be applied to individual `@mcp_tool` methods. `@mcp_tool()` must be the **outermost** decorator; Lauren decorators go **inside**: ```python from lauren import use_guards, use_interceptors, use_exception_handlers, set_metadata from lauren_mcp import mcp_tool @set_metadata("required_role", "admin") # readable via ctx.get_metadata(...) @use_guards(AdminGuard) # runs before method; False → FORBIDDEN @use_interceptors(AuditInterceptor) # wraps the call; call call_handler.handle() @use_exception_handlers(DomainErrHandler) # maps domain exceptions to isError: True @mcp_tool() async def admin_op(self, ctx: McpToolContext) -> dict: ... ``` See also: `McpExecutionContext` (passed to guards/interceptors), `McpForbiddenError` (guard rejection), `McpCallHandler` (`call_handler.handle()` in interceptors). **Example** ```python from lauren_mcp import mcp_tool, ToolAnnotations, McpToolContext @mcp_tool( annotations=ToolAnnotations(readOnlyHint=True), timeout=30.0, tags={"search"}, ) async def search( self, query: str, limit: int = 10, ctx: McpToolContext, ) -> list[dict]: """Search the catalogue. Args: query: Full-text search query. limit: Max results (default 10). """ await ctx.report_progress(0, total=100) ... ``` --- ### mcp_resource ```python def mcp_resource( uri_template: str, *, name: str | None = None, description: str | None = None, mime_type: str | None = None, ) -> Callable[[Callable], Callable]: ``` Method decorator that exposes a URI-addressable resource on an `@mcp_server` class. **URI template operators supported (RFC 6570 subset)** | Template | Matches | Example | |---|---|---| | `{param}` | Single path segment (no `/`) | `/items/{id}` | | `{+param}` or `{param*}` | Multi-segment (crosses `/`) | `/files/{+path}` | | `{?p1,p2}` | Optional query parameters | `/search/{q}{?page,size}` | Path and query variables are passed as keyword arguments; type hints on the method drive coercion (`int`, `float`, `bool`, `Optional[X]` supported). **Return types** | Return type | Wire format | |---|---| | `str` | `text` field | | `bytes` | base64-encoded `blob` field (MIME from `mime_type=`) | | `BlobResource(data, mime_type)` | blob with explicit MIME | | `ResourceResult(contents=[...])` | multi-item response | | `dict` | passed through as-is | **Example** ```python @mcp_resource("/files/{+path}{?version}", mime_type="text/plain") async def read_file(self, path: str, version: int = 1) -> str: """Read a versioned file.""" return load(path, version) @mcp_resource("/img/{name}", mime_type="image/png") async def image(self, name: str) -> bytes: """Return raw PNG bytes.""" return open(f"{name}.png", "rb").read() ``` --- ### mcp_prompt ```python def mcp_prompt( name: str | None = None, *, description: str | None = None, title: str | None = None, ) -> Callable[[Callable], Callable]: ``` Method decorator that exposes a parameterised prompt template on an `@mcp_server` class. The decorated method must be async and must return a `str`. **Parameters** - `name` (`str | None`, default `None`): Override prompt name. Defaults to method name. - `description` (`str | None`, default `None`): Override description. Defaults to docstring. - `title` (`str | None`, default `None`): Human-readable display name shown in client UIs. **Example** ```python @mcp_prompt() async def summarise_prompt(self, topic: str = "general") -> str: """Build a summarisation prompt. Args: topic: What to summarise (default "general"). """ return f"Please provide a concise summary of the following {topic} content:" ``` --- ### mcp_lifespan ```python # from lauren_mcp.server import mcp_lifespan @mcp_lifespan # no parentheses async def lifespan(self) -> AsyncGenerator[dict, None]: ... ``` Method decorator (no arguments) that marks an async generator method as the server's lifespan hook. The generator runs **once** at server startup; the `dict` it yields becomes `McpToolContext.lifespan_context` for every subsequent tool call. Code after the `yield` (typically in a `finally` block) runs at server shutdown. **Rules** - Must be an `async def` with a single `yield`. - Only one `@mcp_lifespan` method per `@mcp_server` class. - The yielded value must be a `dict` (or `None` for an empty context). **Example** ```python from lauren_mcp import mcp_server, mcp_tool, McpToolContext from lauren_mcp.server import mcp_lifespan @mcp_server("/mcp") class MyServer: @mcp_lifespan async def lifespan(self): db = await Database.connect(DATABASE_URL) try: yield {"db": db} finally: await db.close() @mcp_tool() async def query(self, sql: str, ctx: McpToolContext) -> list: return await ctx.lifespan_context["db"].fetch(sql) ``` --- ### mcp_completion ```python # from lauren_mcp.server import mcp_completion def mcp_completion( target: str, argument: str, *, ref_type: str = "ref/prompt", ) -> Callable[[Callable], Callable]: ``` Method decorator that registers a completion handler for a prompt argument or resource template variable. Called by the client when the user types partial input into a prompt or resource field that supports auto-complete. **Parameters** - `target` (`str`): Name of the prompt (for `ref_type="ref/prompt"`) or the URI template of the resource (for `ref_type="ref/resource"`). - `argument` (`str`): The argument name within that prompt or resource for which this function provides completions. - `ref_type` (`str`, default `"ref/prompt"`): Either `"ref/prompt"` or `"ref/resource"`. The decorated method must be `async def` accepting a single `partial: str` argument and returning either `list[str]` or `CompletionResult`. **Example** ```python from lauren_mcp import mcp_server, mcp_prompt from lauren_mcp.server import mcp_completion @mcp_server("/mcp") class MyServer: NAMES = ["Alice", "Bob", "Carol"] @mcp_prompt() async def greet(self, name: str) -> str: """Greet someone. Args: name: Person's name.""" return f"Hello {name}!" @mcp_completion("greet", "name") async def complete_greet_name(self, partial: str) -> list[str]: return [n for n in self.NAMES if n.lower().startswith(partial.lower())] ``` --- ### McpServerModule ```python class McpServerModule: @staticmethod def for_root( server_cls: type, *, transport: str = "ws", server_info: Implementation | None = None, capabilities: ServerCapabilities | None = None, providers: list | None = None, imports: list | None = None, exports: list | None = None, log_level: str = "debug", mounts: list[tuple[type, str]] | None = None, proxies: list[tuple[McpClientProtocol, str]] | None = None, ) -> type: # returns a Lauren @module class ... ``` Builds a Lauren `@module` that mounts *server_cls* in the DI graph and wires all MCP handler coroutines. **Parameters** - `server_cls`: Class decorated with `@mcp_server`. - `transport`: One of `"ws"`, `"sse"`, `"streamable"`, `"both"` (WS+SSE), `"all"` (WS+Streamable). - `server_info`: Override the name/version in the `initialize` response. - `capabilities`: Override auto-detected `ServerCapabilities`. - `providers`: Extra Lauren DI providers. - `imports`: Extra Lauren `@module` classes to import. - `exports`: Extra types to export from the generated module. - `log_level`: Minimum severity for `ctx.log()` notifications (`"debug"` / `"info"` / `"warning"` / `"error"`). - `mounts`: `[(OtherServerCls, "prefix_"), ...]` — expose another `@mcp_server` class's tools/resources/prompts with name prefix. - `proxies`: `[(client, "prefix_"), ...]` — connect a remote MCP client at startup and re-export its tools locally. **Raises** `TypeError` if *server_cls* is not decorated with `@mcp_server`. **Route mounting** for `@mcp_server("/mcp")`: | Transport value | Endpoints | |---|---| | `"ws"` | `ws://host/mcp/ws` | | `"sse"` | `GET /mcp/sse` (stream) + `POST /mcp/` (messages) | | `"streamable"` | `POST /mcp/` + `GET /mcp/` (push) + `DELETE /mcp/` | | `"both"` | WS + SSE | | `"all"` | WS + Streamable HTTP | **Example** ```python from lauren import LaurenFactory, module from lauren_mcp import McpServerModule, McpServer @module(imports=[McpServerModule.for_root( MyServer, transport="all", log_level="info", mounts=[(AnalyticsServer, "analytics_")], proxies=[(McpServer.streamable_http("http://remote/mcp"), "remote_")], )]) class AppModule: pass app = LaurenFactory.create(AppModule) ``` --- ### McpToolContext ```python @dataclass(frozen=True) class McpToolContext: tool_name: str tool_use_id: str | int | None headers: Headers | None # lauren.types.Headers (case-insensitive) execution_context: ExecutionContext | None # lauren.types.ExecutionContext session_id: str | None metadata: dict[str, Any] # from @set_metadata on the @mcp_server class state: dict[str, Any] # mutable per-call scratch space extras: dict[str, Any] # extension bag lifespan_context: dict[str, Any] # dict yielded by @mcp_lifespan ``` Context injected into `@mcp_tool` methods when a parameter is annotated `McpToolContext`. The parameter may have any name; it is **excluded** from the JSON schema so AI clients never see or supply it. Declare it anywhere in the signature: ```python @mcp_tool() async def my_tool(self, query: str, ctx: McpToolContext) -> str: ... @mcp_tool() async def other(self, data: str, tool_ctx: McpToolContext | None = None) -> str: ... ``` **Methods** ```python ctx.get_metadata(key, default=None) # shorthand for ctx.metadata.get(...) # Progress (sends notifications/progress — no-op when no progressToken) await ctx.report_progress(progress: float | int, total: float | int | None = None) # Structured log to client (sends notifications/message) await ctx.log(level: Literal["debug","info","warning","error"], message: str, data: dict | None = None) await ctx.debug(message, data=None) await ctx.info(message, data=None) await ctx.warning(message, data=None) await ctx.error(message, data=None) # Server-initiated LLM call (WS and Streamable HTTP only) result: CreateMessageResult = await ctx.sample( messages: str | list[SamplingMessage], *, max_tokens: int = 1024, system_prompt: str | None = None, temperature: float | None = None, result_type: type | None = None, # parse + validate JSON reply ) # Server asks client to prompt user (WS and Streamable HTTP only) answer: ElicitResult = await ctx.elicit( message: str, response_type = None, # None | str | bool | int | Literal | dataclass | TypedDict | BaseModel ) ``` **Raises** - `McpSamplingNotAvailable` — `ctx.sample()` called on legacy SSE transport, or client did not advertise the `sampling` capability. - `McpElicitationNotAvailable` — same for `ctx.elicit()`. --- ### ToolAnnotations ```python @dataclass(frozen=True) class ToolAnnotations: readOnlyHint: bool = False # tool does not modify state destructiveHint: bool = True # changes cannot be undone (conservative default) idempotentHint: bool = False # repeated calls produce same result openWorldHint: bool = True # tool may interact with external systems (conservative) ``` Behavioural hints transmitted to AI clients in `tools/list`. Clients use them to decide confirmation UX (e.g. ask before calling a destructive tool). The defaults follow the MCP specification's conservative assumptions. ```python from lauren_mcp import mcp_tool, ToolAnnotations @mcp_tool(annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)) async def search(self, q: str) -> list: ... ``` --- ## Client ### McpServer ```python class McpServer: @staticmethod def stdio( command: list[str] | tuple[str, ...], *, max_retries: int = 3, startup_timeout: float = 10.0, ) -> McpClientProtocol: ... @staticmethod def ws( url: str, *, headers: dict[str, str] | None = None, max_retries: int = 3, startup_timeout: float = 10.0, ) -> McpClientProtocol: ... @staticmethod def http( url: str, *, headers: dict[str, str] | None = None, max_retries: int = 3, startup_timeout: float = 10.0, **feature_kwargs, ) -> McpClientProtocol: ... @staticmethod def streamable_http( url: str, *, headers: dict[str, str] | None = None, max_retries: int = 3, startup_timeout: float = 10.0, **feature_kwargs, ) -> McpClientProtocol: ... ``` Factory class for creating MCP client connections. Never instantiate directly. All four factories accept these additional **feature kwargs**: | Kwarg | Type | Description | |---|---|---| | `protocol_version` | `str` | Protocol version to advertise (default `"2025-03-26"`) | | `roots` | `list[Root]` or `Callable` | Filesystem roots exposed to the server | | `progress_handler` | `Callable` | Invoked on `notifications/progress` | | `log_handler` | `Callable` | Invoked on `notifications/message` | | `list_changed_handler` | `Callable` | Invoked on tool/resource/prompt `list_changed` | | `sampling_handler` | `async Callable` | Responds to `sampling/createMessage` requests | | `elicitation_handler` | `async Callable` | Responds to `elicitation/create` requests | **`McpServer.stdio`** — subprocess stdin/stdout transport. No extra deps required. **`McpServer.ws`** — WebSocket (MCP 2025-03-26). Requires `pip install "lauren-mcp[ws]"`. **`McpServer.http`** — Legacy HTTP+SSE (MCP 2024-11-05). Requires `pip install "lauren-mcp[http]"`. **`McpServer.streamable_http`** — Streamable HTTP (MCP 2025-03-26, recommended). Requires `pip install "lauren-mcp[http]"`. **Example** ```python from lauren_mcp import McpServer, Root stdio_client = McpServer.stdio(["python", "-m", "my_server"]) ws_client = McpServer.ws("wss://api.example.com/mcp/ws", headers={"Authorization": "Bearer token"}, progress_handler=lambda p: print(p["progress"])) http_client = McpServer.streamable_http("https://api.example.com/mcp", roots=[Root("file:///workspace")]) ``` --- ### McpClientProtocol ```python class McpClientProtocol(Protocol): # Lifecycle async def connect(self) -> None: ... async def close(self) -> None: ... # Tools async def list_tools(self) -> list[ToolSchema]: ... async def call_tool(self, name: str, arguments: dict | None = None) -> Any: ... # Resources async def list_resources(self) -> list[ResourceSchema]: ... async def read_resource(self, uri: str) -> Any: ... # Prompts async def list_prompts(self) -> list[PromptSchema]: ... async def get_prompt(self, name: str, arguments: dict[str, str] | None = None) -> Any: ... # Utility async def ping(self) -> None: ... # Protocol version (available after connect()) @property def protocol_version(self) -> str: ... # Notification subscriptions def on_progress(self, handler: Callable) -> Callable[[], None]: ... def on_log(self, handler: Callable) -> Callable[[], None]: ... def on_list_changed(self, handler: Callable) -> Callable[[], None]: ... # Roots async def notify_roots_changed(self) -> None: ... ``` The interface implemented by all four transport clients. You receive instances of this from `McpServer.stdio/ws/http/streamable_http`. **Lifecycle methods** - `connect()`: Establishes the transport and runs the MCP `initialize` handshake. - `close()`: Graceful teardown. **Protocol version** - `protocol_version` (property): The version negotiated with the server. Only available after `connect()`. **Notification subscriptions** — each returns an unsubscribe callable: - `on_progress(handler)`: Called with `{"progressToken": ..., "progress": ..., "total": ...}`. - `on_log(handler)`: Called with `{"level": "info", "logger": "...", "data": {...}}`. - `on_list_changed(handler)`: Called with `"tools"`, `"resources"`, or `"prompts"`. **Roots** - `notify_roots_changed()`: Send `notifications/roots/list_changed` to the server. Only valid when `roots=` was supplied to the factory. **Data methods** - `list_tools()` → `list[ToolSchema]` - `call_tool(name, arguments)` → raw dict `{"content": [...], "isError": False}`; raises `McpCallError` on server error - `list_resources()` → `list[ResourceSchema]` - `read_resource(uri)` → raw dict `{"contents": [...]}` - `list_prompts()` → `list[PromptSchema]` - `get_prompt(name, arguments)` → raw dict `{"messages": [...], "description": ...}` - `ping()` → `None`; raises `McpCallError` on failure **Example** ```python import asyncio, json from lauren_mcp import McpServer async def main(): client = McpServer.stdio(["python", "-m", "my_server"]) await client.connect() tools = await client.list_tools() result = await client.call_tool("search", {"query": "coffee"}) print(json.loads(result["content"][0]["text"])) await client.close() asyncio.run(main()) ``` --- ### McpCallError ```python class McpCallError(Exception): code: int # JSON-RPC error code, e.g. -32603 (INTERNAL_ERROR) ``` Raised by `call_tool()`, `list_tools()`, and other client methods when the server returns a JSON-RPC error response. ```python from lauren_mcp import McpCallError try: result = await client.call_tool("divide", {"a": 1, "b": 0}) except McpCallError as exc: print(f"Server error {exc.code}: {exc}") ``` Common codes: `PARSE_ERROR = -32700`, `METHOD_NOT_FOUND = -32601`, `INTERNAL_ERROR = -32603`. --- ### McpServerConfig ```python from dataclasses import dataclass @dataclass class McpServerConfig: alias: str client: Any # McpClientProtocol ``` Configuration dataclass pairing a short alias with an `McpClientProtocol` instance. Used with `McpToolBridge` and `lauren_ai.AgentModule.for_root(mcp_servers=[...])`. **Fields** - `alias` (`str`, required): Short identifier. Tools from this server are namespaced as `alias__tool_name`. - `client` (required): Client instance from `McpServer.stdio/ws/http`. **Example** ```python from lauren_mcp import McpServerConfig, McpServer config = McpServerConfig( alias="fs", client=McpServer.stdio(["python", "-m", "my_mcp_server"]), ) ``` --- ### McpToolBridge ```python class McpToolBridge: def __init__(self, servers: list[McpServerConfig]) -> None: ... def set_registry(self, registry: Any) -> None: ... async def connect_all(self) -> None: ... async def disconnect_all(self) -> None: ... ``` Manages the lifecycle for a list of `McpServerConfig` entries — connects every server, optionally populates a registry, and disconnects cleanly. Used internally by `lauren_ai.AgentModule` when `mcp_servers=[...]` is passed. **Methods** - `set_registry(registry)`: Attach any object with a `register_mcp_server(alias, tools, client)` method. Called by `connect_all()` once per server after tools are fetched. - `connect_all()`: Connect all servers and call `registry.register_mcp_server` for each one. Failures are logged at ERROR and do not abort other servers. - `disconnect_all()`: Close all clients; individual failures are suppressed. **Example** ```python from lauren_mcp import McpToolBridge, McpServerConfig, McpServer bridge = McpToolBridge([ McpServerConfig(alias="alpha", client=McpServer.stdio(["python", "server_a.py"])), McpServerConfig(alias="beta", client=McpServer.stdio(["python", "server_b.py"])), ]) await bridge.connect_all() # ... use connected clients ... await bridge.disconnect_all() ``` **Methods** - `get_tool_names()`: Returns the list of namespaced tool names available from this server (e.g. `["fs__read_file", "fs__list_directory"]`). Must be called after entering the async context manager. - `get_tool_schemas()`: Returns the raw `ToolSchema` objects for all exposed tools (after applying `tool_filter`). - `call(namespaced_name, arguments)`: Strips the alias prefix and calls the underlying tool. Raises `McpToolNotFoundError` if the name is not in this bridge's tool list. **Example** ```python from lauren_mcp import McpToolBridge, McpServerConfig, McpServer config = McpServerConfig(alias="svc", client=McpServer.stdio([...])) bridge = McpToolBridge(config) async with bridge: names = bridge.get_tool_names() # ["svc__search", "svc__add"] result = await bridge.call("svc__search", {"query": "widget"}) ``` --- ## Wire types ### JsonRpcRequest ```python @dataclass class JsonRpcRequest: jsonrpc: str # always "2.0" id: int | str method: str params: dict | None = None ``` An outgoing or incoming JSON-RPC 2.0 request. Always has an `id`; for fire-and-forget messages use `JsonRpcNotification`. --- ### JsonRpcNotification ```python @dataclass class JsonRpcNotification: jsonrpc: str # always "2.0" method: str params: dict | None = None ``` A JSON-RPC 2.0 notification (no `id`). The server does not send a response. Used for MCP lifecycle events like `notifications/initialized`. --- ### JsonRpcResponse ```python @dataclass class JsonRpcResponse: jsonrpc: str # always "2.0" id: int | str result: dict | list | str | int | float | bool | None ``` A successful JSON-RPC 2.0 response. The `result` field contains the method-specific return value. --- ### JsonRpcErrorResponse ```python @dataclass class JsonRpcErrorResponse: jsonrpc: str # always "2.0" id: int | str | None error: JsonRpcError @dataclass class JsonRpcError: code: int message: str data: dict | None = None ``` An error JSON-RPC 2.0 response. `id` is `None` when the error occurred before the request `id` could be determined (e.g. parse error). --- ### JsonRpcError ```python @dataclass class JsonRpcError: code: int message: str data: Any = None ``` The error object embedded inside a `JsonRpcErrorResponse`. `code` is a standard JSON-RPC or MCP error code (see `McpErrorCode`). `data` carries optional extra detail. --- ### McpErrorCode ```python from enum import IntEnum class McpErrorCode(IntEnum): PARSE_ERROR = -32700 INVALID_REQUEST = -32600 METHOD_NOT_FOUND = -32601 INVALID_PARAMS = -32602 INTERNAL_ERROR = -32603 TOOL_NOT_FOUND = -32001 RESOURCE_NOT_FOUND = -32002 PROMPT_NOT_FOUND = -32003 CONNECTION_ERROR = -32004 ``` Standard JSON-RPC 2.0 error codes plus MCP-specific extensions. --- ### parse_message ```python def parse_message( data: str | bytes, ) -> JsonRpcRequest | JsonRpcNotification | JsonRpcResponse | JsonRpcErrorResponse: ``` Parse a raw JSON string or bytes into the appropriate JSON-RPC message type. **Parameters** - `data` (`str | bytes`): Raw JSON-RPC message from the wire. **Returns**: One of the four JSON-RPC message dataclasses. **Raises**: `JsonRpcParseError` if `data` is not valid JSON or does not conform to JSON-RPC 2.0 structure. **Example** ```python from lauren_mcp import parse_message msg = parse_message('{"jsonrpc":"2.0","id":1,"method":"tools/list","params":null}') # → JsonRpcRequest(jsonrpc='2.0', id=1, method='tools/list', params=None) ``` --- ### build_error_response ```python def build_error_response( request_id: int | str | None, code: McpErrorCode | int, message: str, data: dict | None = None, ) -> JsonRpcErrorResponse: ``` Convenience constructor for `JsonRpcErrorResponse`. **Parameters** - `request_id` (`int | str | None`): The `id` from the request that triggered the error. Pass `None` for parse errors. - `code` (`McpErrorCode | int`): Error code (use `McpErrorCode` enum values). - `message` (`str`): Human-readable error description. - `data` (`dict | None`, default `None`): Optional additional error detail. **Example** ```python from lauren_mcp import build_error_response, McpErrorCode resp = build_error_response( request_id=42, code=McpErrorCode.TOOL_NOT_FOUND, message="Tool 'foo' is not registered.", ) ``` --- ### ToolSchema ```python @dataclass class ToolSchema: name: str description: str inputSchema: dict # JSON Schema object describing the tool's parameters ``` Descriptor for a single MCP tool as returned by `tools/list`. `inputSchema` is a JSON Schema object with `type: "object"`, `properties`, and `required` fields. Generated automatically by `@mcp_tool` from Python annotations. --- ### ResourceSchema ```python @dataclass class ResourceSchema: uri: str name: str description: str | None = None mimeType: str = "text/plain" ``` Descriptor for a single MCP resource as returned by `resources/list`. --- ### Role ```python Role = Literal["user", "assistant"] ``` Type alias for MCP audience roles. Used in `ResourceAnnotations.audience` and `SamplingMessage.role`. --- ### ResourceAnnotations ```python @dataclass class ResourceAnnotations: audience: list[Role] | None = None # intended readers; None means unrestricted priority: float | None = None # relevance weight in [0.0, 1.0] ``` Annotations attached to an MCP resource for UI and routing hints. Pass to `@mcp_resource(annotations=...)`. - `audience`: Restricts which roles should see the resource (`"user"`, `"assistant"`). - `priority`: Relevance weight in `[0.0, 1.0]`; higher means higher priority. Raises `ValueError` if outside the valid range. ```python from lauren_mcp import ResourceAnnotations from lauren_mcp.server import mcp_resource @mcp_resource("/reports/{name}", annotations=ResourceAnnotations(audience=["user"], priority=0.8)) async def report(self, name: str) -> str: ... ``` --- ### ResourceContent ```python @dataclass class ResourceContent: uri: str mimeType: str | None = None text: str | None = None blob: str | None = None # base-64 encoded binary ``` The content of a single resource returned inside a `resources/read` response. Exactly one of `text` or `blob` is populated. `text` carries plain-text content; `blob` carries arbitrary binary data encoded as base-64. --- ### PromptSchema ```python @dataclass class PromptSchema: name: str description: str | None = None arguments: list[PromptArgument] = field(default_factory=list) ``` Descriptor for a single MCP prompt as returned by `prompts/list`. --- ### TextContent ```python @dataclass class TextContent: type: Literal["text"] # always "text" text: str ``` A plain-text content block. The most common return type from tool calls. **Example** ```python result = await client.call_tool("search", {"query": "coffee"}) # result[0] is typically TextContent print(result[0].text) ``` --- ### ImageContent ```python @dataclass class ImageContent: type: Literal["image"] # always "image" data: str # base-64 encoded image bytes mimeType: str # e.g. "image/png", "image/jpeg" ``` A base-64 encoded image content block. Returned by tools that produce images. --- ### EmbeddedResource ```python @dataclass class EmbeddedResource: type: Literal["resource"] # always "resource" resource: TextResourceContents | BlobResourceContents ``` An embedded resource returned inside a tool call result. `TextResourceContents` has `uri: str` and `text: str`; `BlobResourceContents` has `uri: str` and `blob: str` (base-64 encoded). --- ### AudioContent ```python @dataclass class AudioContent: data: str # base64-encoded audio bytes mimeType: str # e.g. "audio/wav", "audio/mpeg", "audio/ogg" type: str = "audio" @classmethod def from_bytes(cls, data: bytes, mime_type: str = "audio/wav") -> AudioContent: ... ``` A base64-encoded audio content block. Returned by tools that produce audio output. Use `AudioContent.from_bytes(raw_audio, "audio/wav")` to construct from raw bytes without manually base64-encoding. --- ### ResourceLink ```python @dataclass class ResourceLink: uri: str name: str | None = None description: str | None = None mimeType: str | None = None type: str = "resource_link" ``` A lightweight reference to a resource by URI. Unlike `EmbeddedResource`, a `ResourceLink` does **not** inline the resource's content — it carries only the URI and optional metadata. The MCP client may fetch the resource separately via `resources/read` if needed. --- ### AnyContent ```python AnyContent = TextContent | ImageContent | AudioContent | EmbeddedResource | ResourceLink ``` Union alias for any content item that can appear in a tool result or message. Use this as a type annotation when a value may be any of the five content types. --- ### PromptArgument ```python @dataclass class PromptArgument: name: str description: str | None = None required: bool = False ``` A single argument descriptor within a `PromptSchema`. Generated automatically from `@mcp_prompt` method parameters. --- ### PromptMessage ```python @dataclass class PromptMessage: role: Literal["user", "assistant"] content: TextContent | ImageContent | EmbeddedResource ``` A single rendered message within a `GetPromptResult`. --- ### InitializeParams ```python @dataclass class InitializeParams: protocolVersion: str capabilities: ClientCapabilities clientInfo: Implementation ``` Payload sent by the client in the MCP `initialize` request. Typically constructed internally by the client transport; you rarely need to create this directly. --- ### InitializeResult ```python @dataclass class InitializeResult: protocolVersion: str capabilities: ServerCapabilities serverInfo: Implementation instructions: str | None = None ``` Payload returned by the server in the `initialize` response. Available on the client after `connect()` as `client.server_info`. --- ### ClientCapabilities ```python @dataclass class ClientCapabilities: roots: dict | None = None sampling: dict | None = None elicitation: dict | None = None experimental: dict | None = None ``` Capability flags advertised by the client during the MCP handshake. Non-`None` values signal support: `roots` for filesystem roots, `sampling` for server-initiated LLM calls, `elicitation` for server-initiated user prompts. --- ### ServerCapabilities ```python @dataclass class ServerCapabilities: tools: dict | None = None resources: dict | None = None prompts: dict | None = None logging: dict | None = None experimental: dict | None = None ``` Capability flags advertised by the server during the MCP handshake. The presence of `tools`, `resources`, or `prompts` keys indicates support for the corresponding MCP feature. --- ### Implementation ```python @dataclass class Implementation: name: str version: str ``` Identifies a client or server implementation in the MCP handshake messages. For `lauren-mcp` servers the default is `Implementation(name="lauren-mcp", version=)`. --- ### ToolCallParams ```python @dataclass class ToolCallParams: name: str arguments: dict | None = None ``` The `params` payload for a `tools/call` JSON-RPC request. --- ### ToolResult ```python @dataclass class ToolResult: content: list[TextContent | ImageContent | EmbeddedResource] isError: bool = False structuredContent: dict[str, Any] | None = None ``` Full result envelope from a `tools/call` response. `structuredContent` carries a typed JSON object alongside the human-readable `content` blocks (MCP 2025-06-18 structured output). When `isError=True`, the blocks describe the error. --- ### ReadResourceParams ```python @dataclass class ReadResourceParams: uri: str ``` The `params` payload for a `resources/read` JSON-RPC request. --- ### ReadResourceResult ```python @dataclass class ReadResourceResult: contents: list[TextResourceContents | BlobResourceContents] ``` Result envelope from a `resources/read` response. --- ### GetPromptParams ```python @dataclass class GetPromptParams: name: str arguments: dict[str, str] | None = None ``` The `params` payload for a `prompts/get` JSON-RPC request. --- ### GetPromptResult ```python @dataclass class GetPromptResult: description: str | None messages: list[PromptMessage] ``` Result envelope from a `prompts/get` response. `messages` contains the rendered prompt as a list of role-tagged content blocks, ready to prepend to an LLM conversation. --- ### McpParseError ```python class McpParseError(ValueError): ... ``` Raised by `parse_message()` when a raw JSON string cannot be decoded or does not conform to JSON-RPC 2.0 structure (missing `jsonrpc` field, unknown message shape, etc.). It is a subclass of `ValueError`. --- ### ToolOutput ```python @dataclass class ToolOutput: content: list[Any] | None = None structured_content: dict[str, Any] | None = None is_error: bool = False ``` Rich return type for `@mcp_tool` methods. Lets a tool control the human-readable `content` blocks and the machine-readable `structured_content` dict independently. When `content` is `None` and `structured_content` is set, a JSON text block is auto-generated. ```python from lauren_mcp import mcp_tool, ToolOutput, TextContent @mcp_tool() async def analyse(self, text: str) -> ToolOutput: return ToolOutput( content=[TextContent(text="Sentiment: positive")], structured_content={"sentiment": "positive", "score": 0.92}, ) ``` --- ### BlobResource ```python @dataclass class BlobResource: data: bytes mime_type: str = "application/octet-stream" ``` Explicit blob return type for `@mcp_resource` methods. The `data` bytes are base64-encoded into `ResourceContent.blob`. Using `BlobResource` lets you override the MIME type independently of the `mime_type=` parameter on `@mcp_resource`. ```python from lauren_mcp import BlobResource @mcp_resource("/reports/{name}") async def report(self, name: str) -> BlobResource: return BlobResource(data=generate_pdf(name), mime_type="application/pdf") ``` --- ### ResourceResult ```python @dataclass class ResourceResult: contents: list[Any] ``` Multi-item return type for `@mcp_resource` methods. Each item in `contents` may be a `str`, `bytes`, `BlobResource`, `ResourceContent`, or a dict. ```python from lauren_mcp import ResourceResult @mcp_resource("/bundle/{id}") async def bundle(self, id: str) -> ResourceResult: return ResourceResult(contents=["readme text", b"\x89PNG..."]) ``` --- ### ToolUseContent ```python @dataclass class ToolUseContent: id: str # opaque identifier for this tool call name: str # tool name input: dict[str, Any] # tool arguments type: str = "tool_use" def to_dict(self) -> dict[str, Any]: ... @classmethod def from_dict(cls, obj: dict[str, Any]) -> ToolUseContent: ... ``` A tool invocation block inside a `SamplingMessage`. Represents the LLM requesting to call a tool — returned in a `sampling/createMessage` response when the model decides to use a tool, or included when re-sending past assistant turns that contained tool calls. The `id` must match the `tool_use_id` of the corresponding `ToolResultContent`. --- ### ToolResultContent ```python @dataclass class ToolResultContent: tool_use_id: str # must match ToolUseContent.id content: list[TextContent | ImageContent] = field(default_factory=list) is_error: bool = False type: str = "tool_result" def to_dict(self) -> dict[str, Any]: ... @classmethod def from_dict(cls, obj: dict[str, Any]) -> ToolResultContent: ... ``` The result of a prior tool invocation, placed in a `role="user"` `SamplingMessage` to continue the conversation after a `ToolUseContent`. `is_error=True` signals that the tool call failed; the LLM can decide how to proceed. --- ### SamplingMessage ```python @dataclass class SamplingMessage: role: Literal["user", "assistant"] content: TextContent | ImageContent ``` A single message in a `sampling/createMessage` request sent via `ctx.sample()`. --- ### CreateMessageParams ```python @dataclass class CreateMessageParams: messages: list[SamplingMessage] maxTokens: int systemPrompt: str | None = None includeContext: Literal["none", "thisServer", "allServers"] = "none" temperature: float | None = None stopSequences: list[str] = field(default_factory=list) modelPreferences: dict[str, Any] | None = None metadata: dict[str, Any] | None = None ``` Parameters for a `sampling/createMessage` server-to-client request. Built automatically when you call `ctx.sample()`. --- ### CreateMessageResult ```python @dataclass class CreateMessageResult: role: Literal["assistant"] content: TextContent | ImageContent model: str stopReason: str | None = None @property def text(self) -> str: ... # shorthand for content.text ``` Result of a `sampling/createMessage` request. Returned by `ctx.sample()`. --- ### validate_sampling_messages ```python def validate_sampling_messages(messages: list[SamplingMessage]) -> None: ``` Validate that `ToolResultContent` blocks are properly paired with preceding `ToolUseContent` blocks in a sampling message list. **Raises** `ValueError` if a `ToolResultContent` appears before a `ToolUseContent` with the matching `id`, or if the ordering is otherwise invalid. An empty list is valid. This performs a shallow ordering check only — it does not validate tool input schemas or result content types. ```python from lauren_mcp import validate_sampling_messages, SamplingMessage, TextContent msgs = [SamplingMessage(role="user", content=TextContent(text="Hello"))] validate_sampling_messages(msgs) # passes — no tool use blocks ``` --- ### ElicitResult ```python @dataclass class ElicitResult: action: Literal["accept", "decline", "cancel"] content: dict[str, Any] | None = None ``` Result of an `elicitation/create` server-to-client request. Returned by `ctx.elicit()`. `content` is populated when `action == "accept"`. ```python answer = await ctx.elicit("Delete 500 records?") if answer.action == "accept": ... ``` --- ### UrlElicitResult ```python @dataclass class UrlElicitResult: action: Literal["accept", "cancel"] ``` Result of a URL-elicitation `elicitation/create` request. Unlike `ElicitResult`, there is no `"decline"` state — the only outcomes are completion (`"accept"`) or abandonment (`"cancel"`). No `content` field is present; the URL flow itself carries any data. --- ### McpUrlElicitationNotAvailable ```python class McpUrlElicitationNotAvailable(RuntimeError): ... ``` Raised by `ctx.elicit_url()` when the connected client did not advertise the `urlElicitation` sub-capability, the `elicitation` capability is absent, or the transport cannot carry server-to-client requests. --- ### Root ```python @dataclass class Root: uri: str # file:// URI name: str | None = None ``` A filesystem root exposed by an MCP client to servers that request them via `roots/list`. Pass a list to `McpServer.*(..., roots=[Root("file:///workspace")])`. --- ### CompletionResult ```python @dataclass class CompletionResult: values: list[str] total: int | None = None has_more: bool = False ``` Result of a `completion/complete` request. Returned by `@mcp_completion` handlers (or by the framework after collecting them). `values` contains the completion strings to offer to the user. `total` reports the total number of matches when only a subset is returned; `has_more` signals there are additional results beyond `values`. ```python from lauren_mcp import CompletionResult from lauren_mcp.server import mcp_completion @mcp_completion("greet", "name") async def complete_name(self, partial: str) -> CompletionResult: matches = [n for n in self.NAMES if n.lower().startswith(partial.lower())] return CompletionResult(values=matches[:5], total=len(matches), has_more=len(matches) > 5) ``` --- ### McpSamplingNotAvailable ```python class McpSamplingNotAvailable(RuntimeError): ... ``` Raised by `ctx.sample()` when: - The transport is legacy HTTP+SSE (server-to-client requests not supported). - The connected client did not advertise the `sampling` capability. --- ### McpElicitationNotAvailable ```python class McpElicitationNotAvailable(RuntimeError): ... ``` Raised by `ctx.elicit()` under the same conditions as `McpSamplingNotAvailable`. --- ### McpToolNameCollision ```python class McpToolNameCollision(Exception): ... ``` Raised at server startup when two composition sources (from `mounts=` or `proxies=`) produce the same tool name after applying the prefix. Fix by choosing a different prefix or renaming the conflicting tool. --- ### make_mount_binder ```python # from lauren_mcp.server import make_mount_binder def make_mount_binder(mounted_cls: type, prefix: str) -> type: ``` Build an `@injectable(Singleton)` class that registers all tools, resources, and prompts from `mounted_cls` into the shared catalogue with `prefix` applied to their names. Add the returned class (and `mounted_cls` itself) to `McpServerModule.for_root(providers=[...])`. This is the low-level primitive behind `McpServerModule.for_root(mounts=[...])`. Use `mounts=` directly unless you need custom DI wiring. **Raises** `TypeError` if `mounted_cls` is not decorated with `@mcp_server`. ```python from lauren_mcp.server import make_mount_binder binder = make_mount_binder(AnalyticsServer, "analytics_") # pass binder + AnalyticsServer to providers=[...] ``` --- ### make_proxy_binder ```python # from lauren_mcp.server import make_proxy_binder def make_proxy_binder(client: McpClientProtocol, prefix: str) -> type: ``` Build an `@injectable(Singleton)` class that connects `client` at startup, fetches its remote tool catalogue, and registers each tool locally under `{prefix}{name}`. At shutdown it deregisters the tools and closes the connection. This is the low-level primitive behind `McpServerModule.for_root(proxies=[...])`. Use `proxies=` directly unless you need custom DI wiring. ```python from lauren_mcp import McpServer from lauren_mcp.server import make_proxy_binder binder = make_proxy_binder(McpServer.streamable_http("http://remote/mcp"), "remote_") # pass binder to providers=[...] ``` --- ### RouteEntry ```python @dataclass class RouteEntry: pattern: str # regex matched against the path method: str | None = None # "GET", "POST", … or None for all expose_as: Literal["tool", "exclude"] = "tool" name_override: str | None = None description_override: str | None = None ``` One rule controlling how an OpenAPI operation maps to an MCP tool. Pass a list of `RouteEntry` objects as `route_map=` to `build_openapi_server_class()`. The first matching rule wins; operations with no match default to `"tool"`. --- ### build_openapi_server_class ```python # from lauren_mcp.server import build_openapi_server_class def build_openapi_server_class( spec: dict | str | Path, *, http_client: Any, # httpx.AsyncClient or compatible base_url: str = "", server_path: str = "/mcp", route_map: list[RouteEntry] | None = None, class_name: str = "OpenApiMcpServer", ) -> type: ``` Generates an `@mcp_server` class whose tools call a REST API described by an OpenAPI 3.x spec. Pass the result to `McpServerModule.for_root()`. **Parameters** - `spec`: Parsed dict, or path to a `.json` / `.yaml` file. - `http_client`: `httpx.AsyncClient` used to execute requests. - `base_url`: URL prefix (may be empty when the client has a `base_url`). - `server_path`: Mount path passed to `@mcp_server`. - `route_map`: Ordered `RouteEntry` rules; first match wins. - `class_name`: Name for the generated class (for tracebacks). > **Caveat**: Auto-generated tool descriptions are lower quality than hand-crafted > ones. Recommended for prototyping, not production. ```python import httpx, json from lauren_mcp.server import build_openapi_server_class, RouteEntry spec = json.load(open("openapi.json")) ApiServer = build_openapi_server_class( spec, http_client=httpx.AsyncClient(base_url="https://api.example.com"), route_map=[RouteEntry(r"/admin", expose_as="exclude")], ) ``` --- ## Built-in resource classes ### FileResource ```python # from lauren_mcp.server import FileResource class FileResource: def __init__( self, path: str | Path, uri: str, *, name: str | None = None, description: str | None = None, mime_type: str | None = None, ) -> None: ... ``` Expose a local file as a static MCP resource. MIME type is auto-detected from the file extension when `mime_type` is omitted. Text-like MIME types (`text/*`, `application/json`, `application/xml`) are served as UTF-8 strings; all others are returned as `BlobResource`. ```python from lauren_mcp.server import FileResource, register_file_resource readme = FileResource("README.md", "file:///project/README.md") ``` --- ### HttpResource ```python # from lauren_mcp.server import HttpResource class HttpResource: def __init__( self, url: str, uri: str, *, name: str | None = None, description: str | None = None, mime_type: str | None = None, headers: dict[str, str] | None = None, timeout: float = 30.0, ) -> None: ... ``` Fetch an HTTP URL and expose the response body as an MCP resource. Requires the `[http]` extra (`httpx`). MIME type is taken from the response `Content-Type` header when `mime_type` is omitted. Text-like responses become strings; others become `BlobResource`. ```python from lauren_mcp.server import HttpResource weather = HttpResource( "https://api.example.com/weather", "weather://current", name="current-weather", headers={"Authorization": "Bearer token"}, ) ``` --- ### DirectoryResource ```python # from lauren_mcp.server import DirectoryResource class DirectoryResource: def __init__( self, path: str | Path, uri: str, *, name: str | None = None, description: str | None = None, pattern: str = "*", recursive: bool = False, include_hidden: bool = False, ) -> None: ... ``` List files in a directory and expose the result as a JSON array resource. Returns a JSON-serialised list of relative file paths matching `pattern`. MIME type is always `"application/json"`. Hidden files (names starting with `.`) are excluded by default. ```python from lauren_mcp.server import DirectoryResource src_files = DirectoryResource( "src/", "file:///project/src/", pattern="**/*.py", recursive=True, ) ``` --- ### register_file_resource ```python # from lauren_mcp.server import register_file_resource def register_file_resource(catalog: McpCatalogManager, resource: FileResource) -> None: ``` Register a `FileResource` with an `McpCatalogManager` instance. Convenience wrapper around `catalog.register_resource(resource.as_mcp_resource_meta())`. --- ### register_http_resource ```python # from lauren_mcp.server import register_http_resource def register_http_resource(catalog: McpCatalogManager, resource: HttpResource) -> None: ``` Register an `HttpResource` with an `McpCatalogManager` instance. --- ### register_directory_resource ```python # from lauren_mcp.server import register_directory_resource def register_directory_resource(catalog: McpCatalogManager, resource: DirectoryResource) -> None: ``` Register a `DirectoryResource` with an `McpCatalogManager` instance. --- ### ToolStream ```python @dataclass class ToolStream(Generic[T]): generator: AsyncGenerator[T, None] total: int | None = None accumulate: Callable[[list[T]], Any] | None = None ``` Return type for ``@mcp_tool`` methods that produce results incrementally. Each value yielded by ``generator`` is sent to the client as a ``notifications/progress`` event during the tool call; when the generator is exhausted the accumulated result becomes the ``tools/call`` response. **Accumulation:** ``str`` chunks are joined with ``""`` by default; all other types use the last yielded value (or ``None`` for an empty generator); supply ``accumulate=lambda chunks: ...`` for custom reduction. **Progress notifications** are only delivered when the client includes a ``progressToken`` in ``_meta`` of the ``tools/call`` request. Without one the stream still runs and the accumulated result is returned normally. ```python from lauren_mcp import ToolStream @mcp_tool() async def generate(self, prompt: str) -> ToolStream[str]: """Stream completion tokens for *prompt*.""" async def gen(): async for token in llm.stream_complete(prompt): yield token return ToolStream(gen()) ``` To produce a numeric total with ``accumulate``: ```python return ToolStream( number_generator(), total=expected_count, accumulate=lambda chunks: sum(chunks), ) ``` --- ### LaurenMcpMemoryServer Deployable ``@mcp_server`` backed by in-memory storage (subclassable for SQLite). Exposes tools ``save_conversation``, ``delete_conversation``, ``save_user_fact``, ``get_user_facts``, ``delete_user_fact`` and resource ``memory://conversations/{id}``. Use with :class:`~lauren_ai.mcp.McpConversationStore` / ``McpUserMemoryStore`` in ``lauren-ai``. ```python from lauren_mcp import LaurenMcpMemoryServer, McpServerModule module = McpServerModule.for_root(LaurenMcpMemoryServer) ``` --- ### McpExecutionContext ```python @dataclass(frozen=True) class McpExecutionContext: tool_name: str method_name: str server_class: type headers: Any | None execution_context: Any | None session_id: str | None metadata: dict[str, Any] tool_use_id: str | int | None def get_metadata(self, key: str, default: Any = None) -> Any: ... ``` Lightweight context passed to per-tool `@use_guards` and `@use_interceptors`. Built at dispatch time — before the tool method is invoked. Distinct from `McpToolContext` which is injected into the tool body itself. `headers` is `None` on stdio and WebSocket (upgrade headers available on WS). `execution_context` is `None` on WebSocket and stdio; a Lauren `ExecutionContext` on SSE / Streamable HTTP. --- ### McpForbiddenError ```python class McpForbiddenError(RuntimeError): guard_name: str # name of the guard that rejected the call ``` Raised inside `make_tools_call_handler` when a per-tool `@use_guards` guard returns `False`. The dispatcher catches it and returns `INTERNAL_ERROR` with `data = {"type": "FORBIDDEN", "guard": guard_name}`. ```python from lauren import injectable, use_guards from lauren_mcp import McpForbiddenError, mcp_tool @injectable() class AdminGuard: async def can_activate(self, ctx: McpExecutionContext) -> bool: return ctx.headers.get("x-role") == "admin" if ctx.headers else False @use_guards(AdminGuard) @mcp_tool() async def admin_only(self) -> dict: ... # Calling this without the x-role header → INTERNAL_ERROR with data.type="FORBIDDEN" ``` --- ### McpCallHandler ```python class McpCallHandler: async def handle(self) -> dict[str, Any]: ... ``` Passed to per-tool interceptors as the "next step" in the chain. Call `await call_handler.handle()` to invoke the next interceptor or ultimately the tool method itself. Mirrors `lauren.types.CallHandler` but returns `dict[str, Any]` (the tools/call result) instead of an HTTP Response. ```python from lauren import interceptor, use_interceptors from lauren_mcp import McpCallHandler @interceptor() class AuditInterceptor: async def intercept(self, ctx: McpExecutionContext, call_handler: McpCallHandler) -> dict: result = await call_handler.handle() print(f"Tool {ctx.tool_name} completed, isError={result.get('isError')}") return result @use_interceptors(AuditInterceptor) @mcp_tool() async def my_tool(self) -> dict: ... ``` --- ## Exceptions These exceptions are not in `__all__` but are commonly caught by callers: - `McpError` — base class for all lauren-mcp exceptions - `McpConnectionError` — cannot connect or lost connection - `McpHandshakeError` — protocol version mismatch during initialize - `McpToolError` — server returned an error response for a tool call - `McpToolNotFoundError` — tool name not in server's tool list - `McpTimeoutError` — operation exceeded the configured timeout - `JsonRpcParseError` — raw data is not valid JSON-RPC 2.0 --- ## Internal modules (not in __all__, for contributors) - `lauren_mcp._server._dispatcher.McpDispatcher` — body-based JSON-RPC routing - `lauren_mcp._server._session.SseSessionStore` — HTTP+SSE session lifecycle - `lauren_mcp._server._ws.WsHandler` — WebSocket connection handler - `lauren_mcp._server._sse.SseHandler` — HTTP+SSE connection handler - `lauren_mcp._server._handshake.run_handshake` — MCP initialize sequence - `lauren_mcp._client._McpBaseRemoteClient` — shared base for all client transports - `lauren_mcp._client._ws.WsClient` — WebSocket client implementation - `lauren_mcp._client._http.HttpSseClient` — HTTP+SSE client implementation