Skip to content

Attach MCP tools to a Python agent

Start a local MCP server, let URSA discover its tools, and attach those tools to a ChatAgent. This example keeps the server deliberately small so you can see the complete connection before adapting it to a real service.

The workflow has two processes:

  1. laboratory_server.py serves one list_measurements tool over local Streamable HTTP.
  2. attach_mcp_tools.py reads config.yaml, initializes the configured model, attaches the discovered MCP tool, and asks the agent to use it.

Read the MCP configuration guide for other transports and authentication settings. The Python scripts guide explains model initialization and direct agent use.

Prepare the example

Open a terminal in this folder, install the locked environment, and set your OpenAI key.

cd examples/mcp_agent_tools
uv sync
export OPENAI_API_KEY="your-api-key"
Set-Location examples\mcp_agent_tools
uv sync
$env:OPENAI_API_KEY = "your-api-key"

The example uses URSA's default OpenAI model. Follow models and inference providers before running it with another provider.

Inspect the server configuration

The client reads this MCP server definition:

mcp_servers:
  laboratory:
    transport: streamable-http
    url: http://127.0.0.1:8000/mcp

The endpoint is local and does not include authentication. Keep it bound to your machine for this exercise.

Start the MCP server

In the first terminal, run:

uv run laboratory_server.py

Leave that process running. It serves the MCP endpoint at http://127.0.0.1:8000/mcp.

Attach and use the tool

Open a second terminal in the same folder, set OPENAI_API_KEY there as shown above, and run:

uv run attach_mcp_tools.py

The script prints the tool-to-server mapping returned by add_mcp_tools(), then prints the agent's summary. Confirm that list_measurements is attached from the laboratory server and that the answer identifies alloy-b as the largest reported strength while noting that alloy-c was measured at another temperature.

The client implementation is short enough to inspect in full:

import asyncio
from pathlib import Path

from langchain_core.messages import HumanMessage

from ursa.agents import ChatAgent
from ursa.cli.config import UrsaConfig
from ursa.util.mcp import start_mcp_client


async def main() -> None:
    config = UrsaConfig.from_file(Path("config.yaml")).resolve()
    agent = ChatAgent(
        llm=config.llm_model.init_chat_model(),
        workspace=Path("ursa-script-workspace"),
    )

    mcp_client = start_mcp_client(config.mcp_servers)
    tool_sources = await agent.add_mcp_tools(mcp_client)
    print("Attached MCP tools:", tool_sources)

    result = await agent.ainvoke({
        "messages": [
            HumanMessage(
                content=(
                    "Use the laboratory tools to list the available measurements, "
                    "then summarize the strongest sample and any temperature "
                    "difference that limits a direct comparison."
                )
            )
        ],
        "thread_id": agent.thread_id,
    })
    print(result["messages"][-1].content)


if __name__ == "__main__":
    asyncio.run(main())

add_mcp_tools() accepts tool_name="list_measurements" or a list of names when an agent should receive only selected server tools. The server must already be running when discovery begins.

Adapt the example

Add another @mcp.tool() function to laboratory_server.py, restart the server, and run the client again. Update the prompt so the agent has a clear reason to choose the new tool. Review the MCP reference when you add production transports, credentials, or remote endpoints.