Browse documentation

MCP client compatibility

What the Anectico MCP endpoint requires of a client, which protocol revisions it serves, and which hosts have been checked.

The remote endpoint is one URL:

https://app.anectico.com/mcp

This page states what a client must do to talk to it, and — separately, and honestly — how far any client has actually been checked against it.

What the endpoint requires

Every item here is a property of the running service, not an aspiration.

Requirement Detail
Transport Streamable HTTP, stateless. One endpoint, and it serves POST only.
Other methods GET and DELETE are answered 405 with Allow: POST. There is no server-to-client stream to open and no session to delete.
Accept on POST Must contain both application/json and text/event-stream. A request missing either is answered 400.
Credential A branded an_ API key or an access token obtained through the advertised OAuth flow, in a header. Never in a query string or a request body.
Header form OAuth uses Authorization: Bearer <access-token>. API keys use Authorization: Bearer an_…, Authorization: ApiKey an_…, X-Anectico-API-Key: an_…, or X-API-Key: an_….
Session None. Every request is independent and carries its own credential.
Scopes mcp:read or mcp:write to reach the endpoint at all, plus the domain scope of every tool.

The endpoint is deliberately a tools-only MCP surface. It does not publish MCP prompts or MCP resources, so an empty prompts/list or resources/list response is expected rather than a sign that setup failed. Product data and this documentation are exposed through tools; use tools/list to verify the connection.

The older two-endpoint HTTP+SSE transport is not served — there is no separate /sse or /messages path, and a client that requires it cannot connect.

The credential is checked before anything else. A malformed Accept, an unknown method and an invalid ?profile= are all answered only once the request has authenticated and cleared the umbrella-scope check. So an unauthenticated probe of this endpoint learns 401 and nothing more, whatever else is wrong with it — which is deliberate, and worth knowing when you are debugging a 401 you expected to be a 400.

Authentication choices

The endpoint accepts either an an_ API key or an audience-bound OAuth access token. A request with no credential receives a WWW-Authenticate challenge pointing to the protected-resource metadata; an OAuth-capable client follows that document to the advertised authorization server and uses PKCE. API-key clients can continue sending their configured header without running discovery.

There is nothing to obtain by hand for the OAuth path. The advertised authorization server accepts dynamic client registration, so a host registers itself on first connection and is issued a public client — no client secret exists to store, and PKCE authenticates the exchange instead. You are never asked for a client id, and an operator never has to pre-provision one.

The protected-resource metadata is served on both well-known locations, because the two are constructed differently and clients in the wild ask for either:

https://app.anectico.com/.well-known/oauth-protected-resource/mcp
https://app.anectico.com/.well-known/oauth-protected-resource

The token must be bound to this endpoint’s URL as its audience. A conforming client sends the resource indicator automatically. Both sides enforce it: the authorization server refuses to issue a token for any other resource identifier — including near-misses such as a trailing slash or the bare origin — and this endpoint refuses a token that arrives bound to something else, with a valid signature and a valid issuer. That is deliberate and there is no setting that widens it. A 401 carrying invalid_token after an authorization that appeared to succeed means the client requested the wrong resource identifier; it should be exactly the URL at the top of this page.

An OAuth connection is deliberately less capable than the person who authorized it — it is capped at a fixed delegation ceiling regardless of that person’s role. See How agent access is enforced for exactly what is inside and outside it.

OAuth authenticates an existing Anectico membership; it does not create one. Before a person’s first OAuth MCP connection, a workspace owner must invite or provision them, and the person must complete normal Anectico sign-in (or anectico login) once. A resolution failure is one generic 403 with no account, email, or workspace detail, so a valid token cannot be used to enumerate Anectico identities. See Connect an AI agent with MCP for the remediation.

Protocol revisions

Five revisions are served, and the endpoint negotiates rather than requiring one:

Revision How a client reaches it
2026-07-28 server/discover, with the Mcp-Protocol-Version and Mcp-Method headers and the matching per-request _meta. There is no initialize at this revision.
2025-11-25 The initialize handshake.
2025-06-18 The initialize handshake.
2025-03-26 The initialize handshake.
2024-11-05 The initialize handshake.

One edge worth knowing: an initialize request that names 2026-07-28 in its body but sends none of that revision’s per-request headers or _meta is negotiated down to 2025-11-25, because initialize is precisely the handshake that revision removes. A transitional client that sets the modern headers as well is not downgraded — it gets an error, because initialize no longer exists there.

What we generate for each host, and how far each has been checked

