MCP Server

Connect Claude, ChatGPT, Gemini, or any MCP-capable client to BGPHorizon and query BGP routing data in plain language. The server exposes 25 tools. Each one answers a specific question and returns a result with warnings attached, rather than raw event data.

Overview

The Model Context Protocol (MCP) lets a language model call tools on your behalf. Each BGPHorizon tool covers one question, such as who holds a prefix, whether an origin change persisted, or whether an ASN's RPKI is clean. Results include warnings for the common misreadings: a transient announcement taken for a migration, or a single collector taken for the whole internet.

Hosted

Point your client at https://bgphorizon.com/mcp with your API key. Nothing to install. Works with web clients that cannot run a local process.

Self-hosted

Open source. Run it locally with one command, or on your own infrastructure. See Self-host.

Both routes call the same metered /api/v1 with your API key. Self-hosting does not change your limits or what you can access.

Connect a client

The hosted endpoint is https://bgphorizon.com/mcp. Clients that support MCP sign-in (OAuth) only need that URL: they open a BGPHorizon page where you sign in and allow access. Other clients use an API key, which you create in your User Panel → API. Keys start with bgps_.

Hosted endpoint, with sign-in

Claude Desktop and claude.ai: open Settings → Connectors, choose Add custom connector, and enter https://bgphorizon.com/mcp. A BGPHorizon page opens; sign in and click Allow. Nothing to install.

Claude Code:

claude mcp add --transport http bgphorizon https://bgphorizon.com/mcp

Then run /mcp, select bgphorizon and choose Authenticate.

Cursor, VS Code and other clients with MCP sign-in: add the server by URL only:

{
  "mcpServers": {
    "bgphorizon": { "url": "https://bgphorizon.com/mcp" }
  }
}

Access lasts 90 days, after which the client asks you to sign in again. Each sign-in creates a key named after the client in your API tab. It counts toward your daily API allowance, and revoking it there disconnects the client. Signing in requires an account with API access.

Hosted endpoint, with an API key

For scripts, agents and clients without MCP sign-in, send the key as a bearer token. Claude Code:

claude mcp add --transport http bgphorizon https://bgphorizon.com/mcp \
  --header "Authorization: Bearer bgps_your_key_here"

Config files that accept a remote url with headers (Cursor and similar):

{
  "mcpServers": {
    "bgphorizon": {
      "url": "https://bgphorizon.com/mcp",
      "headers": { "Authorization": "Bearer bgps_your_key_here" }
    }
  }
}

Claude Desktop's config file only starts local programs, so an API key there needs the mcp-remote bridge and Node.js. Use the connector above instead unless you need a fixed key. The config is in the setup guide.

ChatGPT / OpenAI Responses API (hosted MCP):

tools=[{
  "type": "mcp",
  "server_label": "bgphorizon",
  "server_url": "https://bgphorizon.com/mcp",
  "authorization": "Bearer bgps_your_key_here",
  "require_approval": "never"
}]

Self-hosted (local, via uv)

Clone the repository and let uv handle the environment. The package is not on PyPI yet, so uvx bgphorizon-mcp will not resolve; use the checkout, or the hosted endpoint above, which needs no install at all.

git clone https://github.com/bgphorizon/bgphorizon-mcp.git
cd bgphorizon-mcp && uv sync

claude mcp add bgphorizon \
  --env BGPHORIZON_API_KEY=bgps_your_key_here \
  -- uv run --directory "$PWD" bgphorizon-mcp

Or in a config file:

{
  "mcpServers": {
    "bgphorizon": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/bgphorizon-mcp", "bgphorizon-mcp"],
      "env": { "BGPHORIZON_API_KEY": "bgps_your_key_here" }
    }
  }
}

Verify any setup with uv run bgphorizon-mcp --selftest. It prints ✓ API reachable ✓ key valid ✓ 25 tools ✓ 9 resources ✓ 8 prompts. In Claude Code, run /mcp to confirm it's connected.

Authentication

The hosted endpoint reads a bearer token from the Authorization header on each request: either a key from your API tab or the key issued when you sign in through a connector. A request without a valid token gets an HTTP 401 that tells the client where to sign in. The self-hosted server reads the key from BGPHORIZON_API_KEY. Usage is metered per key, and tier entitlements (history window, detections, firehose) apply exactly as they do in the web app. Over-quota and permission errors come back as structured tool errors, so the model can explain the limit rather than retry blindly.

