mcpd-sdk-python
mcpd-sdk-python is a lightweight Python SDK for interacting with the mcpd application.
A daemon that exposes MCP server tools via a simple HTTP API.
This SDK provides high-level and dynamic access to those tools, making it easy to integrate with scripts, applications, or agentic frameworks.
Features
- Discover and list available
mcpdhosted MCP servers - Retrieve tool definitions and schemas for one or all servers
- Dynamically invoke any tool using a clean, attribute-based syntax
- Generate self-contained, deepcopy-safe tool functions for frameworks like any-agent
- Minimal dependencies (
requestsandcachetoolsonly)
Installation in your project
Assuming you are using uv, include it in your pyproject.toml:
uv add mcpd
Dev Setup
Use the Makefile target to ensure uv is installed, and your virtual environment is active and sync'd.
make setup
Testing
Ensure you have the correct dependencies installed for testing:
uv sync --group tests
Then to run all tests:
uv run pytest tests
... or via Makefile:
make test
Lint files using:
make lint
Quick Start
from mcpd import McpdClient, McpdError
client = McpdClient(api_endpoint="http://localhost:8090")
# List available servers
print(client.servers())
# Example: ['time', 'fetch', 'git']
# List tool definitions (schemas) for a specific server
print(client.tools(server_name="time"))
# Dynamically call a tool
try:
result = client.call.time.get_current_time(timezone="UTC")
print(result)
except McpdError as e:
print(f"Error: {e}")
Agentic Usage
Generate dynamic functions suitable for AI agents:
from any_agent import AnyAgent, AgentConfig
from mcpd import McpdClient
# Assumes the mcpd daemon is running
client = McpdClient(api_endpoint="http://localhost:8090")
# Get all tools from healthy servers (default - filters out unhealthy servers)
all_tools = client.agent_tools()
# Get tools from specific servers, only if healthy
time_tools = client.agent_tools(servers=['time'])
# Get tools from multiple servers, only if healthy
subset_tools = client.agent_tools(servers=['time', 'fetch'])
# Filter by tool names (cross-cutting)
math_tools = client.agent_tools(tools=['add', 'multiply'])
# Filter by qualified tool names
specific = client.agent_tools(tools=['time__get_current_time'])
# Combine server and tool filtering
filtered = client.agent_tools(
servers=['time', 'math'],
tools=['add', 'get_current_time']
)
agent_config = AgentConfig(
tools=client.agent_tools(),
model_id="gpt-4.1-nano", # Requires OPENAI_API_KEY to be set
instructions="Use the tools to answer the user's question."
)
agent = AnyAgent.create("mcpd-agent", agent_config)
response = agent.run("What is the current time in Tokyo?")
print(response)
# Force refresh cache to get latest schemas
fresh_tools = client.agent_tools(refresh_cache=True)
# Or clear cache manually and call again
client.clear_agent_tools_cache()
fresh_tools = client.agent_tools()
Examples
A working SDK examples are available in the examples/ folder,
please refer to the relevant example for execution details.
| Method | Docs |
|---|---|
| AnyAgent | README.md |
| Manual | README.md |
| Pydantic AI | README.md |
API
Initialization
from mcpd import McpdClient
# Initialize the client with your mcpd API endpoint.
# api_key is optional and sends an 'MCPD-API-KEY' header.
# server_health_cache_ttl is optional and sets the time in seconds to cache a server health response.
# logger is optional and allows you to provide a custom logger implementation (see Logging section).
client = McpdClient(api_endpoint="http://localhost:8090", api_key="optional-key", server_health_cache_ttl=10)
Core Methods
-
client.servers() -> list[str]- Returns a list of all configured server names. -
client.tools() -> dict[str, list[dict]]- Returns a dictionary mapping each server name to a list of its tool schema definitions. -
client.tools(server_name: str) -> list[dict]- Returns the tool schema definitions for only the specified server. -
client.agent_tools(servers: list[str] | None = None, tools: list[str] | None = None, *, refresh_cache: bool = False) -> list[Callable]- Returns a list of self-contained, callable functions suitable for agentic frameworks. By default, filters to healthy servers only. Useserversto filter by server names,toolsto filter by tool names (supports both raw names like'add'and prefixed names like'time__get_current_time'), orrefresh_cache=Trueto force regeneration of cached functions. Functions are cached - subsequent calls return cached functions immediately without refetching schemas. -
client.clear_agent_tools_cache()- Clears cached generated callable functions. Call this to force regeneration when tool schemas have changed. -
client.has_tool(server_name: str, tool_name: str) -> bool- Checks if a specific tool exists on a given server. -
client.call.<server_name>.<tool_name>(**kwargs)- The primary way to dynamically call any tool using keyword arguments. -
client.server_health() -> dict[str, dict]- Returns a dictionary mapping each server name to the health information of that server. -
client.server_health(server_name: str) -> dict- Returns the health information for only the specified server. -
client.is_server_healthy(server_name: str) -> bool- Checks if the specified server is healthy and can handle requests.
Logging
The SDK includes built-in logging infrastructure that can be enabled via the MCPD_LOG_LEVEL environment variable. Logging is disabled by default to avoid contaminating stdout/stderr.
Available Log Levels
Set the MCPD_LOG_LEVEL environment variable to one of the following values (from most to least verbose):
trace- Most verbose logging (includes all levels below)debug- Debug-level logginginfo- Informational loggingwarn- Warning-level logging (recommended for most use cases)error- Error-level logging onlyoff- Disable all logging (default)
Example Usage
# Enable warning-level logging
export MCPD_LOG_LEVEL=warn
python your_script.py
from mcpd import McpdClient
# Warnings will be logged to stderr when MCPD_LOG_LEVEL=warn
client = McpdClient(api_endpoint="http://localhost:8090")
# For example, the SDK will log warnings for:
# - Non-existent servers when calling agent_tools()
# - Unhealthy servers when calling agent_tools()
# - Servers that become unavailable during tool fetching
Custom Logger
You can provide your own logger implementation that implements the Logger protocol:
import sys
from mcpd import McpdClient, Logger
class CustomLogger:
"""Custom logger that writes to stderr (safe for MCP server contexts)."""
def trace(self, msg: str, *args: object) -> None:
print(f"TRACE: {msg % args}", file=sys.stderr)
def debug(self, msg: str, *args: object) -> None:
print(f"DEBUG: {msg % args}", file=sys.stderr)
def info(self, msg: str, *args: object) -> None:
print(f"INFO: {msg % args}", file=sys.stderr)
def warn(self, msg: str, *args: object) -> None:
print(f"WARN: {msg % args}", file=sys.stderr)
def error(self, msg: str, *args: object) -> None:
print(f"ERROR: {msg % args}", file=sys.stderr)
# Use custom logger
client = McpdClient(
api_endpoint="http://localhost:8090",
logger=CustomLogger()
)
You can also provide a partial logger implementation. Any omitted methods will fall back to the default logger (which respects MCPD_LOG_LEVEL):
import sys
class PartialLogger:
"""Partial logger - only override warn/error, others use default."""
def warn(self, msg: str, *args: object) -> None:
# Custom warning handler (writes to stderr).
print(f"CUSTOM WARN: {msg % args}", file=sys.stderr)
def error(self, msg: str, *args: object) -> None:
# Custom error handler (writes to stderr).
print(f"CUSTOM ERROR: {msg % args}", file=sys.stderr)
# trace, debug, info use default logger (respects MCPD_LOG_LEVEL)
client = McpdClient(
api_endpoint="http://localhost:8090",
logger=PartialLogger()
)
Error Handling
All SDK-level errors raise exceptions that inherit from McpdError. The original exception is chained via __cause__ for full context.
Exception Types
| Exception | Description |
|---|---|
McpdError |
Base exception for all SDK errors |
ConnectionError |
Unable to connect to mcpd daemon |
AuthenticationError |
Authentication failed (invalid API key) |
ServerNotFoundError |
Specified server doesn't exist |
ServerUnhealthyError |
Server exists but is not healthy |
ToolNotFoundError |
Specified tool doesn't exist on server |
ToolExecutionError |
Tool execution failed |
ValidationError |
Input validation failed |
TimeoutError |
Operation timed out |
PipelineError |
Required pipeline processing failed |
Example
from mcpd import (
McpdClient,
McpdError,
PipelineError,
PIPELINE_FLOW_REQUEST,
PIPELINE_FLOW_RESPONSE,
ServerNotFoundError,
ToolExecutionError,
)
client = McpdClient(api_endpoint="http://localhost:8090")
try:
result = client.call.time.get_current_time()
except PipelineError as e:
# A required plugin failed during request or response processing.
if e.pipeline_flow == PIPELINE_FLOW_RESPONSE:
print("Tool was called but results cannot be delivered")
elif e.pipeline_flow == PIPELINE_FLOW_REQUEST:
print("Request was rejected by pipeline")
except ServerNotFoundError as e:
print(f"Server '{e.server_name}' not found")
except ToolExecutionError as e:
print(f"Tool '{e.tool_name}' failed: {e}")
except McpdError as e:
# Catch-all for any other SDK errors.
print(f"Operation failed: {e}")
License
Apache-2.0
Metadata
Release files for mcpd 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| mcpd-0.1.0.tar.gz | 438.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcpd-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 475.4 kB
Release files / mcpd-0.1.0.tar.gz
| Download URL | mcpd-0.1.0.tar.gz |
|---|---|
| Size | 438.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
497b6c96948760af424c159bef39f7dd1b003ebafa29132112a7ae5a86fc4f85
|
|
BLAKE2b-256 checksum How to use checksums |
ae63f0ec798e50c32063e930f3eb201a09749c84cc8b7ee874e7776a3a9b448e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 8, 2026.
Transparency logRelease files / mcpd-0.1.0-py3-none-any.whl
| Download URL | mcpd-0.1.0-py3-none-any.whl |
|---|---|
| Size | 36.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a9f7779ee86ee49b71b44833cf6a8f25dbebd39c74ff1f6a55c83c2a16ee2158
|
|
BLAKE2b-256 checksum How to use checksums |
fb1fd71bcc87382ee598314f95307ed9638da9ea83dea9df8bbf07480be0f010
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 8, 2026.
Transparency logSecurity for mcpd 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for mcpd-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.