Native OAuth is a three-legged handshake — the host, our endpoint, and the authorization server behind it — and “verified” means different things depending on how much of that handshake actually ran. A second question sits behind that one and is easy to skip: once a host is connected, does it get the RIGHT ANSWERS? Both are recorded below, per host, as of 2026-08-28.

Host Verified to How
Codex A full native-OAuth round trip, a real tool call, and answer-for-answer agreement with our API Our own conformance runs drive Codex’s own OAuth login unattended against a disposable deployment of our authorization server: discovery, dynamic client registration, the PKCE authorization round trip, token exchange, and one tool call executed with the resulting access token — no static key at any point. Separately, the read workflows below are run through the real Codex CLI against a deployed environment, and the data it receives is compared field by field with the same objects read over the API.
Claude Code The same three things, to the same standard The unattended round trip runs for Claude Code exactly as for Codex, and completes with no human input; a person has also completed the browser sign-in against our production authorization server, and that session resolved to a real external identity rather than to an API key. The same read workflows are run through the real Claude Code CLI and compared with the API in the same way. Until 2026-08-27 this host was held to a weaker bar than Codex — it only had to appear in the host’s server list, which a host also prints for a static key — and that gap is closed.
A generic client built on the official MCP SDK Spec conformance, not vendor-specific compatibility Our own conformance runs exercise this shape on every run: it proves the endpoint works for any spec-conformant client, not just the named hosts.
Cursor Not run Accepted as compatible on the strength of the two hosts above, since it uses the same protocol machinery — a decision, not a measurement.
Gemini Not attempted No run has been made against it yet.

Separately from any host, our own conformance runs exercise the whole OAuth handshake up to the browser step on every run, with no host involved: reading both protected-resource metadata locations and checking they are identical, following the authorization server’s own discovery document, registering a client dynamically exactly as a cold host would, and confirming that the authorization server binds tokens to this endpoint’s URL and refuses every near-miss (wrong audience, wrong verifier, a replayed code, an unregistered redirect). That covers every part a host cannot work around when it is wrong, independent of which host is in front of it.

What “the right answers” means here

Three read workflows are run end to end through each host and checked against the API:

  • Follow one agent run by id. Every field the host receives is compared with the same field from the API. They must be equal, allowing only for the differences the two surfaces are documented to have — counts that travel as text in one and as numbers in the other, and the markers that flag content your own systems supplied.
  • Ask about something in another project. The host must be told it was not found, in wording that reveals nothing about whether it exists, and in the same wording any other client gets.
  • Read a record whose own text tries to give the reader instructions. It must come back marked as content rather than as instruction, unchanged, and the host must not act on it.

One further check is what makes the rest meaningful: mid-run, the key the host is using is revoked and the identical request is repeated. The host must stop being able to read the data. Without that step, a host answering correctly from its own memory would be indistinguishable from a host actually talking to us.

That is a statement about our testing, not a prediction about yours. Any client that speaks stateless Streamable HTTP and can send a fixed header should be compatible, because that is the whole of what the endpoint requires. What this table says is which files anectico agent bootstrap writes for you — a configuration generated, not an integration verified.

Host What the bootstrap does for it
Anectico CLI Nothing to configure — anectico mcp verify is a client. It speaks protocol 2026-07-28 and uses server/discover; a server that does not offer that version is reported as a failure rather than quietly downgraded.
Claude Code Installs the CLI skill and writes .mcp.json in the working directory: type: "http", the URL, and an Authorization header. That is the same file claude mcp add --scope project writes.
Cursor Installs the CLI skill and writes .cursor/mcp.json with the URL and an Authorization header. Cursor’s remote-server form is documented as URL plus headers, so no transport discriminator is written.
Codex Installs the CLI skill and prints the server entry for you to paste. Codex keeps MCP servers in ~/.codex/config.toml beside unrelated settings, which the bootstrap will not rewrite.
Gemini Installs the CLI skill and prints the entry, as for Codex. Gemini keeps servers in .gemini/settings.json.
Anything else Nothing. Configure a remote Streamable HTTP server with the URL and header above.

anectico agent bootstrap finishes by connecting with Anectico’s own client, using the key it just minted, and reporting how many tools that key is served. That verifies the credential, the endpoint and the scopes — the three things most likely to be wrong. It does not launch your host and cannot tell you your host works. Ask the host itself: list its tools once it has connected.

Environment-variable expansion is the usual failure

The configuration Anectico writes references ${ANECTICO_API_KEY} instead of embedding the secret, so the file stays safe to commit. Claude Code documents that expansion for .mcp.json. Cursor does not document it either way — if it does not expand, the header arrives as the literal text Bearer ${ANECTICO_API_KEY} and the server rejects it. Re-run the bootstrap with --embed-secret to write the literal key into a file created with owner-only permissions, and do not commit it.

