Skip to main content
OpenFang implements both the Model Context Protocol (MCP) and Agent-to-Agent (A2A) protocol, enabling deep interoperability with external tools, IDEs, and other agent frameworks.

MCP Client

Connect to external MCP servers (GitHub, filesystem, databases) and use their tools in OpenFang agents

MCP Server

Expose OpenFang agents as MCP tools for IDEs like Cursor, VS Code, and Claude Desktop

A2A Server

Publish Agent Cards and accept task submissions from other agent frameworks

A2A Client

Discover and interact with external A2A agents

MCP (Model Context Protocol)

Overview

The Model Context Protocol (MCP) is a JSON-RPC 2.0 based protocol that standardizes how LLM applications discover and invoke tools. OpenFang supports MCP in both directions:
  • As a client: Connect to external MCP servers and make their tools available to all agents
  • As a server: Expose agents as MCP tools for external clients
OpenFang implements MCP protocol version 2024-11-05.

MCP Client — Connecting to External Servers

Configuration

MCP servers are configured in config.toml using the [[mcp_servers]] array:
[[mcp_servers]]
name = "github"
timeout_secs = 30
env = ["GITHUB_PERSONAL_ACCESS_TOKEN"]

[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]

Configuration Fields

name
string
required
Display name for this MCP server. Tools are namespaced as mcp_{name}_{tool}.
timeout_secs
number
default:30
JSON-RPC request timeout in seconds.
env
array
default:[]
Environment variable names to pass through to the subprocess (stdio transport only).
transport
object
required
How to connect to the MCP server.

Transport Types

Spawns a subprocess and communicates via stdin/stdout with newline-delimited JSON-RPC.
type
string
stdio
command
string
required
Command to execute (e.g., npx, node, python)
args
array
default:[]
Command arguments
Example:
[mcp_servers.transport]
type = "stdio"
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]

Tool Namespacing

All tools discovered from MCP servers are namespaced using the pattern mcp_{server}_{tool} to prevent collisions:
  • Server github, tool create_issue becomes mcp_github_create_issue
  • Server my-server, tool do_thing becomes mcp_my_server_do_thing
Names are normalized to lowercase with hyphens replaced by underscores.

Auto-Connection on Boot

When the kernel starts, it:
  1. Iterates each [[mcp_servers]] config entry
  2. Spawns subprocesses (stdio) or creates HTTP clients (SSE)
  3. Sends the initialize handshake
  4. Calls tools/list to discover available tools
  5. Namespaces and caches discovered tools
  6. Stores live connections for tool execution
MCP tools are merged into the agent’s available tool set:
built-in tools (23) + skill tools + MCP tools = full tool list

MCP Server — Exposing OpenFang via MCP

How It Works

Each OpenFang agent becomes an MCP tool named openfang_agent_{name} (with hyphens replaced by underscores). The tool accepts a single message string parameter and returns the agent’s response. For example, an agent named code-reviewer becomes the MCP tool openfang_agent_code_reviewer.

CLI: openfang mcp

openfang mcp
This command:
  1. Checks if an OpenFang daemon is running
  2. If found, proxies tool calls to the daemon via HTTP
  3. If no daemon, boots an in-process kernel as fallback
  4. Reads Content-Length framed JSON-RPC from stdin
  5. Writes Content-Length framed JSON-RPC to stdout

HTTP MCP Endpoint

OpenFang also exposes an MCP endpoint over HTTP at POST /mcp. Unlike the stdio server, the HTTP endpoint exposes the full tool set:
  • All 23 built-in tools
  • All installed skill tools
  • All connected MCP server tools

Supported JSON-RPC Methods

MethodDescription
initializeHandshake; returns server capabilities
notifications/initializedClient confirmation
tools/listReturns all available tools
tools/callExecutes a tool and returns result

Protocol Example

