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

MCP servers

Connect any MCP client to Anlyon's hosted MCP server, to one integration's own MCP server, or to the npm package over stdio or HTTP, and act for your company or for one of your users.

An agent that calls production APIs needs credentials, and a credential in an agent’s context can be leaked, misused or replayed. Anlyon’s MCP servers give the agent your actions as tools instead. The agent names an action and passes input. Anlyon adds the credential at dispatch, applies your limits, waits for a person when a policy says so, and records a receipt for the call. The agent never holds a token.

This page is the reference for every way to connect. For step-by-step setup in one client, see Claude Code, Cursor, Codex, Windsurf, ChatGPT, the OpenAI Responses API or the Claude Agent SDK.

Choose a server

Server Address Use it for
Anlyon MCP server https://api.anlyon.com/mcp Every enabled action in one environment, plus approvals and workspace tools
One integration’s MCP server https://api.anlyon.com/mcp/<integration> One integration’s actions only, such as GitHub
npm package, stdio npx -y @anlyonhq/mcp-server A local process started by your MCP client, with an API key
npm package, HTTP npx -y @anlyonhq/mcp-server --http Running the MCP server over HTTP yourself, on your own host

All four serve the same tools and enforce the same scopes, limits and approvals. Every call goes through the Anlyon API at https://api.anlyon.com/api/v2.

Choose how to sign in

The hosted servers accept two credentials on the Authorization header. Anlyon tells them apart by shape: an Anlyon API key starts with anlyon_, and anything else is treated as an OAuth access token.

OAuth (“Login with Anlyon”) API key
Who holds it A coding agent or chat app acting for you Your server or CI job
How to get it Your MCP client opens a browser and you sign in Settings → API keys in the console
Workspace and environment Chosen on the consent screen Fixed: a key belongs to one environment
What it may do The permission groups you granted. The tool list shows only tools they cover The key’s scopes. Every tool is listed, and the API checks the scope on each call
Acting for one of your users Not allowed Yes, with the Anlyon-End-User header
Lifetime Access tokens last 15 minutes. Your client refreshes them Until you revoke the key, or it expires

Sign in with OAuth

Point the client at https://api.anlyon.com/mcp. The server answers the first request with 401 and a WWW-Authenticate header that names its OAuth metadata (https://api.anlyon.com/.well-known/oauth-protected-resource/mcp). The client registers itself and opens a browser. Then:

  1. Sign in to Anlyon, or create an account. A new account gets a workspace during sign-in.
  2. If you belong to more than one workspace, choose the workspace and environment the client acts in. With one workspace, the connection uses its default environment.
  3. Choose permissions. Actions is on. Approve on your behalf, Switch between your workspaces, Secrets, Budgets and Notification channels are off unless you turn them on.

To disconnect, remove the app in the console’s settings. Its next request is refused with 401.

Use an API key

Send the key as a bearer token:

Authorization: Bearer anlyon_live_...

Give the key only what the agent needs:

Tools Scopes
list_actions and the action_<name> tools appearing actions:read
invoke_action, action_<name> actions:invoke
check_approval, wait_for_approval approvals:read
request_approval approvals:write
list_pending_approvals, decide_approval approvals:read, approvals:decide (privileged)

actions:read, actions:invoke, approvals:read and approvals:write are in the Standard preset. approvals:decide is privileged: give it to a reviewer’s key, never to the agent’s.

Who can decide an approval

  • A key can never decide an approval it requested.
  • A person can never decide an approval that their own agent requested: an app signed in as them, or a key they created. Not from that app, not from another app, not with another key. A teammate decides it. The one exception: when nobody else in the environment can decide approvals, the person decides it themselves, directly in the console or in Slack.
  • An approval that needs N approvers counts each OAuth decision as one user and each key decision as one credential.

The workspace tools (list_workspaces, select_workspace) work only with OAuth. A key belongs to one environment and cannot switch.

Act for one of your users

When your product’s agent acts in your users’ accounts (their Gmail, their HubSpot), your server names the user and the model never does. Connect with an API key and add one header:

