Skip to content

Tools and connectors ​

One in-process registry holds native tools. Each tool is declared with an explicit JSON Schema. Before a schema is offered to the model, format is stripped from optional and nullable properties (including nested objects and array items), additionalProperties: false is dropped, and optional strings that do not already say to omit the key get a short “omit when unused” line, so the model can leave those keys out. Before an MCP tool is called, an empty string is dropped only when that key is optional on the tool schema. A required empty string is forwarded. Handlers take ctx first and return strings. Exceptions become ERROR: <message> tool results; full detail goes to operational logs.

Every tool result follows one path: custom filter, exact-value secret redaction (Secrets and access), platform byte cap (default 64 KiB), then a remaining_tool_calls: N trailer. When N is 8 or fewer, a wrap-up line sits above that trailer. The stamped string is what PostgreSQL and the model receive. The raw result is never persisted.

Native tools ​

Notes, collections, memory, and scripts share one artifact store and keep separate tool families:

  • note_find / note_get / note_write / note_delete: Markdown notes in the artifact store. Find searches or lists notes the agent may read, optionally at one level. Get reads one display path. note_write takes level (agent, guild, or org) and key (the .md file name); notes have no folders. The memory archive is not reachable from these tools;
  • record_search / record_list / record_add / record_update / record_remove: records of the organisation's collections, the typed tables the operator defines and grants. Every tool names the table by collection; no tool creates, alters, or deletes one. record_search takes query (words in any column, websearch grammar, substring for a single token, never fuzzy), an optional collection, and where (with collection only); results are { records }, each with its collection and id. record_list takes collection, where ({ column: term }, at most 8: text and an open array item by substring or similar spelling, a column with values exactly, number and datetime exactly or by { gte, lte }, boolean and id exactly, null for a missing value), since (an ISO timestamp, oldest change first), order ({ column, direction } over a declared column (not an array), id, created_at, or updated_at), limit (default 20, at most 100), and offset; results are { total, records }, and an empty narrowed result carries a hint with the collection's size, newest change, and columns. record_add takes body (declared columns only), validates every value, rejects with every problem at once (422 record rejected), assigns the id, and returns { record } plus note: merged into existing record <id> when the unique column matched a live record. record_update takes id and a body of the columns that change (null clears an optional column) and returns { record, warnings? }. record_remove soft-deletes by id and returns { removed }. Reach is the collection grant: the agent's own row when it has one (none keeps it out), otherwise its guild's read or write on an organisation table, or write on the agent's guild's own table: an ungranted collection is 404, a write on read is 403. The four grant bindings (read_guild, read_org, write_guild, write_org) decide note reach only (defaults: guild read/write and org read on; org write off);
  • memory_write: orchestrator, full replacement of the agent's memory row with one content string; archives the previous version in memory_revisions (cap 50); the whole file at most 12 000 characters; offered on direct, group-triggered, and schedule-triggered runs when the binding is on and config.memory is not false; when the binding is off the prompt does not name the tool;
  • browser_navigate / browser_snapshot / browser_click / browser_type / browser_fill / browser_scroll / browser_screenshot / browser_eval: sandbox Playwright against the live Chromium CDP endpoint, offered when the run snapshot has browser: true. Snapshot returns the accessibility tree; click, type, and fill take locators from that tree; browser_scroll moves one viewport up, down, left, or right in a visible overflow panel when the page has one, otherwise the window, and an optional target locator picks the panel; screenshot writes /home/agent/screenshots/latest.jpg. browser_eval runs JavaScript in the current page as an async function body and returns a JSON-serializable value; reuse a snippet by writing it to an agent note and reading it back with note_get. Each call has a 60-second timeout;
  • shell / shell_status / file_read / file_edit / file_write / code_search / code_match / code_map / code_find / code_outline / code_callers: the code tools, sandbox through guilds-code, offered when the run snapshot has coding: true (Code tools);
  • group_post: orchestrator, offered when the binding is on (Group chat);
  • report_issue: orchestrator, always offered. Agents file a tooling problem (tool_failure, truncated, api, or other) onto the guild. The guild's operators see those rows in their inbox and on the guild Issues tab, where they resolve them (one, or every open row at once) or investigate them in Studio;
  • schedule_create_cron / schedule_create_window / schedule_list / schedule_pause / schedule_resume / schedule_delete: orchestrator, offered on direct and group-chat runs. schedule_create_cron takes cron, a 5-field UTC expression (minute hour day month weekday) whose consecutive wake-ups must be at least 5 minutes apart. schedule_create_window takes timezone, start, end, and max_wakes, a daily random window described in Runs. description is a required one-line label at most 400 characters. instruction is markdown at most 2000 characters, stored as instruction on the schedule row. Pause stops firing; resume recomputes next_run_at from now;
  • script_create / script_list / script_read / script_run / script_delete: orchestrator plus sandbox. name matches ^[a-z][a-z0-9_-]{0,62}$; manifest is reserved. description is required and at most 400 characters. Scripts are agent-level artifacts (kind script). script_create of a new name is refused when the agent already has its max_scripts (default 10). A run snapshots source and hash, then materialises /home/agent/scripts/<name>.py. script_run executes that file in the sandbox (/home/agent, 120-second timeout) through guilds-script-run, which writes the optional arguments object to the script's stdin as JSON ({} without one); for an agent without coding it is the only tool that runs a command there. script_delete removes the catalog row, the sandbox file, and the name from this run's snapshot. A row with parameters is also a first-class tool on new runs; its arguments reach stdin the same way. The five tools are omitted when config.scripting is false (Runs);
  • secret_store / secret_request: orchestrator. secret_store saves a value the agent produced as a secret granted to that agent; secret_request asks the operator in a direct chat for one. Both are described in Secrets and access;
  • tavily_search: system tool: orchestrator HTTP to Tavily with the instance key (TAVILY_API_KEY in the orchestrator environment). There is no login to connect; every agent inherits it from the platform binding. query is natural language; depth is advanced. The result is title, URL, and snippet for each source. The key never enters the sandbox. Without TAVILY_API_KEY the tool is not offered on runs.