Initialize Handshake:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {},
    "clientInfo": { "name": "cursor", "version": "1.0" }
  }
}
Response:
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": { "tools": {} },
    "serverInfo": { "name": "openfang", "version": "0.1.0" }
  }
}
Tool Call:
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "openfang_agent_code_reviewer",
    "arguments": {
      "message": "Review this Python function for security issues..."
    }
  }
}
Response:
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{
      "type": "text",
      "text": "I found 3 potential security issues..."
    }]
  }
}

Connecting from IDEs

Add to your MCP configuration file (.cursor/mcp.json or VS Code MCP settings):
{
  "mcpServers": {
    "openfang": {
      "command": "openfang",
      "args": ["mcp"]
    }
  }
}
After configuration, all OpenFang agents appear as tools in the IDE.

MCP API Endpoints

GET /api/mcp/servers
object
List configured and connected MCP servers with their toolsResponse:
{
  "configured": [
    {
      "name": "github",
      "transport": { "type": "stdio", "command": "npx", "args": [...] },
      "timeout_secs": 30,
      "env": ["GITHUB_PERSONAL_ACCESS_TOKEN"]
    }
  ],
  "connected": [
    {
      "name": "github",
      "tools_count": 12,
      "tools": [
        { "name": "mcp_github_create_issue", "description": "[MCP:github] Create a GitHub issue" }
      ],
      "connected": true
    }
  ]
}
POST /mcp
object
Handle MCP JSON-RPC requests over HTTP (full tool execution)

A2A (Agent-to-Agent Protocol)

Overview

The Agent-to-Agent (A2A) protocol, originally specified by Google, enables cross-framework agent interoperability. OpenFang implements A2A in both directions:
  • As a server: Publishes Agent Cards, accepts task submissions, tracks task lifecycle
  • As a client: Discovers external A2A agents, sends tasks, polls for results

Agent Card

An Agent Card is a JSON document describing an agent’s identity, capabilities, and supported interaction modes. Served at /.well-known/agent.json per A2A specification.

Structure

{
  "name": "code-reviewer",
  "description": "Reviews code for bugs, security issues, and style",
  "url": "http://127.0.0.1:50051/a2a",
  "version": "0.1.0",
  "capabilities": {
    "streaming": true,
    "pushNotifications": false,
    "stateTransitionHistory": true
  },
  "skills": [
    {
      "id": "file_read",
      "name": "file read",
      "description": "Can use the file_read tool",
      "tags": ["tool"],
      "examples": []
    }
  ],
  "defaultInputModes": ["text"],
  "defaultOutputModes": ["text"]
}
name
string
required
Agent name
description
string
required
Human-readable description
url
string
required
Endpoint URL (e.g., http://host/a2a)
version
string
required
Protocol version
capabilities
object
required
Agent capabilities
capabilities.streaming
boolean
Supports streaming responses
capabilities.pushNotifications
boolean
Supports push notifications
capabilities.stateTransitionHistory
boolean
Task status history available
skills
array
Array of A2A skill descriptors (not OpenFang skills — these are capability descriptors)

A2A Configuration

[a2a]
enabled = true
listen_path = "/a2a"

[[a2a.external_agents]]
name = "research-agent"
url = "https://research.example.com"

[[a2a.external_agents]]
name = "data-analyst"
url = "https://data.example.com"
enabled
boolean
default:false
Whether A2A endpoints are active
listen_path
string
default:"/a2a"
Base path for A2A endpoints
external_agents
array
default:[]
External agents to discover at boot
external_agents[].name
string
required
Display name for this external agent
external_agents[].url
string
required
Base URL where the agent’s card is published

Task Lifecycle

An A2aTask tracks the full lifecycle of a cross-agent interaction:
interface A2aTask {
  id: string;
  session_id?: string;
  status: 'Submitted' | 'Working' | 'InputRequired' | 'Completed' | 'Cancelled' | 'Failed';
  messages: A2aMessage[];
  artifacts: A2aArtifact[];
}

Task States

StatusDescription
SubmittedTask received but not yet started
WorkingTask is being actively processed
InputRequiredAgent needs more information
CompletedTask finished successfully
CancelledTask was cancelled by caller
FailedTask encountered an error

Message Format

interface A2aMessage {
  role: 'user' | 'agent';
  parts: A2aPart[];
}

type A2aPart = 
  | { type: 'text', text: string }
  | { type: 'file', name: string, mime_type: string, data: string }  // base64
  | { type: 'data', mime_type: string, data: any };

A2A API Endpoints

GET /.well-known/agent.json
object
Agent Card for the primary agent. Public endpoint.
GET /a2a/agents
object
List all agent cardsResponse:
{
  "agents": [
    {
      "name": "code-reviewer",
      "description": "Reviews code for bugs and security issues",
      "url": "http://127.0.0.1:50051/a2a",
      "version": "0.1.0",
      "capabilities": { "streaming": true, "pushNotifications": false, "stateTransitionHistory": true },
      "skills": [...],
      "defaultInputModes": ["text"],
      "defaultOutputModes": ["text"]
    }
  ],
  "total": 1
}
POST /a2a/tasks/send
object
Submit a task to an agentRequest:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tasks/send",
  "params": {
    "message": {
      "role": "user",
      "parts": [{ "type": "text", "text": "Review this code" }]
    },
    "sessionId": "optional-session-id"
  }
}
Response:
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "sessionId": "optional-session-id",
  "status": "completed",
  "messages": [...],
  "artifacts": []
}
GET /a2a/tasks/{id}
object
Get task status and messages. Returns 404 if not found or evicted.
POST /a2a/tasks/{id}/cancel
object
Cancel a running task. Returns 404 if not found.