Authorization: Bearer anlyon_live_...
Anlyon-End-User: user_123
  • The user is fixed for the whole connection. No tool has a user argument, so nothing the model writes can choose or switch it.
  • Each call uses that user’s own connected account, counts toward that user’s limits, and waits for that user’s approval when a policy is decided by the end user.
  • The id is 1 to 128 characters: letters, digits and . _ : @ -.
  • An OAuth connection that sends the header is refused with 400 END_USER_HEADER_NOT_ALLOWED. OAuth connections act for you, with workspace connections.
  • A key created with Require an end user refuses a connection without the header (400 END_USER_REQUIRED).

With the npm package over stdio, pass --end-user <id> or set ANLYON_END_USER instead. See Workspace connections and end-user connections for the model behind this, and Act for your users for a full app.

Tools

Tool What it does Consent group
action_<name> One tool per enabled action, with the action’s own input schema and description. refund-customer becomes action_refund-customer Actions
list_actions Lists the environment’s actions, their input schemas, and which need approval Actions
invoke_action Invokes an action by name: { "name": "...", "input": { } } Actions
request_approval Asks a person to decide something that is not an action. It does not release a gated action Actions
check_approval Reads an approval’s status once: pending, approved, denied or expired Actions
wait_for_approval Waits for the decision inside the turn. timeoutSeconds default 60, max 120. Returns the still-pending approval when time runs out Actions
list_pending_approvals Lists approvals waiting for a decision (limit default 20, max 50) Actions and Approve on your behalf
decide_approval Approves or denies with a reason, recorded as the decision note. Approving a gated action runs it Approve on your behalf
list_workspaces Lists your workspaces and environments, and which one this connection uses. OAuth only None
select_workspace Moves this connection to another workspace and environment. OAuth only Switch between your workspaces

How the action_<name> tools work:

  • An action defined in the console appears in the tool list within 30 seconds, with no restart. The list is cached for 30 seconds per caller.
  • A gated action says so in its description: “Gated. It requires human approval, and the call parks until a human decides.”
  • An action that declares an impact says what one call counts, for example “Each call counts emails toward any shared limit set for this environment”.
  • A plain GET action with no adapter is marked read-only. Every other action is marked destructive, so a client that confirms destructive calls asks you first.
  • A key without actions:read gets the fixed tools only.

Call an action and handle an approval

  1. The agent calls an action tool:

    { "name": "action_update-ticket-priority", "arguments": { "ticketId": "T-1042", "priority": "high" } }
  2. If the action needs approval, nothing is sent yet. The tool answers:

    Awaiting human approval. Call wait_for_approval with approvalId=apr_7Hc2... to block until they decide. The action runs by itself once approved.

    (invoke_action returns the same as JSON: "pendingApproval": true and "approvalId": "apr_...".)

  3. The agent calls wait_for_approval with { "approvalId": "apr_7Hc2..." }. A reviewer decides in the console, in Slack, or with decide_approval from their own connection (who can decide). For an approval decided by the end user, that user decides in your app.

  4. wait_for_approval returns the approval record:

    {
      "id": "apr_7Hc2...",
      "kind": "action",
      "status": "approved",
      "payload": { "invocationId": "inv_...", "actionName": "update-ticket-priority", "method": "PATCH" },
      "decidedBy": { "type": "user", "id": "...", "displayName": "Dana" },
      "requiredApprovals": 1,
      "approvedCount": 1
    }

    If it is still pending, the agent calls it again. If it is denied or expired, the request is never sent.

  5. Approving runs the exact request that was reviewed. The invocation payload.invocationId names holds the outcome, the response and, for a governed action, the receipt grade. Read it in the console’s invocation log, or with GET /api/v2/actions/invocations/{id} (needs actions:read).

An action that does not need approval answers at once with its invocation: status (succeeded, failed or unknown), responseStatus, body, and for a governed action effectId and grade. unknown means the request may have reached the destination: do not call it again. See When the outcome is unknown.

One integration’s MCP server

Each integration has its own server at https://api.anlyon.com/mcp/<integration>. Use it when an agent needs one integration and should not see the rest of your workspace.

Integration MCP server
Asana https://api.anlyon.com/mcp/asana
Attio https://api.anlyon.com/mcp/attio
Discord https://api.anlyon.com/mcp/discord
GitHub https://api.anlyon.com/mcp/github
Intercom https://api.anlyon.com/mcp/intercom
Notion https://api.anlyon.com/mcp/notion
PagerDuty https://api.anlyon.com/mcp/pagerduty
Resend https://api.anlyon.com/mcp/resend
Sentry https://api.anlyon.com/mcp/sentry
Stripe https://api.anlyon.com/mcp/stripe

