# MCP Integration

KOAN integrates with the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) for tool interoperability. You can expose KOAN tools as MCP servers and consume external MCP servers as tool sources.

## Install

```bash
pip install koan[mcp]
```

## Building an MCP Server

Wrap KOAN tools in an MCP server using `KoanMCPServer`:

```python
from koan.mcp import KoanMCPServer
from koan.tools import tool

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"22C and sunny in {city}"

@tool
def get_stock_price(symbol: str) -> str:
    """Get the current stock price for a ticker symbol."""
    prices = {"AAPL": 185.50, "GOOGL": 140.25}
    price = prices.get(symbol.upper(), 0.0)
    return f"{symbol.upper()}: ${price}"

server = KoanMCPServer("finance-tools")
server.add_tool(get_weather)
server.add_tool(get_stock_price)

if __name__ == "__main__":
    server.run()  # Starts stdio MCP server
```

Run in dev mode with the CLI:

```bash
koan mcp dev my_server.py
```

## Connecting to an MCP Server

Use `MCPToolSource` to discover and call tools from any MCP server:

```python
from contextlib import AsyncExitStack
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client

from koan.mcp import MCPToolSource
from koan.agents import AgentConfig, ReactAgent
from koan.llm.client import LLMClient

async with AsyncExitStack() as stack:
    client = await stack.enter_async_context(LLMClient())

    # Launch MCP server as subprocess
    params = StdioServerParameters(command="python", args=["my_server.py"])
    transport = await stack.enter_async_context(stdio_client(params))
    session = await stack.enter_async_context(ClientSession(*transport))
    await session.initialize()

    # Discover tools
    source = MCPToolSource(session)
    await source.discover()

    print([t.name for t in source.definitions()])
    # ['get_weather', 'get_stock_price']

    # Use in an agent
    agent = ReactAgent(
        "finance-bot",
        client=client,
        config=AgentConfig(provider="openai", model="gpt-4o-mini"),
        tools=source,
    )

    result = await agent.run("What's the weather in Tokyo?")
```

## Multiple MCP Servers

### CombinedToolSource

Merge tools from multiple sources into one:

```python
from koan.mcp import MCPToolSource

source_a = MCPToolSource(session_a)
await source_a.discover()

source_b = MCPToolSource(session_b)
await source_b.discover()

from koan.mcp.client import CombinedToolSource
combined = CombinedToolSource([source_a, source_b])

agent = ReactAgent("multi-tool", client=client, config=config, tools=combined)
```

### Per-Agent MCP Servers

In a supervisor pattern, each sub-agent can have its own dedicated MCP server:

```python
import sys
from contextlib import AsyncExitStack
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client

from koan.agents import AgentConfig, ReactAgent, Supervisor
from koan.llm.client import LLMClient
from koan.mcp import MCPToolSource

async def connect_mcp_agent(
    stack: AsyncExitStack,
    name: str,
    server_script: str,
    system_prompt: str,
    client: LLMClient,
) -> ReactAgent:
    """Launch an MCP server and create a connected agent."""
    params = StdioServerParameters(command=sys.executable, args=[server_script])
    transport = await stack.enter_async_context(stdio_client(params))
    session = await stack.enter_async_context(ClientSession(*transport))
    await session.initialize()

    source = MCPToolSource(session)
    await source.discover()

    return ReactAgent(
        name,
        client=client,
        config=AgentConfig(
            provider="openai",
            model="gpt-4o",
            system_prompt=system_prompt,
        ),
        tools=source,
    )

async with AsyncExitStack() as stack:
    client = await stack.enter_async_context(LLMClient())

    finance = await connect_mcp_agent(
        stack, "finance", "finance_server.py",
        "You are a finance specialist.", client,
    )
    hr = await connect_mcp_agent(
        stack, "hr", "hr_server.py",
        "You are an HR specialist.", client,
    )

    supervisor = Supervisor(
        agents={"finance": finance, "hr": hr},
        client=client,
        config=AgentConfig(provider="openai", model="gpt-4o"),
    )

    result = await supervisor.run("Check Alice's PTO balance")
```

The `AsyncExitStack` keeps all MCP server connections alive for the duration of the session.

## ToolProvider Protocol

Both `ToolRegistry` (local tools) and `MCPToolSource` (remote MCP tools) implement the `ToolProvider` protocol. Any agent accepts either:

```python
from koan.tools import ToolProvider

# These all work:
agent = ReactAgent("bot", client=client, config=config, tools=local_registry)
agent = ReactAgent("bot", client=client, config=config, tools=mcp_source)
agent = ReactAgent("bot", client=client, config=config, tools=combined_source)
```
