Integrate DRS with an A2A agent (Node / TypeScript)

Agent-to-Agent (A2A) differs from MCP in shape — both agents sit at equal standing and exchange tasks — but the DRS integration story is the same: the caller attaches a receipt bundle, the receiver verifies it before acting.

This guide covers the receiver side in Node. The caller side is the same as React Native issuance and MCP Node integration — you issue an invocation with issueInvocation.

Architecture

Agent A (initiator)                  Agent B (receiver)
     │
     │ POST /a2a/task
     │ X-DRS-Bundle: eyJ...
     ▼
┌──────────────────┐         ┌─────────────────────┐
│ Agent B (Node)   │────────▶│ drs-verify sidecar  │
│ 1. extract hdr   │  POST   │ ghcr.io/okeyamy/... │
│ 2. /verify       │ /verify │                     │
│ 3. if valid →    │◀────────│                     │
│    run task      │         └─────────────────────┘
└──────────────────┘

Agent B is structurally the same as an MCP tool server — both verify an inbound bundle before executing. If you've already set up the MCP integration the code here is almost identical.

Install

pnpm add -D @okeyamy/drs-sdk

Type-only. The actual verification happens in the drs-verify container.

Compose with Redis for shared replay protection

If Agent B is horizontally scaled across multiple instances, you need shared replay protection or an attacker can submit the same bundle to each replica in turn.

services:
  agent-b:
    build: .
    deploy:
      replicas: 3
    environment:
      DRS_VERIFY_URL: http://drs-verify:8080

  drs-verify:
    image: ghcr.io/okeyamy/drs-verify:latest
    environment:
      NONCE_STORE_BACKEND: redis
      REDIS_URL: redis://redis:6379/0
    deploy:
      replicas: 2      # drs-verify itself can also scale — state is in Redis

  redis:
    image: redis:7-alpine

A2A middleware

// a2a-middleware.ts
import type { ChainBundle, VerificationResult } from "@okeyamy/drs-sdk";

const VERIFY_URL = process.env.DRS_VERIFY_URL ?? "http://localhost:8080";

export async function drsA2A(req, res, next) {
  const bundleHeader = req.headers["x-drs-bundle"];
  if (!bundleHeader || typeof bundleHeader !== "string") {
    return res.status(401).json({ error: "Missing X-DRS-Bundle" });
  }

  const bundle: ChainBundle = JSON.parse(
    Buffer.from(bundleHeader, "base64url").toString("utf8"),
  );

  const r = await fetch(`${VERIFY_URL}/verify`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(bundle),
  });
  const result = (await r.json()) as VerificationResult;

  if (!result.valid) return res.status(403).json(result);

  (req as any).drs = result.context;
  next();
}

Task handler

import express from "express";
import { drsA2A } from "./a2a-middleware.js";

const app = express();
app.use(express.json({ limit: "1mb" }));

app.post("/a2a/task", drsA2A, async (req, res) => {
  // req.drs.root_principal is the original human/organisation
  // req.drs.leaf_policy is the effective policy AFTER attenuation
  const { task_type, payload } = req.body;

  // A2A-specific: enforce that the task matches what's allowed by policy.
  const allowedTools = req.drs.leaf_policy?.allowed_tools ?? [];
  if (allowedTools.length > 0 && !allowedTools.includes(task_type)) {
    return res.status(403).json({
      error: "task_type not in allowed_tools",
      allowed: allowedTools,
    });
  }

  const result = await runA2ATask(task_type, payload, {
    onBehalfOf: req.drs.root_principal,
  });
  res.json(result);
});

app.listen(3000);

JSON-RPC variant

Some A2A deployments use JSON-RPC instead of plain HTTP. The DRS spec allows the bundle to live in _meta["X-DRS-Bundle"] instead of a header.

app.post("/a2a/rpc", express.json(), async (req, res) => {
  const bundleStr = req.body?._meta?.["X-DRS-Bundle"];
  if (!bundleStr) return res.status(401).json({ error: "missing bundle" });

  const bundle = JSON.parse(
    Buffer.from(bundleStr, "base64url").toString("utf8"),
  );
  const r = await fetch(`${VERIFY_URL}/verify`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(bundle),
  });
  const result = await r.json();

  if (!result.valid) {
    return res.json({
      jsonrpc: "2.0",
      id: req.body.id,
      error: { code: -32001, message: "DRS verification failed", data: result.error },
    });
  }

  // dispatch on req.body.method ...
});