Skip to main content
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 nexus.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://nexus.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)
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://nexus.civic.com/hub/mcp?profile=your-production-toolkit

Reference Implementation

dspy-reference-implementation-civic

Complete implementation with FastAPI chat UI and deployment guide

Next Steps

Agent Deployment

Production deployment guide: profile locking, URL params, authentication

Guardrails

Constrain what tools your agent can call

Audit Trail

Query what your agent did via Civic Chat

Get Credentials

Token generation and URL parameter reference