Two runnable demos showing how to embed SourceryKit's interception and verification layer into an MCP tool, connect it to Skilder, and run AI agents that produce cryptographically verifiable claims.
- A custom MCP server (
mcp/server.py) that wraps an HTTP call with SourceryKit'sasync_intercept_context, storing every outbound request in a verifiable database. The server runs externally and is connected to Skilder via Streamable HTTP. - Two agent demos that call the tool via Skilder, extract claims from the response, and run SourceryKit's handoff evaluation to verify those claims against the source of truth.
flowchart TD
Agent["AI Agent\n(any MCP client)"]
Skilder["Skilder\nSkills → Tools"]
MCP["External MCP Server\n(SourceryKit)"]
API["External API\n(Open-Meteo)"]
DB[("SourceryKit\nIntercepts DB")]
Response["Agent Response\n(answer + claims + ref)"]
Payload["Handoff Payload\n(claims + ref)"]
Eval["SourceryKit\nEvaluator"]
Provably["Provably Backend\n(proof verify)"]
Agent -->|"1. calls tool"| Skilder
Skilder -->|"2. routes"| MCP
MCP -->|"3. HTTP call"| API
API -.->|"response"| MCP
MCP -->|"4. stores tool output"| DB
DB -.->|"returns ref"| MCP
MCP -->|"5. returns data + ref"| Response
Response -->|"6. build_handoff_payload"| Payload
Payload -->|"7. evaluate"| Eval
Eval <-->|"9. proof verify"| Provably
Provably -.->|"8. query records"| DB
Eval -->|"10. PASS / CAUGHT"| Result{{"PASS / CAUGHT"}}
- Agent calls a tool through Skilder
- Skilder routes the call to the external MCP server
- MCP server makes the HTTP call to the External API
- MCP server stores the tool output (request + response) in the SourceryKit Intercepts DB, returns data +
sourcerykit_ref - MCP server returns data + ref to the agent
- Agent returns
SourceryKitAgentResponse(answer + claims with ref) build_handoff_payloadbundles claims with intercept metadata- Provably backend queries the Intercepts DB to retrieve the recorded tool output
- Provably backend verifies the cryptographic proof
- SourceryKit locally compares claimed values against proof-verified results
- Returns PASS or CAUGHT
- Python 3.12+
- A SourceryKit account with database access
- A Skilder workspace
- An LLM API key for your chosen agent framework
- Your MCP server must be publicly accessible via a Streamable HTTP URL (or reachable from Skilder's cloud)
pip install sourcerykit
sourcerykit initThe interactive wizard handles account creation/login, organization selection, PostgreSQL database linking, project naming, and bootstrapping (DB tables, Provably handshake, saving IDs to .env).
Important: The MCP server and the local demo must use the same
PROVABLY_API_KEYandSOURCERYKIT_ORG_ID. After runningsourcerykit init, the values are stored in:
- Global config (API key + org ID):
- macOS:
~/Library/Application Support/sourcerykit/config.json- Linux:
~/.config/sourcerykit/config.json- Local
.env(bootstrap IDs, postgres URL): in your project directoryEnsure the MCP server's environment uses the same values.
The MCP server in this example is hosted externally on GCP and is accessible at:
https://mcp-weather.provably.ai/sse
For your own deployment, see the mcp/server.py for reference.
-
Add the MCP server — In Skilder, go to Library → Tools → Add Tools → Add a custom server. Enter the Streamable HTTP URL of your externally-hosted MCP server (e.g.
https://mcp-weather.provably.ai/sse). Save and verify the server status is Connected. -
Create a skill — Go to Library -> Skills → New Skill. Name it, add instructions describing what the agent should do, and select the tools from your registered MCP server. Save.
-
Create a role — Go to Roles → New Role. Name it and assign the skill you just created. Create the role.
Your agent can now discover and use the tool when connected to this role. See the Skilder docs for the full walkthrough.
Configure the root .env with your LLM credentials. Each script has its own requirements — check the comments at the top of the file.
# For Claude demo
MODEL_NAME=claude-haiku-4-5
ANTHROPIC_API_KEY=<YOUR_ANTHROPIC_KEY>
# For OpenAI demo
OPENAI_MODEL_NAME=openai/gpt-5-nano
MODEL_URL=http://127.0.0.1:1234/v1
MODEL_API_KEY=<YOUR_MODEL_KEY>
# Skilder connection
SKILDER_USER_KEY=<YOUR_SKILDER_USER_KEY>
SKILDER_WORKSPACE_ID=<YOUR_SKILDER_WORKSPACE_ID>This repo includes two demo scripts:
python claude_agent_run_skilder.py # Claude Agents SDK
python openai_agent_run_skilder.py # OpenAI Agents SDKBoth demos follow the same flow: a primary orchestrator delegates to a weather specialist subagent, which calls the Skilder-hosted tool, extracts claims, and runs SourceryKit's handoff evaluation.
SourceryKit supports other frameworks too — see the cookbooks for LangChain, LangGraph, CrewAI, and more.
The integration has three layers: an MCP server that intercepts HTTP calls and logs them to a database, agents that use tool output and return structured answers with extracted claims, and a verification step that compares claims against the source of truth.
The MCP server runs independently (e.g. on GCP) and is connected to Skilder via Streamable HTTP. SourceryKit patches the HTTP library process-wide, logs the request/response to the intercepts table of a verifiable database, and returns a ref UUID that links the network traffic to this specific call.
Bootstrap at startup — on the external server, register trusted destinations and initialize the interceptor (mcp/server.py:49-56):
await sourcerykit.bootstrap_system()
await sourcerykit.insert_trusted_endpoint(url=_OPEN_METEO_BASE_URL)Intercept the outbound call — wrap the HTTP request and return the ref alongside the data (mcp/server.py:22-46):
@mcp.tool(
name="get_current_temperature_london",
description="Fetch current temperature for London with SourceryKit intercept context",
)
async def get_current_temperature_london() -> dict[str, Any]:
async with sourcerykit.async_intercept_context(
agent_id=os.getenv("SOURCERYKIT_AGENT_ID", "mcp-demo"),
action_name="get_weather",
) as ref:
async with httpx.AsyncClient() as client:
response = await client.get(
_OPEN_METEO_BASE_URL,
params={
"latitude": 51.5074,
"longitude": -0.1278,
"current": "temperature_2m",
},
timeout=30,
)
data = response.json()
return {
"content": [
{
"type": "text",
"text": json.dumps({**data, "sourcerykit_ref": ref}),
}
]
}The agent must receive the sourcerykit_ref — it's the key that binds the agent's claims back to the raw intercepted data.
Agents must return a typed response with answer and claimed_values. Each claimed value points to a field in the tool output via JSONPath and carries the sourcerykit_ref from the tool call.
The schema (sourcerykit/schemas/agent_response.py):
class ClaimedValue(BaseModel):
path: str # JSONPath into tool output, e.g. "$.current.temperature_2m"
value: str # Extracted value as string
sourcerykit_ref: str # Must match the ref from the tool call
class SourceryKitAgentResponse(BaseModel):
answer: str # Human-readable explanation
claimed_values: list[ClaimedValue] # Extracted claims to verifyThe pattern is the same across frameworks — enforce SourceryKitAgentResponse as the agent's structured output. Here are two examples:
OpenAI Agents SDK — pass as output_type (openai_agent_run_skilder.py:121-127):
weather_specialist_agent = Agent(
name="london_weather_specialist",
instructions=subagent_instructions,
mcp_servers=[skilder_mcp],
model=_DEFAULT_MODEL,
output_type=SourceryKitAgentResponse,
)Claude Agents SDK — pass the JSON schema via output_format (claude_agent_run_skilder.py:42-74):
schema_str = json.dumps(SourceryKitAgentResponse.model_json_schema(), indent=2)
options = ClaudeAgentOptions(
system_prompt="You are a primary orchestrator...",
mcp_servers={"skilder": {"type": "http", "url": "<YOUR_SKILDER_MCP_URL>"}},
agents={
"london_weather_specialist": AgentDefinition(
description="Skilder London Weather Bot.",
prompt=(
"Your output MUST be a raw JSON object strictly adhering to this schema:\n"
f"{schema_str}\n\n"
"Do NOT alter, rename, or drop key names (such as 'sourcerykit_ref')."
),
mcpServers=["skilder"],
tools=["mcp__skilder__*"],
)
},
model=_DEFAULT_MODEL,
output_format={
"type": "json_schema",
"schema": SourceryKitAgentResponse.model_json_schema(),
},
)See the cookbooks for LangChain, LangGraph, CrewAI, and other framework integrations.
After the agent responds, two steps verify the claims:
Build the handoff payload — bundle claims with intercept metadata (claude_agent_run_skilder.py:105-119):
payload = await build_handoff_payload(
{
"answer": final_output.answer,
"claims": [
{
"action_name": "get_weather",
"claimed_value": final_output.claimed_values,
"verification_mode": "field_extraction",
}
],
},
run_id=uuid.uuid4(),
prompt=prompt,
intercept_agent_id="demo",
)Evaluate — send to Provably backend, which verifies the cryptographic proof against the intercepted HTTP records (claude_agent_run_skilder.py:122-125):
eval_result = await evaluate_handoff(payload=payload)
# eval_result["outcome"] → "PASS" | "CAUGHT" | "ERROR"
print(json.dumps(eval_result, indent=2))The backend verify the cryptographic proof. SourceryKit then locally compares the agent's claimed values against the proof-verified results — matching values return PASS, mismatches return CAUGHT.
Skilder's monitoring captures every tool call with full request/response payloads. This can serve as the verifiable audit trail instead of SourceryKit's own PostgreSQL intercepts table. In this setup, the sourcerykit_ref in the agent's claims maps to Skilder's log entry for the tool call.
A Skilder skill can embed SourceryKit's SourceryKitAgentResponse schema in its references or scripts, instructing the agent to return structured claims. The skill's script can also run build_handoff_payload from the agent's output, packaging the claims into a ready-to-evaluate payload. The agent follows the skill's instructions without needing to know SourceryKit internals.
- SourceryKit GitHub — SDK source, cookbooks, docs
- SourceryKit Docs — Architecture, interceptor, handoff, trusted endpoints
- Skilder Docs — Skills, roles, tools, agent connection
- Skilder App — Workspace dashboard
- SourceryKit Cookbooks — LangChain, LangGraph, CrewAI, and more
- MCP Server Endpoint — Live Streamable HTTP endpoint for this example