relay-mcp
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., "@relay-mcpWrap my deploy script as a destructive tool with audit logging."
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.
relay-mcp
Expose any dev tool as a safe, audited MCP server — in about 10 lines.
pip install relay-mcpModel Context Protocol (MCP) is how AI agents and IDEs connect to external tools. Writing an MCP server by hand means hand-rolling JSON schemas, wiring transport handlers, and — critically — deciding what happens when an agent asks to do something destructive.
Relay handles all of that:
@tooldecorator — turns a plain typed Python function into an MCP tool. Input schema is generated from type hints; parameter docs come from your docstring.Permission tiers — every tool is
READ_ONLY,WRITE, orDESTRUCTIVE. Destructive tools are denied by default unless you wire a confirmation hook. Safe by default, not by discipline.Audit log — every invocation (success, error, timeout, and denial) is recorded to SQLite with duration and automatically-redacted sensitive arguments.
Argument validation — malformed calls are rejected with a clear
InvalidArgumentserror before they reach a confirmation prompt or your function.
Quickstart
from relay_mcp import RelayServer, PermissionTier
relay = RelayServer("git-tools", audit_path="audit.db")
@relay.tool()
def git_status(repo: str) -> str:
"""Show working tree status.
Args:
repo: Absolute path to the git repository.
"""
...
@relay.tool(permission=PermissionTier.DESTRUCTIVE)
def delete_branch(repo: str, branch: str) -> str:
"""Force-delete a branch. Irreversible if unmerged."""
...
relay.run() # serves over stdioThat's a complete MCP server. Register it in Claude Desktop / any MCP client:
{
"mcpServers": {
"git-tools": { "command": "python", "args": ["git_server.py"] }
}
}Related MCP server: Pare
Permission model
Tier | Behavior |
| Always allowed. Advertised to clients via MCP |
| Allowed unless the server runs with |
| Requires a |
def confirm(tool_name: str, arguments: dict) -> bool:
# prompt a human, check a policy service, whatever you need
return input(f"Allow {tool_name}({arguments})? [y/N] ") == "y"
relay = RelayServer("ops", confirm_hook=confirm)Hooks can be sync or async. Sync hooks run in a worker thread, so a terminal prompt waiting on a human never stalls other in-flight tool calls. A rejected or missing confirmation raises PermissionDenied — the MCP client receives it with isError: true, and the denial itself is audited.
For headless or Windows deployments where no terminal is available, use the built-in allowlist hook:
from relay_mcp import allowlist_confirm
relay = RelayServer("ops", confirm_hook=allowlist_confirm("rotate_logs"))Argument validation
Before the permission check, every call is checked against the tool's generated schema: missing required parameters or unexpected ones raise InvalidArguments (audited as invalid). A destructive call with a broken payload never triggers a confirmation prompt, and your function never sees a TypeError from a bad **kwargs expansion. Tools that declare **kwargs accept extra keys.
Timeouts
A runaway tool can't hang the server:
@relay.tool(timeout=30) # seconds
def run_build(project: str) -> str: ...Exceeding the limit raises TimeoutError and records a timeout audit entry.
Audit log
for e in relay.audit.entries(status="denied"):
print(e.ts, e.tool, e.detail)Arguments whose names look sensitive (password, token, api_key, ...) are redacted before persistence — recursively, so a secret nested inside a config dict or a list of objects is masked too. The log won't grow forever:
relay.audit.prune(keep_last=10_000) # or older_than_days=30Architecture
agent / IDE (MCP client)
│ stdio (JSON-RPC)
▼
┌─────────────────────────────┐
│ RelayServer │
│ ┌───────────┐ │
│ │ MCP layer │ list_tools / call_tool (official mcp SDK)
│ └─────┬─────┘ │
│ ▼ │
│ validate_arguments ─invalid┼──► AuditLog (sqlite)
│ ▼ │ ▲
│ PermissionPolicy ──denied──┼─────────┤
│ ▼ │ │
│ execute (thread/async) ────┼──ok/err─┘
└─────────────────────────────┘Design rationale
Deny destructive by default. Most MCP frameworks trust the client to gate dangerous actions. Relay assumes the agent will eventually call
delete_branchwith the wrong argument; a missing hook fails closed rather than open.Tiers, not per-tool allowlists. Three tiers are coarse on purpose: an author can classify a tool correctly in one second, which means the classification actually happens. Fine-grained policy belongs in the
confirm_hook, where it can be as elaborate as you like.Validate before you ask. Argument validation runs before the permission check so a human is never asked to approve a call that would have crashed anyway.
Sync tools run in a thread (
asyncio.to_thread) so a slow subprocess call can't block the event loop and stall the transport.Denials are audited, not just failures. In security tooling, "what did the agent try to do" matters as much as what it did.
The execute pipeline is transport-independent, so the full permission/audit path is unit-testable without spawning a subprocess.
Examples
examples/git_server.py— all three tiers + interactive confirmationexamples/db_server.py— read-only server mode + defense in depth (SQLitemode=ro)examples/test_runner_server.py— async tools, subprocess timeouts, output truncation
Development
pip install -e ".[dev]"
pytest # 32 tests, no subprocess or MCP client needed
ruff check . && ruff format --check .CI runs the suite on Linux, macOS, and Windows across Python 3.10–3.13. Releases publish to PyPI via trusted publishing when a GitHub release is tagged vX.Y.Z.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
A MCP server built for developers enabling Git based project management with project and personal…
Host your MCP tool over streamable HTTP in one command.
Self-hosted MCP server: 26 deterministic dev, security, and EVM tools.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA unified MCP server with composable tools for GitHub operations, file management, shell execution, kanban boards, Discord messaging, and package management. Features role-based security, HTTP/stdio transports, and a web-based development UI.-
- AlicenseNot gradedqualityAmaintenanceProvides MCP servers that wrap common developer tools (git, npm, docker, etc.) returning structured JSON output, enabling AI agents to reliably interact with these tools without parsing fragile terminal text.3 npm139MIT
- AlicenseNot gradedqualityAmaintenanceTurns any CLI command into an MCP server via a declarative YAML config, enabling safe, typed tool execution with no shell injection.MIT
- AlicenseNot gradedqualityBmaintenanceProvides a deterministic set of 40 cross-platform command tools as an MCP server, replacing unsafe raw shell calls with structured JSON inputs and outputs for file system, process, network, and git operations.8 npmApache 2.0