MCP Reference
MCP Reference¶
The ctx.mcp API provides access to external MCP (Model Context Protocol) servers configured in your pack manifest. MCP servers expose tools that become callable from your pack commands—no LLM required.
Overview¶
MCP integration allows your pack to:
- Call external tools from MCP servers (GitHub, PostgreSQL, Slack, etc.)
- Discover available tools and their schemas at runtime
- Use a unified interface regardless of the underlying MCP server
Note: In code examples,
Argsrepresents your Pydantic model for command arguments (e.g.,CreateIssueArgs). See Commands Reference for defining argument models.
from huitzo_sdk import command, Context
@command("create-issue", namespace="mypack")
async def create_issue(args: Args, ctx: Context) -> dict:
"""Create a GitHub issue using MCP."""
result = await ctx.mcp.call(
server="github",
tool="create_issue",
arguments={
"owner": args.owner,
"repo": args.repo,
"title": args.title,
"body": args.body
}
)
return {"issue_number": result["number"], "url": result["html_url"]}
Quick Start¶
1. Configure MCP Server in Manifest¶
# huitzo.yaml
mcp_servers:
- name: github
type: stdio
command: ["uvx", "mcp-server-github"]
env:
GITHUB_TOKEN: "${secrets.GITHUB_TOKEN}"
secrets:
user_required:
- name: "GITHUB_TOKEN"
description: "Your GitHub personal access token"
help_url: "https://github.com/settings/tokens"
2. Call MCP Tools in Commands¶
from huitzo_sdk import command, Context
@command("list-repos", namespace="devtools")
async def list_repos(args: Args, ctx: Context) -> dict:
"""List GitHub repositories."""
result = await ctx.mcp.call(
server="github",
tool="list_repositories",
arguments={"owner": args.owner}
)
return {"repositories": result["repos"]}
3. Run Your Pack¶
# Start development session
huitzo pack dev
# Test the command
curl -X POST http://localhost:8080/api/v1/commands/devtools/list-repos \
-H "Content-Type: application/json" \
-d '{"owner": "acme"}'
ctx.mcp API Reference¶
ctx.mcp.call()¶
Execute a tool on an MCP server.
Signature:
async def call(
server: str,
tool: str,
arguments: dict,
timeout: int | None = None
) -> dict
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
server |
str | Yes | — | Name of the MCP server (from manifest) |
tool |
str | Yes | — | Name of the tool to execute |
arguments |
dict | Yes | — | Tool arguments (validated against schema) |
timeout |
int | No | 30 | Timeout in seconds |
Returns: dict — Tool result (structure depends on the tool)
Raises:
- MCPConnectionError — Server unreachable
- MCPToolError — Tool execution failed
- MCPTimeoutError — Execution exceeded timeout
- MCPSchemaError — Arguments don't match schema
Example:
@command("query-db", namespace="data")
async def query_db(args: Args, ctx: Context) -> dict:
result = await ctx.mcp.call(
server="postgres",
tool="query",
arguments={
"sql": "SELECT * FROM users WHERE active = true",
"params": []
},
timeout=60 # Allow 60 seconds for large queries
)
return {"rows": result["rows"], "count": len(result["rows"])}
ctx.mcp.list_tools()¶
List available tools from one or all MCP servers.
Signature:
async def list_tools(server: str | None = None) -> list[MCPTool]
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
server |
str | No | None | Server name, or None for all servers |
Returns: list[MCPTool] — List of available tools
Example:
@command("discover-tools", namespace="admin")
async def discover_tools(args: Args, ctx: Context) -> dict:
# List tools from specific server
github_tools = await ctx.mcp.list_tools("github")
# List tools from all configured servers
all_tools = await ctx.mcp.list_tools()
return {
"github_tools": [t.name for t in github_tools],
"all_tools": [{"server": t.server, "name": t.name} for t in all_tools]
}
ctx.mcp.get_tool_schema()¶
Get the JSON Schema for a specific tool.
Signature:
async def get_tool_schema(server: str, tool: str) -> dict
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
server |
str | Yes | — | Server name |
tool |
str | Yes | — | Tool name |
Returns: dict — JSON Schema for the tool's input
Example:
@command("inspect-tool", namespace="admin")
async def inspect_tool(args: Args, ctx: Context) -> dict:
schema = await ctx.mcp.get_tool_schema("github", "create_issue")
return {
"tool": "create_issue",
"required_fields": schema.get("required", []),
"properties": list(schema.get("properties", {}).keys())
}
ctx.mcp.servers¶
Property that returns information about configured MCP servers.
servers = ctx.mcp.servers # dict[str, MCPServerInfo]
Returns: dict[str, MCPServerInfo] — Server configurations
Example:
@command("list-servers", namespace="admin")
async def list_servers(args: Args, ctx: Context) -> dict:
return {
"servers": [
{
"name": name,
"type": info.type,
"connected": info.connected
}
for name, info in ctx.mcp.servers.items()
]
}
Discovering Tools¶
CLI Commands (Planned)¶
Note: These CLI commands will be implemented alongside the MCP runtime. They are documented here for reference and will be added to the CLI Reference when available.
# List configured MCP servers
huitzo mcp list-servers
# List tools from a specific server
huitzo mcp list-tools github
# Get tool schema
huitzo mcp list-tools github --tool create_issue --schema
# Test a tool directly
huitzo mcp call github create_issue '{"owner": "acme", "repo": "test", "title": "Test"}'
Programmatic Discovery¶
@command("explore-mcp", namespace="admin")
async def explore_mcp(args: Args, ctx: Context) -> dict:
"""Explore available MCP tools."""
servers = ctx.mcp.servers
result = {"servers": {}}
for server_name in servers:
tools = await ctx.mcp.list_tools(server_name)
result["servers"][server_name] = {
"tool_count": len(tools),
"tools": [
{
"name": t.name,
"description": t.description,
"required_args": t.required_args
}
for t in tools
]
}
return result
Error Handling¶
MCPError Hierarchy¶
MCPError (base)
├── MCPConnectionError # Server unreachable
├── MCPToolError # Tool execution failed
├── MCPTimeoutError # Timeout exceeded
└── MCPSchemaError # Invalid arguments
Handling Errors¶
from huitzo_sdk import command, Context
from huitzo_sdk.errors import (
MCPError,
MCPConnectionError,
MCPToolError,
MCPTimeoutError,
MCPSchemaError
)
@command("robust-github", namespace="devtools")
async def robust_github(args: Args, ctx: Context) -> dict:
"""Create issue with comprehensive error handling."""
try:
result = await ctx.mcp.call(
server="github",
tool="create_issue",
arguments={
"owner": args.owner,
"repo": args.repo,
"title": args.title
}
)
return {"success": True, "issue": result}
except MCPConnectionError as e:
ctx.log.error(f"GitHub MCP server unavailable: {e.message}")
return {
"success": False,
"error": "GitHub integration temporarily unavailable",
"retry": True
}
except MCPSchemaError as e:
ctx.log.error(f"Invalid arguments: {e.message}")
return {
"success": False,
"error": f"Invalid input: {e.field}",
"validation_errors": e.errors
}
except MCPToolError as e:
ctx.log.error(f"GitHub tool failed: {e.message}")
return {
"success": False,
"error": f"GitHub error: {e.message}",
"tool_error_code": e.code
}
except MCPTimeoutError as e:
ctx.log.error(f"GitHub call timed out after {e.timeout}s")
return {
"success": False,
"error": "Request timed out",
"timeout": e.timeout
}
Error Attributes¶
MCPConnectionError:
- server — Server name that failed to connect
- message — Error details
MCPToolError:
- server — Server name
- tool — Tool name that failed
- code — Error code from the MCP server
- message — Error message
MCPTimeoutError:
- server — Server name
- tool — Tool name
- timeout — Configured timeout value
MCPSchemaError:
- server — Server name
- tool — Tool name
- field — Field that failed validation
- errors — List of validation errors
- message — Human-readable message
Type Reference¶
MCPTool¶
Information about an MCP tool.
class MCPTool:
name: str # Tool name
description: str # Human-readable description
server: str # Server that provides this tool
input_schema: dict # JSON Schema for arguments
required_args: list[str] # Required argument names
MCPServerInfo¶
Information about a configured MCP server.
class MCPServerInfo:
name: str # Server name
type: str # "stdio" or "http"
connected: bool # Whether currently connected
tool_count: int # Number of available tools
Best Practices¶
1. Validate Tool Existence¶
# ✅ Good: Check before calling
tools = await ctx.mcp.list_tools("github")
tool_names = [t.name for t in tools]
if "create_issue" not in tool_names:
raise CommandError(
message="GitHub MCP server doesn't support create_issue",
details={"available_tools": tool_names}
)
2. Use Appropriate Timeouts¶
# ✅ Good: Short timeout for fast operations
user = await ctx.mcp.call("github", "get_user", {"username": "octocat"}, timeout=10)
# ✅ Good: Long timeout for slow operations
result = await ctx.mcp.call("postgres", "query", {"sql": complex_query}, timeout=120)
3. Handle Partial Failures¶
@command("multi-server", namespace="workflow")
async def multi_server(args: Args, ctx: Context) -> dict:
results = {"github": None, "slack": None, "errors": []}
# Try GitHub
try:
results["github"] = await ctx.mcp.call("github", "create_issue", {...})
except MCPError as e:
results["errors"].append({"server": "github", "error": str(e)})
# Try Slack even if GitHub failed
try:
results["slack"] = await ctx.mcp.call("slack", "post_message", {...})
except MCPError as e:
results["errors"].append({"server": "slack", "error": str(e)})
return results
4. Log Tool Usage for Debugging¶
# ✅ Good: Log before and after
ctx.log.info("Calling GitHub create_issue", extra={
"owner": args.owner,
"repo": args.repo
})
result = await ctx.mcp.call("github", "create_issue", {...})
ctx.log.info("GitHub issue created", extra={
"issue_number": result.get("number"),
"url": result.get("html_url")
})
5. Wrap Tools for Business Logic¶
async def create_labeled_issue(ctx: Context, owner: str, repo: str, title: str, labels: list[str]) -> dict:
"""Business logic wrapper around MCP tool."""
# Create issue
issue = await ctx.mcp.call("github", "create_issue", {
"owner": owner,
"repo": repo,
"title": title
})
# Add labels (if the tool supports it)
if labels:
await ctx.mcp.call("github", "add_labels", {
"owner": owner,
"repo": repo,
"issue_number": issue["number"],
"labels": labels
})
return issue
@command("create-bug", namespace="devtools")
async def create_bug(args: Args, ctx: Context) -> dict:
return await create_labeled_issue(
ctx, args.owner, args.repo, args.title,
labels=["bug", "needs-triage"]
)
Related Documentation¶
- MCP Architecture — How MCP fits into Huitzo
- MCP Pack Integration Guide — Step-by-step tutorial
- Pack Manifest - MCP Servers — Configuration reference
- Error Handling — Complete error reference
- Context Reference — Context API overview