Skip to Content
GuidesConnections (MCP)

Connections (MCP)

A Connection (internally an MCP Server) is an external Model Context Protocol  server that you register once and then assign to one or more Assistants (Agents). Once assigned, the server’s tools become available to that Assistant when it runs — filesystem access, a database client, a web API wrapper, and so on.

Connections are how you extend an Assistant beyond its built-in capabilities without writing custom code. You define how to start the server (a command plus arguments and environment variables), and RondoFlow wires it into the Assistant’s runtime configuration.

A Connection’s type selects its transport. stdio (the default) is a local process RondoFlow starts and talks to over standard input/output. http and sse are remote servers reached over a URL, with optional authentication. Pick stdio for a server you run on the same machine, and http/sse for a hosted one.

Connections apply to Claude Code Assistants only — the default provider. OpenAI and Perplexity Assistants run through HTTP APIs and skip MCP entirely at spawn time, so any Connection assigned to one has no effect. (Those providers expose their own built-in tools, such as web search and deep research, configured on the Assistant instead.)

Two ways an Assistant gets tools

A single Assistant can pull MCP servers from two independent sources, which RondoFlow merges at run time:

SourceWhere it comes fromNaming in the merged config
Direct ConnectionAn MCP server you register and assign to the AssistantPlain registered name (e.g. filesystem)
Skill-bundledA Skill installed on the Assistant that ships its own mcpConfigNamespaced as {skillName}/{serverName}

This split lets a Skill bring its own tools along with it, while you stay free to attach extra standalone Connections on top. A Skill’s mcpConfig is declared in its rondoflow.json manifest (or in the SKILL.md frontmatter that gets merged with it) and is also stdio-only — see Skills for how to bundle one.

Register a Connection

Open the MCP Connections panel and add a server. Which fields apply depends on the chosen transport:

FieldTransportRequiredNotes
nameallyes1–100 chars, unique across all Connections
descriptionallnoOptional free-form string; no enforced maximum (only name is length-bounded)
typeallnoTransport: stdio (default), http, or sse
commandstdioyes (stdio)Executable to start, e.g. npx, python, or an absolute path
argsstdionoArray of arguments; defaults to []
envstdionoMap of environment variables passed to the process
urlhttp / sseyes (remote)Endpoint URL of the remote MCP server
authhttp / ssenoOptional authentication for the remote server — see Authenticating a remote Connection. Secret fields are encrypted at rest.

In the UI, the form swaps between the local and remote fields as you pick the transport. For stdio, arguments are entered comma-separated and split into the args array, and environment variables are entered as key/value pairs. A typical local filesystem server looks like this:

Name: filesystem Type: stdio Command: npx Args: -y, @modelcontextprotocol/server-filesystem, /tmp

A remote server instead takes a URL and (optionally) an auth method:

Name: docs-search Type: http URL: https://mcp.example.com/sse Auth: Bearer token

The env map is stored as-is and may contain secrets (API keys, tokens). Treat the Connection record as sensitive and avoid logging the raw server response in shared environments. When you export a Workspace (Canvas) as a shareable bundle (format rondoflow-canvas), each Connection’s env is stripped from the bundle, so secrets are never shared on export/import — re-add them after importing.

The MCP Connections panel may show a per-server Test button. It is currently a placeholder that reports a mock result — not a live connectivity check against the server.

Authenticating a remote Connection

A remote (http / sse) Connection can carry an auth object so RondoFlow can reach a protected server. Four methods are supported:

Methodauth.typeWhat it sendsFields
NonenoneNothing — an open server
Bearer tokenbearerAuthorization: Bearer <token>token
Custom headerheaderAn arbitrary header, e.g. X-API-Key: <value>headerName, headerValue
OAuth2 (client credentials)oauth2_client_credentialsExchanges client credentials for an access token, then sends Authorization: Bearer <token>tokenUrl, clientId, clientSecret, optional scope

The secret fields — the bearer token, the custom headerValue, and the OAuth2 clientSecret — are encrypted at rest (AES-GCM). The API never returns them: reads come back in a redacted shape that only reports whether each secret is set, and the editor shows a “secret is set — leave blank to keep” hint so you can change other fields without re-entering it.

At spawn time a remote Connection’s auth is resolved into request headers before the merged config is handed to the CLI: a bearer or custom header is injected directly, while OAuth2 client-credentials are first exchanged at the tokenUrl for a short-lived access token. A stdio Connection has no auth — it relies on env instead.

Add a Connection card on the canvas

Connections are also a first-class node (Card) on the Workspace (Canvas). Drag the Connection card (the plug icon, purple accent) from the palette onto the canvas, then draw an edge from it to an Assistant card. That visual link is an association edge — the same kind used by Skill and Safety Rule cards.

Connection cards attach to Assistants only. RondoFlow rejects an edge from a Connection to anything other than an Assistant card and shows a toast explaining why. The Connection card is gated behind the full interface tier.

