Skip to content

Latest commit

 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AgentBridge

Define your AI agent tool once. Use it everywhere.

npm version npm downloads MIT license

One tool definition.  In-process calls, MCP over stdio, and remote HTTP — with the same validation, context, and safeguards.

AgentBridge project icon: a bridge connecting two sides

Why AgentBridge · Quick start · Features · Contributing

Why AgentBridge

An AI agent can reach the same capability through different doors: an in-process copilot, an MCP server, or a remote API.

When each door has its own implementation, the code eventually drifts. A validation rule is updated in one place but not another. A tenant check is forgotten. A retry creates the same campaign twice.

AgentBridge gives those doors one shared implementation:

                  ┌──────────────────┐
                  │  Tool definition │
                  │  schema + handler│
                  └────────┬─────────┘
                           │
            ┌──────────────┼──────────────┐
            │              │              │
      In-process          MCP            HTTP
       copilot           stdio       Streamable HTTP

Define the tool once. The catalog takes care of schema validation, tenant context, structured errors, and delivery through the interface your agent needs.

Quick start

Install

bun add @andersonbrdev/agentbridge
# or
npm install @andersonbrdev/agentbridge

Define a tool

import { z } from "zod";
import { createToolCatalog, defineTool } from "@andersonbrdev/agentbridge";

const searchCreators = defineTool({
  name: "search_creators",
  schema: z.object({ niche: z.string() }),
  handler: async (input, ctx) => {
    return db.query.creators.findMany({
      where: eq(creators.enterpriseId, ctx.tenantId),
    });
  },
});

const catalog = createToolCatalog({ tools: [searchCreators] });

Call it directly

call() returns a typed result instead of throwing for tool failures:

const { data, error } = await catalog.call(
  "search_creators",
  { niche: "beleza" },
  { tenantId, jwt },
);

if (error) {
  return handleFailure(error);
}

return data;

Expose the same catalog over MCP

await catalog.stdio({ tenantId, jwt });

Claude Desktop, Claude Code, and other MCP clients can now use the same tool implementation.

Serve it over HTTP

const handle = catalog.http((req) => ({
  tenantId: getTenantFromJWT(req.headers.get("authorization")),
  jwt: req.headers.get("authorization") ?? "",
}));

Bun.serve({ port: 3000, fetch: handle });

The tenant context is resolved from every request. The handler uses standard Request/Response APIs and works with Node, Bun, and Cloudflare Workers.

Features

One catalog, multiple interfaces

Use the same validated tool from your own copilot, a local MCP server, or a remote HTTP endpoint. There is no second copy to drift.

Tenant guard

Pass tenant context to every handler and optionally validate returned records before they leave the tool:

const listCreators = defineTool({
  name: "list_creators",
  schema: z.object({}),
  tenantField: "enterpriseId",
  handler: async (input, ctx) => {
    return db.query.creators.findMany({
      where: eq(creators.enterpriseId, ctx.tenantId),
    });
  },
});

If a returned record belongs to another tenant, the call fails with TENANT_LEAK before the data reaches the caller.

Audit trail

Every call can be recorded with onCall, including its tool, tenant, input, duration, result, and error code.

Rate limiting and idempotency

Protect your system from runaway agent loops and duplicate side effects:

rateLimit: { max: 20, windowMs: 60_000 }
dedupe: { windowMs: 5_000 }

Identical calls are coalesced within the configured deduplication window. Failed calls are never cached.

Approval gates

Require human approval before sensitive tools run:

requiresApproval: true

Without an approval hook, the catalog fails closed.

Tenant-aware visibility

Hide tools that a plan or tenant cannot use. Hidden tools do not appear in tools/list and are rejected as UNKNOWN_TOOL when called directly.

Shadow mode

Test how an agent would use a tool without running its real handler:

shadow: true

The full pipeline still runs, but only the audit hook fires.

Structured errors

Failures use stable codes such as RATE_LIMITED, TENANT_LEAK, and APPROVAL_REJECTED. Agents can use the retryable flag and, for rate limits, retryAfterMs.

See it work

bun install
bun run example   # in-process call + real MCP stdio call
bun run bench     # measures in-process dispatch overhead

The example defines search_creators once, calls it directly, and then calls it through a real MCP client. Both paths return the same result.

in-process: { tenant: "acme", creators: ["@beleza_creator_1", "@beleza_creator_2"] }
via MCP:    { tenant: "acme", creators: ["@beleza_creator_1", "@beleza_creator_2"] }

Scope

Available today:

  • call(name, input, ctx) — in-process, schema-validated invocation.
  • stdio(ctx) — local MCP server over stdio.
  • http(resolveTenant) — stateless Streamable HTTP handler.

The HTTP transport is stateless. Each request gets a fresh MCP server instance, so tenant context is not shared between concurrent requests.

Why this is not an agent framework

AgentBridge does not orchestrate agents, manage conversations, or choose an LLM. It solves one focused problem: defining a tool once and making it safely reachable from multiple callers.

If a tool has only one caller, a plain function may be all you need.

Contributing

Issues, pull requests, tests, documentation, and ideas are welcome.

git clone https://github.com/Andsu-dev/agentbridge.git
cd agentbridge
bun install
bun test

Please open an issue before a larger change so we can align on the direction first.

License

MIT © Anderson BR Dev

Links

Built in public by @andersonbrdev.

About

Define an AI agent tool once, call it in-process or serve it over MCP.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages