Activation required. AI access management must be enabled for your tenant before you can use it. To get started, contact the C1 support team for a walkthrough.
How the gateway fits in
C1 is an MCP gateway. AI clients connect to one C1 MCP endpoint, and C1 sits in front of every MCP server and integration your organization has approved. Agents never connect to those servers directly. On each call, C1 authenticates the human or workload behind the agent, applies per-tool governance, and routes the call to the right upstream server — a hosted server from the catalog, a vendor MCP server, or a private server reached over an MCP bridge. One MCP connection, many governed systems behind it.What code mode changes
Code mode changes how the gateway presents those governed tools to the client. Instead of advertising every enabled tool as its own named tool, C1 exposes two entrypoints:
A third entrypoint,
get_execution, retrieves the result of a program that outlived the synchronous wait window.
The practical consequence for admins: your enabled tools won’t appear one by one in the client’s tool list. That’s expected, not a discovery failure. A tenant with three hundred enabled tools still presents a handful of entrypoints, which keeps the client’s tool list — and its context window — from being consumed by tool definitions.
Governance is unchanged. Every underlying call still runs the same per-tool checks: the tool must be Enabled, and the caller must hold a grant for it. Tool call hooks still fire on each call and can rewrite inputs, redact outputs, or deny outright. Every call is still written to the audit log with full identity context. Code mode moves where the agent names a tool; it moves nothing about who is allowed to call it.
Which clients use code mode
Code mode is a tenant-level AI governance setting, on by default.- Personal and shared clients — the interactive, human-backed ones — use code mode.
- Service and ephemeral clients get each enabled tool as a directly named tool instead.
Write a code-mode program
Anexecute call takes a JSON object with one required key:
Values placed beside
source_code rather than inside args are dropped silently — the program sees undefined. Where an agent has already resolved an ID during discovery, inlining it as a const is more reliable than parameterizing.
Program skeleton
Every program has the same shape: one import, one default-exportedmain, and a JSON object as the return value.
@c1/code-mode is the only valid import. Governed tools are called as tools.<toolName>(args), where the tool name and its argument names come from describe — never from convention or from another tool’s schema. main must resolve to a JSON object; a bare array, string, or number is rejected by the runtime, so wrap it (return { users, count }).
Discovery belongs outside the program. The agent calls describe at the top level first, then writes the program; discovery entrypoints aren’t callable from inside execute.
One program per call
Eachexecute call deploys an ephemeral function, while tools.X() calls inside the running program are fast. An agent that needs six tool calls should write one program that makes all six, not six execute calls. Dependent calls become sequential awaits; independent lookups go in a Promise.all().
Example: paginate a large result set
Loops are the clearest payoff. Collecting every page of a large list takes one round trip instead of one per page:records and nextPageToken keys above belong to this tool; read the real keys from describe output rather than carrying an envelope key over from a different tool. Optional keys should be omitted entirely — never passed as undefined.
Reading the results
Most calls return what you’d expect: the upstream tool’s normal output, minus anything a post-tool-use hook redacted or capped. Two results are specific to the gateway, and both matter more than they look.Access requests instead of failures
When the caller doesn’t hold a grant for a tool, the call doesn’t fail opaquely. Anytools.X() call can return one of these envelopes in place of domain data:
result?.status === 'request_created' || result?.status === 'denied' before touching any domain field, and returns the envelope verbatim from main so the human sees the task_url or the denial reason. It should not map over the missing fields, retry inside the same program, or swallow the envelope into a default value. After a request_created, the agent’s job is to hand the user the task link and stop that line of work until the request is approved.
request_created and denied look alike to an agent but mean different things. request_created is a pending approval with a link to follow; denied means no access path exists for that caller.Long-running programs
A program that runs past the synchronous wait window of roughly 25 seconds doesn’t fail.execute returns { "status": "pending", "execution_id": "..." } and the program keeps running server-side.
The agent then polls get_execution with that execution_id. Each response carries a status of pending, running, success, or error, plus the full output and logs once the program finishes either way. Poll with exponential backoff — 1 second, then 2, 4, 8, 16, 30, capped at 30 seconds — and do other independent work in between rather than polling in a tight loop.
Executions are capped at 15 minutes total; the polling response reports elapsed and remaining time. The execution_id is opaque and scoped to the calling session’s tenant and principal, so it’s useful for the polling loop and nothing beyond it.
Where to go from here
- Setting up the servers behind the gateway? See Set up an MCP server.
- Governing which tools are callable? See Govern tools and toolsets.
- Constraining calls at runtime? See Tool call hooks.
- Reviewing what agents actually called? See Audit AI tool usage.
- Connecting a client as an end user? See Connect your MCP client to C1.