A2A Client — Discovering External Agents

When the kernel starts with A2A enabled and external agents configured, it:
  1. Creates an A2aClient
  2. Iterates each configured ExternalAgent
  3. Fetches each agent’s card from {url}/.well-known/agent.json
  4. Logs successful discoveries (name, URL, skill count)
  5. Stores discovered agents for runtime interaction
Failed discoveries are logged as warnings but do not prevent boot.

Sending Tasks to External Agents

let client = A2aClient::new();
let task = client.send_task(
    "https://other-agent.example.com/a2a",
    "Analyze this dataset for anomalies",
    Some("session-123"),
).await?;
println!("Task {}: {:?}", task.id, task.status);

Security

MCP Security

Subprocess Sandboxing

Stdio MCP servers run with env_clear() — subprocess environment completely cleared. Only whitelisted environment variables passed through.

Path Traversal Prevention

Command paths validated to reject .. sequences.

SSRF Protection

SSE transport URLs checked against known metadata endpoints (169.254.169.254, metadata.google).

Request Timeout

All MCP requests have configurable timeout (default 30s) to prevent hung connections.

Message Size Limit

Stdio MCP server enforces 10 MB max message size to prevent out-of-memory attacks.

A2A Security

Rate Limiting

A2A endpoints use same GCRA rate limiter as all API endpoints.

API Authentication

When api_key is set, all endpoints (including A2A) require Authorization: Bearer <key> header. Exception: /.well-known/agent.json and health endpoint.

Task Store Bounds

A2aTaskStore is bounded (default 1000 tasks) with FIFO eviction of completed/failed/cancelled tasks.

External Agent Discovery

A2aClient uses 30-second timeout and sends User-Agent: OpenFang/0.1 A2A header. Failed discoveries logged but don’t block boot.

Kernel-Level Protection

Both MCP and A2A tool execution flows through the same security pipeline:
  • Capability-based access control
  • Tool result truncation (50K character hard cap)
  • Universal 60-second tool execution timeout
  • Loop guard detection (blocks repetitive tool call patterns)
  • Taint tracking on data flowing between tools

Build docs developers (and LLMs) love