Skip to main content
Glama

mcp-tool-agent

A minimal end-to-end demonstration of the Model Context Protocol (MCP): one MCP server exposing three real tools, and a command-line agent that lets an LLM decide which tool to call and invokes it over the protocol.

Tool calls always travel through MCP (tools/list + tools/call over a stdio JSON-RPC connection). The agent never imports the tool functions directly.

See ARCHITECTURE.md for the design choices and the security model.

The server

server.py is a FastMCP server named tool-agent-demo that speaks over stdio. It exposes three tools:

Tool

Description

Inputs

search_github_repos

Lists a GitHub user's public repos, filtered by a substring match on name/description. Calls the live GitHub REST API, following pagination up to 10 pages.

username (str), query (str, optional), limit (int 1–20, default 5)

fetch_url_text

Fetches a web page and returns its visible text (scripts/styles/nav stripped) plus the title. Refuses non-public hosts (enforced at connection time, pinned to the validated IP), oversized responses, and non-text content.

url (http/https), max_chars (int 500–20000, default 4000)

query_books

Queries a local SQLite database of 20 books. Filters are AND-combined; results ordered by rating descending.

author (substring), genre (substring), min_year (int or null), min_rating (float or null), limit (int 1–20, default 10)

Each tool's input schema is generated from its typed signature and returned in tools/list.

It also exposes two resources: books://schema (the catalog's columns) and books://catalog (the full catalog as JSON).

Related MCP server: github-repos

The agent

agent.py takes a natural-language question and:

  1. Spawns server.py and connects over stdio with the MCP client.

  2. Runs the initialize handshake, then tools/list.

  3. Runs resources/list and reads the small ones (the schema) into the system prompt.

  4. Converts the MCP tool definitions into the LLM's tool-calling format.

  5. Asks the LLM (OpenAI, gpt-4o-mini by default) to answer, with the tools available.

  6. For each tool call the model returns, executes it via tools/call and feeds the result back. Bad tool arguments and protocol errors are turned into text the model can react to.

  7. On the final turn the tools are withheld so the model must answer from what it has.

  8. Prints the model's answer.

Transient LLM errors are retried with backoff. Every protocol step and token usage is logged to stderr with an [agent] prefix.

python agent.py [--model MODEL] [--max-turns N] "your question"

Setup

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

cp .env.example .env      # then set OPENAI_API_KEY
python seed_books.py      # creates books.db

.env keys:

  • OPENAI_API_KEY – required by the agent.

  • OPENAI_MODEL – optional, defaults to gpt-4o-mini.

  • OPENAI_TEMPERATURE – optional, defaults to 0; set to none to omit the field.

  • AGENT_MAX_TURNS – optional, defaults to 5.

  • GITHUB_TOKEN – optional, raises the GitHub API rate limit.

Installing the package (pip install -e .) also puts a mcp-tool-agent command on your path, equivalent to python agent.py.

Running

python agent.py "Which fantasy books in the database are rated above 4.3?"
python agent.py "Find repos owned by 'tiangolo' related to 'fastapi', top 3 by stars."
python agent.py "Fetch https://peps.python.org/pep-0020/ and list three aphorisms."

To exercise the server on its own without an LLM:

python - <<'EOF'
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    params = StdioServerParameters(command="./venv/bin/python", args=["server.py"])
    async with stdio_client(params) as (r, w):
        async with ClientSession(r, w) as s:
            await s.initialize()
            print([t.name for t in (await s.list_tools()).tools])
            res = await s.call_tool("query_books", {"genre": "fantasy", "min_rating": 4.4})
            print(res.content[0].text)

asyncio.run(main())
EOF

Recorded runs against all three tools, plus error paths (unknown user, unreachable/404/non-http URLs, missing database, schema validation), are in TEST_RUN.md.

Tests

pip install -r requirements-dev.txt
python seed_books.py
ruff check .
mypy server.py agent.py seed_books.py
pytest -q --cov

The suite covers the tool logic with mocked HTTP, the SSRF pre-check and the connection-time backend guard (including IP pinning), resources/list and resources/read, a real stdio round-trip that spawns the server, and agent.run() driven by a scripted LLM against that real server. CI runs ruff, mypy, and the suite on Python 3.11–3.13.

Notes

  • Pinned to mcp==1.29.x (the 1.x FastMCP API). The 2.x release renames the server class and changes several APIs.

  • No secrets are committed; .env and books.db are gitignored.

Related MCP Connectors

Related MCP Servers