Tools (25)

Every tool returns warnings[] (may be empty) and a meta block with source (rollup / raw_events / registry / composed). Give ASNs with or without AS; prefixes as plain CIDR.

Investigation analyzing a network you don't run

identify

Registry, RPKI, IRR and PeeringDB records for an ASN or prefix in one call. Warns with irr_origin_mismatch when an IRR object names an origin that has never announced the space, and with irr_object_deleted when an object is no longer in the registry. Includes the registry country and the rir that answered.

Params: asn or prefix, include. Usually the first call in an investigation.

inventory

The prefixes an ASN announces, each with a server-computed persistence class (persistent, intermittent, transient) and a prefix-length distribution.

Params: asn, start, end, min_prefix_len, only (one persistence class), summary_only (counts without the list, for large networks). Use this for persistence rather than first_seen.

timeline

Counts over time for an ASN or prefix, optionally grouped by origin or collector. Replaces bulk event downloads; group_by=origin daily is the handover chart. hour, 10m and 1m buckets cover up to 72 hours, and for an ASN each bucket counts the distinct prefixes it originated, which is how a short leak shows up. Withdrawals cannot be tied to an ASN, so for ASN targets they are returned as null, not zero.

Params: target, start, end (dates, or RFC3339 for sub-day), granularity (day, week, hour, 10m, 1m), group_by.

origin_history

Day-by-day origin ASNs for a prefix: each day's origin set, MOAS days, and each transition classified as handover, episode, or intermittent.

Params: prefix, start, end. Run this before describing any origin change as a migration.

reachability

How many observing peers had no route, and when. Event-driven series plus server-computed outage windows. Accepts multiple prefixes at once.

Params: prefixes, start, end. Reads raw events, so keep the window short.

global_reach

How globally reachable a prefix is: the share of full-table feeds that see it over a 30-day footprint (both the count and the total are full-table feeds), classified global/regional/local with a per-region breakdown. A regional or local result can mean a leak, upstream filtering, or limited propagation. Each region lists the feeds there that saw the prefix (seen of feeds). Its pct compares seen with baseline, what a widely routed prefix reaches in that region, so 100% means as visible as a typical global route.

Params: prefix. reachability measures per-peer outages over a short span; this measures worldwide propagation.

detections

Routing-anomaly findings for an ASN or prefix. The direction field states whether the queried entity is the actor, the affected party, or a network announcing inside its own space, so actor_as and baseline_asns are not read backwards. Pages through every match and reports complete and total_matching; a summary gives counts by type and direction, by hour, and distinct prefixes and counterpart networks.

Params: asn or prefix, start, end, detection_type, anomalous_only, prefix_status, role, state, max_incidents, summary_only, format. Above 200 incidents the list comes back as compact rows without details (format full overrides this). Each page of up to 500 incidents is one API request.

paths

Transit structure with prepending resolved: immediate upstreams and their share, plus top paths with collapsed_path and prepend_count.

Params: prefix, start, end, origin_as (only one origin's paths, so a short hijack's routes are visible on a contested prefix).

relationships

An ASN's upstreams (providers) and downstreams (customers) over a window, plus observed neighbours that could not be classified. Relationships are inferred provider-to-customer, anchored on the Tier-1 clique (about 94% agreement with CAIDA). Peering is not inferred. data_through is the newest day the relationship data covers; a relationships_stale warning says when the window ends later, and served_window says which days were used.

Params: asn, start, end. The transit topology; pair with path_diversity for observed usage.

path_diversity

How an origin's announcements spread through its upstreams toward the collectors, weighted by vantage points. Built from observed AS paths only. One dominant upstream means single-threaded transit; several high shares mean redundancy.

Params: asn, prefix, start, end. Observed usage; pair with relationships for topology.

translate_communities

Translate BGP community strings such as 3356:2065 into their published meaning, using a dictionary built from operators' IRR objects, NLNOG, and the IANA/RFC well-known values. Returns known=false when no definition is published.

Params: communities. Resolves the owner AS + name even when the community itself is unknown.

compare_windows

Compare a baseline window with an event window for a target, by volume, origin, or collector. A new origin or a changed collector mix shows up directly.

Params: target, window_a, window_b, dimension.

locate

