Skip to content

MCP (Model Context Protocol)

The Model Context Protocol (MCP) provides a standardized way for AI agents and LLM-powered applications to interact with EvidentSource. MCP uses a minimal tool surface design where agents navigate the API through TOON (Token-Oriented Object Notation) hypermedia responses.

EvidentSource implements MCP using a “front door” pattern: A minimal set of tools provides entry points, and agents navigate via hypermedia links in TOON responses.

AI Agent → MCP Resources → Discovery (guides, database catalog)
→ MCP Tools → fetch → REST API (Accept: text/plain)
← TOON hypermedia responses
  • Minimal Tool Surface: Only 3 tools instead of one tool per operation
  • Hypermedia Navigation: TOON responses include links and actions for discovery
  • Direct REST Fallback: Sophisticated agents can bypass MCP using HTTP directly
  • Authentication Forwarding: Auth headers are forwarded to the REST API

The MCP server runs standalone on port 3001 (separate from the REST API on port 3000):

POST http://localhost:3001/

The MCP server uses Streamable HTTP transport with Server-Sent Events (SSE) for responses.

HeaderValueDescription
Content-Typeapplication/jsonRequest body format
Acceptapplication/json, text/event-streamBoth required for SSE
mcp-session-id<session-id>Required after initialization
AuthorizationBearer <token>Optional, forwarded to REST API

MCP requires a specific initialization sequence:

Terminal window
# Step 1: Initialize session
INIT=$(curl -s -i -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "my-agent", "version": "1.0.0"}
},
"id": 1
}')
# Extract session ID from response headers
SESSION=$(echo "$INIT" | grep -i mcp-session-id | awk '{print $2}' | tr -d '\r')
echo "Session: $SESSION"
# Step 2: Send initialized notification (REQUIRED)
curl -s -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{"jsonrpc": "2.0", "method": "notifications/initialized"}'
# Step 3: Now you can use tools
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{"jsonrpc": "2.0", "method": "tools/list", "params": {}, "id": 2}'

Responses are SSE (Server-Sent Events) format:

data: {"jsonrpc":"2.0","id":2,"result":{...}}
id: 0/0

Use timeout with curl since SSE streams stay open.

MCP resources provide discovery entry points. Agents read resources to understand what’s available, then use fetch to navigate.

URINameDescription
evidentsource://guides/rolesAI Roles GuideStart here - understand Author vs User roles
evidentsource://databasesDatabase CatalogList all databases with navigation guidance
evidentsource://guides/authoringComponent Authoring GuideHow to build State Views and State Changes
evidentsource://guides/apiAPI Reference GuideREST endpoints, queries, and constraints
URI TemplateDescription
evidentsource://db/{name}Per-database guidance with revision semantics
Terminal window
# Read the roles guide (start here)
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {"uri": "evidentsource://guides/roles"},
"id": 3
}'
# Read the database catalog
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{
"jsonrpc": "2.0",
"method": "resources/read",
"params": {"uri": "evidentsource://databases"},
"id": 4
}'

MCP provides exactly 3 tools. All other operations use the fetch tool with appropriate paths.

Generic HTTP tool for navigating TOON hypermedia responses. Use this for all read operations and JSON writes (including transactions).

Parameters:

NameTypeRequiredDescription
methodstringNoHTTP method: GET or POST (default: GET)
pathstringYesAPI path (e.g., /db/orders/latest)
query_paramsobjectNoQuery parameters (auto-encoded)
bodyobjectNoRequest body for POST requests

Examples:

Terminal window
# List databases
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "fetch",
"arguments": {"path": "/"}
},
"id": 3
}'
# Get database details
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "fetch",
"arguments": {"path": "/db/orders/latest"}
},
"id": 4
}'
# Query events (query object auto-encoded to base64)
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "fetch",
"arguments": {
"path": "/db/orders/latest/events",
"query_params": {
"q": {
"selector": {"equals": {"subject": {"hasValue": true, "value": "order-123"}}},
"range": {"revision": {}},
"direction": "FORWARD"
}
}
}
},
"id": 5
}'
# Create a database
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "fetch",
"arguments": {
"method": "POST",
"path": "/api/v1/db",
"body": {"database_name": "my-new-db"}
}
},
"id": 6
}'
# Transact events with DCB conditions
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "fetch",
"arguments": {
"method": "POST",
"path": "/api/v1/db/orders",
"body": {
"events": [{
"specversion": "1.0",
"type": "OrderCreated",
"source": "order-service",
"id": "evt-001",
"subject": "order-123",
"data": {"orderId": "order-123", "total": 99.99}
}],
"conditions": []
}
}
},
"id": 7
}'
# Get a state view
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "fetch",
"arguments": {"path": "/api/v1/db/orders/latest/state-views/order-summary/v1"}
},
"id": 8
}'

