Language Bindings
Python
ZAP Python SDK - Async/await API
Python Binding
The Python binding provides an async/await API built on pycapnp with full type hints.
Installation
pip install zap-protocol
# Or with uv
uv add zap-protocolQuick Start
import asyncio
import json
import uuid
from zap_protocol import Zap, ClientInfo, ToolCall
async def main():
# Connect to ZAP server
client = await Zap.connect("zap://localhost:9000")
# Initialize
server = await client.init(ClientInfo(
name="my-agent",
version="1.0.0"
))
print(f"Connected to: {server.name} v{server.version}")
# List tools
tools = await client.list_tools()
for tool in tools:
print(f" {tool.name} - {tool.description}")
# Call a tool
result = await client.call_tool(ToolCall(
id=str(uuid.uuid4()),
name="read_file",
args=json.dumps({"path": "/etc/hosts"}).encode()
))
if result.error:
print(f"Error: {result.error}")
else:
print(f"Result: {result.content.decode()}")
await client.close()
asyncio.run(main())Client API
Connection
# Simple connection
client = await Zap.connect("zap://localhost:9000")
# With options
client = await Zap.connect(
"zap://localhost:9000",
timeout=30.0,
identity=identity,
)
# Context manager
async with Zap.connect("zap://localhost:9000") as client:
tools = await client.list_tools()Tools
# List tools
tools = await client.list_tools()
# Call tool
result = await client.call_tool(ToolCall(
id="call-1",
name="my_tool",
args=b'{"param": "value"}'
))
# With typed result
content = json.loads(result.content)Resources
# List resources
resources = await client.list_resources()
# Read resource
content = await client.read_resource("file:///etc/hosts")
print(content.text) # or content.blob for binary
# Subscribe to updates
stream = await client.subscribe("file:///var/log/app.log")
async for update in stream:
print(f"Update: {update.content}")Prompts
# List prompts
prompts = await client.list_prompts()
# Get prompt messages
messages = await client.get_prompt(
"code_review",
{"topic": "python"}
)
for msg in messages:
print(f"{msg.role}: {msg.content}")Gateway
from zap_protocol import Gateway, ServerConfig, Transport, Auth
import os
# Create gateway
gateway = Gateway()
# Add MCP server
fs_id = await gateway.add_server(
"filesystem",
"stdio://npx -y @modelcontextprotocol/server-filesystem /tmp",
ServerConfig(transport=Transport.STDIO)
)
# Add with authentication
gh_id = await gateway.add_server(
"github",
"stdio://npx -y @modelcontextprotocol/server-github",
ServerConfig(
transport=Transport.STDIO,
auth=Auth(bearer=os.environ["GITHUB_TOKEN"]),
timeout=60000
)
)
# List all tools
tools = await gateway.list_tools()
# Check status
status = await gateway.server_status(fs_id)
print(f"Status: {status}")Type Hints
Full type annotations are available:
from zap_protocol import (
Zap,
Tool,
ToolCall,
ToolResult,
Resource,
ResourceContent,
ServerInfo,
ClientInfo,
)
async def process_tools(client: Zap) -> list[str]:
tools: list[Tool] = await client.list_tools()
return [t.name for t in tools]Error Handling
from zap_protocol import ZapError, DisconnectedError, TimeoutError
try:
result = await client.call_tool(call)
except DisconnectedError:
print("Connection lost, reconnecting...")
await client.reconnect()
except TimeoutError:
print("Request timed out")
except ZapError as e:
print(f"ZAP error: {e}")
if result.error:
print(f"Tool error: {result.error}")Server Implementation
from zap_protocol.server import Server, ZapHandler
from zap_protocol import (
ServerInfo,
ClientInfo,
Tool,
ToolCall,
ToolResult,
Capabilities,
)
import json
class MyHandler(ZapHandler):
async def init(self, client: ClientInfo) -> ServerInfo:
return ServerInfo(
name="my-server",
version="1.0.0",
capabilities=Capabilities(
tools=True,
resources=True,
prompts=False,
logging=True
)
)
async def list_tools(self) -> list[Tool]:
return [
Tool(
name="hello",
description="Says hello",
schema=json.dumps({
"type": "object",
"properties": {"name": {"type": "string"}}
}).encode()
)
]
async def call_tool(self, call: ToolCall) -> ToolResult:
if call.name == "hello":
args = json.loads(call.args)
name = args.get("name", "World")
return ToolResult(
id=call.id,
content=f"Hello, {name}!".encode()
)
return ToolResult(
id=call.id,
error="Unknown tool"
)
async def main():
server = Server(MyHandler())
await server.serve("0.0.0.0", 9000)
asyncio.run(main())Async Patterns
Concurrent Calls
import asyncio
async def call_tools_concurrently(client: Zap, calls: list[ToolCall]):
results = await asyncio.gather(*[
client.call_tool(call) for call in calls
])
return resultsBackground Tasks
async def monitor_resource(client: Zap, uri: str):
stream = await client.subscribe(uri)
async for update in stream:
print(f"Update: {update.content}")
# Run in background
task = asyncio.create_task(monitor_resource(client, "file:///log"))Cancellation
async def with_timeout():
try:
async with asyncio.timeout(10.0):
result = await client.call_tool(call)
except asyncio.TimeoutError:
print("Call timed out")Testing
import pytest
from unittest.mock import AsyncMock
from zap_protocol import Zap, Tool
@pytest.fixture
def mock_client():
client = AsyncMock(spec=Zap)
client.list_tools.return_value = [
Tool(name="test", description="Test tool", schema=b"{}")
]
return client
@pytest.mark.asyncio
async def test_list_tools(mock_client):
tools = await mock_client.list_tools()
assert len(tools) == 1
assert tools[0].name == "test"Examples
Full examples at:
Last updated on