LaserData Cloud
Laser SDK

Interop

Reach the same agents over A2A, MCP, and AG-UI.

Internal agents use AGDX on the log. External clients keep their public protocols. Optional bridges map shared fields into AGDX envelopes. Protocol-specific data stays unchanged in the body.

A2A (a2a-bridge)

A2aBridge exposes an internal agent to A2A JSON-RPC clients and serves the v1.0 Agent Card 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 surface provides router() for mounting the JSON-RPC endpoint and Agent Card route. TypeScript and Python expose the same bridge operations for the host HTTP adapter.

With the sign feature, A2aBridge::signed_card attaches a detached JWS over the agent card so a client can verify authenticity before trusting it.

MCP (mcp-bridge)

McpBridge is an MCP JSON-RPC server mapping tool calls onto AGDX commands and awaiting the correlated reply over the log.

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 the Axum router(). TypeScript and Python expose the protocol operations directly so the application can connect them to its HTTP framework.

AG-UI (agui)

AG-UI is frontend-facing. Two pieces ship over the log:

  • State sync - publish_state_snapshot / publish_state_delta emit shared state and RFC 6902 patches. reconstruct_state replays a snapshot plus later deltas into the current state at any historical offset.
  • Event rendering - agui_events turns a conversation into AG-UI events: chat chunks to TEXT_MESSAGE_*, reasoning to REASONING_MESSAGE_*, tool-arg streams to TOOL_CALL_START/ARGS/END, status updates to RUN_STARTED/RUN_FINISHED, and error terminals 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

Independent of any bridge, Agdx::request_input pauses an agent on a human decision and AgentCtx::respond_input resolves it - both compose the existing command/response verbs, adding nothing new to the wire:

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

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

Rust's with_default_stream, TypeScript's withDefaultStream, and Python's with_stream select a stream on one connection. Use separate credentials when Iggy RBAC must isolate access. Governance covers audience and step-up checks.

Running it

Bridges do not depend on a model provider. They run on every VSR target. Use Laser Stack when a scenario also needs managed APIs.

On this page