Nodal-Agents
Concepts

Connectors & MCP

How integrations reach your agents — OAuth/API-key connectors and MCP servers over HTTP or stdio.

Nodal-Agents gives agents access to external services through two complementary mechanisms: Connectors and MCP servers. Both are assigned per agent; neither changes the runner's generic architecture — the agent's tool whitelist is computed from its assigned integrations at job-start time.

Connectors

A connector is a first-party integration backed by a dedicated adapter in the runner. The connector catalog lists every integration the runner knows how to talk to. Adding a connector from the catalog guarantees the adapter ships with it — no connector is exposed without a working implementation.

Auth types

Auth typeHow you connect
OAuth 2Click "Connect" in the dashboard; an OAuth flow authorizes the integration. One Google credential backs Drive, Gmail, Sheets, and Docs simultaneously.
API keyPaste a key or Personal Access Token.

Available connectors

Fifteen connectors ship in the catalog:

NameAuth
NotionAPI key
Notion (OAuth)OAuth 2
Google DriveOAuth 2 (google-oauth)
GmailOAuth 2 (google-oauth)
Google CalendarOAuth 2 (google-oauth)
Google SheetsOAuth 2 (google-oauth)
Google DocsOAuth 2 (google-oauth)
Outlook MailOAuth 2 (microsoft-oauth)
Airtable (OAuth)OAuth 2
AirtableAPI key (Personal Access Token)
ApifyAPI key
CloudflareAPI key
FirecrawlAPI key
TavilyAPI key
PoyoAPI key

The connectors reference is generated from the catalog itself, one page per connector, with the full tool inventory grouped by risk and the exact OAuth scopes each one requests.

Assign a connector to an agent

  1. Go to API Connectors in the dashboard and connect the integration (OAuth flow or paste a key).
  2. Open the target agent's edit page and go to the Connectors tab.
  3. Enable the integration. Its tools become available on the agent's next job.

The Connectors tab also carries an enabled-operations allowlist per connector: destructive operations (delete, overwrite) can be switched off for one agent while the rest of the connector stays available. Every connector page in the reference marks which of its tools are destructive.

Every operation an agent holds has its setting on the agent's Autonomy tab, and that setting is what applies. An operation marked ⚠ on the Connectors tab (sending an email, publishing a site, deleting a file, an event or a record) starts on Ask for approval under propose_confirm; under destructive_gate a destructive operation starts on Ask and the others on Run without asking; under fully_autonomous, all start on Run without asking.

Credentials are fetched from the database at execution time — they are never baked into a job at enqueue time.


MCP servers

MCP (Model Context Protocol) servers expose tools that the runner discovers at job-start and injects into the agent's whitelist. Tools are namespaced with the server's slug to avoid collisions: a server with slug stripe exposes tools like stripe__retrieve_balance.

The catalog carries 24 ready-made servers plus two "add your own" entries, one HTTP and one stdio. Each has its own generated page with transport, auth and setup.

Transports

Nodal-Agents supports two transports:

HTTP (Streamable HTTP) — a hosted server reachable over HTTPS. The runner connects, authenticates (header, query-param, or Bearer token), and calls tools/list. Examples: Stripe, Airtable (hosted), Cogni Cortex, Composio.

stdio — a local subprocess spawned by the runner on first use. The runner executes the command (typically npx -y <package>) and communicates via stdin/stdout. The subprocess is torn down after the job. Examples: Filesystem, Git, GitHub, PostgreSQL, Playwright.

For stdio servers that use npx, the package is downloaded on first use (requires network; expect 5–30 s latency on the initial connection; subsequent runs use the npx cache).

Auth

HTTP servers authenticate with a static key. How the key is injected depends on the server:

  • header — sent as a named request header (e.g. x-api-key: <key>)
  • bearer — sent as Authorization: Bearer <key>
  • query — appended to the URL as a query parameter

