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 owner policy and Budgets preflight
  3. Enforces the owner's quota (tools.invoke)
  4. Decrypts the auth token transiently
  5. Forwards the call to the tool URL with the auth injected
  6. 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, sessionId, approvalId?}) works the same for both.

OAuth / SaaS connection metadata

V2 adds provider-agnostic toolConnections metadata for SaaS credentials: owner, agent, provider, scopes, status, createdAt, revokedAt, and encrypted token references. V1 is manual registration plus server-side token storage; it does not ship a catalog of SaaS integrations. The pattern is simple: agents invoke tools without seeing credentials, and revoked connections block future credential reads or invocations.

Firestore schema

PathPurpose
agents/{agentId}/tools/{name}One tool registration. Fields: name, kind, url, authTokenEnc (KMS), kmsKey, manifest, enabled, createdAt, updatedAt.
agents/{agentId}/toolConnections/{id}OAuth/SaaS connection metadata. Fields: provider, scopes, status, createdAt, revokedAt, encrypted token references.

Auth tokens are encrypted with projects/axy-ujex/locations/us-central1/keyRings/ujex/cryptoKeys/secrets before hitting Firestore. The plaintext lives only inside invokeTool 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. Runs policy + Budgets preflight before fetch, injects credentials, audits tool.invoke with status + error, meters one tools.invoke unit. Timeout: 15s default, 60s max.
getToolCredentialAgentDeprecated compatibility export. Production servers deny plaintext credential reads; use invokeTool.
toolsRegisterConnectionOwnerManually registers provider/scopes and KMS-encrypted access/refresh tokens for a SaaS connection.
toolsListConnectionsOwner or agentLists connection metadata; token references are not returned.
toolsRevokeConnectionOwnerMarks a connection revoked; linked tools stop decrypting credentials.

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.getCredentialagentDeprecated compatibility call denied before token return
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},
sessionId: 'sess_run_20260618_001',
timeoutMs: 30_000,
});

If policy requires human review, the first call returns {status: 'waiting_approval', approvalId, reason} without calling the remote tool. Retry with the returned approvalId; Ujex executes only when the approval matches the exact name,args digest.

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.