Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ Add to your Claude Code config (`~/.claude.json` or project `.mcp.json`):
"args": ["/absolute/path/to/agent-bridge/dist/index.js"],
"env": {
"AGENT_BRIDGE_URL": "https://your-project.supabase.co",
"AGENT_BRIDGE_KEY": "your-anon-key-here"
"AGENT_BRIDGE_KEY": "your-anon-key-here",
"AGENT_BRIDGE_AGENT": "codex"
}
}
}
Expand All @@ -98,7 +99,7 @@ Restart your MCP client. Three tools become available: `post_context`, `get_cont

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `source` | string | yes | Agent name (e.g. `"claude-code"`) |
| `source` | string | yes | Agent name. When `AGENT_BRIDGE_AGENT` is set, this must match it. |
| `category` | string | yes | Entry type (see [Categories](#categories)) |
| `content` | string | yes | The context message |
| `priority` | string | no | `info` (default), `high`, `urgent` |
Expand Down Expand Up @@ -155,6 +156,8 @@ cp bin/agent-bridge ~/.openclaw/scripts/agent-bridge

The CLI reads credentials from `~/.agent-bridge/config` (created in step 3).

When `AGENT_BRIDGE_AGENT` is set for the MCP server or CLI, posts with a different `source` are rejected. This prevents a wrapped runtime from writing rows labelled as another agent after atrib has already signed the original tool arguments.

### CLI Reference

```bash
Expand Down
9 changes: 9 additions & 0 deletions bin/agent-bridge
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ cmd_post() {
esac
done

if [[ -n "${AGENT_BRIDGE_AGENT:-}" ]]; then
if [[ -z "$source" ]]; then
source="$AGENT_BRIDGE_AGENT"
elif [[ "$source" != "$AGENT_BRIDGE_AGENT" ]]; then
echo "Error: --source must match AGENT_BRIDGE_AGENT ($AGENT_BRIDGE_AGENT); got $source" >&2
exit 1
fi
fi

if [[ -z "$source" || -z "$category" || -z "$content" ]]; then
echo "Error: --source, --category, and content are required" >&2
exit 1
Expand Down
18 changes: 15 additions & 3 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
filterContextRows,
mergeEnvelopeMetadata,
} from "./message-envelope.js";
import { resolvePostSource } from "./source-identity.js";

const headers = {
apikey: SUPABASE_KEY,
Expand Down Expand Up @@ -67,7 +68,8 @@ export async function startServer() {
properties: {
source: {
type: "string",
description: 'Agent posting this entry (e.g. "claude-code", "sido")',
description:
"Agent posting this entry. Must match AGENT_BRIDGE_AGENT when configured.",
},
category: {
type: "string",
Expand Down Expand Up @@ -236,10 +238,20 @@ export async function startServer() {

switch (name) {
case "post_context": {
let source: string | undefined;
try {
source = resolvePostSource(args?.source, process.env.AGENT_BRIDGE_AGENT);
} catch (e) {
throw new McpError(
ErrorCode.InvalidParams,
e instanceof Error ? e.message : "source identity mismatch"
);
}
const postArgs = { ...(args ?? {}), source };
const receiptId = normalizeReceiptId(args?.atrib_receipt_id);
const envelope = buildMessageEnvelope(args, receiptId);
const envelope = buildMessageEnvelope(postArgs, receiptId);
const body: Record<string, unknown> = {
source: args?.source,
source,
category: args?.category,
content: args?.content,
priority: args?.priority || "info",
Expand Down
21 changes: 21 additions & 0 deletions src/source-identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
function normalizeSource(value: unknown): string | undefined {
if (typeof value !== "string") return undefined;
const trimmed = value.trim();
return trimmed ? trimmed : undefined;
}

export function resolvePostSource(
requestedSource: unknown,
configuredAgent: unknown
): string | undefined {
const source = normalizeSource(requestedSource);
const configured = normalizeSource(configuredAgent);

if (source && configured && source !== configured) {
throw new Error(
`source must match AGENT_BRIDGE_AGENT (${configured}); got ${source}`
);
}

return source ?? configured;
}
22 changes: 22 additions & 0 deletions test/source_identity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { resolvePostSource } from "../src/source-identity.js";

describe("resolvePostSource", () => {
it("accepts a source that matches the configured agent", () => {
expect(resolvePostSource("codex", "codex")).toBe("codex");
});

it("falls back to the configured agent when no source is passed", () => {
expect(resolvePostSource(undefined, "claude-code")).toBe("claude-code");
});

it("keeps legacy source arguments when no configured agent exists", () => {
expect(resolvePostSource("sido", undefined)).toBe("sido");
});

it("rejects source labels that do not match the configured agent", () => {
expect(() => resolvePostSource("claude-code", "codex")).toThrow(
"source must match AGENT_BRIDGE_AGENT (codex); got claude-code"
);
});
});
Loading