Geolocation from routing data: the cities where all of an entity's upstreams have PeeringDB presence. More reliable than GeoIP for leased or anycast space.

Params: asn or prefix.

subprefixes

More-specific prefixes announced inside a block, plus an estimate of unrouted space: allocated addresses never seen in the routing table, which can be announced without anyone noticing.

Params: prefix, start, end.

events_sample

Raw BGP events for a narrow window, newest first. Capped at 500 events. Windows over 24 hours are rejected with a suggestion to narrow, rather than truncated silently. Filters are applied by the server, so a filtered request finds matching events on a busy prefix.

Params: prefix, start, end (dates or RFC3339), limit, origin_as, peer_asn, collector_id, event_type.

platform_baseline

Exact daily counts of anomalous detections across the platform, each type's median, and how a given day compares, so an ordinary busy day is not mistaken for an event. Call it before describing anything as anomalous.

Params: window, by, day.

notable_events

A scored feed of platform-wide events where one network announces space another normally originates. Prominent affected networks, corroborating detectors, and larger prefix counts rank higher; probable leaks and leased space rank lower. These are leads to investigate, not confirmed hijacks.

Params: hours, limit, and for a past incident asn, start, end. The same feed as the dashboard's Potentially Notable Detections.

origin_episode

What a network announced during a short window that it does not announce normally, and whose space it was. Each prefix comes with when it was first and last seen and how widely. The summary counts conflicts, groups the prefixes by how many peers saw them, and names the networks that carried the routes. Each prefix has its own conflict count. Start here when writing up a hijack or route leak.

Params: asn, start, end, baseline_days, after_days, min_peers, max_prefixes (default 100, most widely seen first).

origin_reach

A propagation curve for one prefix and one origin: how many collector sessions carried the route, as a share of full-table feeds, minute by minute, with separate waves reported as phases. Only BGP updates are stored, so a long-established route is undercounted (sessions that carried it throughout without an update are not seen); a route_predates_window warning says when, and global_reach is the right tool for an established route.

Params: prefix, origin_as, start, end, interval_seconds.

bulk_registry

RPKI, IRR and RDAP for up to 2,000 prefixes and ASNs, with an RPKI verdict per prefix for a given origin and the registry holder, country and RIR. States registry facts for a whole incident instead of a sample. A summary counts ROA and IRR coverage and prefixes by RIR and country.

