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 TurboMCP

mcp = TurboMCP()

@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 TurboMCP 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.1.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.1-py3-none-any.whl (21.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: turbomcp-0.2.1.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.1.tar.gz
Algorithm Hash digest
SHA256 b05c662d9719f725a7cb072fa818ddf272b969b98a00b27587f79cbb8d2dabe7
MD5 1c8327069bcffdf564ea599b0c6d6c2f
BLAKE2b-256 3bf1cb60903620ae139dc9d53950c0ea02f845a611d4fb2697538623b11e1c54

See more details on using hashes here.

File details

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

File metadata

  • Download URL: turbomcp-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 21.3 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 aa0fdd669915d8ac368761cbad341b35ead9c6abf2dc0f846857c52c94f2992f
MD5 d64662bac0d5829f1e0673c4ff63d380
BLAKE2b-256 7cef6359b0498d9ed01771df672eba1832714956395e7b54d1035d2c4124d4dd

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