Skip to main content
A connector is a grant a person makes, for themselves or for a workspace, plus the policy over the tools that use it. You declare it once, the directory renders it, and the credential is attached server-side when a tool runs. Your code never holds a token and neither does the browser.
catalog.py
Declare the same objects on both builders. Web serves the directory and the connect routes, LLM decides whose tools the loop may offer.

Three kinds of grant

Authorization code with PKCE against an app you registered, or against an MCP server that publishes its own authorization server and registers clients dynamically.
Tokens refresh when stale. The state that rides through the provider is signed and names the user and workspace, so the callback carries no JWT.For an MCP server that registers clients itself, pass mcp= in place of the endpoints and client. The endpoints are discovered and a client is registered once per organization, so there is no app to register and no secret to keep.
For an app you register, the redirect URI is https://my-agent.cycls.ai/connectors/{name}/callback. When the deployment sets CYCLS_RELAY_URL, every OAuth connector uses that one shared redirect instead. Pass relay=False to keep a connector on its own callback.

Where a grant lives

A mailbox is a person, so user. A team analytics key is shared, so workspace. either lets an admin connect one account for everyone while individuals can still link their own.

Calling a REST API

Give a connector an api base and no MCP server, and the loop offers one {name}_request tool. The model builds a path, the platform attaches the credential, and the host is bounded to that base. A call with a body is sent as content-type: application/json.
The same relay backs cycls.connector(name) inside a generated app, so a dashboard an agent builds can read live data without ever holding a key.

Microsoft 365

Microsoft 365 works over the Graph REST API, with one connector per product so each consent screen asks only for what that product needs. Register a multitenant app in Microsoft Entra, add each connector’s callback as a web redirect URI, and add MICROSOFT_CLIENT_ID and MICROSOFT_CLIENT_SECRET to the container’s environment.
The organizations endpoints keep out personal Outlook.com accounts, and the Prefer header returns mail bodies as text instead of HTML. The other products use the same endpoints, client, api and scope with their own scopes: ChannelMessage.Read.All always needs an admin’s consent.

MCP servers

cycls.MCP connects to a remote MCP server. The harness speaks Streamable HTTP itself, so every provider gets MCP, not just Anthropic. Tools are discovered once and cached, and a session opens only when a tool is actually called.

Loading tools on demand

A directory of connectors can hold hundreds of tools, and their schemas would not fit in a system prompt. Each connector contributes one index line of about fifteen tokens instead, and the model calls find_tools to load the schemas it needs. Loaded tools stay for the rest of the conversation.

Approvals

Every tool call is classified before it runs. Each person picks Auto or Manual in Settings, General, Approvals, the row below Connectors. It appears only when the deployment declares connectors. Auto is the default, the choice is kept in the browser, and Manual is sent with each request as auto: false, which your code reads as context.auto. When a call needs approval, the model stops and a card asks above the message box, where the agent’s other questions appear. It asks about the action in plain words and shows the connector’s logo, or a trash can for a delete. Approving binds to those exact arguments, so a changed call asks again. Not now is sent to the model too. The conversation keeps one line where the call was: waiting, then how it went (approved, done, failed, declined, or skipped when the person wrote something else instead). Each person can also set allow, ask or never per tool, including an API connector’s single request tool, and an organization admin can switch a connector off for everyone. Don’t ask again on a card sets that tool to allow. A tool set to allow still asks before a destructive call. A connector’s CMS record words its calls with actions. Each entry matches a tool by name, or an API connector’s call by METHOD path with * for one segment, and gives the card’s question, a line under it, the button and the receipt, in English and Arabic. show lists arguments to show as rows, read from the call or from its JSON body, and an id puts one in the sentences as {id}. A call no entry matches gets a plain question with the agent’s own note on the call.
Deletes through bash are not destructive by default, because the sandbox shims rm into a thirty day trash. Commands with nothing behind them, such as shred, mkfs or git push --force, always ask.

Directory copy

What the directory shows can come from your code or from a CMS, field by field, with code winning. In code, these connector arguments set it: title and description also describe the connector to the model. To read copy from a CMS instead, point Web at it:
This keeps behavior in Python, where it belongs, and copy in a CMS where a writer can change it without a redeploy.
Connector objects are built on your machine and pickled into the deployment. Keep the declaration file free of functions and module-level values a callable would close over: a writes classifier or a tool handler must live in the file you deploy, not in an imported module.

Next

Sandbox and trust model

What isolates the bash tool, and what it does not cover.