Params: prefixes, origin_asn, asns, as_of (judge RPKI and IRR against a past day's data; use it for any past incident, since holders often publish ROAs right after one), summary_only (counts plus only the prefixes with a ROA, a matching IRR object or an error). Each 200 items is one API request.

Operator watching a network you own

health_check

Hygiene and exposure audit for an ASN you operate: RPKI and IRR coverage, MOAS, ROA max-length exposure, transit diversity, visibility, and unrouted space. Each finding includes a remediation step.

Params: asn, window, checks.

validate_announcement

Checks whether announcing a prefix from an origin will validate: covering ROA and max-length, IRR objects, the origins that announced it in the last 7 days, and whether the space was recently transferred (old ROAs persist after a transfer). Returns clear, warn, or blocked.

Params: prefix, origin_asn, check_holder, as_of (check against a past day's ROAs and IRR objects).

visibility

Where a prefix is visible and where it is not: peer and collector reach, upstreams, and a ratio against sibling prefixes that reveals filtering.

Params: prefix, compare_to, window.

Your monitoring your own alerts, for reports

my_alerts

Every alert your monitors fired over a window, with totals by detection type, severity, and monitor. Scoped to your account plus anything your organisation shares with you. Not a platform-wide search.

Params: window (today, 24h, 7d…), start, end, detection_type, severity, prefix_status (established, new, new_more_specific, returned), monitor_id, include_dismissed, limit.

my_monitors

Your monitors, each with its alert count over the window. Use it to state coverage in a report or to find noisy monitors.

Params: window, start, end, scope (mine/org), resource, status, detection_type, q, limit.

Resources (9)

Read-only reference material. Reading a resource does not count as a tool call.

  • bgphorizon://reference/detection-types: every detection type, its severities, and how to read actor_as against baseline_asns.
  • bgphorizon://reference/collectors: the RouteViews and RIPE RIS collectors, and how to use concentration metadata.
  • bgphorizon://reference/glossary: BGP terms in plain language, for use in report output.
  • bgphorizon://reference/data-horizon: the retention floor, rollup versus raw events, why first_seen is not a persistence signal, why withdrawals have no origin, how far relationship data lags, and what counts as a covering holder.
  • bgphorizon://reference/report-template: the HTML report skeleton with the stylesheet already inlined.
  • bgphorizon://reference/writing-guide: the style rules for reports.
  • bgphorizon://reference/qa-checklist: the checks to run before publishing a report.
  • bgphorizon://reference/methodology: the investigation procedure.
  • bgphorizon://reference/report-examples: published reports with the error each one caught in review.

Prompts (8)

Guided workflows that follow the methodology. In Claude Code they appear as slash commands, for example /bgphorizon:audit_my_network.

  • investigate_entity: full workup of an ASN or prefix, findings only. For a suspected hijack or leak by one network it starts from origin_episode and origin_reach.
  • write_report: a complete HTML report using the methodology and template. It asks first whether you want to review the findings before it writes, so you can question or correct them while the report is still a draft.
  • alert_report: pull your alerts for a period and write them up, separating security findings from routine registry changes.
  • triage_incident: a quick verdict: real event, measurement artifact, or nothing.
  • locate_infrastructure: geolocation from routing data, with a stated confidence.
  • audit_my_network: hygiene report for your ASN with prioritized remediation.
  • preflight_change: go or no-go for an announcement or renumbering.
  • explain_incident: plain-language incident summary for a non-technical reader.

Write your own investigative reports

The server ships the methodology and templates used for BGPHorizon reports, so a connected model can produce a complete report. The write_report prompt runs the process.

Nothing to clone

The standards travel with the server, so the hosted endpoint produces the same document as a local checkout. The model fetches them itself:

  • writing-guide: style rules and the banned list
  • qa-checklist: the pre-publication pass
  • methodology: evidence order and the two checks
  • report-examples: published reports and what review caught in each
  • report-template: the skeleton, with the stylesheet already inlined

You do not need to name those. The write_report prompt reads them first, and the server's instructions send any connected model to the writing guide and QA checklist before it drafts anything.

Run it. In Claude Code:

/bgphorizon:write_report AS54994 over the last 60 days

It asks whether you want to review the findings before it writes. Say yes and it gathers the evidence, then stops and walks you through what it found, numbered, with the confidence behind each one and what it could not determine. You can question it, ask for more lookups, or correct it from what you know about the network while the report is still a draft. If your input changes a conclusion, the report records the correction.

What cloning adds: the two scripts the server cannot run for you. style-lint.py checks the prose against the banned list, and build-report.sh inlines the CSS, validates the HTML, runs that lint, and renders a PDF and PNG. Clone when you want the build to fail on a style violation and to get rendered output. The standards are the same either way.

git clone https://github.com/bgphorizon/bgphorizon-mcp
./reporting/build-report.sh my-report.html

The method requires two checks, and the tools' warnings enforce them: confirm persistence before describing a change (a prefix seen for a few days is transient, not a migration), and confirm attribution before describing an anomaly (a spike from one collector is a measurement artifact).

The same methodology, writing guide, HTML template, QA checklist and worked examples are also on disk under reporting/ in the GitHub repository, for editing or for use without a connected server.

Self-host from GitHub

The server is open source. Fork it, read the tools, add your own. A self-hosted server still calls the metered /api/v1 with your key, so your access does not change.

View on GitHub

Repository: https://github.com/bgphorizon/bgphorizon-mcp

From source:

git clone https://github.com/bgphorizon/bgphorizon-mcp && cd bgphorizon-mcp
uv sync
export BGPHORIZON_API_KEY=bgps_your_key_here
uv run bgphorizon-mcp --selftest

Host the HTTP transport yourself:

uv run bgphorizon-mcp --transport http --port 8931

Troubleshooting

SymptomFix
401 on every callKey missing or wrong. Confirm the Authorization header / BGPHORIZON_API_KEY; test with --selftest.
Quota errors mid-sessionYou reached the daily request limit for your tier. It resets at 00:00 UTC.
Server not listed after restartThe self-hosted binary is not on the client's PATH. Use an absolute command path, or the hosted URL.
Responses truncatedNarrow the window. events_sample caps at 500 events; use timeline for longer spans.

See also the API reference (the endpoints the MCP is built on) and the detection types.