# lauren-mcp

> Model Context Protocol (MCP) server and client for the Lauren web framework.

## What is lauren-mcp?

`lauren-mcp` extends the Lauren Python web framework with support for the Model Context
Protocol. It provides two capabilities:

**Server**: Expose any Lauren service as an MCP server. AI clients (Claude, custom
agents, etc.) can discover and call your service's tools over WebSocket, HTTP+SSE, or
Streamable HTTP.

**Client**: Connect to any remote MCP server (stdio, WebSocket, HTTP+SSE, Streamable
HTTP) and wire its tools into a Lauren AI agent with automatic namespacing.

## Installation

```
pip install lauren-mcp                   # core
pip install "lauren-mcp[ws]"             # WebSocket client
pip install "lauren-mcp[http]"           # HTTP+SSE client (legacy 2024-11-05)
pip install "lauren-mcp[pydantic]"       # Pydantic schema generation
pip install "lauren-mcp[msgspec]"        # msgspec.Struct schema generation
pip install "lauren-mcp[cli]"            # lmcp CLI (typer + uvicorn)
pip install "lauren-mcp[otel]"           # OpenTelemetry tracing
pip install "lauren-mcp[all]"            # everything
```

## Protocol versions

Supported: `2024-11-05`, `2025-03-26`, `2025-06-18`, `2025-11-25` (LATEST)

## Server API

- `@mcp_server(path, *, transport="ws")` — transport: `"ws"` | `"sse"` | `"streamable"` | `"both"` | `"all"`
- `@mcp_tool(*, name, description, annotations, timeout, tags, meta, output_schema, structured_output)`
- `@mcp_resource(uri_template, *, name, description, mime_type)` — RFC 6570: `{+p}`, `{p*}`, `{?p1,p2}`
- `@mcp_prompt(name, *, description)`, `@mcp_completion(target, argument)`, `@mcp_lifespan`
- `McpToolContext` — injected via type annotation in tool methods
  - `ctx.report_progress()`, `ctx.log()`, `ctx.sample()`, `ctx.elicit()`, `ctx.cancel_requested`
  - `ctx.lifespan_context` — dict yielded by `@mcp_lifespan`
- `McpServerModule.for_root(cls, *, transport, log_level, mounts, proxies, event_store, instrument_otel, stateless_http)`
- `ToolAnnotations(readOnlyHint, destructiveHint, idempotentHint, openWorldHint)`
- Schema: Pydantic, `msgspec.Struct`, `@dataclass`, `TypedDict`, `Literal`, `Annotated+Field` all supported
- Binary resources: `bytes` → blob; `BlobResource(data, mime_type)`; `ResourceResult(contents)`
- Server composition: `mounts=[(OtherCls, "prefix_")]`, `proxies=[(client, "prefix_")]`
- Built-in resources: `FileResource`, `HttpResource`, `DirectoryResource`
- `build_openapi_server_class(spec, http_client=...)` — OpenAPI → MCP

## Per-method decorators (from Lauren framework)

Order: `@mcp_tool()` outermost, Lauren decorators inside (closer to `async def`).

- `@use_guards(GuardClass)` — runs before the method; guard receives `McpExecutionContext`; return `False` → `INTERNAL_ERROR` with `data.type="FORBIDDEN"`
- `@use_interceptors(InterceptorClass)` — wraps the method call; call `await call_handler.handle()` to proceed; receives `McpExecutionContext` + `McpCallHandler`
- `@use_exception_handlers(HandlerClass)` — catches domain exceptions and returns `isError: True` in the tool result
- `@set_metadata(key, value)` — per-tool metadata; read via `ctx.get_metadata(key)` inside the tool body
- `@use_middlewares` on `@mcp_tool` → `TypeError` (not meaningful at dispatch level)

New types: `McpExecutionContext`, `McpForbiddenError`, `McpCallHandler`

## Client API

- `McpServer.stdio(command)` — subprocess stdin/stdout, no extra deps
- `McpServer.ws(url)` — WebSocket (requires `[ws]`)
- `McpServer.http(url)` — legacy HTTP+SSE 2024-11-05 (requires `[http]`)
- `McpServer.streamable_http(url)` — Streamable HTTP, recommended (requires `[http]`)

All factories accept: `protocol_version=`, `roots=[Root(...)]`, `progress_handler=`,
`log_handler=`, `list_changed_handler=`, `resource_updated_handler=`,
`sampling_handler=`, `elicitation_handler=`

Post-connect: `client.protocol_version`, `client.on_progress/on_log/on_list_changed()` → unsubscribe callable

Additional methods: `client.subscribe_resource(uri)`, `client.unsubscribe_resource(uri)`,
`client.complete(ref, argument)`, `client.set_logging_level(level)`

OAuth: `ClientCredentialsProvider(token_endpoint, client_id, client_secret, scopes=)`

## Wire types (selected)

`TextContent`, `ImageContent`, `AudioContent`, `EmbeddedResource`, `ResourceLink`,
`AnyContent`, `ToolUseContent`, `ToolResultContent`, `ResourceAnnotations`,
`UrlElicitResult`, `CompletionResult`, `validate_sampling_messages`

## Quick example

```python
from lauren_mcp import mcp_server, mcp_tool, McpToolContext, McpServerModule, McpServer
from lauren_mcp.server import mcp_lifespan
from lauren import LaurenFactory, module

@mcp_server("/mcp")
class MyServer:
    @mcp_lifespan
    async def lifespan(self):
        db = await init_db()
        try:
            yield {"db": db}
        finally:
            await db.close()

    @mcp_tool(timeout=30.0)
    async def search(self, query: str, ctx: McpToolContext) -> list:
        """Search items. Args: query: Search terms."""
        await ctx.report_progress(0, 100, "searching")
        return await ctx.lifespan_context["db"].search(query)

@module(imports=[McpServerModule.for_root(MyServer, transport="all")])
class App:
    pass

app = LaurenFactory.create(App)

# Client
client = McpServer.streamable_http("http://localhost:8000/mcp",
    progress_handler=lambda p: print(p["message"]))
await client.connect()
result = await client.call_tool("search", {"query": "coffee"})
```

## Wire protocol

JSON-RPC 2.0 over four transports:

| Transport | Extra | Endpoint |
|---|---|---|
| stdio | (none) | subprocess stdin/stdout |
| WebSocket | `[ws]` | `{path}/ws` |
| HTTP+SSE (legacy) | `[http]` | `GET {path}/sse` + `POST {path}/` |
| Streamable HTTP | `[http]` | `POST/GET/DELETE {path}/` |

## Key utility types

- `McpCallError` — raised when the server returns a JSON-RPC error response
- `McpServerConfig(alias, client)` — config for one remote MCP server
- `McpToolBridge([configs])` — lifecycle manager for a list of McpServerConfig
- Tool namespacing: `{alias}__{tool_name}` prevents collisions
- `LATEST`, `STABLE`, `SUPPORTED` — protocol version constants

## Links

- Documentation: https://lauren-framework.github.io/lauren-mcp/
- Source: https://github.com/lauren-framework/lauren-mcp
- PyPI: https://pypi.org/project/lauren-mcp/
- llms-full.txt: https://raw.githubusercontent.com/lauren-framework/lauren-mcp/refs/heads/main/llms-full.txt
