Skip to content

Protocol Integration

Duration: 60 minutes Level: Advanced Module: fcc.protocols

This tutorial teaches you how to expose FCC personas as interoperable agents using the A2A (Agent-to-Agent) protocol and the MCP (Model Context Protocol). You will generate Agent Cards from R.I.S.C.E.A.R. specs, set up an MCP server with tools, resources, and prompts, route messages across protocols with the ProtocolBridge, and generate an AGENTS.md manifest.

Prerequisites

  • Completed beginner/intermediate tutorials
  • Working FCC development environment
  • Familiarity with PersonaRegistry and WorkflowActionRegistry

A2A Agent Card Generation

The A2A protocol allows agents to discover and communicate with each other. The CardBuilder class translates FCC persona specifications into A2A-compatible Agent Cards.

Each R.I.S.C.E.A.R. field maps to an Agent Card field:

  • persona.name becomes card.name
  • persona.riscear.role becomes card.description
  • persona.riscear.archetype becomes a capability
  • persona.riscear.role_skills become additional capabilities
  • Workflow actions become skill definitions
from fcc.personas.registry import PersonaRegistry
from fcc.protocols.a2a.card_builder import CardBuilder

# Load the persona registry
registry = PersonaRegistry.from_yaml_directory("src/fcc/data/personas")

# Create a card builder with a custom base URL
builder = CardBuilder(base_url="https://agents.example.com")

# Build a card for a single persona
persona = registry.get("RC")
card = builder.build_card(persona)

print(f"Agent: {card.name}")
print(f"ID: {card.id}")
print(f"Description: {card.description}")
print(f"Endpoint: {card.url}")
print(f"Capabilities: {len(card.capabilities)}")

# Serialize to JSON
json_str = CardBuilder.to_json(card, indent=2)
print(json_str)

Building Cards for the Entire Registry

You can generate Agent Cards for every persona at once. When you pass an action_registry, the builder maps workflow actions to A2A skill definitions:

from fcc.workflow.actions import WorkflowActionRegistry

action_registry = WorkflowActionRegistry.from_yaml_directory(
    "src/fcc/data/personas/actions"
)

# Build cards for all 102 personas
cards = builder.build_all_cards(registry, action_registry)
print(f"Generated {len(cards)} Agent Cards")

# Write a single card as .well-known/agent.json
from pathlib import Path
path = CardBuilder.write_well_known(cards[0], Path("output"))
print(f"Written to: {path}")

MCP Server Setup

The FccMcpServer class provides an MCP-compatible server with 14 tools, 14 resources, and 6 prompts out of the box. It integrates with the FCC event bus to publish protocol lifecycle events.

from fcc.messaging.bus import EventBus
from fcc.protocols.mcp.server import FccMcpServer

# Create a server with event bus integration
bus = EventBus()
server = FccMcpServer(event_bus=bus)

print(f"Tools: {server.tool_count}")        # 14
print(f"Resources: {server.resource_count}") # 14
print(f"Prompts: {server.prompt_count}")     # 6

# List all available tools
for tool in server.list_tools():
    print(f"  - {tool['name']}: {tool['description']}")

Registering Tool Handlers

Tools require registered handler functions before they can be invoked. Each handler receives keyword arguments and returns a result dictionary:

def persona_lookup_handler(persona_id: str = "RC") -> dict:
    persona = registry.get(persona_id)
    return {
        "id": persona.id,
        "name": persona.name,
        "role": persona.riscear.role,
        "category": persona.category,
    }

# Register the handler for a built-in tool
server.register_tool_handler("persona_lookup", persona_lookup_handler)

# Call the tool
result = server.call_tool("persona_lookup", {"persona_id": "BC"})
print(result)
# {"status": "ok", "result": {"id": "BC", "name": "Blueprint Crafter", ...}}

Reading Resources and Rendering Prompts

Resources expose data via URI patterns. Prompts provide reusable templates:

# Read a resource
resource_data = server.read_resource("fcc://personas")
print(resource_data["status"])  # "ok"

# Render a prompt with arguments
prompt_result = server.get_prompt(
    "persona_review",
    {"persona_id": "RC", "content": "Draft research plan"}
)
print(prompt_result["description"])
for message in prompt_result["messages"]:
    print(message)

Protocol Bridge

The ProtocolBridge routes messages between A2A and MCP protocols through the event bus. It publishes protocol-specific lifecycle events and delegates to registered handlers:

from fcc.protocols.bridge import ProtocolBridge

bus = EventBus()
bridge = ProtocolBridge.create_default(bus)

# Default routes are registered for "a2a" and "mcp"
print(bridge.list_routes())  # ["a2a", "mcp"]

# Route an A2A message
result = bridge.route("a2a", {
    "task_id": "task-001",
    "skill": "review",
    "payload": {"document": "research_plan.md"},
})
print(result)
# {"status": "accepted", "protocol": "a2a", "message_id": "task-001"}

# Route an MCP message
result = bridge.route("mcp", {
    "tool": "persona_lookup",
    "arguments": {"persona_id": "DE"},
})
print(result)
# {"status": "accepted", "protocol": "mcp", "tool": "persona_lookup"}

Custom Protocol Handlers

Replace the default stubs with real implementations that wire into your agent infrastructure:

def custom_a2a_handler(message: dict) -> dict:
    task_id = message.get("task_id", "unknown")
    # Dispatch to the appropriate persona agent
    return {"status": "completed", "task_id": task_id, "result": "..."}

bridge.register_route("a2a", custom_a2a_handler)

AGENTS.md Generation

The AgentsMdGenerator produces a standard AGENTS.md file listing every persona as an A2A-compatible agent entry:

from fcc.protocols.agents_md import AgentsMdGenerator

# Build from the persona registry
gen = AgentsMdGenerator.from_registry(registry, action_registry)
print(f"Agents listed: {len(gen)}")

# Preview the rendered markdown
print(gen.render()[:500])

# Write to disk
gen.write(Path("AGENTS.md"))

The output follows the Google A2A standard format, with each agent listing its ID, description, endpoint, capabilities, and skills.

Event Bus Integration

All protocol operations publish events to the FCC event bus, enabling observability and auditing:

  • PROTOCOL_A2A_TASK_RECEIVED -- when an A2A task arrives
  • PROTOCOL_MCP_TOOL_CALLED -- when an MCP tool is invoked
  • PROTOCOL_MCP_TOOL_COMPLETED -- when a tool call succeeds
  • PROTOCOL_MCP_TOOL_FAILED -- when a tool call errors
  • PROTOCOL_MCP_RESOURCE_READ -- when a resource is accessed
  • PROTOCOL_BRIDGE_ERROR -- when routing fails

Subscribe to these events for monitoring and debugging:

from fcc.messaging.events import EventType

bus.subscribe(
    EventType.PROTOCOL_MCP_TOOL_CALLED,
    lambda event: print(f"Tool called: {event.payload['tool']}")
)

Summary

In this tutorial you learned how to:

  • Generate A2A Agent Cards from R.I.S.C.E.A.R. persona specs with CardBuilder
  • Set up an MCP server with 14 tools, 14 resources, and 6 prompts
  • Route cross-protocol messages with ProtocolBridge
  • Generate an AGENTS.md manifest with AgentsMdGenerator
  • Monitor protocol operations through the event bus

Next Steps