See The canvas for the full card catalog and edge types.

Assign a Connection to an Assistant

Register the Connection

Create the MCP server once (see above). It now exists in your Connection library independent of any Assistant.

Attach it to an Assistant

Assign the Connection to the Assistant that needs it — either by drawing the edge on the canvas or via the API. The same Connection can be attached to multiple Assistants — assignments are idempotent (an upsert), so re-attaching is a no-op rather than an error.

Run the Assistant

When the Assistant starts, RondoFlow builds its merged MCP config and exposes the server’s tools to the run.

To remove access, detach the Connection from the Assistant. Detaching only removes the link — the Connection itself stays in your library. Deleting a Connection outright cascades: all of its Assistant assignments are removed automatically by the database.

How the merged config is built

When an Assistant runs, the MCP config builder assembles one mcpServers map from both sources:

Load Skill-bundled servers

It reads the Assistant’s enabled Skills (ordered by priority, ascending) and pulls each Skill’s mcpConfig. A Skill’s config may be a single server, or a map of serverName → config — both shapes are supported. Each entry is registered under a namespaced logical name:

  • A map entry becomes {skillName}/{serverName}.
  • A single-server config becomes {skillName}/default.

Load direct Connections

It reads every MCP server assigned directly to the Assistant and registers each under its plain registered name (no namespace).

Detect conflicts

For every server it computes a signature of command::args. If two or more sources resolve to the same signature, that signature is reported as a conflict, listing the logical names (sources) that produced it.

The result is a structure shaped like this:

interface MergedMcpConfig { readonly mcpServers: Readonly<Record<string, McpServerConfig>> readonly conflicts: ReadonlyArray<{ readonly name: string // human-readable "command args" readonly sources: readonly string[] // logical names that collided }> }

Only the mcpServers map is forwarded to the spawned Claude Code CLI (as the --mcp-config argument); the conflicts list is internal metadata used for diagnostics. The serialized config is redacted in logged spawn arguments so secrets in env never reach logs.

Why namespacing matters

Because Skill-sourced servers are prefixed with the Skill name, two different Skills can each bundle a server called search without clobbering each other — they become web-tools/search and docs/search. Direct Connections keep their plain names, so you control those names directly through the unique name field.

A conflict means multiple sources start the exact same command + arguments — usually a Skill and a direct Connection both providing the identical server. Both entries still appear in the merged map under their own names; the conflict list is a heads-up so you can drop the redundant one.

Connections API

All routes return the standard { success, data?, error?, meta? } envelope. They sit behind the default-deny auth middleware: GET reads are open to any authenticated user, while the mutating verbs (POST/PATCH/DELETE, including assign/unassign) require the write capability — that is, an editor or higher role. See Users & roles for the capability matrix.

Manage Connections

MethodPathPurpose
GET/api/mcp-serversList all Connections (newest first)
GET/api/mcp-servers/:idGet one Connection, including its Assistant assignments
POST/api/mcp-serversRegister a new Connection (returns 201)
PATCH/api/mcp-servers/:idUpdate fields on a Connection (partial)
DELETE/api/mcp-servers/:idDelete a Connection (cascades assignments)

Attach / detach to an Assistant

MethodPathPurpose
POST/api/agents/:agentId/mcp/:mcpServerIdAssign a Connection to an Assistant (idempotent, returns 201)
DELETE/api/agents/:agentId/mcp/:mcpServerIdUnassign a Connection from an Assistant
GET/api/agents/:agentId/mcpList the Connections assigned to an Assistant

Create example:

curl -X POST http://localhost:3001/api/mcp-servers \ -H 'content-type: application/json' \ -d '{ "name": "filesystem", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"], "env": { "READONLY": "1" } }'

Remote (http/sse) Connection with bearer auth:

curl -X POST http://localhost:3001/api/mcp-servers \ -H 'content-type: application/json' \ -d '{ "name": "docs-search", "type": "http", "url": "https://mcp.example.com/sse", "auth": { "type": "bearer", "token": "sk-..." } }'

Attach example:

curl -X POST http://localhost:3001/api/agents/AGENT_ID/mcp/SERVER_ID

Data model

Two tables back Connections:

model McpServer { id String @id @default(uuid()) name String @unique description String? type String @default("stdio") // 'stdio' | 'http' | 'sse' command String? // stdio transport only args String[] env Json? url String? // http/sse transport only auth Json? // { type, ... } — secret fields encrypted at rest createdAt DateTime @default(now()) agents AgentMcpServer[] } model AgentMcpServer { agentId String mcpServerId String agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade) mcpServer McpServer @relation(fields: [mcpServerId], references: [id], onDelete: Cascade) @@id([agentId, mcpServerId]) }

AgentMcpServer is a join table with a composite primary key, so each Assistant–Connection pairing is unique and deleting either side cascades the link away.

Last updated on