@arbium/enterprise-agent-connect

中文文档
0.4.0发布于 2026/11/09Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 2276

Enterprise Agent Connect server: the service, its migrations and the built-in connectors

原始使用文档
文档目录8 节
  1. Quick start (about 10 minutes)
  2. Use it in your code
  3. Error reporting
  4. When a runner does not answer
  5. Kubernetes
  6. Layout
  7. License
  8. Development

Enterprise Agent Connect

Connect your company's apps once, then give each AI agent its own key with only the access you grant it. Agents reach the apps through one MCP endpoint; every call is checked against the agent's grants and written to an audit log. Agents never hold the apps' credentials.

  • Built-in apps: Jira, Microsoft 365 (Outlook mail and calendar, SharePoint files and Excel, Teams, Entra ID, Intune, Dynamics 365), Jamf Pro, Okta, Slack, Salesforce, Gong, SAP S/4HANA, Oracle NetSuite, ServiceNow, Workday, FedEx, Dell TechDirect, GitHub; and, described in JSON only, GitLab, Argo CD, SonarQube, Datadog, PagerDuty, Grafana, Zephyr Scale, Confluence, Lusha, ChurnZero, Adestra, Cvent, Forsta Surveys (Decipher), Intercom, Sprout Social, Synthesia, WordPress, Automox, Qualys, Druva, Drata, SafeBase, Adobe Admin Console, Ironclad, OneStream, Zip, UKG Pro, iCIMS, ZoomInfo, DocuSign, Axonius.
  • Your own apps without code: describe an internal REST API or a SaaS app's in one connector.json, or draft one from its OpenAPI spec (docs/http-connector.md); a SOAP service (docs/soap-connector.md) or an OData service such as SAP S/4HANA, Dynamics 365 or SuccessFactors (docs/odata-connector.md), drafted from its WSDL or $metadata; or a database (PostgreSQL, SQL Server, MySQL, MariaDB, Oracle) as named queries (docs/sql-connector.md).
  • Per-agent access: tick what each agent can do per app, and limit it to values (mailboxes, sites, projects, customers). Anything that deletes needs an explicit confirmation.
  • Agents started by app events: a Jira issue created or updated starts the agents you choose, each within its own limits, by webhook or by polling apps that cannot send one (docs/triggers.md).
  • Self-hosted: one Node service and Postgres. No UI ships; the admin API (packages/server/openapi.json) and the typed client (packages/client) are for building your own admin pages.

How it is secured: docs/security.md. Adding apps: docs/connectors.md. Running it next to internal systems (your own certificate authority, a proxy in front): docs/inside-your-network.md.

Quick start (about 10 minutes)

Needs Docker with Compose v2, openssl and curl. This runs the service, Postgres and a small example internal app (an orders API), then gives an agent read access to one customer's orders.

cd deploy/docker
cp .env.example .env
sed -i.bak "s|^CONNECT_ENCRYPTION_KEY=|CONNECT_ENCRYPTION_KEY=$(openssl rand -base64 32)|; \
  s|^AGENT_CONNECT_ADMIN_TOKEN=|AGENT_CONNECT_ADMIN_TOKEN=$(openssl rand -hex 32)|; \
  s|^POSTGRES_PASSWORD=|POSTGRES_PASSWORD=$(openssl rand -hex 24)|" .env && rm .env.bak
docker compose -f docker-compose.yml -f docker-compose.example.yml up --build --wait

Keep CONNECT_ENCRYPTION_KEY: it seals every stored credential, and nothing saved opens without it. The service listens on port 3000 of this machine only and applies its database migrations.

1. Make an organization and connect the example app (its API key is example-key):

TOKEN=$(grep ^AGENT_CONNECT_ADMIN_TOKEN .env | cut -d= -f2)
admin() { curl -s -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  "http://localhost:3000/agent-connect/admin/v1$@"; }

ORG=$(admin /orgs -X POST -d '{"name":"Example Org"}' | sed 's/.*"id":"\([^"]*\)".*/\1/')
admin /orgs/$ORG/connections/orders -X PUT \
  -d '{"capabilities":["read","notes"],"fields":{"api_key":"example-key"},"connectedBy":"Example Admin"}'

capabilities is the most any agent may get from this app; connecting runs the app's check.

2. Make an agent and grant it "Read orders" for customer acme only. The key is shown once:

