Skip to content

Overview

An AI agent in a workbench can only use what it can reach. An MCP server is how it reaches something: current documentation for the library you are using, your issue tracker, a service’s own API. Register one in the workbench yml and every agent in the container gets it on the next apply, in its own config format, without you touching anything inside the container.

New to Monoceros? Start with Installation.

Add one by name, at init or later:

Terminal window
monoceros init acme --with-mcp-servers=context7
Terminal window
monoceros add-mcp-server acme context7

The curated set:

ServerFor
Context7current documentation for the libraries you use
Atlassian Rovothe Jira issue and the Confluence page behind the work
Atlassian ForgeForge modules, manifest rules and UI Kit, first-hand
Notionrequirements that live in Notion pages and databases
LinearLinear issues, projects and cycles
Figmathe design itself: layout, components, design tokens
Microsoft Learnfirst-party .NET and Azure docs with code samples

Some want a credential, which lands in your env file as an empty key for you to fill:

mcpServers:
- name: context7
options:
apiKey: ${CONTEXT7_API_KEY}

Others sign in interactively, and then the entry is one line and your env file stays untouched:

mcpServers:
- name: linear

The catalog will stay small on purpose. It carries the servers almost every workbench wants, verified against the workbench, and everything else goes in the same block with its own definition.

There are thousands of MCP servers and every one of them publishes its config as the same JSON block. Transpose that into the mcpServers: block and it is just as declarative as a curated connector, so it travels to your other machine with the rest of the workbench:

mcpServers:
- name: acme-internal-docs
transport: http
url: https://mcp.internal.acme.example/mcp
headers:
Authorization: 'Bearer ${ACME_MCP_TOKEN}'
- name: acme-crm
transport: stdio
command: npx
args: ['-y', '@acme/mcp-server']
env:
ACME_API_TOKEN: ${ACME_API_TOKEN}

transport is stdio for a server that runs inside your container, or http / sse for one you reach over the network. Either way the credential is a ${VAR} resolved from your env file, exactly as a curated connector’s is, so the yml stays shareable.

An entry that carries its own definition is never resolved against the catalog, even if a curated connector of the same name ships later. Your yml keeps meaning what it meant when you wrote it.

Only a stdio server stays behind the container boundary. It runs as a process inside your container, which also means a package from someone else runs there on every session, so it is worth reading what you point it at. An http or sse server is a remote endpoint: the request leaves the container, and the credential goes with it.

Both are legitimate; the difference is worth knowing before you add one, which is why monoceros check reports the transport per server.

Where a connector takes an API key, that key never goes in the yml. It is a ${VAR} placeholder resolved from your env file at apply time, so the yml stays shareable, and add-mcp-server seeds the empty key for you.

Fill it before you apply. A server registered without its credential would appear in the agent’s tool list and then fail on first use, in the middle of a run, so apply stops on an empty one and names the key and the file.

Two kinds of connector ask for nothing at all here. Some sign in interactively, which is the next section. Others serve public documentation and need no account either way: Atlassian Forge and Microsoft Learn are one line in the yml and nothing in your env file.

Some MCP servers authenticate with OAuth, and there is no key anywhere to fill. You sign in once from inside the container, in a browser, and the agent keeps the grant from then on. Most of the curated set works this way - Atlassian Rovo, Notion, Linear and Figma - and a server you write in yourself does too, with no credential anywhere in the entry:

mcpServers:
- name: acme-tracker
transport: http
url: https://mcp.acme.example/mcp

Apply registers it for every agent in the container. The sign-in itself is the agent’s own, so it happens once per agent, and the container opens the browser on your host for you:

AgentSign in with
Claude Code/mcp inside a session
OpenCodeopencode mcp auth linear in the container’s shell

Both keep the grant in a path the workbench persists, so it survives your next apply.

monoceros check reports such a server as what it is rather than as broken:

MCP servers
context7 → Claude Code, OpenCode, Rovo Dev
tools: resolve-library-id, query-docs
linear → Claude Code, OpenCode, Rovo Dev
signs in interactively; authenticate once inside the container

The check itself holds no grant, so it can tell you that this server wants a sign-in, but not whether you have already done it. It still reports a server that refuses a credential you did give it. A stale token stays a finding.

The chain from a yml entry to a tool the agent calls has several links, and an agent’s own account of its tool list is not reliable evidence. So ask the workbench instead:

Terminal window
monoceros check acme
MCP servers
context7 → Claude Code, OpenCode, Rovo Dev
tools: resolve-library-id, query-docs

That is the whole chain in one place: the server is in each agent’s config, and those are the tools it actually serves. If an agent then claims it has no such tool, you have the list to hold against it.

All of them. Claude Code, OpenCode and Rovo Dev each keep MCP servers in their own file and in their own shape, and apply writes to each in the format it wants. A workbench with mcpServers: and no AI agent installed fails to apply rather than quietly doing nothing.

Anything you register yourself inside the container, with claude mcp add for instance, stays untouched. Apply only manages the entries it wrote. It also stops rather than guessing if the same name exists on both sides.