stdio servers use environment variables instead of HTTP auth. Env var values are encrypted at rest.

Tool risk levels

The runner maps MCP tool annotations to its own risk model:

MCP annotationRisk level
readOnlyHint: trueread (autonomous, no approval gate)
destructiveHint: truedestructive
no annotationwrite (conservative default — passes through the approval gate)

What the agent reads of a tool result

A tools/call result has two channels: content (blocks meant for the model) and structuredContent (the machine form). The agent reads both:

  • every text block, in the order the server wrote them;
  • then structuredContent, serialized, so the agent keeps what its next call needs (an id, a status). It is not repeated when a text block already is its serialization (compared as JSON: indentation and key order do not matter);
  • a server on the 2024-10-07 protocol (toolResult) is read the same way;
  • images, audio, embedded binary resources and resource links are each named in one line (type, size, link) — the agent is told they exist, it does not receive their bytes;
  • isError: true reaches the agent as a failed call, with the server's text framed as external data, like a successful result;
  • the tool call's log keeps the whole result, structuredContent included.

The agent reads at most 25,000 characters of one tool result, the same budget for every tool, counted on what it actually reads: the frame around external data included. Within it, the structured payload has priority — also when a text block is its serialization: a long text is cut to the room left, and the cut is said. A structured payload that alone exceeds the budget is reduced, not cut at its end: its longest strings and lists give way first, each cut said where it happened, so its short fields (an id, a status) are kept. The text it pushed out is named.

The tool call's log keeps more: up to 50,000 characters (MCP_RESULT_CHAR_CAP) for all the content blocks together, and as much again for the structured payload, reduced the same way. Blocks past that are counted, never dropped in silence.

When a server asks a question

