Skip to content
Last9
Book demo

Add remote MCP servers to Last9 AI

Register a remote MCP server for Last9 AI, test connectivity, and activate it for chat and investigations.

Connect Last9 AI to a remote MCP server your organization runs (for example an internal incident, payments, or ticketing MCP). After registration, tools from that server are available on the next activated chat turn. To make the assistant prefer those tools during investigations, bind them on a knowledge topic.

This page covers organization MCP management for Last9 AI. For connecting a coding agent to Last9’s hosted MCP, see Last9 MCP.

Prerequisites

  • Last9 AI enabled for your organization. See Enable Last9 AI.
  • An Admin user (writes and MCP diagnostics need an administrator).
  • An API refresh token with write scope to create or update, and delete scope to remove servers. See Getting started with API.
  • Your remote MCP endpoint reachable from Last9 over HTTPS, with any auth headers you will store on the server record. See Connection requirements and limits.
  • Your organization slug (from the dashboard URL: app.last9.io/v2/organizations/<org_slug>/...).

GET requests are available to organization users with an allowed read scope. Create, PATCH, and both diagnostic routes require an administrator and write scope; DELETE requires an administrator and delete scope. Both the user’s role and the token’s scope are checked. Agent tokens cannot access these management endpoints, including GET.

Register a remote MCP server

Worked example name: payments. Substitute your server name, URL, and credentials.

  1. Mint an access token

    Exchange a refresh token that includes write (and delete if you need cleanup):

    curl -sS -X POST 'https://app.last9.io/api/v4/oauth/access_token' \
    -H 'Content-Type: application/json' \
    -d '{"refresh_token":"<refresh_token>"}'

    Export the access token:

    export LAST9_TOKEN='<access_token>'
    export LAST9_ORG='<org_slug>'
    export LAST9_AI="https://app.last9.io/api/v4/organizations/${LAST9_ORG}/ai"
  2. Run a diagnostic on the remote MCP without saving

    Confirm connectivity and tool discovery before you persist configuration. A failed diagnostic does not create a server.

    curl -sS -X POST "${LAST9_AI}/mcp-servers/test" \
    -H "X-LAST9-API-TOKEN: Bearer ${LAST9_TOKEN}" \
    -H 'Content-Type: application/json' \
    -d '{
    "name": "payments",
    "transport": "streamablehttp",
    "url": "https://mcp.example.com/mcp",
    "headers": {"Authorization": "Bearer <mcp_credential>"}
    }'

    Use transport: "sse" only if your server speaks SSE instead of Streamable HTTP. Fix URL or credentials until the diagnostic succeeds. Create and PATCH also run the same diagnostic and reject a failing candidate with 422 before saving it. A server that becomes unavailable after saving can still block later activation.

    You can also run the diagnostic on a saved server with POST ${LAST9_AI}/mcp-servers/payments/test (send no body).

  3. Register the MCP server

    curl -sS -X POST "${LAST9_AI}/mcp-servers" \
    -H "X-LAST9-API-TOKEN: Bearer ${LAST9_TOKEN}" \
    -H 'Content-Type: application/json' \
    -d '{
    "name": "payments",
    "transport": "streamablehttp",
    "url": "https://mcp.example.com/mcp",
    "headers": {"Authorization": "Bearer <mcp_credential>"}
    }'

    Expected: 201 with activation.status: "pending". Responses list header names and whether auth is configured — never secret values. Header values are stored as literal strings (they are not expanded from environment variables).

    Server name must match [a-z][a-z0-9_-]{0,99} (lowercase, start with a letter). The name test is reserved because POST /mcp-servers/test is the diagnostic route. The URL must be https://.

  4. List and read servers

    curl -sS "${LAST9_AI}/mcp-servers" \
    -H "X-LAST9-API-TOKEN: Bearer ${LAST9_TOKEN}"
    curl -sS -D - "${LAST9_AI}/mcp-servers/payments" \
    -H "X-LAST9-API-TOKEN: Bearer ${LAST9_TOKEN}" -o /tmp/mcp-payments.json

    Keep the quoted ETag from the GET response for later PATCH or DELETE. Deployment-managed servers (such as Last9’s own MCP) appear in the list but cannot be mutated or tested through these routes.

  5. Activate with a chat turn

    There is no separate activate endpoint. After create or PATCH, activation.status is pending. Open AI Assistant and send a turn, then check:

    curl -sS "${LAST9_AI}/config/status" \
    -H "X-LAST9-API-TOKEN: Bearer ${LAST9_TOKEN}"

    Expect active. If activation fails (bad MCP URL or auth), new turns stay blocked until you fix or delete the server.

