Connect your AI application to Civic Bot in under 30 minutes.
MCP uses JSON-RPC 2.0 over HTTP. Start by sending an initialize request to establish the protocol version.
curl -X POST http://localhost:4091/mcp/message \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": {
"name": "my-app",
"version": "1.0.0"
}
}
}'
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"serverInfo": {
"name": "civic-bot",
"version": "0.1.0"
},
"capabilities": {
"tools": {}
}
}
}
List all available tools to understand what queries you can make.
curl -X POST http://localhost:4091/mcp/message \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {}
}'
Civic Bot provides 8 tools:
search_jurisdictions
Find government entities
get_jurisdiction
Get details + hierarchy
search_agencies
Find agencies
get_agency
Get agency + services
search_services
Find permits, benefits
get_service
Get service details
search_officials
Find elected officials
get_official
Get official details
Use tools/call with the tool name and arguments.
curl -X POST http://localhost:4091/mcp/message \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "search_services",
"arguments": {
"query": "business license"
}
}
}'
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"services\":[...],\"count\":1}"
}
]
}
}
Note: The result content is JSON-encoded text. Parse result.content[0].text to get the actual data.
import requests
import json
def call_civic_bot(tool_name, arguments):
response = requests.post(
"http://localhost:4091/mcp/message",
json={
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": tool_name,
"arguments": arguments
}
}
)
result = response.json()
return json.loads(result["result"]["content"][0]["text"])
# Example: Search for services
services = call_civic_bot("search_services", {"query": "license"})
print(f"Found {services['count']} services")
async function callCivicBot(toolName, args) {
const response = await fetch("http://localhost:4091/mcp/message", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: { name: toolName, arguments: args }
})
});
const result = await response.json();
return JSON.parse(result.result.content[0].text);
}
// Example: Get jurisdiction hierarchy
const data = await callCivicBot("get_jurisdiction", { id: "vacaville-ca" });
console.log(data.hierarchy.map(j => j.name).join(" → "));
defmodule CivicBotClient do
def call_tool(tool_name, arguments) do
request = %{
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: %{name: tool_name, arguments: arguments}
}
{:ok, response} = Req.post("http://localhost:4091/mcp/message",
json: request
)
response.body["result"]["content"]
|> List.first()
|> Map.get("text")
|> Jason.decode!()
end
end
# Example: Search for agencies
CivicBotClient.call_tool("search_agencies", %{query: "police"})
Check for errors in two places:
Protocol-level errors (invalid method, parse error):
{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}
Tool execution errors (not found, invalid args):
{"result":{"content":[{"type":"text","text":"Error: Not found"}],"isError":true}}
Tip: Always check for isError: true in the result before processing the response.