mcp-toolkit
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-toolkitLook up order ORD-1234 in the database."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-toolkit
Expose internal systems to LLM agents over the Model Context Protocol, without hand-writing JSON-RPC or JSON Schema.
Point it at a database, an internal HTTP API or a directory, decide what the agent may reach, and run it. Python and Go implementations live in the same repository and speak the same wire format.
from mcp_toolkit import MCPServer
server = MCPServer("internal-tools")
@server.tool()
def get_order(order_id: str) -> dict:
"""Look up an order in the fulfilment database.
Args:
order_id: Internal order identifier, e.g. "ORD-8812".
"""
return db.orders.find(order_id)
server.run()That is a complete MCP server. The tool name, description and input schema are derived from the function, so they cannot drift away from the code they describe.
Try it without installing anything: aseydaaksakal.github.io/mcp-toolkit/playground.html — the real package running in your browser.
Why this exists
Wiring an agent to an internal system is mostly not protocol work. The protocol is a few hundred lines. The work is everything around it:
Schemas are prompt surface. A model picks tools by reading their JSON Schema. Hand-maintained schemas fall out of step with the functions, and the failure is silent: the model keeps calling the tool with the arguments the schema promised.
The caller is not trusted. Anything in the model's context can steer a tool call, including text the model was merely asked to summarise. "Only expose read queries" has to be enforced, not documented.
Backend errors leak. A stack trace containing a connection string is a stack trace the model can be persuaded to repeat.
mcp-toolkit takes positions on all three. Schemas come from type hints. Access rules, rate limits and redaction are constructor arguments rather than an integration guide. Unexpected exceptions reach the audit log in full and the model as a type name.
Related MCP server: local-env-mcp
Install
pip install mcp-toolkit # once published
pip install -e ".[dev]" # from a clone, with the test extrasPython 3.10+. No runtime dependencies — the core is standard library only.
The Go implementation is a separate module:
go get github.com/aseydaaksakal/mcp-toolkit/goTry it in 30 seconds
No local setup: click Open in GitHub Codespaces above, wait for the terminal, run pytest and cd go && go test ./....
Serve the current directory over stdio and talk to it by hand:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05"}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| python -m mcp_toolkit --root .To use it from Claude Desktop or any other MCP client, add it to the client's server config:
{
"mcpServers": {
"workspace": {
"command": "python",
"args": ["-m", "mcp_toolkit", "--root", "/path/to/project"]
}
}
}Adapters
Three ready-made bridges for the systems that come up most often.
SQL — read-only, allowlisted
from mcp_toolkit import MCPServer, SqlAdapter
server = MCPServer("warehouse")
server.mount(
SqlAdapter(connection, allowed_tables=["orders", "order_lines"], max_rows=100),
prefix="db.",
)The guard rejects anything that is not a single SELECT or WITH: no second statement, no comments, no DDL or DML keywords, and no table outside the allowlist. api_keys is not reachable no matter how the request is phrased. Results are capped and flagged with truncated so the agent knows it saw a page rather than the whole answer.
REST — one endpoint at a time
api = RestAdapter("https://orders.internal", headers={"X-Api-Key": key})
api.endpoint(
"lookup_order", "GET", "/v1/orders/{order_id}",
description="Fetch one order by its internal identifier.",
path_params={"order_id": 'Internal identifier, e.g. "ORD-8812".'},
)
server.mount(api)There is no "expose the whole API" switch, because which endpoints an agent may call is a security decision. Path parameters are URL-quoted, so ../admin/keys stays inside the declared path. Write methods raise unless you pass allow_write_methods=True.
Files — sandboxed
server.mount(FileAdapter(root="/srv/runbooks", include=["*.md"]), prefix="fs.")Paths are resolved and checked against the root, so symlinks and ../ cannot walk out. Reads are size-capped and restricted to text suffixes; .pem, .key and .env are excluded by default.
Guardrails
Each one is an object you pass to the server, and each one is independently testable.
from mcp_toolkit import AccessPolicy, AuditLog, MCPServer, RateLimiter, Redactor
server = MCPServer(
"internal-tools",
policy=AccessPolicy(allow=["orders.*"], deny=["orders.delete"]),
rate_limiter=RateLimiter(rate=5, burst=20),
redactor=Redactor(extra={"employee_id": r"\bEMP-\d{5}\b"}),
audit_log=AuditLog(stream=sys.stderr),
)AccessPolicy filters tools/list as well as tools/call, so a blocked tool is invisible rather than merely refused — the model never learns it exists and never spends turns trying. Deny beats allow. Tools registered with scopes=[...] also need those scopes granted on the session:
@server.tool(scopes=["orders:write"])
def cancel_order(order_id: str) -> str:
...
server.policy = server.policy.with_scopes("orders:write") # after your auth checkRateLimiter is a token bucket per tool name. Policy is checked before the limiter, so a denied caller cannot drain another tool's budget.
Redactor scrubs every tool result and every audit record. The defaults catch emails, bearer tokens, sk-/ghp- style keys, AWS access key ids, IBANs, card numbers and PEM headers. It does not know your internal identifier formats — add those.
AuditLog writes one JSON object per call to stderr (stdout is the protocol channel). Arguments are redacted before they are written, so the log is safe to ship to a normal pipeline.
Errors
Two paths, on purpose:
You raise | The agent sees | Use it for |
|
| Expected failures the model can recover from |
anything else |
| Bugs and backend failures |
The second path keeps exception text out of the model's context. The full message still reaches the audit log:
raise RuntimeError("connect failed: postgres://user:hunter2@db")
# model sees: Tool 'lookup' failed: RuntimeError
# audit log: {"tool": "lookup", "ok": false, "error": "RuntimeError: connect failed: ..."}Argument validation happens before the handler runs, and failures come back as JSON-RPC -32602 with a message naming the specific problem — missing required argument 'order_id' rather than a schema dump.
Go
Same protocol, same argument validation, no dependencies:
server := mcp.NewServer("internal-tools", "0.1.0")
server.Tool("lookup_order", "Look up an order by its identifier.",
mcp.ObjectSchema(map[string]mcp.Property{
"order_id": {Type: "string", Description: "Internal order identifier."},
}, []string{"order_id"}),
func(args map[string]any) (any, error) {
return lookup(args["order_id"].(string))
})
server.Serve(os.Stdin, os.Stdout)cd go && go test ./... && go run ./cmd/example-serverGo has no type hints to introspect, so schemas are declared with ObjectSchema. Validation accounts for encoding/json decoding every number as float64: "integer" means a float64 with no fractional part, so 1.5 is rejected and 2.0 is not.
Documentation
Guide | Covers |
Install, first server, connecting a client | |
Schema generation, docstrings, overrides, errors | |
SQL, REST and file adapters in depth | |
Threat model and how each guardrail addresses it | |
Wire format and supported methods |
Runnable examples: examples/.
Status
Early. The API described here is tested and works; it is not yet stable across minor versions. Pre-1.0 releases may break it, and the changelog will say so when they do.
Not implemented: server-initiated messages (sampling, roots, progress notifications), resource subscriptions, HTTP/SSE transport, async handlers.
Contributing
pytest and go test ./... both pass before anything merges. See CONTRIBUTING.md.
License
MIT — see LICENSE.
Available Tools
3 toolslist_filesB
List readable files, recursively.
| Name | Required | Description | Default |
|---|---|---|---|
| subdirectory | No | Restrict the listing to this path, relative to the exposed root. Empty means the whole sandbox. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral burden. It discloses that only readable files are included and that traversal is recursive, but it does not describe output format, handling of directories, permission errors, or invalid subdirectory paths.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is four words and front-loaded: it states the action, resource, filter, and recursion mode with no filler. Every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter tool, the description gives enough to understand the core operation, but with no output schema and no sibling differentiation, an agent is left without guidance on return format, error cases, or when to prefer this over read_file or search.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% for the single optional subdirectory parameter, so the schema documents the parameter's meaning and default behavior. The description itself adds no parameter detail, but the baseline of 3 applies because the schema already covers the parameter semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('List') and resource ('files'), and adds the recursive scope, making the tool's basic purpose clear. It does not explicitly differentiate from siblings read_file and search, though listing files is naturally distinct from reading or searching.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use this tool versus read_file or search. No context, prerequisites, or exclusions are provided, leaving the agent to infer appropriate usage from the tool name alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_fileB
Read one text file from the exposed directory.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | File path relative to the exposed root. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden. It states the action but does not disclose behavior for missing paths, non-text files, permissions errors, or whether the operation is strictly read-only beyond the verb itself.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single concise sentence that directly states the tool's purpose without unnecessary words or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple read operation, the description provides sufficient context. It does not explicitly describe the return value, but the output schema is absent and the purpose strongly implies file content is returned.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single parameter path is fully described in the schema as 'File path relative to the exposed root.' The tool description adds no extra semantic detail, but schema coverage is 100%, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the action ('Read'), the resource ('one text file'), and the scope ('from the exposed directory'). It is distinguishable from sibling tools like list_files and search, though it does not explicitly name them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the verb 'Read' and the singular 'one text file', contrasting with listing or searching, but no explicit guidance is given about when to prefer this tool over siblings or when not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchA
Find lines containing pattern across the exposed files.
| Name | Required | Description | Default |
|---|---|---|---|
| pattern | Yes | Case-insensitive literal substring to look for. | |
| max_matches | No | Stop after this many hits. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description must carry the burden of explaining behavior. It states the basic operation but does not disclose how results are returned, potential side effects (likely none), or behavior when no matches are found or when max_matches is reached. It is adequate but not comprehensive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, concise sentence that delivers the essential meaning without superfluous text. It is well-structured and front-loaded with the core action.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple, with no output schema or nested objects. The description adequately states the scope ('across the exposed files') and the filter. It could mention the format of results, but this is not strictly necessary for a basic search tool. Overall, it is sufficiently complete for the tool's simplicity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the parameter descriptions already explain 'pattern' as a case-insensitive literal substring and 'max_matches' as a stop condition. The description adds no additional semantic information beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action (find lines), the resource (exposed files), and the filter (pattern). It distinguishes itself from siblings: list_files and read_file are for listing and reading, while search is for content matching.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implicitly indicates when to use the tool: whenever you need to find lines containing a specific pattern across files. It does not explicitly exclude alternatives, but the purpose is unambiguous enough for an agent to infer usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
3 tool updates
v0.1.0- First observed
list_files - First observed
read_file - First observed
search
TDQS
Scored across 3 tools
Each tool has a unique, well-defined purpose: listing, reading, and searching files. There is no overlap or ambiguity between them.
list_files and read_file follow a clear verb_noun pattern, but search is a bare verb; renaming it to search_files would make the set fully consistent.
Three tools is a minimal, focused set for read-only file access, covering the essential operations without unnecessary bloat.
The toolset fully supports listing, reading, and searching files, but lacks write/edit/delete operations; this may be intentional for a read-only toolkit but limits completeness for general file management.
Maintenance
Related MCP Connectors
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Zero-secret MCP gateway for AI agents: risk-scored, audited calls with human-in-the-loop approval.
Let AI agents query data and act across all your business apps via MCP.
One MCP endpoint for Claude, GPT & Gemini: 100+ tools + no-code connectors + agent workers.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceThe MCP bridge that audits your routes before exposing them to LLMs and blocks prompt-injection at runtime.1MIT
- FlicenseNot gradedqualityDmaintenanceExposes your local machine's filesystem, git, shell, network, databases, and system to any MCP-compatible LLM client over HTTP.6-
- FlicenseNot gradedqualityCmaintenanceEnables LLM-powered agents to securely communicate with and orchestrate downstream microservices via FastAPI endpoints exposed as MCP tools.-
- AlicenseNot gradedqualityBmaintenanceProvides a secure MCP gateway for AI agents to access APIs without exposing raw credentials, with scoped access, audit logging, and OAuth support.MIT