A server can ask a person something in the middle of one of its tool calls — "how many copies?", "print it now?" — with MCP elicitation in form mode. Nodal announces that it supports forms ({ "elicitation": { "form": {} } }) and puts the question in front of a person:

  • The run keeps going while it waits: the server keeps its call open, and the call's own time limit is paused for as long as the person thinks.
  • The question shows on the run, in its conversation thread, on the Approvals page and in the bell. A request made on the web, by an MCP client, the API or a schedule keeps its question there.
  • A request made on a messaging channel gets its question in the same conversation, never in the owner's: the images first, then a card to fill in place.
    • Telegram, Discord, Slack: a button per choice, Yes / No per switch, and ✏️ next to a number or a text field. Tap ✏️, then reply to the card with the value (on Slack, in the card's thread). The card shows the values so far and is redrawn after each step. Its accept and decline buttons come first, above the settings, and settle it. A value outside the form is refused, and the reason is posted.
    • A form with more buttons than the channel can show (Discord: 5 rows of 5) gets the question and a pointer to the dashboard, with the reason.
    • WhatsApp has no buttons: the question and a pointer to the dashboard.
  • The form is drawn from the server's requestedSchema: choices, numbers with their bounds, switches, text. The answer is checked against it before it leaves. The accept button returns accept with the values, the decline button returns decline (their labels: see below). The server reads cancel when nobody answers in time (10 minutes, NODALAI_ELICITATION_TIMEOUT_MS), when the run stops, or when the server withdraws its question.
  • The server's own wait must be at least as long. The MCP SDK gives up after 60 seconds unless told otherwise: elicitInput(params, { timeout }).
  • URL mode is not supported yet.

A tool that needs approval (the default for MCP tools) and then asks its own question asks twice. Always allow on that tool removes the first one.

Button labels

A button should say what it does. A server names its two buttons in params._meta under the key nodal/actions:

{
  "message": "Print 1 page on A4?",
  "requestedSchema": { "type": "object", "properties": {} },
  "_meta": { "nodal/actions": { "accept": "Print", "decline": "Cancel" } }
}
  • Both keys are optional. Without a label the buttons read ✅ Confirm and Decline.
  • A label is plain text, at most 32 characters once line breaks and invisible formatting characters are removed. A label that breaks the rule is ignored and logged; its button keeps the default, and the question is still asked.

Images with a question

A server can show images with its question, a print preview for instance, by adding them to params._meta under the key nodal/attachments:

{
  "message": "Print 1 page on A4?",
  "requestedSchema": { "type": "object", "properties": {} },
  "_meta": {
    "nodal/attachments": [{ "mimeType": "image/png", "data": "<base64>", "caption": "Page 1" }]
  }
}
  • Accepted types: PNG, JPEG, WebP and GIF, and the bytes must start like the declared type.
  • At most 4 images, 5 MB each once decoded. A caption longer than 300 characters is cut.
  • An image that breaks a rule is dropped and logged. The question is still asked.

Adding an MCP server

From the catalog:

  1. Go to MCP Connectors in the dashboard (the Agents board, under Connect).
  2. Pick a server from the library (verified or pending badge) and click Connect.
  3. Fill in the URL (if required), API key, and any env vars shown.
  4. Open the target agent's edit page → Connectors tab → enable the MCP server.

Custom HTTP server:

Select "Custom MCP (HTTP)" in the catalog. Provide a short slug (becomes the tool-name prefix), the server URL, and auth details.

Custom stdio server:

Select "Custom MCP (stdio)". Provide a slug, the executable command (e.g. npx), args, and any required env vars. The runner spawns it with your permissions — only connect servers you trust.

Catalog status badges

  • Verified — tested end-to-end against a live server.
  • Test pending — shipped but not yet live-verified. Connection parameters may need adjustment.

Nodal as an MCP server

The direction also works in reverse: Nodal can expose itself as an MCP server, so an external client — your terminal, a coding agent like Claude Code — hands work to your agents instead of the other way around.

Enable the MCP server switch in Settings → Safety (off by default). The same section then gives, for the machine that hosts Nodal:

  • Claude Code: the exact command to run. It is built from the Node and the script that started this install, so it works whether Nodal was installed globally, launched with npx, or run from source. Its shape:

    claude mcp add nodal -- "<path to node>" "<path to the nodal-agents CLI>" mcp serve
  • Claude Desktop: the mcpServers entry for its configuration file, and an Add to Claude Desktop button that writes it for you. The button keeps your other servers and saves a copy of the previous file first.

Both clients can be connected at the same time; their runs land on the same page. If the section asks you to start Nodal with nodal-agents up, the web was started some other way and does not know the command: it shows none rather than a guess.

The client gets exactly two tools. run_task creates a tracked job for your workspace's root agent (or a chosen agent by slug) and returns the job id immediately. The work itself runs asynchronously through the normal loop — the agent's own tools, approval rules, budgets, and delegation. get_task reads the job back with that id (read-only): its status, the agent's result, its deliverables and anything waiting for you, which you still answer on the dashboard. It can wait up to 60 seconds for the run to finish. A client that must not land anywhere else can state the workspace it expects (expectedWorkspaceId): if the server serves another one, the call is refused before any job is created.

Nobody typed these runs, so they have no conversation: they land in their own MCP folder under Channels, whose rows open the run page directly, and they appear in Logs → Activity like any other run.

What the door never grants, whatever the switch says: the configuration tools. An MCP job can ask for work; it cannot create agents, skills, connectors or automations — that is enforced in the runner. At most 5 MCP jobs of a workspace run at the same time, whichever client or server process started them: a sixth is refused with the list of the running ones, and a job that finishes frees its place. What a job may spend is bounded by its agent's own budget. Turning the switch off cuts clients that are already connected — the server re-checks it on every call.


How tools reach the agent

At job-start the runner fetches the agent's assigned connectors and MCP servers from the database, opens connections, calls tools/list on each MCP server, and builds the complete tool whitelist for that job. The agent only sees tools it has access to — there are no implicit defaults.

See Agents for the full picture of how a job runs.

On this page