What a run offers ​

The note, record, memory, script, schedule, search, secret, and group_post tools are bindings: a run offers the ones resolved enabled for that agent. A later turn also drops a native tool that is now off, including tavily_search, the eight browser tools when config.browser is false, the code tools when config.coding is false, the five script tools when config.scripting is false, and memory_write when config.memory is false. A native tool the run does not offer is refused if the model names it. report_issue is always offered. The eight browser tools are omitted from offered_tools when the snapshot's browser flag is false, the code tools when its coding flag is false, the script tools when its scripting flag is false, and memory_write when its memory flag is false. The six schedule tools are further limited to direct and group-chat runs, and secret_request to direct runs. tavily_search is omitted when the orchestrator has no TAVILY_API_KEY, and the Tools view then lists it as not offered, with the variable to set. Each browser call runs /usr/local/bin/guilds-browser in the sandbox, which attaches Playwright to the existing Chrome at http://127.0.0.1:9222 and leaves that process running.

Code tools ​

A coding agent's run offers eleven native tools. Like the browser tools they follow the coding flag and are not bindings.

ToolArgumentsResult
shellcommand, timeout_seconds (default 120, at most 600), backgroundCombined stdout and stderr, shaped; exit_code: N when not 0
shell_statusid, stopWithout id, the background commands with their state; with it, that command's state, exit code, and the last 40 lines of its log; stop ends it
file_readpath, offset (first line, default 1), limit (default 200 lines, at most 500)Numbered lines under a path · lines a–b of n header
file_editpath, old_string, new_string, replace_alledited <path> (+a −b)
file_writepath, contentwrote <path> (n lines); missing folders are created
code_searchpattern, path, glob, mode, fixed, ignore_case, context, limitText search (Code search)
code_matchpattern, lang, path, limit (default 30, at most 100)Structural matches as path:line: first matched line
code_maprepoDirectory clusters with file and symbol counts and their most referenced symbols, then the most referenced symbols overall
code_findquestion, in, source, limit (default 8, at most 20)Ranked symbols and files with path:line and their signature; with source, up to 8 lines of each
code_outlinepathEvery signature in the file with its line span, without bodies
code_callerssymbol, direction (in or out), depth (1 to 3), inWhat references the symbol, or what it references, indented by depth, with path:line

Every call runs /usr/local/bin/guilds-code in the sandbox, the way the browser tools run guilds-browser. The request is base64 JSON on the command line; a request whose encoding passes 64 KiB is written through the sandbox file API to /run/guilds/requests/ instead, and the helper reads and deletes it. The helper answers with a base64 JSON envelope holding the shaped text and the fields the call's metadata records. The orchestrator never opens a workspace file for these tools, so a link in the workspace resolves inside the container.

  • A relative path resolves against the run's working directory, kept in /home/agent/.runs/<run id>/cwd. It starts at /home/agent; shell updates it when a command changes directory, and environment changes do not persist between calls. The file tools refuse a path that resolves, links followed, outside /home/agent.
  • shell runs bash -c with stdin closed and PAGER=cat, GIT_PAGER=cat, GIT_TERMINAL_PROMPT=0, NO_COLOR=1, TERM=dumb, DEBIAN_FRONTEND=noninteractive, GUILD_RUN_ID, and GUILD_AGENT_ID in its environment. A command past its timeout gets SIGTERM, then SIGKILL two seconds later, for its whole process group, and its result says so with exit_code: 124.
  • A background command's output goes to its log and it runs in its own session; shell_status names it b1, b2, … for the life of the container. Background commands do not hold the sandbox awake: they end with the idle stop.
  • file_edit replaces an exact string. A string that is missing, or that occurs more than once without replace_all, is an error and changes nothing. Edits and writes replace the file atomically and keep its mode.
  • The helper caps each result at CODE_RESULT_MAX_BYTES (default 8192). The platform pipeline then runs unchanged: redaction, the 64 KiB cap, and the remaining_tool_calls trailer.

