API / @aihu/agent-server

@aihu/agent-server

Agents & governance

Server-side glue: mount an aihu component server-side and let an MCP client drive it through the agent-service live-dispatch gate, forwarding approved invocations to a browser bridge.

version
0.5.0
exports
29
values
10
types
19
01

createAgentServer

functionagent
function createAgentServer(options: AgentServerOptions): AgentServer
02

createBridgeClient

functionagent
function createBridgeClient(options: BridgeClientOptions): BridgeClient

Start a browser bridge client.

03

createBridgeNonceStore

functionagent
function createBridgeNonceStore(): BridgeNonceStore
04

createComponentMcpServer

functionagent
function createComponentMcpServer( agentServer: AgentServer, metas: AgentMetadata[] = getAllAgentMetadata(), ): Server
05

opaqueActionId

functionagent
function opaqueActionId(tag: string, member: string): string

Deterministic, stable opaque ID for an agent-exposed member — identical to the compiler's `opaque_member_id(tag, member)`.

06

opaqueActionIdForTool

functionagent
function opaqueActionIdForTool(toolName: string): string | null

Opaque ID directly from a `"<tag>/<member>"` tool name; null if malformed.

07

parseToolName

functionagent
function parseToolName(toolName: string): { tag: string; member: string } | null

Split a `"<tag>/<member>"` tool name.

08

serveComponentMcp

functionagent
async function serveComponentMcp( agentServer: AgentServer, metas?: AgentMetadata[], ): Promise<Server>

Convenience: create the component MCP server and connect it over stdio (the same transport `@aihu/mcp` uses).

09

verifyBridgeUpgrade

functionagent
function verifyBridgeUpgrade( req: Request, options: VerifyBridgeUpgradeOptions, ): BridgeUpgradeVerdict

Decide whether an HTTP upgrade request for the capability bridge should be accepted, based on its `Origin` header.

10

BRIDGE_PROTOCOL_VERSION

constagent
const BRIDGE_PROTOCOL_VERSION
11

AgentDispatcher

interfaceagent
interface AgentDispatcher {
  readonly tag: string
  /** opaqueId → action invoker (called with the positional args array). */
  readonly actions: Record<string, (args: unknown[]) => unknown>
  /** opaqueId → read accessor (current signal value). */
  readonly reads: Record<string, () => unknown>
  /** opaqueId → write accessor. */
  readonly writes: Record<string, (value: unknown) => void>
}

The compiler-emitted `__agentDispatcher` shape (client artifact, T1).

12

AgentServer

interfaceagent
interface AgentServer {
  /** The underlying agent-service (exposes `handleToolCall`, `asMiddleware`). */
  readonly service: AgentService
  /** The `MountScope` of the server-mounted component. */
  readonly mount: MountScope

  /**
   * Run a tool call through the full agent-service gate. On authorization, the
   * dispatch is applied to the server-mounted binding AND, if a browser bridge
   * is attached, forwarded to it as a {@link BridgeInvokeMessage}.
   *
   * Returns the agent-service envelope: `{ result }` on success, or
   * `{ error, code, jsonrpc }` on a gate rejection (404/401/403/429). The HTTP
   * code is never mutated here — the gate is the sole policy authority.
   */
  callTool(toolName: string, params: unknown, ctx?: RequestContext): Promise<unknown>

  /** Current serialized state of the server-mounted component. */
  serialize(): Snapshot

  /**
   * Attach a browser bridge channel. Approved invocations are forwarded to it;
   * `result`/`error`/`snapshot` frames from it resolve pending `callTool`
   * promises. Returns a detach function.
   *
   * The channel MUST complete the `hello` handshake (see
   * {@link BRIDGE_PROTOCOL_VERSION}) before anything is delegated to it. Until
   * it does, `hello` is the only frame it may send, and `callTool` refuses to
   * forward — an unverified channel never becomes the execution authority.
   * Attaching a new channel always resets this state; verified status is never
   * inherited from a previous peer.
   */
  attachBridge(channel: BridgeChannel): () => void

  /** Issue a single-use nonce for a forthcoming bridge handshake. */
  issueBridgeNonce(ttlMs?: number): BridgeNonce

  /** Revoke a verified bridge identity (or its exact grant) and cancel its pending calls. */
  revokeBridgeSession(identity: string, scope?: { readonly grantVersion: string }): void

  /**
   * Build the MCP `Server` (stdio-ready) exposing each component action as an
   * MCP tool backed by `callTool`. Lazily constructed; the SDK is imported only
   * when this is called. See {@link createComponentMcpServer}.
   */
  // (the MCP server factory is a separate export to keep the SDK import lazy)

