MCP Server
Ostorlab exposes an MCP server, letting AI assistants and agent frameworks work with your scans, vulnerabilities, tickets and assets directly — no custom GraphQL client required.
Where the GraphQL API gives you the full platform surface to build against, the MCP server gives an AI client a curated set of typed tools it can discover and call on its own. Ask your assistant "what did the last scan of my app find?" and it will pick the right tools, chain them, and answer.
Endpoint
https://api.ostorlab.co/apis/mcp/{api_key}/
The transport is streamable HTTP. The API key is part of the URL path.
Warning
The trailing slash is required. Without it the server answers with a 307 redirect. Clients that follow redirects will still work but pay an extra round trip on every request; clients that do not follow redirects on POST will fail.
Warning
The key in the URL is a credential. URLs are commonly recorded in proxy logs, browser history and shell history, and MCP client configuration files are usually stored in plain text. Treat the endpoint URL exactly as you would the key itself. To retire a key, revoke it — setting an expiry date does not stop the key from working on this path.
Getting an API key
Create one from the web application under Integrations/API → API Keys
(https://report.ostorlab.co/integrations/api). The key's role determines what the MCP tools
can do — see Permissions.
Connecting a client
Most clients take a server name, a transport and a URL. The transport is streamable HTTP, so
where a client asks for a type, use http.
Claude Code
claude mcp add --transport http ostorlab https://api.ostorlab.co/apis/mcp/YOUR_API_KEY/
Claude Desktop, Cursor, VS Code, Windsurf, Zed
These read a JSON configuration file. The location differs per client and per platform — check your client's documentation for where it lives — but the entry has the same shape:
{
"mcpServers": {
"ostorlab": {
"type": "http",
"url": "https://api.ostorlab.co/apis/mcp/YOUR_API_KEY/"
}
}
}
Some clients name the section servers rather than mcpServers, and some use serverUrl in
place of url. If yours rejects the block above, check its documentation for the key names — the
transport and the URL are the parts that matter.
Confirming it works
Ask the client to run a read tool, for example listing your scans. A bad key returns
Invalid API Key. Asking it to list its tools is not a useful check: the tool list is returned
for any key, valid or not.
Permissions
Every tool checks the API key's permissions before doing anything. Keys carry a role, and roles map to actions:
| Tool type | Required permission |
|---|---|
| Reading scans, vulnerabilities, tickets | read |
| Creating or modifying tickets, comments, checklists | write |
| Triaging vulnerabilities | write |
| Listing attack-surface assets | attack_surface_read |
A key without the required permission gets a clear error rather than a partial result.
attack_surface_read is held by every role, so any key can call list_assets. It is worth
listing because the reverse is not true: an attack_surface_auditor key has neither read nor
write, so list_assets is the only tool it can use.
If your organisation has object-level access enabled, tools respect it, following the same rules as the GraphQL API-key path:
- Scans and tickets — a non-admin key only sees the individual scans and tickets it has been granted.
- Assets — the grant is on the owner, not the asset. A key sees every asset belonging to the owners it has been granted.
Available tools
Scans
| Tool | Description |
|---|---|
list_scans |
List scans, filtered by status, asset_id, scan_profile, risk_rating, created_after, archived. |
get_scan |
One scan's details. |
get_scan_status |
A scan's progress, its recent status history, and any errors it reported. |
get_scan and get_scan_status do not return archived scans, even though list_scans can list
them. Calling either on an archived scan returns Scan not found.
Vulnerabilities
| Tool | Description |
|---|---|
list_vulnerabilities |
Findings, filtered by scan_id, risk_rating, kb_detail_id, or the ticket they belong to. |
get_vulnerability |
One finding: technical detail, exploitation detail, CVSS vectors and location metadata. |
update_vulnerability |
Triage: set a custom risk rating or CVSS vector, or mark a finding false positive or an accepted exception. |
Two things to know about update_vulnerability:
- Marking a finding false positive or exception acts on the tickets linked to that finding. A finding with no ticket is unchanged, and the call still reports success.
- Its
all_vulnerabilitiesparameter applies the change to every finding in the same scan sharing the same knowledge-base detail, and recomputes the scan's overall risk rating. The blast radius is much larger than a single finding.
Every finding carries a dna field — a fingerprint derived from the finding's title, technical
detail and location. The same issue found again in a later scan keeps the same dna as long as
those three are unchanged, which makes it useful for deduplicating when you import findings
elsewhere. It is not guaranteed stable when the technical detail varies between runs.
Tickets
| Tool | Description |
|---|---|
list_tickets |
Tickets, filtered by status, priority, assigned_email, agent, tag, title, created_since, modified_since. Sortable, with a limit. |
get_ticket |
One ticket, by id or by key (for example os-1234). |
create_ticket |
Create a ticket, optionally with tags, an assignee, a ticket agent, a due date. |
update_ticket |
Change any of the same fields. |
delete_ticket |
Delete a ticket. Not reversible. |
Comments and checklists
| Tool | Description |
|---|---|
list_comments, create_comment, update_comment, delete_comment |
Comment thread on a ticket. |
list_checklists, create_checklist, update_checklist, delete_checklist |
Remediation checklists attached to a ticket. |
create_checklist_item, update_checklist_item, delete_checklist_item |
Individual checklist items. |
Note
Adding a comment to a ticket that has a ticket agent assigned will start an agent run.
Comments created through MCP have no author attached, because the server authenticates an API key rather than a user. They render without an author in the web application.
Assets
| Tool | Description |
|---|---|
list_assets |
Attack-surface assets, filtered by asset_type, owner_id, search, tags. |
Result limits
Every list tool caps how much it returns, and the cap is silent — there is no "there is more" marker and no cursor to page with.
| Tool | Default | Maximum |
|---|---|---|
list_scans, list_vulnerabilities, list_assets |
200 | 200 |
list_tickets |
50 | 200 |
get_scan_status (status history) |
50 | 50 |
list_comments, list_checklists |
no limit | no limit |
Asking for more than the maximum is clamped down to it rather than rejected. So an organisation with 500 scans gets 200 back with nothing to indicate the other 300 exist. Narrow with filters when the count matters — do not ask a model to count from a list result.
Deleting
delete_ticket, delete_comment, delete_checklist and delete_checklist_item remove data
permanently. They are not currently flagged to clients as destructive, so your client will not
prompt you to confirm before running them. Treat any prompt that could lead to a delete with
that in mind.
Errors
A tool that fails returns a normal successful response whose body carries an error field:
{"error": "Invalid API Key."}
The JSON-RPC isError flag stays false. If your client branches on isError, it will read
authentication and permission failures as successes — check for the error field instead.
Examples
Prompts that work well once the server is connected:
- "List the scans that finished in the last week with a high risk rating."
- "Show me the critical findings from scan 12345 and open a ticket for each one."
- "What's the technical detail of vulnerability 987, and has anyone commented on its ticket?"
- "Mark vulnerability 654 as a false positive."
- "Which open tickets are assigned to rubberduck?"
Troubleshooting
HTTP 404 on connect
The URL is missing the key segment, or the trailing slash. It must be
/apis/mcp/YOUR_API_KEY/.
Invalid API Key.
The key does not resolve. Check it was copied whole, and that it has not been revoked.
API Key does not have write permission.
The tool needs a higher role than the key has. Issue a key with the required role.
Scan not found. for a scan you can see in the web application
Three possible causes: the scan is archived (get_scan and get_scan_status refuse archived
scans), the scan belongs to another organisation, or your organisation uses object-level access
and this key has not been granted that scan. An inaccessible scan is deliberately reported the
same way as one that does not exist.