NEW=$(admin /orgs/$ORG/agents -X POST -d '{"name":"demo","createdBy":"Example Admin"}')
KEY=$(echo "$NEW" | sed 's/.*"key":"\([^"]*\)".*/\1/')
AGENT=$(echo "$NEW" | sed 's/.*"agent":{[^}]*"id":"\([^"]*\)".*/\1/')
admin /orgs/$ORG/agents/$AGENT/grants/orders -X PUT \
  -d '{"capabilities":["read"],"limits":{"customers":["acme"]},"by":"Example Admin"}'

3. Use it from Claude Code:

claude mcp add --transport http orders http://localhost:3000/agent-connect/mcp \
  --header "Authorization: Bearer $KEY"

Ask Claude for acme's orders, then for order 1002 (a different customer's): the first works, the second is refused, and both are in the audit log (admin /orgs/$ORG/audit). Any MCP client works the same way, for example curl:

curl -s http://localhost:3000/agent-connect/mcp -H "Authorization: Bearer $KEY" \
  -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"orders_get","arguments":{"id":"1001"}}}'

Stop with docker compose -f docker-compose.yml -f docker-compose.example.yml down (add -v to delete the database too).

Going further: connect a real app from GET /agent-connect/admin/v1/connectors (each one lists its setup steps, as click paths and as commands), add your own app (docs/http-connector.md), or embed the service in your own Node app (below).

Only /agent-connect/mcp, /agent-connect/teams and /agent-connect/events may be public. The admin API refuses any request that carries X-Forwarded-For, so never route it through a public load balancer.

Use it in your code

npm install @arbium/enterprise-agent-connect @arbium/enterprise-agent-connect-client

Both packages ship TypeScript types. Every snippet here is run by scripts/consumer-test.sh (full files: scripts/consumer/) against the packed packages, in CI.

Embed the service. One instance per process, in a module of its own:

import { builtinConnectors, createAgentConnect } from "@arbium/enterprise-agent-connect";

export const eac = await createAgentConnect({
  database: { url: process.env.DATABASE_URL! }, // or a postgres.js instance
  encryptionKey: process.env.CONNECT_ENCRYPTION_KEY!,
  adminToken: process.env.AGENT_CONNECT_ADMIN_TOKEN!,
  connectors: [...builtinConnectors, orders, inventory], // default: every built-in app
});
eac.start(); // the Teams and app event workers; await eac.stop() on shutdown

connectors is the list of apps this deployment offers. Leave it out for every built-in app; pass builtinConnectors.filter((c) => ["jira", "fedex"].includes(c.id)) for only some.