Execute a State Change command handler. Use this when content type varies (not just JSON).

Parameters:

NameTypeRequiredDescription
databasestringYesDatabase name
state_changestringYesState change name
versionintegerYesState change version
commandobjectYesCommand payload (JSON)

Example:

Terminal window
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "execute_state_change",
"arguments": {
"database": "orders",
"state_change": "place-order",
"version": 1,
"command": {"order_id": "123", "items": [{"sku": "ABC", "qty": 2}]}
}
},
"id": 9
}'

Deploy a WASM State View or State Change component. Use this for binary uploads.

Parameters:

NameTypeRequiredDescription
databasestringYesDatabase name
component_typestringYesstate_view or state_change
namestringYesComponent name
versionintegerYesComponent version
content_typestringYesContent type (e.g., application/json)
wasm_base64stringYesBase64-encoded WASM binary
key_typestringNoState view key type: String, Int64, or None
reduce_fnstringNoState view reduce function name (default: reduce)
eventsarrayNoState view event type filter

Example:

Terminal window
timeout 2s curl -s -N -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "publish_component",
"arguments": {
"database": "orders",
"component_type": "state_view",
"name": "order-summary",
"version": 1,
"content_type": "application/json",
"key_type": "String",
"wasm_base64": "AGFzbQEAAAA..."
}
},
"id": 10
}'

TOON (Token-Oriented Object Notation) is a text format designed for LLM consumption:

[Natural language summary]
[Structured data - key-value pairs or CSV-like tables]
---
Links:
[Discoverable URLs for navigation]
Actions:
[Available operations]
Database "orders" is at revision 1247.
database:
name: orders
revision: 1247
created_at: 2024-01-15T10:30:00Z
updated_at: 2024-01-20T14:22:00Z
---
Links:
events: GET /db/orders/1247/events
streams: GET /db/orders/1247/streams
subjects: GET /db/orders/1247/subjects
event_types: GET /db/orders/1247/event-types
Actions:
transact: POST /api/v1/db/orders

You can request TOON format directly from the REST API:

Terminal window
# List databases in TOON format
curl -H "Accept: text/plain" http://localhost:3000/
# Get database details in TOON format
curl -H "Accept: text/plain" http://localhost:3000/db/orders/latest
# Query events in TOON format
curl -H "Accept: text/plain" http://localhost:3000/db/orders/latest/events

When authentication is enabled, pass the auth header during initialization:

Terminal window
INIT=$(curl -s -i -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "my-agent", "version": "1.0.0"}
},
"id": 1
}')

The MCP server extracts the Authorization header and forwards it to the REST API for each tool call.

  1. Read the database catalog resource
  2. Use fetch to get database details
  3. Follow links in TOON response to explore events, streams, etc.
read_resource("evidentsource://databases")
→ fetch(path="/db/orders/latest")
→ fetch(path="/db/orders/1247/events")
  1. Get current database state
  2. Build events with appropriate subjects and types
  3. Use fetch with POST to transact
fetch(path="/db/orders/latest")
→ fetch(method="POST", path="/api/v1/db/orders", body={events: [...], conditions: [...]})
  1. Read the authoring guide resource
  2. Build WASM component following the guide
  3. Use publish_component to deploy
read_resource("evidentsource://guides/authoring")
→ publish_component(database="orders", component_type="state_view", ...)

Errors are returned as MCP error responses:

{
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32603,
"message": "Request failed with status 404: Database not found"
}
}

”Failed to create service: expect initialized notification”

Section titled “”Failed to create service: expect initialized notification””

You must send notifications/initialized after the initialize request:

Terminal window
curl -s -X POST http://localhost:3001/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $SESSION" \
-d '{"jsonrpc": "2.0", "method": "notifications/initialized"}'

MCP uses SSE (Server-Sent Events) which keeps connections open. Use timeout with curl:

Terminal window
timeout 2s curl -s -N -X POST http://localhost:3001/ ...

Both application/json and text/event-stream are required:

Terminal window
-H "Accept: application/json, text/event-stream"