Skip to main content

mcpsync

PyPI License: MIT Python 3.11+ Tests Min runtime deps

Synchronous Python API for MCP servers — stdio and HTTP transports, plus a CLI for ad-hoc inspection.

mcpsync wraps the official mcp Python SDK's async client behind a blocking SyncMCPClient so you can call MCP tools, list resources, and read resources from synchronous code (CLI tools, Django views, Flask handlers, scripts). It also ships a CLI for ad-hoc server inspection from the shell.

Quick Start

Install from source:

pip install git+https://github.com/prasad-a-abhishek/mcpsync.git

Connect to a stdio MCP server and call a tool:

from mcpsync import SyncMCPClient, StdioServerParameters

params = StdioServerParameters(
    command="python", args=("-m", "my_mcp_server"),
    env={"DEBUG": "1"},
    cwd="/srv/myapp",
)

with SyncMCPClient(params) as client:
    tools = client.list_tools()
    for tool in tools:
        print(f"- {tool.name}: {tool.description}")

    result = client.call_tool("add", {"a": 2, "b": 3})
    for block in result.content:
        print(block.text)

Connect to an HTTP MCP server:

from mcpsync import SyncMCPClient, HttpServerParameters

params = HttpServerParameters(
    url="https://mcp.example.com/api",
    headers={"Authorization": "Bearer ..."},
    timeout=10.0,
)

with SyncMCPClient(params) as client:
    resources = client.list_resources()
    contents = client.read_resource(resources[0].uri)

Turn any async MCP helper into a sync function with the @sync decorator:

from mcpsync import sync

@sync
async def load_schema(server_url: str) -> dict:
    async with some_async_helper(server_url) as helper:
        return await helper.fetch_schema()

⚡ Performance & Benchmarks

We publish benchmarks/BENCHMARK.md per Invariant 14 — including the methodology caveat. The honest summary: mcpsync uses asyncio.Runner per call, which actually benchmarks ~15% faster than the SDK's asyncio.run pattern in this setup (interpreter boot dominates; relative comparison is meaningful). Reproduce locally:

python benchmarks/run_benchmark.py

The full results table, methodology, and the trade-off transparency statement are in benchmarks/BENCHMARK.md.

Why mcpsync? (Problem & Trade-Off Statement)

The MCP Python SDK ships an async-only client. Synchronous callers (Django views, CLI tools, scripts) hit the same wall: every asyncio.run() from sync code re-creates an event loop, leaks resources, and breaks under asyncio.run() recursion if the surrounding runtime already runs a loop.

Two GitHub issues document the gap:

  • modelcontextprotocol/python-sdk#1223 — "Sync client API" (open, multiple reactions)
  • modelcontextprotocol/python-sdk — the SDK explicitly documents the client.session.stdio use pattern as async only.

What mcpsync is: a thin wrapper that gives you SyncMCPClient + a @sync decorator, plus a CLI for ad-hoc inspection. It uses the official mcp SDK under the hood and never re-implements the JSON-RPC protocol or transport.

What mcpsync is NOT:

  • Not a replacement for the mcp SDK — it depends on it.
  • Not an MCP server implementation. It's a client.
  • Not magic. Each SyncMCPClient.list_tools() call creates a one-shot event loop on a worker thread. If you need 100k calls/sec, use the SDK's async client directly.

Trade-offs you accept by using mcpsync:

  • Each SyncMCPClient call creates a fresh asyncio.Runner on a worker thread. The bench shows this is ~15% faster than the SDK baseline, but for a true long-lived async loop you should use the SDK's async client directly.
  • The close path needs a wall-clock fuse to escape a known deadlock in the mcp SDK's stdio_client.__aexit__ shielded cancel scope. Without the fuse the caller's process hangs. See src/mcpsync/client.py::_run_bounded and the docstring on SyncMCPClient.close().
  • HTTP transport requires the mcp SDK's optional httpx2 dep, which is re-exported by mcp for convenience.

Key Features & Complete API / CLI Reference

Library API

Name Returns Notes
StdioServerParameters(command, args, env, cwd) dataclass spawn the server as a subprocess
HttpServerParameters(url, headers, timeout) dataclass connect to a streamable-HTTP MCP server
SyncMCPClient(params) context manager open the session; use as with block
client.list_tools() list[Tool] tool descriptors from mcp.types.Tool
client.list_resources() list[Resource] resource descriptors from mcp.types.Resource
client.call_tool(name, arguments) CallToolResult invoke a tool; result.content is a list of typed blocks
client.read_resource(uri) ReadResourceResult fetch a resource by URI
@sync decorator wraps an async function into a sync one uses a per-call event loop
MCPError exception re-exported from mcp.shared.exceptions

All public types are fully type-hinted. A py.typed marker ships in src/mcpsync/ for PEP 561.

CLI

$ mcpsync --help
usage: mcpsync [-h] [--version] {stdio,http} ...

Synchronous client for MCP servers.

$ mcpsync stdio list-tools -- python -m my_server
[
  {"name": "echo", "description": "Echo the input message back", "inputSchema": {...}},
  ...
]

$ mcpsync stdio call-tool add '{"a":2,"b":3}' -- python -m my_server
{"content":[{"type":"text","text":"5"}]}

$ mcpsync stdio list-resources -- python -m my_server
[{"uri": "file:///greeting.txt", "name": "greeting", "mimeType": "text/plain"}, ...]

$ mcpsync stdio call-resource 'file:///greeting.txt' -- python -m my_server
{"contents":[{"uri": "file:///greeting.txt", "text": "Hello!", "mimeType": "text/plain"}]}

$ mcpsync http list-tools --url https://mcp.example.com/api
$ mcpsync http call-tool add '{"a":2}' --url https://mcp.example.com/api

The stdio subcommand takes a -- separator before the server command + args. The http subcommand takes --url, --header (repeatable KEY=VAL), and --timeout.

Out of scope

  • MCP server implementation
  • Long-lived event-loop reuse across calls (each call creates a new one; see trade-off above)
  • Streaming / subscription primitives
  • Server-side protocol: mcpsync is a client only
  • Any protocol version below what the bundled mcp SDK supports

Limitations

  • One shot per call. Each SyncMCPClient use opens the server process, runs the request, and closes. If you need a long-lived connection, keep the with block open and make many calls inside.
  • HTTP transport requires the httpx2 runtime dep (re-exported by mcp for convenience).
  • Close path is bounded by a wall-clock fuse to avoid a known deadlock in the mcp SDK's stdio teardown.

Tests

pytest tests/ -q

129 tests across 1 file (split into 16 test classes by behavior). Coverage spans every spec acceptance criterion plus angular sweep (empty/None inputs, unicode, large payloads, malformed JSON, CLI end-to-end, concurrent @sync calls, parameter adversarial inputs, and regression guards for the close-path deadlock).

License

MIT — see LICENSE.

Metadata

Release files for mcpsync-cli 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcpsync-cli 0.1.0
File Size Uploaded
mcpsync_cli-0.1.0.tar.gz 32.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mcpsync-cli 0.1.0
File Interpreter ABI Platform
mcpsync_cli-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 52.2 kB

Release files / mcpsync_cli-0.1.0.tar.gz

Download URL mcpsync_cli-0.1.0.tar.gz
Size 32.3 kB
Tags Source
SHA-256 checksum
How to use checksums
754af2487d641d71d27f210b51f6536b02bdb3b6ea9de0cad47ca49a2f954bb6
BLAKE2b-256 checksum
How to use checksums
851ae1c1315b0530aca9c84b5af670e6439b224b95e376385936c6c3e444f64b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / mcpsync_cli-0.1.0-py3-none-any.whl

Download URL mcpsync_cli-0.1.0-py3-none-any.whl
Size 20.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c582494f43963de359f3950bb18ed77b28d1ed5f1db4b09ed18b3aaaa411f992
BLAKE2b-256 checksum
How to use checksums
227a960ddbe69aca3274ec83dad011bc85b3cff57ca2b06f3d7fedf119af544a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page