eac.fetch is a plain (Request) => Promise<Response> serving /agent-connect/*. Mount it at that path, before any body parser (Express and plain Node use @hono/node-server):

// Hono
app.all("/agent-connect/*", (c) => eac.fetch(c.req.raw));

// Express 5
import { getRequestListener } from "@hono/node-server";
app.all("/agent-connect/*splat", getRequestListener(eac.fetch));

// Next.js: app/agent-connect/[...path]/route.ts (Node runtime)
const handler = (req: Request) => eac.fetch(req);
export { handler as GET, handler as POST, handler as PUT, handler as PATCH, handler as DELETE };

// Plain Node
import { serve } from "@hono/node-server";
serve({ fetch: eac.fetch, port: 3000 });

The typed admin client. The admin API refuses requests that came through a load balancer (X-Forwarded-For), so call it from your server, over the network or in the same process (behind a proxy: docs/inside-your-network.md):

import { createClient } from "@arbium/enterprise-agent-connect-client";

const admin = createClient({ baseUrl: "http://localhost:3000", token: process.env.AGENT_CONNECT_ADMIN_TOKEN! });
// In the same process as eac, with no network hop:
// createClient({ baseUrl: "http://agent-connect", token, fetch: (input, init) => eac.fetch(new Request(input, init)) })

const org = await admin.organizations.create("Example Org");
await admin.connect(org.id, "orders", {
  capabilities: ["read"],
  fields: { api_key: "example-key" },
  connectedBy: "Example Admin",
});
const { agent, key } = await admin.createAgent(org.id, { name: "demo", createdBy: "Example Admin" });
await admin.setGrant(org.id, agent.id, "orders", ["read"], { customers: ["acme"] }, "Example Admin");
const rows = await admin.audit(org.id, { agent: agent.id });

Your own app, no code. Pass a connector definition with an http block (docs/http-connector.md), or a soap or odata block, straight into connectors. It is checked like a connector.json; the type is ConnectorJson:

import type { ConnectorJson } from "@arbium/enterprise-agent-connect";

const orders: ConnectorJson = {
  id: "orders",
  name: "Orders",
  // ...setup steps, fields, capabilities, limits and verify, as in a connector.json
  http: {
    baseUrl: "https://orders.internal.example.com/api",
    auth: { type: "header", header: "X-Api-Key", value: "api_key" },
    tools: [{ name: "orders_get", description: "Read one order by its id.", method: "GET", path: "/orders/{id}",
      params: { id: { type: "string", description: "The order id", in: "path" } } }],
  },
};

Your own app, per organization. Each organization can add its own apps through the admin API, as many as it wants, without a restart: see Add internal apps through the admin API.

Your own app, as code. Build a ConnectorModule on the connector kit and give it its definition as connector (docs/connectors.md). create gets a ConnectorContext, { connector }: the definition parsed, with the branding filled in.

import { z } from "zod";
import { audited, Denied, json, listLimit, type ConnectorModule } from "@arbium/enterprise-agent-connect/kit";

const inventory: ConnectorModule = {
  id: "inventory",
  connector: { id: "inventory", /* ...the rest of its connector.json */ },
  create: () => ({
    vendor: { connect: async (fields) => ({ ... }), call: async (connection, request) => ({ ... }) },
    toolset: {
      names: ["inventory_get_stock"],
      register(server, ctx, allowed) {
        if (allowed.has("inventory_get_stock"))
          server.registerTool("inventory_get_stock",
            { description: "How many of one item are in stock.", inputSchema: z.object({ sku: z.string() }) },
            ({ sku }) => audited(ctx, "inventory_get_stock", sku, async (mark) => {
              const skus = listLimit(ctx.limits, "skus");
              if (skus && !skus.includes(sku)) throw new Denied(`This agent may not read ${sku}`);
              const res = await ctx.call({ method: "GET", path: `/stock/${encodeURIComponent(sku)}` });
              mark(res.status);
              return json(res.data);
            }));
      },
    },
  }),
};

Connect an agent. Each agent uses its own key (key above) on one MCP endpoint, <your origin>/agent-connect/mcp:

  • Claude Code: claude mcp add --transport http agent-connect https://agents.example.com/agent-connect/mcp --header "Authorization: Bearer $KEY"
  • The Claude API's MCP connector (beta mcp-client-2025-11-20): mcp_servers: [{ type: "url", url: "https://agents.example.com/agent-connect/mcp", name: "agent-connect", authorization_token: key }] with tools: [{ type: "mcp_toolset", mcp_server_name: "agent-connect" }]. The endpoint must be reachable from the internet.
  • Claude Managed Agents: a vault static_bearer credential holding the key, keyed on the same URL.
  • Any MCP client over Streamable HTTP: Authorization: Bearer <key>; the quick start's curl shows the raw call.

Error reporting

Pass onError to createAgentConnect to hear of failures as they happen, in whatever error service you use. Left out, nothing is called. The container's entry is exported as serveFromEnv from @arbium/enterprise-agent-connect/server, so a small file of your own can start the same server from the same environment and add the hook:

import { serveFromEnv } from "@arbium/enterprise-agent-connect/server";

await serveFromEnv({
  onError: (e) => {
    // e.g. your error service's capture call, or a POST to its ingest endpoint
    console.log(JSON.stringify({ level: "error", ...e }));
  },
});

Each kind is reported where it is decided, at most once a minute per kind, app, organization, subscription and bot in each process (so a broken one cannot flood the sink or hide another). What the hook throws, or an async hook rejects with, is logged and never reaches the service; a hook that sends over the network should not block (fire and forget).

KindWhen
teams_send_faileda file, file card, approval card edit or notice the bot sends in Teams fails
teams_event_faileda message from Teams could not be handled
teams_run_start_faileda Teams trigger's run could not start
event_failedan app event could not be handled
event_run_start_failedan app event trigger's run could not start
event_poll_failedan app event subscription's poll or Graph sync starts failing (not each retry)
vendor_auth_failedan app answers 401 or 403 to an audited call

The event (AgentConnectError):

Field
kindone of the kinds above; group by it
tagsthe ids that apply, as strings: org, agent, app, bot, subscription, source, tool, runner, what (what Teams refused: file, file_card, approval_card, notice), status
errorClass, code, statusthe error's class name, its code and HTTP status, when it has them
frameswhere it happened, {function, file, line, column}, innermost first, when there is an error
timestampISO 8601

