MCP server

Your assistant reads the incident, and asks before it acts.

Unhandled runs a Model Context Protocol server at /mcp. Connect Claude Code, Cursor or any client that supports remote MCP servers, sign in through OAuth, and the assistant can query issues, traces, logs, alerts and monitors, and change only what you allowed.

OAuth sign-in
No API key to paste into a config file
Scoped access
Pick read and write scopes per app
Human approval
Sensitive calls wait for a person
Safe retries
A repeated request key acts only once
#connect

Add one URL to your client

Every client uses the same address, /mcp on your Unhandled domain. The client registers itself and opens the consent page in your browser, where you sign in and choose what it may do.

  • Claude Code, Cursor, VS Code and Claude Desktop all connect with that URL
  • Clients are public and use PKCE, so there is no client secret to store
  • Access tokens last an hour, and refresh tokens are replaced on every refresh
.cursor/mcp.json
{
  "mcpServers": {
    "unhandled": { "url": "https://unhandled.example.com/mcp" }
  }
}
#approval

Sensitive calls wait for a person

Human approval is on by default. A call that grants admin access, deletes something, issues a key or sends data to a new destination does not run. The agent gets a link instead, and a person approves the exact arguments in the browser.

  • Webhook URLs and custom header values are hidden on the approval page
  • An approval is bound to the connection, the tool and a hash of the arguments, and runs once
  • Approving needs a recent sign-in, and an approval expires after two hours
#connected-apps

Revoke an app in one click

Every app you allowed is listed under Connected apps in your account, with the organizations it reaches, its scopes and when it last made a call. Edits apply to its next call, and Revoke stops its token right away.

  • Organization admins see every app that reaches their organization and can cut one off there
  • Admins can turn MCP off or cap the scopes any app may use in their organization
  • An activity list records the tool, the app, the member and which fields it set, never their values
#untrusted-text

Text from your users stays data

Exception messages, log lines and comments can be written by anyone who has your public DSN. Each result lists the fields that hold such text, so the assistant treats them as data and never as instructions.

  • Paths such as items[].message point into the JSON, and an object path covers everything inside it
  • Characters that hide or reorder text arrive escaped, and the JSON still decodes to the stored value
  • The server instructions and every tool description repeat that this text is not an instruction
tools/call result
{
  "content": [{ "type": "text", "text": "{\"project\":\"default\",\"items\":[{\"id\":\"1f0…\",\"name\":\"api-health\"}],\"nextCursor\":null}" }],
  "_meta": { "unhandled.net/untrustedFields": ["items[].id", "items[].name", "project"] }
}
#faq

Questions

Try it on your own stack.

Every feature is on every plan, priced by the telemetry you send.