  /** Tear down: dispose the mount + detach any bridge. */
  dispose(): void
}
13

AgentServerOptions

interfaceagent
interface AgentServerOptions {
  /** The component to mount + drive. */
  target: AgentServerTarget
  /**
   * Optional host factory for the server-side mount. When omitted,
   * `createAgentServer` stands up an internal jsdom-backed global DOM (if the
   * runtime has none) and mounts into a fresh detached `<div>` — so a plain
   * Bun/Node consumer needs no DOM glue at all. Provide this only to mount into
   * a specific host element (an explicit factory always wins).
   */
  createHost?: () => Element
  /** Auth plugin — forwarded to `createAgentService` for `$scope` checks. */
  authPlugin?: AuthPlugin
  /** Rate-limit plugin — forwarded to `createAgentService`. */
  rateLimitPlugin?: RateLimitPlugin
  /**
   * Per-request auth resolver — forwarded to `createAgentService.asMiddleware`.
   * Lets scoped/rate-limited tools be reachable over the bundled HTTP path.
   */
  resolveAuth?: AgentServiceOptions['resolveAuth']
  actorResolver?: ActorResolver
  authorizeDataRead?: CapabilityGrantResolver
  /** Maximum duration for each injected security hook and pending bridge call. Defaults to 5000ms. */
  securityHookTimeoutMs?: number
  /**
   * Auth-discovery URL forwarded to `createAgentService` — included in every
   * 401 envelope so a refused agent knows where to obtain a credential (e.g.
   * the deployment's `/.well-known/oauth-protected-resource`). Informational
   * only; never a policy input.
   */
  authDiscoveryUrl?: AgentServiceOptions['authDiscoveryUrl']
  /** Verify a bridge token and return its stable identity/grant binding. Missing verifier denies attachment. */
  verifyBridgeSession?: (token: string) => BridgeVerifiedSession | Promise<BridgeVerifiedSession>
  /**
   * How long (ms) `callTool` waits for an attached bridge channel to complete
   * its `hello` handshake before refusing to delegate to it (503
   * `BRIDGE_UNVERIFIED`). Defaults to 1000ms.
   *
   * The wait exists only to absorb the attach/connect race over a real socket;
   * it is not a grace period. A channel that sends a MISMATCHED or non-numeric
   * protocol version is rejected immediately, without waiting out this timeout.
   */
  bridgeHandshakeTimeoutMs?: number
  /** Maximum duration for a bridge invocation reply. Defaults to 5000ms. */
  bridgeCallTimeoutMs?: number
  /** Maximum in-flight bridge calls for one attachment. Defaults to 64. */
  maxPendingBridgeCalls?: number
  /** Maximum in-flight bridge calls across the server. Defaults to 1024. */
  maxPendingBridgeCallsTotal?: number
  /** Maximum bridge calls from admission through final result authorization. Defaults to 1024. */
  maxInFlightBridgeCalls?: number
  /** Maximum admitted calls for one verified actor organization. Defaults to 64. Calls without an actor share one bucket. */
  maxInFlightBridgeCallsPerTenant?: number
  /** Bounds projected bridge read results. Defaults to depth 32 and 10,000 total nodes. */
  projectionLimits?: CapabilityProjectionLimits
  /** Revocation memory lifetime. Defaults to 24 hours; host session storage remains authoritative. */
  bridgeRevocationTtlMs?: number
  /** Maximum retained revocation bindings. New handshakes fail with BRIDGE_REVOCATION_STORE_FULL while full. Defaults to 10,000. */
  bridgeRevocationMaxEntries?: number
  /** Re-check the bound session/grant before every invocation. */
  reauthorizeBridgeInvoke?: (binding: {
    readonly sessionToken?: string
    readonly identity: string
    readonly grantVersion?: string
    /** Actor resolved by the service gate for this invocation, if verified. */
    readonly actor?: Actor
  }) => BridgeVerifiedSession | false | Promise<BridgeVerifiedSession | false>
  /** Optional host-side diagnostics for rejected bridge frames. Disabled when omitted. */
  onBridgeDiagnostic?: (diagnostic: {
    readonly event: string
    readonly detail?: unknown
  }) => void | Promise<void>
}
14

AgentServerTarget

interfaceagent
interface AgentServerTarget {
  /** arbor render tree (root `Node`) to mount server-side. */
  node?: Node
  /** Compiler-emitted `__agentBinding` server export for the component. */
  agentBinding?: AgentBindingSpec
  /** A pre-built `MountScope` (its binding is already registered). */
  mount?: MountScope
}