Never the error's message, a message body, a token or a person's details: a message can hold a vendor's or a person's words. The log lines stay as they were.

When a runner does not answer

A Teams message or app event whose runner does not answer, or answers 5xx, 408, 409 or 429, is handed over again for up to 15 minutes (RUNNER_RETRY_SECONDS, 0 to 3600; 0 never retries), each attempt carrying the same X-Agent-Connect-Delivery id for the receiver to dedupe on. docs/triggers.md, When the runner does not answer, has the schedule.

Kubernetes

To offer only some built-in apps, set BUILTIN_APPS to their ids, comma-separated (for example jira,servicenow,sap-s4hana; GET /agent-connect/admin/v1/connectors lists every id). Unset, every built-in app is offered. Apps you add with CONNECTOR_DIRS are always offered.

deploy/helm/agent-connect is a small chart: a Deployment (replicaCount, one by default, any number is safe), a Service, and an optional Ingress with only the public paths. It needs the image (image.repository, image.tag; releases are ghcr.io/try-caret/enterprise-agent-connect:<version>) and a Secret with DATABASE_URL, CONNECT_ENCRYPTION_KEY and AGENT_CONNECT_ADMIN_TOKEN (existingSecret). For apps whose certificates your own authority signed, name a ConfigMap or Secret holding its bundle in caBundle (docs/inside-your-network.md).

Layout

  • packages/server: the service (HTTP, admin API, MCP, Teams, app events, store, migrations) and its tests
  • packages/connector-kit: what a connector builds on (schemas, tool helpers, vendor types)
  • packages/client: typed admin API client
  • connectors/<app>: one folder per app: connector.json, and for apps with code index.ts, tools.ts, vendor.ts
  • connectors/http, connectors/soap, connectors/odata, connectors/sql: run any app described only in its connector.json (docs/http-connector.md, docs/soap-connector.md, docs/odata-connector.md, docs/sql-connector.md)
  • examples/internal-app: a small internal API and its connector, for the quick start
  • scripts/openapi-to-connector.ts: drafts an http connector from an OpenAPI 3.x spec (the drafting itself is connectors/http/openapi.ts, also behind the admin API's internal app drafts)
  • examples/legacy-apps: a SOAP and an SAP OData connector, described only in JSON
  • scripts/wsdl-to-connector.ts, scripts/odata-to-connector.ts: draft a connector from a WSDL or an OData $metadata
  • examples/legacy-orders: a small orders database and its connector
  • deploy/docker, deploy/helm: image, Compose file and chart

License

Apache License 2.0.

Development

npm ci
npm run check
scripts/test-databases.sh postgres://postgres@localhost:5432   # a scratch Postgres
TEST_DATABASE_URL=postgres://postgres@localhost:5432/ac_test DATABASE_SSL=disable npm test

版本与发布记录

@arbium/enterprise-agent-connect 共发布 24 个版本,首次发布于 2026/10/27;过去 12 个月发布了 24 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.4.0latest2026/11/09今天
0.3.1-dev.7cb2817dev2026/11/09今天
0.3.1-dev.0d984ff2026/11/08今天
0.3.1-dev.ec259a32026/11/08今天
0.3.1-dev.b4962322026/11/08今天
0.3.1-dev.e46a2202026/11/08今天

关于 @arbium/enterprise-agent-connect 的常见问题

@arbium/enterprise-agent-connect 是什么?

Enterprise Agent Connect server: the service, its migrations and the built-in connectors

如何安装 @arbium/enterprise-agent-connect?

在项目目录运行 npm install @arbium/enterprise-agent-connect,或使用 pnpm add / yarn add / bun add。

@arbium/enterprise-agent-connect 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@arbium/enterprise-agent-connect 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@arbium/enterprise-agent-connect 可以商用吗?

@arbium/enterprise-agent-connect 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@arbium/enterprise-agent-connect 的下载量有多少?

根据 npm 官方下载统计,@arbium/enterprise-agent-connect 在 2026/10/07 – 2026/11/05 这 30 天内被下载 2,276 次,日均约 76 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@arbium/enterprise-agent-connect 还在维护吗?

最新版本 0.4.0 发布于 2026/11/09(今天);至今共发布 24 个版本,其中过去 12 个月发布了 24 个;首次发布于 2026/10/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。