Skip to main content

Zero-dependency tool registry and MCP server for LLM agents, with built-in tool search.

Project description

turbomcp

A zero-dependency tool registry and MCP server for LLM agents — with built-in tool search so agents don't drown in schemas.

Write plain Python functions. Get JSON Schema, OpenAI function-calling definitions, a real MCP server, and ranked tool search — without installing anything else.

from turbomcp import ContextAware

mcp = ContextAware()

@mcp.tool()
def greet(name: str = "World"):
    """Greet a user by name.

    Args:
        name (str): Who to greet
    """
    return f"Hello, {name}!"

That's a working tool. Schemas are generated from the signature and docstring — types from annotations, descriptions from the Args: section, required/optional from defaults.

Why turbomcp?

Most tool servers dump every tool schema into the model's context on every request. That's fine for 5 tools and a disaster for 50. turbomcp is built around progressive disclosure — an agent can work its way down this ladder and only pay for what it needs:

Call Returns Context cost
mcp.list_tools() just the names tiny
mcp.list_tools(with_summary=True) names + one-liners small
mcp.search_tools("fetch a webpage") ranked matches, full schemas only what's relevant
mcp.get_capabilities(name="read_file") one full schema on demand one tool

Search is keyword-based out of the box (name/description/parameter matching with field weighting), and optionally semantic via static embeddings — no GPU, ~1s startup, instant queries.

Install

pip install turbomcp              # core: registry + MCP server, zero dependencies
pip install turbomcp[web]         # + built-in web tools (search, scrape)
pip install turbomcp[semantic]    # + semantic tool search (model2vec)
pip install turbomcp[http]        # + Flask REST API for debugging
pip install turbomcp[all]         # everything

Use it as an MCP server

Any MCP client (Claude Code, Claude Desktop, ...) can launch your registry directly:

turbomcp serve my_tools.py          # finds the ContextAware instance in the file
turbomcp serve my_tools.py:mcp     # or name the variable explicitly
turbomcp demo                       # try it with the built-in default tools
turbomcp init [my_tools.py]         # create a starter template
turbomcp init my_tools.py --with-web --with-semantic  # include optional features
turbomcp help                       # show all commands

Claude Code:

claude mcp add my-tools -- turbomcp serve /path/to/my_tools.py

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "my-tools": {
      "command": "turbomcp",
      "args": ["serve", "/path/to/my_tools.py"]
    }
  }
}

The server implements MCP over stdio natively (JSON-RPC 2.0): initialize, ping, tools/list, tools/call, and it emits notifications/tools/list_changed when tools are registered or removed at runtime — register a new tool while the server is live and connected clients find out immediately.

One rule when serving stdio: don't print() to stdout in your tool file — it corrupts the protocol stream. Use sys.stderr.

Lazy mode: tool search over MCP

turbomcp serve my_tools.py --lazy

In lazy mode the client doesn't see your catalog at all. It sees three meta-tools — search_tools, get_tool, call_tool — and discovers what it needs by searching. A 100-tool registry costs the model three schemas instead of a hundred.

Use it with OpenAI-compatible APIs

The same registry plugs straight into any chat-completions API (OpenAI, Gemini's OpenAI endpoint, Ollama, llama.cpp, vLLM, ...):

from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="unused")

response = client.chat.completions.create(
    model="your-model",
    messages=[{"role": "user", "content": "Greet Ada"}],
    tools=mcp.to_openai_tools(),          # <- emitted in OpenAI format
)

for call in response.choices[0].message.tool_calls or []:
    import json
    result = mcp.call(call.function.name, json.loads(call.function.arguments))

Also available: mcp.to_mcp_tools() (MCP format) and mcp.get_tools_prompt() (plain text for models without native tool calling).

Tool search

mcp.search_tools("fetch a webpage", top_n=3)
# {"query": ..., "semantic": False, "tools": [{"name": "webpage_scrape", "score": ..., ...}]}

Enable semantic search to close the synonym gap ("say hello" → greet):

mcp.enable_semantic_search()   # pip install turbomcp[semantic]

Keyword and semantic scores are blended (tunable alpha); without the extra installed, search falls back to pure keyword scoring automatically.

Built-in default tools

mcp.register_default(all=True)                              # everything
mcp.register_default(names=["calculate", "read_file"])      # or pick
Tool Needs [web] extra
internet_search — DuckDuckGo (default) or Google, text or images yes
webpage_scrape — fetch a page, strip boilerplate, return text yes
get_current_time — local/IANA-timezone/UTC time no
read_file — read a local text file no
calculate — math expressions via a safe AST evaluator (no eval) no
get_system_info — OS, arch, hostname, Python version no

Writing good tools

Schemas are only as good as your signatures and docstrings:

from typing import Literal, Optional

@mcp.tool()
def convert(path: str, format: Literal["mp4", "webm", "gif"], quality: Optional[int] = None):
    """Convert a video file to another format.

    Args:
        path (str): Path to the input file
        format (str): Output format
        quality (int): Output quality 1-100, default keeps source quality
    """
    ...
  • Annotations → types: str, int, float, bool, list[str], dict, Optional[X], Literal[...] (becomes an enum) all map to proper JSON Schema.
  • Defaults → optional: parameters without defaults are required.
  • Args: section → descriptions: Google-style docstrings, one line per parameter.
  • The first docstring paragraph becomes the tool description — make it say when to use the tool, not just what it does.

Optional HTTP API

For curl-debugging or non-MCP clients (pip install turbomcp[http]):

mcp.start_server(port=5000)   # generates an API token, printed to stderr
Endpoint Description
GET /tools?summary=true names (+ summaries)
GET /capabilities full catalog
GET /tool/<name> one tool's schema
GET /search?q=... ranked search
POST /call_tool execute — {"name": ..., "params": {...}}, token via X-API-Key header or api_key field

The server binds 127.0.0.1 and only /call_tool requires the token. It's a Flask dev server: fine on localhost, not meant for the open internet.

Security notes

  • Tools run with your user's permissions. A tool like "run shell command" gives the model exactly that power — register it knowingly.
  • calculate uses an AST walker, not eval — sandbox-escape expressions like (1).__class__... are rejected.
  • The HTTP token is generated per-process and printed to stderr; MCP stdio needs no token because the client launches the process itself.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

turbomcp-0.2.0.tar.gz (24.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

turbomcp-0.2.0-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

File details

Details for the file turbomcp-0.2.0.tar.gz.

File metadata

  • Download URL: turbomcp-0.2.0.tar.gz
  • Upload date:
  • Size: 24.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for turbomcp-0.2.0.tar.gz
Algorithm Hash digest
SHA256 30ec66ff3a0f717843f44bc8bdbe6e644a3e8439b9630b79d0eae6296b142ee3
MD5 42b9f80f49e06c43d1b9305a94344573
BLAKE2b-256 91f866fb95ed9e2a671f3d9598ef92a82c018fc073c91da77fe0a5d94f47abd2

See more details on using hashes here.

File details

Details for the file turbomcp-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: turbomcp-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 21.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for turbomcp-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1cd2efaf4b9bb016b7fb5a0b1484b3ea1d5c4cb59f16e1308caf623a3e31ca80
MD5 e854723e7509ddfd13e3ce41a859ae5f
BLAKE2b-256 59b9addf35f0c7ac9a25eacca5da6aa0555f0b4413093dc1e1b4f72f11979728

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page