The thing being driven.

15

BridgeChannel

interfaceagent
interface BridgeChannel {
  /** Send one (already-serialized) message frame to the peer. */
  send(data: string): void
  /** Register a frame handler. Returns an unsubscribe function. */
  onMessage(handler: (data: string) => void): () => void
  /** Register a close handler. Returns an unsubscribe function. */
  onClose(handler: () => void): () => void
  /** Close the underlying peer after a protocol violation, when supported by the transport. */
  close?(): void
  /** True while the channel can still deliver frames. */
  readonly connected: boolean
}

The minimal duplex transport the bridge needs.

16

BridgeClient

interfaceagent
interface BridgeClient {
  /** Stop handling frames. Does not close the underlying channel. */
  dispose(): void
}

A running bridge client.

17

BridgeClientOptions

interfaceagent
interface BridgeClientOptions {
  /** The compiler-emitted `__agentDispatcher` for the mounted component. */
  dispatcher: AgentDispatcher
  /** Duplex channel to the server (a `ws` WebSocket in production). */
  channel: BridgeChannel
  /** Server-issued single-use handshake nonce. */
  nonce?: string
  /** Verified session credential, bound to this bridge peer. */
  sessionToken?: string
  /** Identity returned by the server's session verifier. */
  sessionIdentity?: string
  /** Current actor/grant version, rechecked by the server on every invoke. */
  grantVersion?: string
  /**
   * Optional snapshot source (e.g. the mount's `serialize`). When provided, a
   * `snapshot` frame is pushed after each successful invocation so the server
   * and any read-only viewer reflect the visible instance's new state.
   */
  serialize?: () => unknown
  /** Optional host-only diagnostic callback. Its raw error text is never sent over the bridge. */
  onDiagnostic?: (diagnostic: { code: string; message: string }) => void
}
18

BridgeErrorMessage

interfaceagent
interface BridgeErrorMessage {
  type: 'error'
  callId: string
  code?: string
  message: string
}

Client → server.

19

BridgeHelloMessage

interfaceagent
interface BridgeHelloMessage {
  type: 'hello'
  protocol: number
  sessionToken?: string
  sessionIdentity?: string
  nonce?: string
  grantVersion?: string
}

Handshake, client → server, sent once on connect.

20

BridgeInvokeMessage

interfaceagent
interface BridgeInvokeMessage {
  type: 'invoke'
  /** Correlates the eventual `result`/`error`/`snapshot` reply. */
  callId: string
  /** Opaque action identifier the client maps to a concrete action. */
  opaqueActionId: string
  /** Positional arguments for the action. */
  args: unknown[]
}

Server → client.

21

BridgeNonce

interfaceagent
interface BridgeNonce {
  readonly nonce: string
  readonly expiresAt: number
}

Single-use, short-lived bridge handshake nonces.

22

BridgeNonceStore

interfaceagent
interface BridgeNonceStore {
  issue(ttlMs: number | undefined, connectionId: string): BridgeNonce
  consume(value: unknown, connectionId: string): boolean
}
23

BridgeResultMessage

interfaceagent
interface BridgeResultMessage {
  type: 'result'
  callId: string
  result: unknown
}

Client → server.

24

BridgeSnapshotMessage

interfaceagent
interface BridgeSnapshotMessage {
  type: 'snapshot'
  callId?: string
  snapshot: Snapshot
}

Client → server (or server → a read-only viewer).

25

BridgeVerifiedSession

interfaceagent
interface BridgeVerifiedSession {
  readonly identity: string
  readonly grantVersion?: string
}
26

VerifyBridgeUpgradeOptions

interfaceagent
interface VerifyBridgeUpgradeOptions {
  /**
   * Origins allowed to open the capability-bridge WebSocket, exact string
   * match against the `Origin` request header (e.g. `http://localhost:5108`).
   * There is no wildcard support — a bridge that trusts every origin is the
   * vulnerability this function exists to close.
   */
  allowedOrigins: readonly string[]
}
27

BridgeClientMessage

typeagent
type BridgeClientMessage =
  | BridgeHelloMessage
  | BridgeResultMessage
  | BridgeErrorMessage
  | BridgeSnapshotMessage

Any message a client may send to the server over the bridge.

28

BridgeServerMessage

typeagent
type BridgeServerMessage = BridgeInvokeMessage

Any message the server may send to a client over the bridge.

29

BridgeUpgradeVerdict

typeagent
type BridgeUpgradeVerdict = { ok: true } | { ok: false; status: number; reason: string }