Server request fields

POST /mcp-servers and POST /mcp-servers/test accept the same JSON object. Only the following fields are allowed; unknown fields, duplicate JSON keys, and incorrect types are rejected.

FieldTypeRequired?Meaning, defaults, and validation
nameStringYesStable identifier used in URLs, topic bindings, and mcp__{server}__{tool} names. Must match [a-z][a-z0-9_-]{0,99}: 1–100 characters, starts with a lowercase letter, then lowercase letters, digits, hyphens, or underscores. test is reserved. Cannot be changed with PATCH.
transportStringYes; no default"streamablehttp" for Streamable HTTP or "sse" for SSE. "http" is accepted as an alias and returned as "streamablehttp"; it does not permit an HTTP URL. "stdio" is not supported for API-created servers.
urlStringYesFull HTTPS MCP endpoint, including its path and optional port. Maximum 4,096 characters; printable ASCII without spaces. Must include a hostname. Embedded username/password, query strings, fragments, backslashes, and percent escapes in the hostname/authority are rejected. Put credentials in headers, not the URL.
headersObject of string valuesNo; defaults to {}Literal HTTP headers for requests to the remote server, for example {"Authorization":"Bearer ..."} or {"X-API-Key":"..."}. Maximum 64 entries; each value at most 8,192 printable ASCII characters. No environment-variable expansion. Header names must be valid HTTP token names and unique ignoring case.

Header values cannot contain tabs, newlines, other control characters, or non-ASCII characters. Redaction placeholders such as [redacted], <redacted>, redacted, ***redacted***, or three or more asterisks are rejected; submit the actual credential when changing it. These transport-controlled headers are forbidden, ignoring case: Host, Content-Length, Transfer-Encoding, Connection, Upgrade, Accept-Encoding, Proxy-Authorization, Proxy-Connection, Keep-Alive, TE, and Trailer.

PATCH accepts only transport, url, and headers, with at least one field and a current If-Match header. Omitted fields keep their values. If supplied, headers replaces the entire header map rather than merging individual keys. Send all headers you want to retain; {} clears them and the resulting unauthenticated candidate must still pass the live diagnostic. null is not a clearing operation.

Minimal unauthenticated server

After exporting LAST9_AI and LAST9_TOKEN as above, this is the smallest create body:

curl --fail-with-body -sS -X POST "${LAST9_AI}/mcp-servers" \
-H "X-LAST9-API-TOKEN: Bearer ${LAST9_TOKEN}" \
-H 'Content-Type: application/json' \
--data '{"name":"payments","transport":"streamablehttp","url":"https://mcp.example.com/mcp"}'

Replace the example endpoint with a working MCP server. Registration performs MCP initialization and tool discovery; an ordinary HTTPS API is not sufficient.

Connection requirements and limits

The endpoint must pass TLS certificate and hostname validation. Redirects are rejected: provide the final endpoint directly, including the correct trailing slash where required. Discovery responses must be uncompressed (Content-Encoding: identity or absent). The client requests identity encoding.

Destinations must resolve to public addresses by default. Private, loopback, link-local, multicast, and other non-public addresses are rejected unless the destination is explicitly permitted by your Last9 deployment. A private network endpoint being reachable from your laptop does not make it reachable from Last9. Contact support if you need a private destination.

Default discovery limits are 10 seconds, 10 tool-list pages, 1,000 tools, and 1 MiB of response bytes during initialization/discovery. These are connection limits, not request fields. JSON management requests are limited to 1 MiB. The total saved API-managed MCP configuration, including credentials, is also limited to 1 MiB; exceeding that saved-configuration limit returns 400 invalid_request.

Server responses

GET /mcp-servers returns {"servers": [...]}. GET /mcp-servers/{name} returns one server object and an ETag response header. Lists have no pagination parameters. Do not send query parameters on management routes.

