Skip to main content
Glama
yujoonko1

relay-mcp

by yujoonko1

relay-mcp

CI PyPI Python License: MIT

Expose any dev tool as a safe, audited MCP server — in about 10 lines.

pip install relay-mcp

Model 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:

  • @tool decorator — 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, or DESTRUCTIVE. 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 InvalidArguments error 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 stdio

That'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

READ_ONLY

Always allowed. Advertised to clients via MCP readOnlyHint.

WRITE

Allowed unless the server runs with read_only=True.

DESTRUCTIVE

Requires a confirm_hook; denied if none is configured. Advertised via destructiveHint.

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=30

Architecture

 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_branch with 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

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

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A 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.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides 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 npm
    139
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides 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 npm
    Apache 2.0