Output shaping:

ToolShaping
shellOutput that fits is returned whole. Otherwise its first 2 KiB and its last 6 KiB, less room for the trailers, around a line giving the elided byte count and the log path, /home/agent/.runs/<run id>/<call id>.log; the end is kept because build and test errors are there. ANSI escapes are removed and a line redrawn with carriage returns keeps its last state
file_readA line longer than 400 characters is clipped with the count of what was cut; the range stops before the cap and says which offset continues it; a binary file is named with its size, not shown

A full shell log is stored unredacted in the workspace, inside the sandbox that already holds the values; reading it back with file_read passes through redaction like any result. .runs/ keeps the folders of the agent's 20 most recent runs.

No repeated reads. Each file_read records the file's path, the lines it returned (range), and the SHA-256 of the whole file (content_hash). A later read in the same run whose lines an earlier read already returned, with the same hash, returns one line instead: <path> lines a–b: unchanged since step N; read that result., where N is the earlier result's message seq, and records repeat_of: N. An edit changes the hash, so the next read returns content.

Three engines sit behind tool names that do not name them, so an engine can be replaced without changing a prompt or a run view.

EngineToolsAnswers
ripgrepcode_search modes files and contentText in every file, whatever its language, respecting .gitignore
ast-grepcode_matchSyntax patterns (a call shape, a declaration form) free of hits in comments and strings; the language comes from lang or each file's extension
Graftcode_map, code_find, code_outline, code_callers, code_search mode symbolsWhere things are and how they connect

code_search modes, each searching path (default the working directory) with an optional glob, fixed for a literal pattern, and ignore_case:

  • files (default): matching paths with their match counts, highest first, at most 30, then the totals.
  • content: path:line: text for matches and path-line- text for context lines (context 0 to 5), at most 40 lines and at most 10 matches per file, then the totals.
  • symbols: hits grouped under their enclosing function or class, with that symbol's span and how many places reference it, at most 20 groups of 5 lines.

Paths in every result are relative to the working directory, so they can be passed back to file_read. A matched line longer than 300 characters is clipped; a result that would pass the cap stops at a line and says how many lines it left out.

The Graft-backed tools address a repository as a folder under /home/agent/repos/: code_map takes its name, the others the repository holding their path or in, else the one holding the working directory, else the only one there. in narrows to a folder or file of it. Graft runs in its structural tier only:

  • The graph is built by tree-sitter with no model, no key, and no network. It lives at /home/agent/.cache/graft/<name> (--dir), is built on the first Graft-backed call for a repository, and Graft refreshes it against the working tree before every query, uncommitted edits included.
  • With GRAFT_NO_GITIGNORE=1 and GRAFT_NO_IGNORE=1 a build writes nothing into the repository.
  • DO_NOT_TRACK=1 turns its usage telemetry off, the install event included. Its once-a-day npm version lookup has no switch; it is one background request from the sandbox.
  • Every call reads Graft's --json output and builds its own compact lines; Graft's text output and its diagnostics on stderr never reach the model.
  • A file in a language Graft does not parse is not in the graph: code_outline says so and code_search in files or content mode still finds it.
  • No tool runs graft build --deep, init, trail, viz, upgrade, or mcp. No provider key is present, so the model-written tier cannot run.

A first graph build can take minutes on a large repository: Graft-backed calls wait up to 11 minutes, a text or syntax search up to 60 seconds.

Exploration. After each generation of a coding run, the run's metadata.exploration records calls_before_first_edit (tool calls before its first successful file_edit or file_write, null until there is one) and read_bytes_unedited (bytes file_read returned from files the run never edited). They measure whether the search tools pay for themselves; the run payload carries them as exploration.

Metadata. Beside the fields every tool call records: exit_code, cwd, log_path (when output was elided), and timed_out on shell; background_id, state, and exit_code on shell_status; path, range, content_hash, and lines on file_read; path, lines_added, lines_removed, and content_hash on file_edit and file_write; matches (and files) on code_search and code_match; repository and hits on the Graft-backed tools.

Catalog, connections, bindings ​