Server response fieldMeaning
nameStable server identifier.
transportNormalized transport; an input of "http" is returned as "streamablehttp". Deployment-managed entries may use other transports such as "stdio".
urlSaved endpoint. A deployment-managed non-HTTP server may have an empty URL.
header_namesSorted array of configured header names. Values are never returned.
auth_configuredtrue when any headers are configured, even if those headers are not authentication headers. It is not proof that credentials work.
managed_by"api" for editable API-managed servers; "deployment" for protected servers. Deployment-managed entries cannot be created over, patched, deleted, or tested through the named diagnostic route.

Create returns 201; PATCH and DELETE return 200. Successful writes wrap the resource:

{
"resource": {
"name": "payments",
"transport": "streamablehttp",
"url": "https://mcp.example.com/mcp",
"header_names": ["Authorization"],
"auth_configured": true,
"managed_by": "api"
},
"saved": true,
"activation": { "status": "pending" }
}

resource is the saved server, or {"name":"payments"} after deletion. saved: true confirms persistence; activation.status: "pending" means the configuration still needs activation. Create and PATCH return an ETag header for the new saved state. The ETag accounts for credential changes even though credentials are omitted from the response.

Diagnostic responses

Candidate test: POST /mcp-servers/test with the complete create-shaped JSON body. Saved test: POST /mcp-servers/{name}/test with no body (even {} is rejected). Neither requires If-Match; neither saves or activates configuration. The saved route uses stored credentials and accepts only API-managed servers.

A completed diagnostic normally returns HTTP 200, including when the diagnostic fails. Inspect the JSON fields rather than treating HTTP 200 as success:

{
"reachable": true,
"authentication": "accepted",
"protocol_valid": true,
"tool_count": 3,
"stage": "complete"
}
FieldMeaning
reachableWhether an HTTP response was received. true alone does not prove MCP compatibility, accepted credentials, or a usable destination.
authentication"rejected" if the remote returned 401/403; "accepted" after a successful protocol test with configured headers; "not_required" after success without headers; otherwise "unknown". accepted does not independently establish what each header did.
protocol_validWhether MCP initialization and bounded tool discovery completed successfully.
tool_countNumber of tools in the completed discovery result. A successful zero-tool server is valid; a failed/incomplete discovery does not return a partial inventory.
stageLast stage reached: "destination", "initialize", "tools_list", or "complete".
error_codeIncluded on failure, absent on success. Common values are listed below.

The diagnostic returns a count, not tool names or schemas. Use your server’s advertised tool names when configuring topic bindings. Create/PATCH run the same diagnostic and return an HTTP 422 error instead of saving a candidate with rejected authentication or an invalid protocol. A successful diagnostic does not guarantee the server will remain reachable at activation or tool-call time.

Tool names and approval

Call shapeTypical approval
mcp__{server}__{tool} for an API-managed serverapprove (human must confirm)
{topic_id}__{tool} from a knowledge topic mcp bindingAs configured on the topic (auto or approve)
Deployment-managed Last9 tools (mcp__last9__…)Per Last9 product policy

Creating an MCP server alone does not make the model prefer those tools. Register the server here, then bind tools on a knowledge topic with use: ["incident_triage"] and put the procedure in the topic overview. Prefer topic-prefixed tools in runbooks so auto approval applies where you configured it.

Update or remove

Read the server first and keep the quoted ETag. Send it as If-Match on PATCH or DELETE. Missing If-Match returns 428; a stale value returns 412.

curl -sS -X PATCH "${LAST9_AI}/mcp-servers/payments" \
-H "X-LAST9-API-TOKEN: Bearer ${LAST9_TOKEN}" \
-H 'Content-Type: application/json' \
-H 'If-Match: "<etag_from_get>"' \
-d '{"url":"https://mcp.example.com/mcp/v2"}'

If PATCH validation fails, the previous saved configuration remains unchanged. To rotate one credential while keeping other headers, include the full desired map. For example:

curl --fail-with-body -sS -X PATCH "${LAST9_AI}/mcp-servers/payments" \
-H "X-LAST9-API-TOKEN: Bearer ${LAST9_TOKEN}" \
-H 'Content-Type: application/json' \
-H 'If-Match: "<etag_from_latest_get>"' \
--data '{"headers":{"Authorization":"Bearer <new_mcp_credential>","X-Team":"payments"}}'

The ETag must match the exact current quoted value. Do not use *, weak ETags, or a list of tags. After every successful edit, keep the new response ETag or GET again. After a stale ETag error, re-read and review the latest state before you retry. Do not bypass the ETag check.