It serves:

  • The integration’s actions, as action_<name> tools. These are the actions installed from that integration’s toolset, whose names start with <integration>-, for example action_github-create-issue. Connect the account and turn on its operations in the console first.
  • check_approval and wait_for_approval, so the agent can follow a gated action to its decision.

Everything else stays on https://api.anlyon.com/mcp: list_actions, invoke_action, other integrations’ actions, deciding approvals and switching workspaces. An action you created yourself, or installed without the prefix, is served there too.

Sign-in, API keys, Anlyon-End-User, approvals and limits work exactly as on /mcp. An OAuth token issued for /mcp is accepted here too. An address that is not an integration answers 404 NOT_FOUND.

Run the npm package

@anlyonhq/mcp-server (Node 18 or later) serves the same tools from your own machine or host. It talks to the Anlyon API with an API key.

Over stdio

Your MCP client starts the process:

ANLYON_API_KEY=anlyon_live_... npx -y @anlyonhq/mcp-server

To act for one of your users, fix the user for the process:

ANLYON_API_KEY=anlyon_live_... npx -y @anlyonhq/mcp-server --end-user user_123

A malformed id stops the server at startup.

Over HTTP

Run the MCP server over HTTP yourself, for a client that needs a URL but cannot sign in with OAuth:

PORT=3001 npx -y @anlyonhq/mcp-server --http

It serves POST /mcp (streamable HTTP, stateless) on PORT, default 3001. GET /mcp returns the server’s name and version. It holds no key of its own: each request’s Authorization: Bearer <key> is the key the tools act with, and Anlyon-End-User on a request names the user. --end-user and ANLYON_END_USER are refused in this mode. It answers 401 without a bearer, 400 INVALID_END_USER for a malformed id, 415 without a JSON content type, and 413 for a body over 1 MB.

The hosted https://api.anlyon.com/mcp also accepts an API key, so run your own only when you need it on your network.

Environment variables

Variable Meaning
ANLYON_API_KEY The API key, for stdio. Required
ANLYON_END_USER The end user every call acts for, for stdio. Same as --end-user
ANLYON_BASE_URL The API origin. Default https://api.anlyon.com
ANLYON_MCP_DYNAMIC_ACTIONS 0 turns off the action_<name> tools, for a client that dislikes a tool list that changes
MCP_TRANSPORT http is the same as --http
PORT The HTTP port. Default 3001

Fix a refused connection or call

What you see Why What to do
401 with invalid_token The OAuth token expired or the app was disconnected, or the API key is unknown, revoked or expired Sign in again from your client. For a key, check it in Settings → API keys. A client that receives this 401 may open the OAuth sign-in, even when you meant to use a key
400 END_USER_REQUIRED The key was created with Require an end user and the request had no Anlyon-End-User Send the header, or use --end-user
400 END_USER_HEADER_NOT_ALLOWED Anlyon-End-User was sent with an OAuth sign-in Use an API key to act for a user
400 INVALID_END_USER The id is empty, longer than 128 characters, or has other characters Use letters, digits and . _ : @ -
503 Anlyon could not check the credential Retry shortly
Error: API key is missing required scope: actions:invoke The key lacks the scope that tool needs. With OAuth: This authorization is missing required scope: ... Add the scope to the key, or reconnect and grant the group
Unknown tool: ... The tool is not served to this connection: its consent group was not granted, or it is not on this integration’s server Grant the group, or use https://api.anlyon.com/mcp
"status": "connect_required" The user has not connected the integration the action needs. Nothing was sent Send the user to connectUrl (below)
"status": "reconnect_required" The user’s connection was revoked or stopped refreshing Send the user to connectUrl to connect again
ANLYON_API_KEY is not set The stdio server started without a key Set it in the client’s env block

connect_required and reconnect_required come back as a tool result, not an error, so the model can pass the message on:

{
  "status": "connect_required",
  "integration": "gmail",
  "connectUrl": "https://api.anlyon.com/connect/...",
  "message": "The user has not connected gmail yet, so nothing was done. Ask them to connect it, then try again."
}

The same fields are in the result’s structuredContent. connectUrl works once, for 10 minutes. It is null when the environment has no default redirect URL: create a link with endUsers.createConnectLink() instead.

Was this page helpful?