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. Usesys.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 anenum) 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.
HTTP Transports
MCP Streamable HTTP (JSON-RPC 2.0) — for remote MCP clients
Spec-compliant 2025-06-18 transport (pip install turbomcp[http]), equivalent to mcp_stdio but over HTTP:
turbomcp serve my_tools.py --http --port 8000 # no-auth for localhost
turbomcp serve my_tools.py --http --host 0.0.0.0 --port 8000 # requires token
# or in code:
from turbomcp.mcp_http import serve_http
serve_http(mcp, host="127.0.0.1", port=8000, require_auth=False)
| Endpoint | Method | Description |
|---|---|---|
/mcp |
POST |
JSON-RPC 2.0: initialize, tools/list, tools/call, ping (single or batch) — returns application/json or text/event-stream if Accept: text/event-stream |
/mcp |
GET |
SSE stream for notifications/tools/list_changed (Accept: text/event-stream required) |
/health |
GET |
{"status":"ok","name":...} |
Claude Desktop remote (claude_desktop_config.json via mcp-remote or native Streamable HTTP):
{
"mcpServers": {
"my-tools": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:8000/mcp"]
}
}
}
Streamable HTTP binds 127.0.0.1:8000 by default; use a reverse proxy + Bearer/X-API-Key (registry.security()) for internet.
Legacy REST API (non-MCP) — for curl debugging
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 REST 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.
calculateuses an AST walker, noteval— 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
Metadata
Release files for turbomcp 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| turbomcp-0.3.0.tar.gz | 32.1 kB | Without provenance | Details for turbomcp-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| turbomcp-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for turbomcp-0.3.0-py3-none-any.whl |
Total release size: 59.1 kB
Release files / turbomcp-0.3.0.tar.gz
| Download URL | turbomcp-0.3.0.tar.gz |
|---|---|
| Size | 32.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
591f52b50077e41665c6988a7a27cc8e529bff5579141d56bb3aa8aa1882fa4a
|
|
BLAKE2b-256 checksum How to use checksums |
a0641f53b3f8d4a66b2157ab6d4f3943f30143efa212292fefc18459003e2f4a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / turbomcp-0.3.0-py3-none-any.whl
| Download URL | turbomcp-0.3.0-py3-none-any.whl |
|---|---|
| Size | 27.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bef03c79c571e87034db2e7d93200ff6f071032754d1640a4599269a0a9d0a16
|
|
BLAKE2b-256 checksum How to use checksums |
8f12d651dc60aa90270a8ee5ea53d962f3376e53c3fefdd05dea62feb2b86092
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Security for turbomcp 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.