Delete (needs delete scope on the token):

curl -sS -X DELETE "${LAST9_AI}/mcp-servers/payments" \
-H "X-LAST9-API-TOKEN: Bearer ${LAST9_TOKEN}" \
-H 'If-Match: "<etag>"'

DELETE must have an empty body. It does not run a diagnostic, so you can remove a server that has become unreachable. Update any topic bindings that depend on it before deleting it.

Activation status

There is no activate API. Saving a create, PATCH, or DELETE marks the shared knowledge/MCP configuration pending; the next AI turn attempts activation. Reading status and running diagnostics do not activate it. Already-running turns retain their admitted configuration.

GET /config/status fieldMeaning
status"pending" after saving, "active" after successful activation, or "error" if activation fails.
last_attempt_atISO 8601 UTC timestamp of the most recent activation attempt; null before any attempt. A new save can retain the previous attempt timestamp.
request_idCorrelation value for the latest mutation, or null when unavailable; not a resource version.
error_codePresent after failure as "configuration_unavailable"; cleared by a new save or successful activation.

An activation error blocks new turns rather than silently using stale settings. Fix or delete the problematic server, then send another turn and recheck status. A server can pass registration but later fail because its endpoint, credentials, or available tools changed. active proves configuration activation; verify actual tool behavior in a chat too.

Checklist

  1. Admin has AI enabled and an API refresh token with write and delete scopes.
  2. POST /mcp-servers/test succeeds against the real HTTPS MCP URL and credentials.
  3. POST /mcp-servers returns 201 and the server appears in GET /mcp-servers.
  4. After one AI Assistant turn, GET /config/status is active.
  5. A test chat can request mcp__payments__… (expects approval) or a topic-bound {topic}__… tool.
  6. Servers that become unreachable after they are saved are fixed or deleted, so they cannot block activation.
  7. For investigations, continue with knowledge topics to bind tools and upload runbooks.

Troubleshooting

Management errors return a JSON body of the form {"error":"code"}. A diagnostic can instead return HTTP 200 with protocol_valid: false and an error_code.

Symptom or errorFix
400 invalid_requestCheck required fields, unknown or duplicate keys, the name format (lowercase; cannot be test), the transport, the HTTPS URL restrictions, reserved headers, and header value limits. PATCH must be nonempty and cannot contain name. Correct the field and send the request again.
403 on DELETEToken has write but not delete scope. Create a refresh token that includes delete, or ask an Admin.
404 not_foundCheck the server name and the organization slug.
409 resource_conflictThe name already exists, or the server is deployment-managed and protected. Choose a new name, or PATCH the existing API-managed server.
412 / 428 on PATCH or DELETESend a fresh If-Match from the latest GET.
413 request_too_largeThe JSON body exceeds 1 MiB. Reduce the request size, for example by removing unneeded headers.
422 mcp_authentication_rejected on saveRemote returned 401/403; provide valid literal credentials.
mcp_destination_rejectedPublic destination/DNS rules, redirects, cross-origin endpoints, or compressed responses. Use a direct allowed HTTPS endpoint with uncompressed responses.
mcp_deadline_exceededInitialization/discovery did not finish in the connection deadline. Check endpoint responsiveness and pagination.
mcp_discovery_limitToo many tool pages/tools or excessive discovery response bytes. Reduce the advertised inventory/schema size.
mcp_tool_collisionThe server advertises duplicate tool names. Give each tool a unique name on the remote server. During activation, topic-prefixed name collisions can instead surface as configuration_unavailable; bind the colliding tools on separate topics.
mcp_connection_failedCheck DNS, TLS, the endpoint path, the transport, headers auth, and MCP protocol support; inspect remote server logs. Run the diagnostic again after each fix.
503 persistence_uncertainA write may have persisted. GET and compare the current resource before retrying, using its fresh ETag.
503 persistence_unavailable or upstream_unavailableRead back state and retry later; contact support if persistent.
Config stuck / new turns rejectedRun GET /config/status. Fix or delete the failing MCP server, then send another chat turn.
Model never calls your MCPServer registered but no knowledge binding or overview that names the tools. Bind the tools on a topic with incident_triage and name them in the overview.
Auto approval does not applyYou called mcp__{server}__{tool} instead of {topic_id}__{tool}. Bind the tool on a knowledge topic.

Please get in touch with us on Discord or Email if you have any questions.