What is not supported

  • Credentials outside a header. No query-string or body form is accepted, on any path.
  • Shared caching of the tool list. Tool-list responses carry ttlMs: 0 and cacheScope: "private", because what a key is offered depends on that key’s scopes. Re-list when you need it — do not carry a list across a change of credential or of permissions.
  • Cancelling a call in progress, before the 2026-07-28 protocol. Cancellation is propagated to the running handler only for clients on that revision; on the earlier ones, disconnecting ends your side of the exchange and leaves the call to finish on its own.

Tool-list size, and when it bites

MCP hosts do not report having too many tools. The overflow simply stops being offered to the model inside the client. Anectico therefore keeps its list small on purpose, reaching the long tail through gateway tools, and designs against two self-imposed ceilings: 40, past which we assume a host may silently drop tools, and 50, past which we assume tool-selection accuracy degrades. Those two numbers are our conservative design targets, not measurements of any particular client — no host publishes its real limit, which is exactly why we budget against a pessimistic one.

What a key is actually served, derived from the current capability catalog and registration rules on 2026-09-04. Server tests pin the published bundle and served-tool counts, but catalog or profile changes can alter them; treat them as a sizing guide and ask your own key:

Key Tools offered
investigate bundle (20 scopes, read-only) 36
agent-observability bundle (10 scopes) 14, or 9 with ?profile=agent-observability
incident bundle (38 scopes) 49
Every scope the surface uses 49

An incident key is therefore above the 40 line. Two things fix it, and only one of them is a credential change:

  • Connect with ?profile=investigate for a read-only session — that key is then served 35 tools and no write door at all, and cannot invoke a mutation for the life of the connection. It also loses the incident, on-call and delivery reads, which are not investigate-native. Its scopes are unchanged; reconnecting without the parameter restores the full surface.
  • Mint a narrower key for the job. Scope recipes for agent jobs sizes each one.

Adding ?profile=incident to an incident key subtracts list_agent_fleet, which belongs to the agent-observability profile, so the key is served 48 tools. A profile only ever subtracts — never the reverse.

Failure symptoms, in the order you will meet them

Symptom Cause
401, empty body, WWW-Authenticate: Bearer realm="anectico", resource_metadata="…" No credential header reached the server at all. There is no error code and no body, on purpose: nothing was attempted, so there is nothing to report a failure about. The resource_metadata parameter is the recovery path — follow it.
401authentication required Malformed, unknown, revoked and expired keys all answer identically, on purpose: the endpoint will not confirm that a key it rejects ever existed. Check first that the variable expanded, then that the key is live and unexpired.
401invalid_token, the access token signature is invalid A bearer that is not an an_ key was validated against the advertised authorization server and failed. Most often this is a signed-in user’s own Anectico session token, which is not a credential here; it is also what a token from some other issuer gets.
401invalid_token, after an authorization that succeeded The token is bound to the wrong audience. The client requested a resource identifier that is not this endpoint’s URL.
403 OAuth challenge with insufficient_scope and no named scope The token validated but no existing Anectico membership could be resolved. Follow the explicit first-access steps above or contact support with the attempt time; the response is intentionally generic.
403the mcp:read or mcp:write scope is required for the MCP endpoint The key authenticates but was never granted an umbrella scope.
405, Allow: POST The client opened a GET stream or sent a DELETE. This endpoint is POST-only and stateless.
400Accept must contain both 'application/json' and 'text/event-stream' The client is not speaking Streamable HTTP, or something is stripping the header. Note this arrives after authentication, so a bad Accept on an unauthenticated request still answers 401.
400invalid_profile See the four shapes below.
Connects, zero tools The key holds an umbrella scope and no domain scope. Every tool needs both.

Because it is refused rather than ignored, ?profile= has four distinct rejections and each names what to fix:

What you sent Message
?profile=incidnet unknown profile "incidnet" — use one of: …
?profile= profile must not be empty — use one of: …
?profile=%20 profile must not be empty — use one of: …
?profile=a&profile=b profile must be supplied exactly once — use one of: …

Every one is a 400 carrying "error": "invalid_profile" and the list of valid names. The last two matter more than they look: a URL builder that appends an empty parameter, and one that appends the same parameter twice, are both common, and either silently serving the whole surface would look exactly like the filter working. Names are matched exactly and are case-sensitive — ?profile=Incident is an unknown profile, not incident.