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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcpsync_cli-0.1.0.tar.gz (32.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcpsync_cli-0.1.0-py3-none-any.whl (20.0 kB view details)

Uploaded Python 3

File details

Details for the file mcpsync_cli-0.1.0.tar.gz.

File metadata

  • Download URL: mcpsync_cli-0.1.0.tar.gz
  • Upload date:
  • Size: 32.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for mcpsync_cli-0.1.0.tar.gz
Algorithm Hash digest
SHA256 754af2487d641d71d27f210b51f6536b02bdb3b6ea9de0cad47ca49a2f954bb6
MD5 b9d09f52dc2822188c47e46d6b55a07d
BLAKE2b-256 851ae1c1315b0530aca9c84b5af670e6439b224b95e376385936c6c3e444f64b

See more details on using hashes here.

File details

Details for the file mcpsync_cli-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: mcpsync_cli-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 20.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for mcpsync_cli-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c582494f43963de359f3950bb18ed77b28d1ed5f1db4b09ed18b3aaaa411f992
MD5 7c0debe798cff6a444649e398ea12937
BLAKE2b-256 227a960ddbe69aca3274ec83dad011bc85b3cff57ca2b06f3d7fedf119af544a

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page