Three objects:

  • Catalog: what exists. Native tools are the TypeScript registry. MCP servers are platform catalog rows: transport, connect config, auth of none, token, or oauth, plus operator-facing copy (description, limitations, setup_hint). The catalog is seeded with six remote MCP servers: Notion (https://mcp.notion.com/mcp), Drive (https://drivemcp.googleapis.com/mcp/v1), and Sentry (https://mcp.sentry.dev/mcp), all http / oauth; Cigale (https://api.cigale.ai/api/v1/public/mcp), http / token; OpenSea (https://mcp.opensea.io/mcp), http / token; and Etherscan (https://mcp.etherscan.io/mcp), http / token. GitHub (https://api.github.com), http / oauth, is backed by the box's GitHub App and lists no tools (GitHub). Nine local adapters reuse that same catalog so a family of tools groups under one Connect row: CoinGecko (https://pro-api.coingecko.com/api/v3), Pinned prices (https://pro-api.coingecko.com/api/v3), Wallet (https://eth-mainnet.g.alchemy.com/v2), Alchemy (https://eth-mainnet.g.alchemy.com/nft/v3), X (https://api.x.com/2), TwitterAPI.io (https://api.twitterapi.io), Qonto (https://thirdparty.qonto.com), DexPaprika (https://api.dexpaprika.com), all http / token, and Gmail (https://gmail.googleapis.com/gmail/v1), http / oauth. Server rows and their published tool copy come from apps/orchestrator/src/db/seed/mcp-manifests.ts: Cigale, CoinGecko, Wallet, Alchemy, X, TwitterAPI.io, Qonto, DexPaprika, and Gmail seed their tool lists into mcp_catalog_tools at boot; Notion, Drive, Sentry, OpenSea, and Etherscan publish tools as they are listed from a connected session. Pinned prices publishes none. Admin edits (copy_source=admin) survive reseeds. GET /api/mcp/servers lists published servers. The instance admin edits the catalog under Admin → Connector catalog (/api/admin/mcp/servers). The builder names only published servers with published tools. PUT /api/bindings accepts catalog_kind=mcp when the catalog id is a server row, and catalog_kind=mcp_tool when the catalog id is a namespaced tool {slug}_{name} listed on a connection.
  • Connections: one login for one catalog server (a remote MCP server or a local adapter), owned at a scope, with account_hint for display and status of pending, connected, or disconnected. Token and OAuth payloads stay in the orchestrator. One connection per server per owner. Connect creates a pending connection at that scope, or retries a pending or disconnected one; it is a 409 while a connected login exists. Token (Cigale, CoinGecko, Pinned prices, Alchemy, Wallet, OpenSea, Etherscan, X, TwitterAPI.io, Qonto, DexPaprika) connect marks it connected and writes the enabled binding in the same request. Browser OAuth (Notion, Gmail, Drive, Sentry) and the GitHub App install leave the connection pending and write the enabled binding on a successful callback. List and get return metadata only. Delete cascades bindings that point at it. An organisation Notion login is searchable from the guild builder so the coach can suggest existing databases. Notion stays the home for long-lived operator records; queues and working files stay in local notes or collections.
  • Bindings: which scope offers which catalog item. Bindings are the grant. catalog_kind is native_tool, mcp, mcp_tool, or artifact_grant; PUT or DELETE /api/bindings with any other kind is 422. An mcp binding may carry a connection_id; native-tool and mcp_tool bindings never do. A listed MCP tool inherits the server grant until a tool overlay replaces enabled. Server Off keeps every tool from that server Off.

Visibility follows ownership: a platform connection may be bound anywhere; an organisation one at that organisation or its guilds and agents; a guild one at that guild or its members; an agent one only at that agent.

Resolution walks platform, then the organisation, then the agent's guild, then the agent. Each present row replaces enabled and, when it carries a connection, the current connection. An absent row leaves the current state. The empty chain is disabled. That walk covers enable-downward, opt-out, enable-extra, override-login, and re-enable.

Platform seed bindings enable the note tools (note_find, note_get, note_write, note_delete), the record tools (record_search, record_list, record_add, record_update, record_remove), memory_write, the five script tools, the six schedule tools, tavily_search, secret_store, secret_request, and group_post. New agents inherit that set; an organisation, guild, or agent can turn any of them Off. The Tools view groups the two secret tools as Secrets. report_issue and the eight browser tools are never bindings. Deleting a connection, agent, or guild cascades the bindings that point at it.

The per-scope views that show and edit these bindings are described in Secrets and access.

MCP client and OAuth ​

The orchestrator holds MCP sessions. It does not start MCP servers inside agent sandboxes. The pool key is connection_id: agents that inherit the same login share a session; two connections are two sessions. CoinGecko, Pinned prices, Alchemy, Wallet, X, TwitterAPI.io, Qonto, DexPaprika, and Gmail are local adapters on that catalog: Connect validates credentials, writes a curated tool list into mcp_tools when the adapter has tools, and each call skips the session. CoinGecko hits Pro REST with the stored key; its prices tool quotes coin ids on demand. Pinned prices is its own Connect row: a CoinGecko Pro API key plus up to 20 coins (id, symbol, name). Connect pings the key and refuses ids CoinGecko does not quote. Those coins are quoted into State prices at every run start for agents using that login. The Tools view's coin picker searches with POST /api/mcp/pinned-prices/search before Connect and GET /api/mcp/connections/{id}/pinned-prices/search?q= after, and writes PUT /api/mcp/connections/{id}/pinned-prices. Connect Pinned prices at org, guild, or agent; the ticker list lives on that login and inherits with it. A local relogin gets its own list. CoinGecko lists tokenized equities (xStocks and similar) as coins, so a stock is pinned through its tokenized listing. Alchemy hits NFT REST with the stored key. Wallet signs over Alchemy JSON-RPC with the stored keys. X hits https://api.x.com/2 with the stored bearer token. TwitterAPI.io hits https://api.twitterapi.io with the stored x-api-key. Qonto hits https://thirdparty.qonto.com with Authorization: {login}:{secret_key}. DexPaprika hits https://api.dexpaprika.com with the stored key as the raw Authorization header, retrying https://api-pro.dexpaprika.com on 403. Gmail hits https://gmail.googleapis.com/gmail/v1 with the stored Google access token (gmail.readonly and gmail.compose). Those keys never enter the sandbox. The Tools view shows each as one server with a switch per tool. Test on a connected login opens a function, fills its arguments from the listed schema, and calls POST /api/mcp/connections/{id}/call. The result is redacted against that login's credentials and byte-capped.

A guild connection plus a guild MCP binding gives every member that server's listed tools. An agent overlay with enabled=false revokes that server for that agent. An agent overlay with a different connection_id is a local relogin. Per-tool Inherit / On / Off walk the same platform → organisation → guild → agent chain. A new listed tool inherits the server grant until overlaid. Tool On does not punch through server Off.

Listed tools persist per connection in mcp_tools. Connect and a successful callback list immediately; afterwards the stored catalog serves run starts and access views, and a remote tools/list runs again only once the stored catalog is 10 minutes old. A listing failure at run start keeps the stored catalog and logs; individual calls report their own errors. The model sees {slug}_{name} (dots become _). A call maps that namespaced name through the run snapshot to remote_name and McpPool.call(connection_id, remote_name, args). A remote tools/call waits up to 90 seconds; tools/list stays at 15 seconds. Adapter HTTP stays at 30 seconds. Results use the same output pipeline.

Status at run start:

  • pending: start the run; omit that server's tools.
  • disconnected and the server is enabled: generation-start error.
  • connected: list, persist, then offer the enabled subset.

GitHub is left out of this resolution: it offers no tools, and a coding run reaches it through its guild's repositories, which fail that run alone when their connection is disconnected.

Gmail remote names matching send (case-insensitive) are listed and written Off at the connection owner scope on first appearance. Drive create_file and copy_file use the same overlay. A later sync leaves an existing overlay in place. Notion, Drive, Sentry, Cigale, OpenSea, and Etherscan tool rows appear after a successful remote list. CoinGecko writes its curated tool list after a successful /ping. Pinned prices writes an empty tool list after a successful /ping. Alchemy writes holdings, nft, and collection after a successful NFT API ping. X writes search, user, posts, and post after a successful user lookup of X. TwitterAPI.io writes search, user, posts, post, followers, following, mentions, replies, quotes, and trends after a successful /oapi/my/info balance check. Qonto writes list_bank_accounts, list_missing_invoices, get_transaction, and upload_invoice after a successful /v2/organization ping. DexPaprika writes search, token, prices, pool, pools, ohlcv, and swaps after a successful /usage check. Gmail writes search_threads, get_thread, get_message, list_labels, list_drafts, create_draft, and send_message after a successful /users/me/profile ping. Wallet writes get_address, get_balance, send, call, submit, swap, opensea_buy, opensea_list, opensea_accept_offer, opensea_collection_offer, and opensea_cancel_order after a successful Alchemy JSON-RPC ping and OpenSea chain list; the spend tools use the same default-off overlay. A Uniswap API key is optional: when present and accepted, sync also writes uniswap_swap (default Off). Without that key the rest of the Wallet tools still list. OpenSea remote names get_instant_api_key, manage_collections, get_favorites, manage_watchlist, manage_profile, manage_wallets, manage_drops, check_drop_eligibility, and cancel_orders use it after a successful remote list. Per-connection listed tools live in mcp_tools; the published catalog copy lives in mcp_catalog_tools.

Notion Connect starts a browser OAuth 2.1 Authorization Code flow with PKCE. The orchestrator discovers the remote server's protected-resource and authorization-server metadata, registers a public client (RFC 7591) when the catalog row has no client for this redirect URI, and stores that client on mcp_servers.config. The callback is PUBLIC_BASE_URL plus /api/mcp/oauth/callback when that variable is set (Notion rejects non-loopback http:// redirect URIs). Without it the inbound host is used, which is enough for loopback. The pending connection holds the PKCE verifier; oauth_state looks up the callback. A successful callback writes access and refresh tokens, sets status=connected, writes the enabled binding, and fills account_hint from Notion's email_domain when present. A denied or failed callback deletes a new pending connection; a retry over a disconnected login is left disconnected with its bindings intact. Refresh uses the stored client; invalid_grant marks the connection disconnected.

Gmail Connect follows the same callback and refresh path. The client is the Google Cloud Web application in the orchestrator environment (GMAIL_OAUTH_CLIENT_ID, GMAIL_OAUTH_CLIENT_SECRET); missing credentials are a Connect error. The Cloud project enables gmail.googleapis.com. Authorize requests gmail.readonly and gmail.compose with access_type=offline against Google's user OAuth endpoints. The redirect URI is PUBLIC_BASE_URL + /api/mcp/oauth/callback; the operator's browser follows that redirect, so the provider needs no path to the box (Remote access). Register that URI on the Cloud client.

Drive Connect follows the same callback and refresh path. The client is the same Google Cloud Web application. The Cloud project enables drive.googleapis.com and drivemcp.googleapis.com. Authorize requests drive with access_type=offline. After Google login, Connect lists My Drive, Shared with me, and Shared drives, and requires one folder that is not My Drive. The connection's account_hint is that folder's name. Each Drive tool call is limited to that folder and its descendants. Search and recent-file lists use the Drive API with Shared drive flags, then keep only files in that folder. Read, download, metadata, permissions, copy, and create use that API too. create_file takes file_path under /home/agent; the orchestrator reads the file. download_file_content writes that path and returns it; it does not return file bytes. Both follow the workspace file rules. Small UTF-8 text may use textContent. Create and copy stay off until the operator enables them. Official Drive MCP has no delete. The Google token can see the rest of the Drive. drive is a restricted OAuth scope.

Sentry Connect follows the same dynamic-registration path as Notion against https://mcp.sentry.dev/mcp. A successful callback lists tools from that session.

Cigale Connect is a Bearer API key: the UI collects the key, the connection stores it encrypted, status is connected, and the orchestrator lists tools over POST https://api.cigale.ai/api/v1/public/mcp. The catalog publishes ten tools: knowledge-base tweet search (about five months), 24-hour semantic search, reply drafts from a Cigale agent or campaign, trend reports, reply opportunities, and view forecasts. A rejected key is a Connect error. Token values join run redaction.

OpenSea Connect is an API key on the same token form. The orchestrator lists tools over POST https://mcp.opensea.io/mcp with X-API-KEY. An empty catalog, or a catalog of only get_instant_api_key, is a rejected key. Calls use that session. The default-off overlay covers get_instant_api_key, manage_collections, get_favorites, manage_watchlist, manage_profile, manage_wallets, manage_drops, check_drop_eligibility, and cancel_orders.

Etherscan Connect is a Bearer API key on the same token form. The orchestrator lists tools over POST https://mcp.etherscan.io/mcp with Authorization: Bearer. A rejected key is a Connect error. Calls use that session. Every tool is read-only public onchain data; some need a paid Etherscan plan. Each call counts against the operator's API quota.

CoinGecko Connect is a Pro API key on the same token form. The orchestrator checks GET https://pro-api.coingecko.com/api/v3/ping with x-cg-pro-api-key, then writes eight tools (search, coin, prices, new, movers, supply, chart, trending) into mcp_tools. Calls hit Pro REST and return compact TSV: no images, HTML, tickers, or localization; lists default to 15 rows (cap 30); charts downsample to 48 points. new, movers, and supply need Analyst plan and above. A rejected key is a Connect error.

Pinned prices Connect is a Pro API key plus at least one coin on its own token form. The orchestrator checks /ping with x-cg-pro-api-key and /simple/price for each pinned id. A rejected key or an id CoinGecko does not quote is a Connect error. The connector lists no model tools.

Alchemy Connect is an NFT API key on the same token form. The orchestrator checks GET https://eth-mainnet.g.alchemy.com/nft/v3/{key}/getNFTsForOwner with a zero owner, then writes three tools (holdings, nft, collection) into mcp_tools. Calls hit NFT REST and return compact TSV: no images, descriptions, or token URIs; lists default to 15 rows (cap 30). holdings takes an owner address (ENS on ethereum) and optional chain, contract filter, and page_key. Chains are ethereum, polygon, base, arbitrum, bsc, optimism, ink, robinhood, and solana. Solana holdings use DAS getAssetsByOwner on solana-mainnet; EVM chains use NFT REST. A rejected key is a Connect error.

X Connect is a bearer token on the same token form. The orchestrator checks GET https://api.x.com/2/users/by/username/X with Authorization: Bearer, then writes four tools (search, user, posts, post) into mcp_tools. Calls hit X API v2 and return compact TSV: no profile images; lists default to 15 rows (cap 30); post text clips at 280 characters. search is recent search (last 7 days). user and posts take a username or user id. post takes a post id or status URL. A rejected token is a Connect error.

TwitterAPI.io Connect is an API key on the same token form. The orchestrator checks GET https://api.twitterapi.io/oapi/my/info with x-api-key, then writes ten tools (search, user, posts, post, followers, following, mentions, replies, quotes, trends) into mcp_tools. Calls hit TwitterAPI.io REST and return compact TSV: no profile images; lists default to 15 rows (cap 30); post text clips at 280 characters. search is advanced search for the last 24 hours, ranked by Top. user, posts, followers, following, and mentions take a username; user and posts also accept a user id. post, replies, and quotes take a post id or status URL. trends takes an optional WOEID (default 1, worldwide). A rejected key is a Connect error.

Qonto Connect collects the organization's login and secret key from Integrations → API key. The orchestrator checks GET https://thirdparty.qonto.com/v2/organization with Authorization: {login}:{secret_key}, then writes four tools (list_bank_accounts, list_missing_invoices, get_transaction, upload_invoice) into mcp_tools. Calls hit Qonto Business API v2 and return compact TSV. list_missing_invoices finds completed transactions that still need an invoice: empty attachment_ids, attachment_required true, and attachment_lost not true (default last 90 days, debit side). upload_invoice attaches a PDF, JPEG, or PNG from the agent workspace (/home/agent, under the workspace file rules); Qonto processes the file in the background and the call waits up to 60 seconds. Production API only. A rejected key is a Connect error. Login and secret key join run redaction.

DexPaprika Connect is an API key on the same token form. The orchestrator checks GET https://api.dexpaprika.com/usage with the key as the entire Authorization header, then writes seven tools (search, token, prices, pool, pools, ohlcv, swaps) into mcp_tools. A keyless plan, or HTTP 401/403, is a rejected key. Calls hit REST and return compact TSV: no images, websites, or descriptions; lists default to 15 rows (cap 30); OHLCV downsamples to 48 candles. search is cross-network. token, prices, pool, pools, ohlcv, and swaps take a network id (ethereum, solana, base, …). prices batches at most 10 addresses. ohlcv defaults to the last 24 hours at 1h; a free key allows 10m and up over 7 days. A rejected key is a Connect error.

Wallet Connect collects an EVM private key, a Solana private key, an Alchemy API key, an OpenSea API key, and an optional Uniswap API key. The orchestrator checks eth_blockNumber on eth-mainnet, getSlot on Solana, and OpenSea's chain list with X-API-KEY. A Uniswap key is checked against Uniswap's supported-chain list with x-api-key; omitting it is not a Connect error. Then it writes the curated wallet tools into mcp_tools. Calls hit Alchemy JSON-RPC, except arc which uses https://rpc.mainnet.arc.io. Polygon, Arbitrum, Optimism, and BSC fall back to a public RPC when Alchemy rejects the key for that network. Amounts are human decimal strings. Chains are ethereum, polygon, base, arbitrum, bsc, optimism, ink, robinhood, hyperevm, arc, and solana. send transfers native coin or a token. call sends 0x calldata on an EVM chain. submit signs a prebuilt Solana transaction (base64). swap quotes then executes: Jupiter on Solana, LiFi on ethereum, polygon, base, arbitrum, bsc, optimism, hyperevm, and robinhood. uniswap_swap quotes then executes on Uniswap v2/v3/v4 through the Trading API on ethereum, polygon, base, arbitrum, bsc, optimism, ink, robinhood, and arc, and is listed only when a Uniswap key is stored. The OpenSea tools buy a listed NFT or a collection floor, list, accept offers, place collection bids, and cancel orders on EVM chains with the stored OpenSea key. Spend tools are listed Off at the connection owner until the operator toggles them. A later sync leaves an existing overlay in place, so a connected Wallet picks up new spend tools as Off without changing send. account_hint is the two truncated addresses. A rejected key is a Connect error. Wallet key values join run redaction.

Platform-scope Connect and platform credential mutation stay reserved. The UI and API create organisation, guild, and agent logins.

GitHub ​

github is backed by one GitHub App per box: the orchestrator's GITHUB_APP_ID, GITHUB_APP_SLUG, GITHUB_APP_PRIVATE_KEY (PEM, with \n escapes or real newlines), GITHUB_APP_CLIENT_ID, and GITHUB_APP_CLIENT_SECRET; a missing value is a Connect error (422 with setup_state: unsupported). Every connector's access view carries oauth_setup_state: not_applicable, dynamic, configured, or unsupported when the box holds no OAuth client or App for it. The Tools view marks an unsupported connector Not set up, names the variables the box lacks, and disables Connect (Integrations). The operator registers the box's own App. The App:

  • asks for the repository permissions Contents and Pull requests (read and write) and Metadata (read);
  • requests user authorization (OAuth) during installation and redirects on update, with PUBLIC_BASE_URL + /api/mcp/oauth/callback as its callback URL;
  • subscribes to no webhook.

Connect is offered at organisation, guild, and agent scope and follows the pending-connection path of the browser logins. The App is installed once per GitHub account; later Connects reuse that install. Each login then chooses its own repositories on the GitHub card (Tools), the same way Pinned prices picks coins. A narrower scope inherits the wider login's repositories until it connects its own login and picks a different set.

  1. Connect creates a pending connection with its oauth_state. When the organisation has no connected GitHub login the operator can see, it answers with authorization_urlhttps://github.com/apps/<slug>/installations/new?state=…, where the operator chooses the account and the repositories the App may reach. When a login already exists, Connect sends the operator to user authorization at /login/oauth/authorize instead, so a second guild can attach the existing installation without installing the App again. A short-lived cookie stores that state so the callback can find the login if GitHub omits it after the authorize hop.
  2. After install, GitHub either sends code (and sometimes installation_id and state) or a setup redirect with installation_id and state and no code. A setup redirect starts user authorization at /login/oauth/authorize with the same state and the callback URL. The orchestrator exchanges code for a user token and keeps an installation only when that user can reach it: the callback's id when present, otherwise this App's only installation on the token. Several installations open a picker (/api/mcp/oauth/github-installation) so the operator chooses the account, or installs the App on another. An id in the URL alone proves nothing. The user token is discarded. A refused callback deletes a new pending connection.
  3. The connection stores the installation id, takes the account's login as account_hint, becomes connected, and writes its enabled binding.

An installation that needs an organisation admin's approval returns setup_action=request. Connect again after they approve, and GitHub's update redirect completes it. The connection lists no model tools; agents use git and gh in the sandbox, and the Tools view shows it without an On/Off switch. GET /api/mcp/connections/:id/github/repositories lists the repositories the installation reaches (name, full_name, owner, private, default_branch, url). PUT /api/mcp/connections/:id/github/selected stores { repositories: [{ name, url, default_branch }] } on that login; each must be one the installation reaches, and is stored with GitHub's id for it (github_id), which a rename of the repository or its owner keeps. The access payload includes github_repositories when the login has a selection. A guild-owned login also mirrors the selection into guild_repositories. GET /api/guilds/:id/repositories still lists that guild's extra git URLs and the GitHub logins the operator can see.

Tokens. The App's private key and the installation id never leave the orchestrator. At each generation start of a coding agent the orchestrator signs an App token (RS256, 9 minutes) and requests, for each connection the guild's repositories use, an installation access token limited to those repositories by their GitHub ids, with contents and pull requests write; GitHub makes it valid for one hour. A repository stored without an id gets the one GitHub lists under its name in the installation, and GitHub's answer names every repository as it is now: a repository whose name or owner GitHub changed gets its current URL, on the guild repository and in the login's selection. When GitHub refuses the token because the installation no longer reaches a repository, the error names it. The tokens go to /run/guilds/git-tokens.json through the sandbox file API. guilds-git-credential is the credential helper for https://github.com with useHttpPath, set in the image's system git configuration and again through the environment (GIT_CONFIG_COUNT), which git reads after every configuration file: a helper the agent configures in its home, such as the one gh auth setup-git writes, cannot replace it. git gets the token of the repository it is talking to, and shell exports the token of the repository that holds the working directory as GH_TOKEN for gh. Before a shell call, a token within 10 minutes of expiry is replaced. Every token joins the run's redaction set. What a coding run does with the repositories is in Runs.

Removal. When GitHub refuses a token because the installation was removed or suspended (404 or 403), the connection becomes disconnected, and the coding generation that needed it fails at start. Deleting the connection deletes the guild repositories added through it.

Integrations ​

An integration is something the box as a whole can do once the operator sets its key in .env, as opposed to a login an organisation connects. One registry (apps/orchestrator/src/instance/integrations.ts) names each one, the environment variables it reads, and what it switches on; modules ask the registry, not the environment. An integration is on when every variable it requires is set, off when none is, and incomplete when some are.

IntegrationVariablesSwitches on
openrouterOPENROUTER_API_KEYOpenRouter models for an organisation with no OpenRouter key of its own
deepinfraDEEPINFRA_API_KEYDeepInfra models for an organisation with no DeepInfra key of its own
web-searchTAVILY_API_KEYtavily_search
group-routingTYPESAFE_API_KEY, and TYPESAFE_MODEL to tune itRecipient and history selection in group chat
github-appGITHUB_APP_ID, GITHUB_APP_SLUG, GITHUB_APP_PRIVATE_KEY, GITHUB_APP_CLIENT_ID, GITHUB_APP_CLIENT_SECRETThe GitHub connector and a guild's repositories
google-connectorsGMAIL_OAUTH_CLIENT_ID, GMAIL_OAUTH_CLIENT_SECRETThe Gmail and Drive connectors
error-reportingSENTRY_DSNSentry

GET /api/instance (any user of the box) reports the box's auth_mode and each integration's state, which of its variables are set, never a value, and what it switches on: model providers, native tools, and connector slugs. The operator app reads it and turns off what depends on a missing integration, naming the variables that would switch it on: Admin, Platform keys lists every integration with its state; the Tools view lists tavily_search as not offered and marks a connector without its App or OAuth client Not set up; a banner above every page says no model key is set when the box and the organisation hold none at all; the model picker marks a provider neither holds a key for No key yet. Setting the variables is Configure.

Free software under the GNU Affero General Public License, version 3 only.