Skip to content
Anlyon
Esc
↑↓navigate↵open⌘Jpreview
On this page

Govern a remote MCP server

Register a remote MCP server with Anlyon, and adopt its tools as actions with approvals and receipts. Your agent can set it up; a person signs off what it can call.

An agent connected straight to an MCP server holds the server’s credential and can call every tool on it, including tools nobody reviewed and tools whose definition changed after you trusted them. Register the server with Anlyon instead, and adopt the tools you want. The agent calls those tools as actions, the credential stays in Anlyon, and each call goes through your approvals and leaves a receipt.

This page is about MCP servers behind Anlyon. To connect your agent to Anlyon, see MCP servers.

Why this cuts risk and effort

  • Risk: only what a person adopted can be called. A tool you did not adopt is not offered and cannot be called through Anlyon.
  • Risk: a tool that changes stops. An adopted tool is pinned to the definition a person reviewed. If the server changes it, the call is refused, nothing is sent, and it stays refused until a person adopts it again.
  • Risk: the credential stays out of the agent’s context. It is stored as a secret, or as a connected account for a server that signs people in.
  • Effort: your agent does the setup. It can register the server and read its tools from a CLI login or an API key. You make one decision, at one link, instead of working through the setup yourself.

Set one up

Register the server

anlyon mcp add https://mcp.tracker.example/mcp \
  --header "Authorization: Bearer {{secret:TRACKER}}"
Registered tracker: 2 tools listed, none adopted.
TOOL          SUGGESTED    STATE        APPROVAL  ACTION
delete_issue  destructive  not adopted  -         -
list_issues   read         not adopted  -         -

Anlyon lists the server’s tools and adopts none of them, so no agent can call anything yet. Store the credential first in the console’s Secrets, and name it in the header as a reference. Naming a secret needs the secrets:read scope.

A registration with no stored credential applies at once. It waits for a sign-off when it would send a stored secret, or when the server signs people in with OAuth. Registering from the console needs no sign-off, because a person is already there.

Read what it offers

anlyon mcp tools tracker

The suggested class comes from what the server says about the tool, so treat it as a suggestion. Server text is untrusted: a server can describe a tool that deletes data as harmless. Read the tool yourself before you adopt anything that writes.

Ask to adopt tools

anlyon mcp adopt tracker list_issues delete_issue
Needs a human's sign-off. A workspace admin must approve this change before it applies:
  https://console.anlyon.com/sign-offs/sor_...

The command exits 15 and nothing is adopted. No API key, CLI login or MCP session can approve the request, including the one that asked. With --wait 15m it follows the request until a person decides.

By default, a tool suggested as irreversible or destructive waits for approval on every call. --approval all gates every tool you name, and --approval none gates none of them. The person signing off sees which you asked for.

A person signs it off

A workspace admin opens the link, or the email they were sent, reads the tools with their definitions and suggested classes, and approves or rejects. A person signed in to the console can also adopt from the server’s page directly.

Your agent calls the tool

Each adopted tool is an action named <server>-<tool>, here tracker-list_issues. Over MCP it appears as the tool action_tracker-list_issues within 30 seconds. From the SDKs and the CLI, invoke it like any other action.

What needs a person

Step From an API key, CLI login or MCP session Why
Register a server that takes no stored credential Applies at once It adds nothing an agent can call
Register a server with no stored credential Applies at once Nothing stored goes anywhere, and no agent can call anything yet
Register a server with a stored secret Needs a sign-off Listing the server sends the secret to that address, before any tool is adopted
Register a server that signs people in with OAuth Needs a sign-off It can create an OAuth client at a third party under your deployment’s name, and shows a sign-in page under a name the server chose
Read the listing again (anlyon mcp tools --refresh) Applies at once Adopted tools keep the definition they were adopted at
Adopt a tool Always needs a sign-off It is what lets an agent call the tool
Adopt a tool again after it changed Always needs a sign-off The change is what a person has to read
Connect your own account to a server that signs people in Only a person, in the console It is your consent, at the vendor
Archive a server Applies at once It archives every action adopted from it, which leave your actions list. Their call history stays

A sign-off binds the definitions it showed. If the server’s listing changes between the request and the decision, approving fails with SIGN_OFF_STALE and nothing is adopted: ask again and a person reads the new definition.

When a tool changes

Anlyon checks an adopted tool’s definition before each call. When it differs from what was adopted, or the server no longer lists it, the tool is not called, nothing is sent, and the evidence names tool_changed or tool_missing.

anlyon mcp tools tracker --refresh lists the server again and shows the tool as changed. To accept the change:

anlyon mcp adopt tracker list_issues

A person signs that off with a diff of the definition. The action keeps its approval setting and impact declaration, and an approval pending on the old definition cannot release a call against the new one.

Hear about a change before a call fails

Anlyon also lists each registered server again on a schedule, every 24 hours by default, with the same credential as a refresh. When a listing finds an adopted tool changed or gone, or a tool it has not seen before, Anlyon sends one security alert per change to the workspace’s notification channels, records mcp_upstream.tools_changed in the audit log, and marks the server in the console. The check adopts nothing and accepts nothing: a changed tool stays refused until a person adopts it again. A server that signs people in is checked once a person has connected an account on its page, or if it lists its tools without a sign-in.

Servers that sign people in

anlyon mcp add https://mcp.tracker.example/mcp --oauth

--oauth registers a server that uses OAuth. Pass --client-id and --client-secret "{{secret:NAME}}" if you registered a client with the vendor. That secret must have allowed hosts, and they must include the vendor’s token host. Otherwise Anlyon brings its own client on a verified vendor’s server, and registers a client where the server allows it. If neither applies, you need a client from the vendor. The registration waits for a sign-off. A person then connects an account from the server’s page in the console, which also lists its tools.

