About OneUptime
OneUptime’s MCP server lets AI agents manage observability and incident-response data in OneUptime. It offers CRUD operations across operational models, read-only telemetry queries, and dedicated response workflows.
Key features
- 01Manage incidents, alerts, and monitors
- 02CRUD for operational records
- 03Query telemetry resources
- 04Acknowledge and resolve incidents
- 05Public status page tools
Use cases
- 01Triage and resolve incidents
- 02Review logs and exceptions
- 03Update customer status pages
MCP support
Exposes OneUptime data and actions to MCP-compatible clients through a stateless Streamable HTTP endpoint. Authentication supports OAuth 2.1 sign-in or per-request API keys.
Configuration
Remote servers need no local install. Pick your client, then copy the config or install in one click.
https://oneuptime.com/mcpclaude mcp add --transport http com-oneuptime https://oneuptime.com/mcp{"mcpServers":{"com-oneuptime":{"type":"http","url":"https://oneuptime.com/mcp"}}}Open in Cursor{"servers":{"com-oneuptime":{"type":"http","url":"https://oneuptime.com/mcp"}}}Open in VS Code{"mcpServers":{"com-oneuptime":{"type":"http","url":"https://oneuptime.com/mcp"}}}{"mcpServers":{"com-oneuptime":{"serverUrl":"https://oneuptime.com/mcp"}}}Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.
https://oneuptime.com/mcpAuth requiredTools
Details
From the project READMEOneUptime MCP Server
A Model Context Protocol (MCP) server that exposes OneUptime to AI agents. It lets MCP-compatible clients (Claude, VS Code with Copilot, Cursor, and others) manage incidents, alerts, monitors, status pages, on-call, and telemetry through ~190 tools.
How it works
- Transport: Streamable HTTP at
/mcp. The server is stateless — no session IDs are issued or required, so it is safe behind load balancers and multi-replica deployments. - Hosted endpoint:
https://oneuptime.com/mcp - Self-hosted endpoint:
https://<your-host>/mcp(served by the App container behind Nginx) - Auth: sign in with OneUptime (OAuth 2.1, per the MCP authorization specification), or a per-request API key via the
x-api-keyheader orAuthorization: Bearer <key>(scheme is case-insensitive). There is no environment-variable API key — every request carries its own credential.
Connecting a client
By signing in
Give the client the URL and nothing else. The first call to a tool that needs an identity is answered 401 with a WWW-Authenticate challenge; the client discovers the authorization server from it, opens the consent screen in the user's browser, and gets tokens for the project the user picks.
claude mcp add --transport http oneuptime https://oneuptime.com/mcp
{ "servers": { "oneuptime": { "type": "http", "url": "https://oneuptime.com/mcp" } } }
The rest of this section configures the same clients with an API key instead, for agents that run unattended.
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"oneuptime": {
"transport": "streamable-http",
"url": "https://oneuptime.com/mcp",
"headers": {
"x-api-key": "your-api-key-here"
}
}
}
}
Claude Code
claude mcp add --transport http oneuptime https://oneuptime.com/mcp \
--header "x-api-key: your-api-key-here"
VS Code (GitHub Copilot)
.vscode/mcp.json or the user MCP configuration:
{
"servers": {
"oneuptime": {
"type": "http",
"url": "https://oneuptime.com/mcp",
"headers": {
"x-api-key": "${input:oneuptime-api-key}"
}
}
},
"inputs": [
{
"type": "promptString",
"id": "oneuptime-api-key",
"description": "OneUptime API Key",
"password": true
}
]
}
Cursor
.cursor/mcp.json:
{
"mcpServers": {
"oneuptime": {
"url": "https://oneuptime.com/mcp",
"headers": {
"x-api-key": "your-api-key-here"
}
}
}
}
For self-hosted instances, replace oneuptime.com with your OneUptime host in any of the above.
Authentication
API key
Create a project API key in OneUptime under Project Settings → Advanced → API Keys and grant it the least privilege the agent needs (read-only keys work for all get_/list_/count_ tools). The project is inferred from the key — create tools never need a projectId argument.
Warning — never give an AI agent a master key. A OneUptime master API key is also accepted on this header and grants instance-wide admin access. Always use a project-scoped API key with least privilege for AI agents.
Public status page tools and oneuptime_help / oneuptime_list_resources work without any credential.
OAuth sign-in
The code is in OAuth/; each file opens with the reasoning behind it. The shape of it:
- One issuer under
/mcp.https://<host>/mcpis both the protected resource (RFC 9728) and the authorization server's issuer (RFC 8414). Every endpoint sits under/mcp/oauth, and every URL is built fromHOST/HTTP_PROTOCOL, never from the request. - Lazy, at the HTTP layer.
OAuth/RequestGateruns before the MCP SDK sees a request.initialize,tools/listand the public tools pass with no credential; a call to a tool that needs an identity is refused401(or403 insufficient_scopefor a read-only grant calling a write tool) with a Bearer challenge. It cannot live in a tool handler: a refusal from inside the SDK is a200, and a client only starts signing its user in on a real401. - Clients identify themselves. A Client ID Metadata Document (
OAuth/ClientIdMetadataDocument, fetched through the egress guard with private addresses always blocked) or Dynamic Client Registration (OAuth/RegistrationEndpoint). Nobody registers a client by hand. - Authorization code + PKCE (S256 only), resource indicators (RFC 8707),
issin the authorization response (RFC 9207), rotating refresh tokens with reuse detection, revocation (RFC 7009). - Opaque tokens, stored as hashes.
oumcp_ac_…/oumcp_at_…/oumcp_rt_…; only the SHA-256 is kept (McpOAuthToken). A grant (McpOAuthGrant) is one member letting one client act for them in one project; revoking is deleting the grant, which cascades to its tokens. - Only an access token is a credential.
x-api-keycarries an API key and nothing else;Authorizationcarries an API key or an access token. Any otheroumcp_secret presented in either header — a refresh token, a code, a client secret, or an access token inx-api-key— is refused401 invalid_tokenbyOAuth/RequestGateand is never passed on to the API as an API key. - Scopes
mcp:readandmcp:write(write includes read). The consent screen can narrow what a client asked for, never widen it. - The API never sees the client's token. Having accepted an access token, the MCP layer calls the REST API with a 60-second signed delegation token (
Common/Server/Utils/Mcp/McpDelegationToken).Common/Server/Middleware/McpDelegationAuthorizationturns that into a request authorized as the member — never as a master admin — in the grant's project, with their live permissions, and marked read-only when the grant is. A stolen access token is therefore worth exactly what the MCP tools can do with it. - Checked on every use.
Common/Server/Utils/Mcp/McpOAuthGrantAccessre-evaluates the grant per request and per token exchange: the member still exists, is not blocked, still belongs to the project, is not on a team that blocksAuthorizeMcpClient, and still satisfies the project's SSO requirement. - Consent is the Accounts page
/accounts/mcp-authorize; managing connected clients is Settings → MCP Server in the Dashboard.
Two settings: DISABLE_MCP_OAUTH=true switches the whole thing off (the routes answer 404 and an unauthenticated tool call gets the in-band "API key is required" result it always got), and DISABLE_MCP_OAUTH_CLIENT_ID_METADATA_DOCUMENTS=true is for instances that cannot make outbound requests.
Tool catalog
Database resources — full CRUD
Each of these 27 models gets six tools: create_, get_, list_, update_, delete_, count_ (e.g. create_incident, get_incident, list_incidents, update_incident, delete_incident, count_incidents):
Incident, Alert, Incident Alert, Monitor, Status Page, Scheduled Maintenance Event, Team, Monitor Status, On-Call Policy, Incident State, Incident Severity, Alert State, Alert Severity, Incident State Timeline, Alert State Timeline, Incident Public Note, Incident Internal Note, Alert Internal Note, Status Page Announcement, Scheduled Maintenance State, Scheduled Maintenance State Timeline, Label, Monitor Status Event, Security Case, Security Case Note, Security Detection, Security Playbook.
Telemetry resources — read-only
Log, Metric, Span, Exception Instance, Monitor Log, Security Event, Change Event, Kubernetes Cost Allocation, and Threat Intel Indicator get list_ and count_ tools only (e.g. list_logs, count_spans, list_exception_instances). There are no create tools for telemetry — ingestion happens via OpenTelemetry.
Workflow tools
Purpose-built shortcuts for incident/alert response (packages/App/FeatureSet/MCP/Tools/WorkflowTools.ts):
acknowledge_incident,resolve_incidentacknowledge_alert,resolve_alertadd_incident_note(withvisibility: "internal" | "public"— public notes post to the status page)add_alert_noteoneuptime_whoami— returns the project (ID and name) the credential belongs to; for an OAuth sign-in, also who is signed in, through which client, and whether the connection is read-only
Helper and public tools
oneuptime_help,oneuptime_list_resources- No credential needed:
get_public_status_page_overview,get_public_status_page_incidents,get_public_status_page_scheduled_maintenance,get_public_status_page_announcements
Annotations and results
Every tool has a title and states all three MCP hints, set in Tools/ToolAnnotations.ts, so clients can auto-approve safe calls and confirm the rest:
readOnlyHint: trueon the tools that change nothing:get_,list_,count_,oneuptime_whoami, the helper tools and the public status page tools.destructiveHint: trueon every tool that changes something: create, update and delete tools, acknowledging and resolving, and adding notes. Most writes overwrite or delete a record, change an incident's or alert's state, page someone on call or post to a status page, and the directories allowfalseonly for a write that adds and sends nothing.openWorldHint: trueonly on the public status page tools, which read any public status page on the instance. Every other tool stays inside the caller's project.
MCP directories require them: the Claude Connectors Directory flags any tool without a title or a readOnlyHint/destructiveHint, and ChatGPT's requires all three hints as booleans. Tests/ToolAnnotationsForDirectories.test.ts fails on a tool that lacks one, or that breaks the rules above. On the server itself, MCP_ALLOW_DESTRUCTIVE=false removes the delete tools only, and MCP_READ_ONLY=true removes every tool that is not read-only (Tests/WritePolicy.test.ts). Tool results include structuredContent alongside the JSON text. Errors come back as in-band tool results (isError: true) with statusCode, details, and a suggestion — not as protocol errors.
Query, sort, select, and pagination
Queries
Query fields accept a direct value or an operator object:
{
"query": {
"title": { "_type": "Search", "value": "database" },
"createdAt": { "_type": "GreaterThan", "value": "2026-07-01T00:00:00.000Z" }
}
}
Operators: EqualTo, NotEqual, IsNull, NotNull, EqualToOrNull, GreaterThan, LessThan, GreaterThanOrEqual, LessThanOrEqual, InBetween, Search, Includes.
Sort
{ "sort": { "createdAt": "DESC" } }
Values are "ASC" or "DESC".
Select
get_ and list_ tools take an optional select array of field names. By default all readable fields are returned except heavy ones (JSON / VeryLongText / HTML columns), which must be requested explicitly.
Pagination
limit defaults to 10 (max 100); skip offsets into the result set. List responses return the full requested page with honest pagination metadata:
{
"returnedCount": 10,
"totalCount": 42,
"skip": 0,
"limit": 10,
"hasMore": true,
"data": ["..."]
}
Example workflow
A typical incident-response loop an agent can run:
oneuptime_whoami— confirm which project the credential belongs to.list_incidentswith{"sort": {"createdAt": "DESC"}, "limit": 5}— find the active incident.acknowledge_incident— take ownership.list_logswith a time-range filter ({"time": {"_type": "GreaterThan", "value": "..."}}) andlist_exception_instances— investigate.add_incident_notewith"visibility": "public"— post a status update for customers.resolve_incident— close it out.
HTTP endpoints
| Endpoint | Method | Behavior |
|---|---|---|
/mcp | POST | JSON-RPC (tool calls and all MCP operations) |
/mcp | GET | Without an SSE Accept header: friendly JSON discovery payload. With one: 405 (no standalone SSE stream in stateless mode). |
/mcp | DELETE | No-op (stateless — nothing to terminate) |
/mcp/tools | GET | REST listing of available tools |
/mcp/health | GET | Health check, including the protocol versions this build speaks |
/mcp/.well-known/oauth-protected-resource | GET | Protected resource metadata (also at /.well-known/oauth-protected-resource[/mcp]) |
/.well-known/oauth-authorization-server/mcp | GET | Authorization server metadata (also at /mcp/.well-known/oauth-authorization-server and the origin's root) |
/mcp/oauth/authorize | GET, POST | Authorization endpoint: checks the client's request and redirects to the consent screen |
/mcp/oauth/token | POST | Code exchange and refresh |
/mcp/oauth/register | POST | Dynamic Client Registration |
/mcp/oauth/revoke | POST | Token revocation |
/mcp/oauth/consent/{details,approve,deny} | POST | The consent screen's own calls (session cookie, same origin only) |
Protocol compatibility
The server negotiates rather than rejects, so a client does not have to match the MCP SDK bundled in the App container exactly.
Protocol version. The MCP-Protocol-Version request header is negotiated down
to the highest version both sides support. A header sent twice is read as its
first value — Node collapses repeated headers into one comma-joined string, and
a version never contains a comma:
- a version the server supports is used as-is;
- a version newer than anything the server knows about (for example a client on
a newer revision of the spec) is negotiated down to the server's latest — the
same answer
initializegives; - a version older than everything the server supports, or one that is not a
YYYY-MM-DDversion at all, has no common ground. The request is answered with400and a JSON-RPC error that names the requested version and lists the supported ones, instead of failing silently at the transport layer.
An unusable header on an initialize request is dropped rather than rejected,
because that request negotiates the version in its body. A header that is
present but blank is dropped too: an empty string is not the same as an absent
header to the SDK, which would otherwise reject it like any other unknown
version.
GET /mcp and GET /mcp/health both list the supported versions, so a failing
handshake can be diagnosed without reading container logs.
Response format. A POST is answered with whatever the client's Accept header
allows: text/event-stream when the client asks for it by name or via the
text/* range (what spec-compliant MCP clients do), and a plain
application/json body otherwise — including for Accept: application/json, an
application/* or */* wildcard, or no Accept header at all. Both are valid
replies to a POST under the MCP spec. A client that accepts neither gets a 406
naming the media types the endpoint can produce.
Diagnostics. Negotiation logs describe the client: they are built from a snapshot taken before the headers are rewritten, so they report what was actually sent rather than the values this server normalised them to. Every field in them — header values and the JSON-RPC method alike — is stripped of control characters and length-capped, so a caller cannot forge log lines or flood the log through them. The API key or access token is never logged.
Self-hosting
The MCP server ships as part of the App container and is served at /mcp behind Nginx — no separate deployment is needed. Its tools call the OneUptime API at the App's internal address - SERVER_APP_HOSTNAME and APP_PORT, the address Nginx proxies /api to (inherited from the App service's environment) - never through the public HOST, which the App container cannot always reach (with HOST=localhost it is the container itself). API keys are never configured on the server; clients supply them per request.
OAuth sign-in needs nothing configured either, but it does depend on HOST and HTTP_PROTOCOL being the address people actually use: the issuer, the token audience and every endpoint in the discovery documents are built from them. A reverse proxy in front of OneUptime has to forward /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server (and everything under them) to the App along with /mcp; the bundled Nginx does.
MCP Registry
OneUptime Cloud's server is listed in the official MCP Registry, which MCP clients and directories read, as com.oneuptime/mcp. server.json is the entry: streamable HTTP at https://oneuptime.com/mcp, signed in to with OAuth, or given a project API key in the optional x-api-key header. Its version is a placeholder, 0.0.0.
Every release publishes it at the release's version. The publish-mcp-registry job in .github/workflows/release.yml runs Scripts/MCP/PublishToMcpRegistry.sh once the GitHub release is published, and skips a version the registry already has, so re-running a release publishes nothing twice. A published version cannot be changed.
Only whoever controls oneuptime.com may publish under com.oneuptime. The job signs in with an Ed25519 private key whose public key is in a TXT record on oneuptime.com, and it fails, publishing nothing, until both are in place. To set them up (Ed25519 needs OpenSSL 3; on macOS, brew install openssl@3 and use its openssl, not the system LibreSSL):
-
Make a key pair:
openssl genpkey -algorithm Ed25519 -out mcp-registry-key.pem -
Add a TXT record on
oneuptime.comitself, not on a subdomain such as_mcp.oneuptime.com, with the value this prints:echo "v=MCPv1; k=ed25519; p=$(openssl pkey -in mcp-registry-key.pem -pubout -outform DER | tail -c 32 | base64)" -
In the repository's Settings → Environments, create
mcp-registry-publish, limit its deployment branches torelease, and give it the secretMCP_REGISTRY_PRIVATE_KEY: the private key in hex, which this prints:openssl pkey -in mcp-registry-key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n'
Then delete mcp-registry-key.pem, or keep it somewhere safe: whoever has the key can publish, and overwrite, any entry under com.oneuptime. To change the key, replace the TXT record and the secret together; an old record left beside the new one is tried first and fails the sign-in.
To publish before the next release, run the script by hand from the repository root, with mcp-publisher and jq installed: PACKAGE_VERSION=<version> MCP_REGISTRY_PRIVATE_KEY=<hex key> bash Scripts/MCP/PublishToMcpRegistry.sh.
ChatGPT plugin directory
ChatGPTPlugin/ is the package OneUptime uploads to the ChatGPT plugin directory, which ChatGPT and Codex share: the listing, five positive and three negative review test cases, and mcp.json, which points at https://oneuptime.com/mcp and signs in with OAuth for read and write access. npm run build-chatgpt-plugin builds the ZIP. Its README lists what else a submission needs: a verified developer identity, a domain-verification token served at https://oneuptime.com/.well-known/openai-apps-challenge, reviewer credentials for a demo project, and a demo video. Tests/ChatGPTPlugin.test.ts holds the package to the Agent Plugins schemas and OpenAI's limits, and checks that every tool a test case names is one this server has.
Development
The MCP server lives in packages/App/FeatureSet/MCP and is mounted into the App service at startup (packages/App/Index.ts). Tests live in packages/App/FeatureSet/MCP/Tests and run as part of the App test suite:
cd packages/App
npm install
npx jest ./FeatureSet/MCP/Tests --runInBand
New models are picked up automatically: decorate a database model with @EnableMCP (or set enableMCP on an analytics model) and the tool generator creates its tools on the next start.
License
Apache-2.0 - See LICENSE file for details.
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.