Skip to main content

Tools

A registry of callable MCP or HTTP endpoints per agent, dispatched server-side so credentials never leave the control plane and every invocation is audited + metered.

Why server-side dispatch?

If an agent were given raw API keys, an injected prompt could exfiltrate them in a single message. Ujex owners register tool credentials once, encrypted with a KMS key the agent never sees, and the agent invokes them through a Cloud Function that:

  1. Authenticates the agent
  2. Enforces the owner's quota (tools.invoke)
  3. Decrypts the auth token transiently
  4. Forwards the call to the tool URL with the auth injected
  5. Logs the invocation + status to the hash-chained audit log

The agent only learns the tool's response. The credential stays behind glass.

Two tool kinds

KindBody shapeUse when
mcpJSON-RPC 2.0: {jsonrpc, id, method: "tools/call", params: {name, arguments}}The tool server speaks the MCP protocol (a normal AI-tool server)
httpRaw JSON pass-through of argsThe tool is a plain REST/JSON endpoint

Owners declare kind at registration time. The agent side is identical — invokeTool(name, args) works the same for both.

Firestore schema

PathPurpose
agents/{agentId}/tools/{name}One tool registration. Fields: name, kind, url, authTokenEnc (KMS), kmsKey, manifest, enabled, createdAt, updatedAt.

Auth tokens are encrypted with projects/axy-ujex/locations/us-central1/keyRings/ujex/cryptoKeys/secrets before hitting Firestore. The plaintext lives only inside the invokeTool / getToolCredential Cloud Functions for the duration of a single invocation.

Routes

FunctionCallerWhat it does
registerToolOwner (human)Creates or updates a tool doc. Validates URL, auto-fetches the MCP manifest if not supplied, KMS-encrypts the auth token. Audits tool.register.
listToolsAgentReturns enabled tools (name/kind/url/manifest) — credentials excluded.
invokeToolAgentServer-side dispatch. Fetches credentials, builds the body, forwards the call, audits tool.invoke with status + error, meters one tools.invoke unit. Timeout: 15s default, 60s max.
getToolCredentialAgent(Escape hatch.) Decrypts and returns the token directly. Audits every access. Prefer invokeTool so the token never touches agent memory.

Quotas

tools.invoke is metered per owner in agents/{agentId}/usage/{YYYY-MM}. On network failure the unit is credited back so a flaky tool doesn't consume quota.

Audit events emitted

ActionActorMeta
tool.registerhumankind, url
tool.getCredentialagent
tool.invokeagentstatus, ok, error

Typical flow

// One-time, as the owner
await ujex.tools.register({
agentId: 'agent-alice',
name: 'search',
kind: 'http',
url: 'https://search.example.com/query',
authToken: process.env.SEARCH_API_KEY,
});

// Repeatedly, from inside the agent
const {tools} = await ujex.tools.list();
const {status, result} = await ujex.tools.invoke({
name: 'search',
args: {q: 'latest inflation print', limit: 5},
timeoutMs: 30_000,
});

Gotchas

  • MCP manifest auto-fetch is best-effort. If the manifest URL is unreachable at registration time, the tool is still stored without a manifest and agents can invoke it.
  • Disabled tools stay in Firestore. Flip enabled to false rather than delete so the audit trail remains resolvable.
  • Timeouts are clamped. A caller asking for timeoutMs: 600_000 gets 60s; Cloud Functions v2 max invocation time governs.
  • No retry policy yet. If a tool returns 500 once, the call fails — the agent is responsible for retry. A circuit-breaker is planned.

See also the API reference for exact function signatures, request/response shapes, and error codes.