Calls that act for one of your users use that user’s own account. Calls for no end user use the account the person connected.

A call for a user who has not connected the server is answered before anything is recorded or sent, as for an integration: 409 END_USER_NOT_CONNECTED, or 409 END_USER_RECONNECT_REQUIRED when their connection needs renewing. The answer names the server’s key in integration (mcp-<id>) and its name in displayName. It carries a connectUrl when the environment has a default redirect URL. So the AI SDK adapter returns connect_required and <ConnectPrompt> shows a Connect card with the server’s name.

To make a link yourself, call POST /api/v2/mcp-upstreams/{id}/connect-links, which needs end-users:write. In the browser, an end-user session can call POST /api/v2/end-user-session/connect-links with the mcp-<id> key from the 409. That is the call <ConnectPrompt> makes inside <AnlyonProvider>.

A call that waited for approval checks the connection again when it runs. If the user’s connection was removed or revoked in the meantime, the call fails with no_connection and nothing is sent.

How a tool’s input is checked

Anlyon checks each call’s arguments against the tool’s input schema before anything is sent, as JSON Schema 2020-12 or the draft the schema names in $schema (draft-07 and 2019-09 too). $ref within the schema, allOf, anyOf, oneOf, not, if/then/else, prefixItems, unevaluatedProperties and the other validation keywords are enforced, and the formats uri, date, date-time, email and uuid are checked. Another format is only a description, as the standard says. Anlyon never fetches anything a schema points to. A schema it cannot check in full, because it points outside itself, is invalid, is very large or uses a pattern that could run for too long, does not stop the tool from being adopted: the tool is marked Loosely checked, its arguments only have to be a JSON object of at most 256 KiB, approval on every call is suggested for it, and an approval shows its arguments in full. The tool is still pinned, so a change on the server still stops it. The API reports this as schemaCheck (full or loose) with a schemaCheckReason on each listed and adopted tool.

What it does not do

  • Limits need the API, at first adoption. A tool draws on an impact limit only if the first request to adopt it, through the API or an SDK, sets its impact. The console, the CLI and anlyon.yaml adopt with none, and it cannot be added afterwards.
  • Remote servers only. Anlyon speaks MCP’s Streamable HTTP transport to an https endpoint. Servers on the older SSE transport work too: Anlyon finds that out when you add one. A stdio server is not supported.
  • Receipts grade acknowledged. The server answered. Anlyon does not read the change back, so these receipts never grade confirmed. See Receipts and grades.
  • It only governs calls that go through Anlyon. An agent that still has the server in its own configuration, or still holds the credential, can reach the server directly. Remove the direct route: see Put your coding agent behind Anlyon.

Keep it in code

anlyon plan and anlyon apply read the mcp section of anlyon.yaml. apply registers what is missing and asks once for each server’s tools. See Config as code.

From the SDKs

import { Client, SignOffRequiredError } from '@anlyonhq/sdk';

const anlyon = new Client({ apiKey: process.env.ANLYON_API_KEY! });

const { data: upstream } = await anlyon.mcpUpstreams.register({
  name: 'tracker',
  url: 'https://mcp.tracker.example/mcp',
  headers: { Authorization: 'Bearer {{secret:TRACKER}}' },
});

try {
  await anlyon.mcpUpstreams.adopt(upstream!.id, { list_issues: {} });
} catch (error) {
  if (!(error instanceof SignOffRequiredError)) throw error;
  console.log(`Ask a workspace admin to approve: ${error.signOffUrl}`);
}
from anlyon import Client, SignOffRequiredError

anlyon = Client()

upstream = anlyon.mcp_upstreams.register(
    "tracker",
    "https://mcp.tracker.example/mcp",
    headers={"Authorization": "Bearer {{secret:TRACKER}}"},
)

try:
    anlyon.mcp_upstreams.adopt(upstream["id"], {"list_issues": {}})
except SignOffRequiredError as error:
    print(f"Ask a workspace admin to approve: {error.sign_off_url}")

Reading needs actions:read. Registering, refreshing, adopting and archiving need actions:write. Naming a stored secret also needs secrets:read. The API reference has every field.

Fix the errors you will meet

What you see When What to do
202 with signOffRequired: true, or exit 15 The change needs a person Give the link to a workspace admin
The server asks for a sign-in (HTTP 401) The server needs a credential you did not send Add --oauth, or put its credential in a header as a {{secret:NAME}} reference
Header ... carries a credential A value, not a reference Store it as a secret and write {{secret:NAME}}
403 naming secrets:read The credential cannot name stored secrets Use a credential with secrets:read, or have a person register the server
409, An MCP upstream named ... already exists The name is taken in this environment Use --name, or archive the old one
MCP_SERVER_NOT_FOUND, MCP_TOOL_NOT_LISTED The CLI could not find that server or tool Run anlyon mcp list, or anlyon mcp tools <name> --refresh
failed with SIGN_OFF_STALE The listing moved after the request was made Ask to adopt again
Tool error naming tool_changed or tool_missing The server changed or removed an adopted tool. Nothing was sent Adopt it again
403 on POST /api/v2/mcp-upstreams/{id}/connect Connecting an account is a person’s consent Open the server’s page in the console
409 END_USER_NOT_CONNECTED with integration: "mcp-..." The call is for a user who has not connected this server. Nothing was sent Send the user to connectUrl, then call again

Was this page helpful?