DSPy
Connect a DSPy agent to Civic by wrapping MCP tools as Python callables and passing them to dspy.ReAct. DSPy v2.6 does not include a built-in MCP client, so tools are discovered via the raw mcp package.
Prerequisites
- Python 3.11+
- A Civic account at app.civic.com with a configured toolkit
- A Civic token and an Anthropic API key
Installation
pip install dspy mcp python-dotenv
Environment Variables
CIVIC_URL=https://app.civic.com/hub/mcp?profile=your-toolkit
CIVIC_TOKEN=your-civic-token
ANTHROPIC_API_KEY=your-anthropic-key
Get Your Credentials
How to generate a Civic token and configure toolkit URL parameters
Connecting to Civic
Discover tools via the raw mcp client and wrap each one as a Python callable for DSPy:
import os
import asyncio
from dotenv import load_dotenv
import dspy
from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession
load_dotenv()
CIVIC_URL = os.environ["CIVIC_URL"]
CIVIC_TOKEN = os.environ["CIVIC_TOKEN"]
async def call_tool(name: str, args: dict) -> str:
"""Call a single MCP tool and return the result as a string."""
headers = {"Authorization": f"Bearer {CIVIC_TOKEN}"}
async with streamablehttp_client(CIVIC_URL, headers=headers) as (r, w, _):
async with ClientSession(r, w) as session:
await session.initialize()
result = await session.call_tool(name, args)
return str(result.content)
async def get_civic_tools():
"""Discover all Civic tools and wrap them as Python callables."""
headers = {"Authorization": f"Bearer {CIVIC_TOKEN}"}
async with streamablehttp_client(CIVIC_URL, headers=headers) as (r, w, _):
async with ClientSession(r, w) as session:
await session.initialize()
result = await session.list_tools()
def make_tool(name: str, desc: str):
def tool_fn(**kwargs):
return asyncio.run(call_tool(name, kwargs))
tool_fn.__name__ = name
tool_fn.__doc__ = desc or f"Call the {name} tool"
return tool_fn
return [make_tool(t.name, t.description or "") for t in result.tools]
# Configure DSPy with Claude
dspy.configure(lm=dspy.LM("anthropic/claude-sonnet-4-6"))
# Discover and wrap tools
tools = asyncio.run(get_civic_tools())
print(f"{len(tools)} tools loaded")
# Create a ReAct agent
react = dspy.ReAct(signature="question -> answer", tools=tools)
Running the Agent
result = react(question="What events do I have today?")
print(result.answer)
note
DSPy v2.6 has no built-in MCPClient. This pattern uses the raw mcp package for discovery and wraps each tool as a Python callable. Each tool call opens a new MCP connection — for production use, consider connection pooling.
Production Configuration
For production agents, lock to a specific toolkit using the profile URL parameter:
CIVIC_URL=https://app.civic.com/hub/mcp?profile=your-production-toolkit
Reference Implementation
dspy-reference-implementation-civic
Complete implementation with FastAPI chat UI and deployment guide