Skip to main content
Glama
talhayme

mcp-toolserver

by talhayme

mcp-toolserver — an MCP server built the way a production one should be

A Model Context Protocol server exposing four tools to an LLM: a grounded document search, a document fetch, a sandboxed calculator and date arithmetic.

The tools themselves are small on purpose. What this repository actually demonstrates is the part that decides whether tool calling works in production: strict schemas, errors written for the model, and a protocol layer thin enough that the logic underneath stays testable.

git clone https://github.com/talhayme/mcp-toolserver
cd mcp-toolserver
pip install -e ".[dev]"
pytest -q          # 50 tests, no SDK and no API key required

Connect it to Claude Desktop

pip install -e ".[mcp]"

Then add to claude_desktop_config.json:

{
  "mcpServers": {
    "toolserver": {
      "command": "python",
      "args": ["-m", "mcp_toolserver.server"]
    }
  }
}

Restart Claude Desktop and ask it something like "What's our refund policy after 20 days, and what would 14 months of Pro cost with a 15% discount?" — it will call search_documents and then calculate.

Related MCP server: rag-tool-agent

The tools

Tool

Purpose

search_documents

Ranked excerpts from the knowledge base, with a grounding instruction attached

fetch_document

Full text of one document by id

calculate

Arithmetic evaluated exactly, in a sandbox

date_difference

Days between two ISO dates

The last two exist because they are precisely what language models get wrong unaided: arithmetic and date maths.

Three decisions worth explaining

1. Errors are written for the model, not for a log

Every failure path raises ToolError with a message the model can act on:

search_documents("refund", team="marketing")
# ToolError: unknown team 'marketing'; known teams are: engineering, people, sales, support

dispatch("calculate", {"expr": "2+2"})
# ToolError: invalid arguments for 'calculate': calculate() got an unexpected keyword argument 'expr'

A model that receives "known teams are: engineering, people, sales, support" fixes its next call. A model that receives a stack trace retries the same mistake, or gives up and invents an answer. The protocol layer returns the message as tool output rather than raising, so the conversation continues instead of failing.

2. The calculator is a sandbox, not an eval

eval() on a model-generated string is remote code execution with extra steps. This implementation applies four separate restrictions:

calculate("__import__('os').system('rm -rf /')")   # ToolError: forbidden token
calculate("().__class__.__bases__[0].__subclasses__()")  # ToolError: forbidden token
calculate("open('/etc/passwd').read()")            # ToolError: name 'open' is not allowed
calculate("1 / 0")                                 # ToolError: division by zero

A character allow-list, a __ ban, compile() with every name checked against an explicit maths allow-list, and eval with __builtins__ emptied. Five escape attempts are in the test suite as regression tests.

3. Schemas are strict, so the model gets it right first time

{
  "limit": { "type": "integer", "minimum": 1, "maximum": 20, "default": 3 },
  "team":  { "type": "string", "enum": ["support", "sales", "engineering", "people"] },
  "additionalProperties": false
}

additionalProperties: false stops the model inventing parameters. Bounds and enums turn a class of runtime errors into something the client catches before the call is made. A test asserts that every tool has a description, every property is documented, and every schema is strict — vague schemas cause retry loops that cost tokens and latency.

Grounding travels with the data

search_documents returns an instruction alongside its results:

{
  "count": 0,
  "results": [],
  "note": "No documents matched. Say so rather than answering from memory."
}

An empty search is the moment a model is most likely to hallucinate. Putting the instruction in the payload means it cannot drift out of sync with the system prompt, and it is right where the model is looking.

Architecture

mcp_toolserver/
  tools.py     # pure functions, zero MCP imports — all the logic lives here
  server.py    # schemas, dispatch, ToolError -> model-readable output. Thin.

tools.py imports nothing from the MCP SDK, which is why the test suite runs without it and why the same functions could be served over HTTP tomorrow without a rewrite. build_server() imports the SDK lazily and fails with an actionable message when it is missing:

RuntimeError: The MCP SDK is not installed. Install it with:
    pip install 'mcp-toolserver[mcp]'
The tool logic and its tests run without it.

Tests

pytest tests/ -q --cov=mcp_toolserver --cov-report=term-missing
# 50 passed — 84% coverage

Covering: ranking and prefix matching, the empty-result grounding path, every validation branch, five sandbox-escape attempts, leap-year date handling, dispatch with wrong and missing arguments, and a schema completeness check.

CI runs the suite on Python 3.10/3.11/3.12, then installs the real MCP SDK, builds the server, asserts the registered tool names match the declared schemas, and makes a live call through the protocol layer — including a failing one, to confirm errors come back readable rather than as a crash.

That job earned its place: the first version of this server was written against an older SDK decorator API and CI caught it on the first push.

Why this exists

I build custom MCP servers connecting LLMs to internal tools and services as part of my AI-implementation consulting work. This is a standalone, readable extract of the patterns I use — not production code, and deliberately small enough to read in one sitting.

License

MIT

Related MCP Connectors

  • Public web tools for agents: product extraction, claim checks, webpage QA and ranked audits.

  • Pay-per-call agent tools: PDF, OCR, translation, news search, prediction markets, web, chain reads.

  • Pay-per-call tools for AI agents, paid in USDC on Base or Solana. Web search, page and PDF reading, crypto news, Hyperliquid perps, market pulse, Chainlink price history, token checks, SEC filings, weather and more. Free calls every day, and a failed call costs nothing. Works with Tollkit Wallet.

  • Search Fragments — two tools for the queries an agent can't place, both built to decline rather than guess. resolve_fragment takes a half-remembered, cross-source query ("a musician who became famous for stopping performing") and returns a grounded answer, ranked web sources to confirm by eye, or an explicit no-resolution. verify_claim takes a specific factual assertion and returns supported, partially_supported, insufficient_evidence, or unsupported, with cited evidence and a stated_limits field that is always present. There is no confidence score — insufficient_evidence fires freely, and unsupported requires a source that explicitly contradicts, never mere absence of confirmation. Every verdict is decide-by-eye: "supported" means current web sources confirm it, not that the claim is true. Calibrated against 18 known claims before release. Free, no signup. Streamable HTTP (MCP 2025-11-25). Read-only.

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides exact, deterministic tools for math, dates, units, validation, and more to AI agents, returning precise answers with explicit assumptions and warnings instead of model guesses.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables MCP-compatible clients to perform retrieval-grounded Q&A over FordA dataset notes and safe arithmetic calculations through callable tools.
    -
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI agents to perform exact, reproducible mechanical computation — text diffing, hashing, JSON querying, calendar arithmetic, deadline-bounded regex extraction, CSV parsing, similarity scoring, unit and base conversion — instead of relying on probabilistic guessing. Runs dependency-free, read-only and offline over stdio, with schema validation and hard caps on every operation so no call can hang the client.
    15
    38 npm
    MIT
  • F
    license
    A
    quality
    C
    maintenance
    Enables an LLM client to perform basic arithmetic on two numbers — addition, subtraction, multiplication, and division — plus reverse a supplied text string, through five simple tools exposed over stdio. Inputs and structured text results follow JSON schemas, with invalid operations such as division by zero surfaced as tool errors.
    5
    -