LaserData Cloud
Laser SDK

Interop

Expose agents through A2A, MCP, and AG-UI without changing their internal messages

Bridges connect external A2A, MCP, and AG-UI clients to agents that use AGDX. They map shared fields into AGDX messages. Protocol-specific data stays unchanged in the body.

A2A (a2a-bridge)

A2aBridge serves an internal agent through A2A JSON-RPC. Its v1.0 Agent Card is available at /.well-known/agent-card.json.

A2A methodMapping
SendMessagePublishes a typed AGDX command on a fresh task conversation - the task id is the conversation. Returns Submitted.
SendStreamingMessageSame publish as SendMessage - the stream is consumed log-natively, never re-emitted as SSE.
GetTaskReads the reply topic and maps the answering response/error envelope to the A2A task. Working until one lands.
CancelTaskPublishes an AGDX error terminal (Cancelled). Returns Canceled.
const bridge = new A2aBridge(
  laser,
  AgentId.new("a2a-gateway"),
  AgentTopic.Commands,
  AgentTopic.Responses
)
const card = bridge.card()
let bridge = Arc::new(A2aBridge::new(
    laser.clone(),
    "a2a-gateway".parse()?,
    AgentTopic::Commands,
    AgentTopic::Responses,
));
let card = bridge.card();
let app = bridge.router();
bridge = laser.a2a_bridge(
    "a2a-gateway",
    ls.Topics.COMMANDS,
    ls.Topics.RESPONSES,
)
card = bridge.card()

Rust's optional HTTP support provides router() for the JSON-RPC and Agent Card routes. TypeScript and Python expose bridge operations for the application's HTTP adapter.

With sign, A2aBridge::signed_card adds a detached JWS signature to the card. Clients can use it to authenticate the card before trusting it.

MCP (mcp-bridge)

McpBridge maps MCP JSON-RPC tool calls to AGDX commands. It waits for a reply with the matching correlation, the identifier that links a reply to its request.

MCP methodMapping
initializeEchoes the client's protocol version and advertises only the capabilities actually served.
tools/list / tools/callTools configured via with_tool - a call publishes an AGDX command and renders the correlated reply as a tool result.
resources/list / resources/readResources configured via with_resource, served from config.
prompts/list / prompts/getPrompts configured via with_prompt.
const mcp = new McpBridge(
  laser,
  AgentId.new("mcp-gateway"),
  AgentTopic.ToolCalls,
  AgentTopic.ToolResults,
  "my-server"
).withTool(
  "ask",
  "ask the assistant",
  { type: "object" }
)

const tools = mcp.listTools()
let mcp = Arc::new(
    McpBridge::new(
        laser.clone(),
        "mcp-gateway".parse()?,
        AgentTopic::ToolCalls,
        AgentTopic::ToolResults,
        "my-server",
    )
    .with_tool(
        "ask",
        Some("ask the assistant".into()),
        serde_json::json!({ "type": "object" }),
    ),
);

let tools = mcp.list_tools();
let app = mcp.router();
mcp = laser.mcp_bridge(
    "mcp-gateway",
    ls.Topics.TOOL_CALLS,
    ls.Topics.TOOL_RESULTS,
    "my-server",
    tools=[
        {
            "name": "ask",
            "description": "ask the assistant",
            "input_schema": {"type": "object"},
        }
    ],
)

tools = mcp.list_tools()

Rust's mcp-http feature supplies an Axum router(). Connect TypeScript or Python protocol operations to the application's HTTP framework.

AG-UI (agui)

AG-UI provides state and events for frontends. publish_state_snapshot and publish_state_delta record shared state and RFC 6902 patches. reconstruct_state applies a snapshot and later patches up to a historical offset.

agui_events converts a conversation into AG-UI events. Chat chunks become TEXT_MESSAGE_*, and reasoning becomes REASONING_MESSAGE_*. Tool arguments map to TOOL_CALL_START, ARGS, and END. Status maps to RUN_STARTED and RUN_FINISHED, while terminal errors map to RUN_ERROR.

await laser.publishStateSnapshot(
  AgentTopic.Audit,
  AgentId.new("ui"),
  conversation,
  { count: 0 }
)

const events = await laser.aguiEvents(
  conversation,
  AgentTopic.LlmIo
)
laser
    .publish_state_snapshot(
        AgentTopic::Audit,
        "ui".parse()?,
        conversation,
        &serde_json::json!({ "count": 0 }),
    )
    .await?;

let events = laser
    .agui_events(conversation, AgentTopic::LlmIo)
    .await?;
await laser.publish_state_snapshot(
    ls.Topics.AUDIT,
    "ui",
    conversation_id,
    {"count": 0},
)

events = await laser.agui_events(
    conversation_id,
    ls.Topics.LLM_IO,
)

Human-in-the-loop

Agdx::request_input pauses an agent for a human decision. AgentCtx::respond_input supplies the decision. Both use existing command and response operations and need no bridge or new wire type.

const decision = await laser
  .agdx(
    AgentTopic.HumanInput,
    AgentId.new("orchestrator"),
    conversation
  )
  .requestInput(
    AgentTopic.Responses,
    utf8("approve a $500 credit?"),
    300_000
  )
let decision = laser
    .agdx(
        AgentTopic::HumanInput,
        "orchestrator".parse()?,
        conversation.into(),
    )
    .request_input(
        AgentTopic::Responses,
        b"approve a $500 credit?".to_vec(),
        Duration::from_secs(300),
    )
    .await?;
decision = await laser.agdx(
    ls.Topics.HUMAN_INPUT,
    "orchestrator",
    conversation_id,
).request_input(
    ls.Topics.RESPONSES,
    b"approve a $500 credit?",
    timeout_secs=300,
)

Authorization

Add authentication in the hosting HTTP framework. The JSON-RPC bridges do not authenticate HTTP requests.

with_default_stream in Rust, withDefaultStream in TypeScript, and with_stream in Python select a stream on the same connection. Use separate credentials when Iggy permissions must isolate access. See Governance for audience and step-up requirements.

Running it

Bridges work with Iggy and do not depend on a model provider. Use Laser Stack if the application also needs managed operations.

On this page