{
  "server": {
    "name": "canireach",
    "version": "0.1.0",
    "protocol_version": "2025-06-18",
    "transport": "stdio — JSON-RPC 2.0, newline-delimited"
  },
  "invocation": {
    "command": "node",
    "args": [
      "tools/mcp-server.mjs",
      "--manifest",
      "2026-08-22T0815Z.final.manifest.json"
    ],
    "manifest_naming": "<capture-id>.manifest.json",
    "source": "https://github.com/BrunoOGclaw/canireach",
    "captures": "https://github.com/BrunoOGclaw/canireach/releases",
    "note": "Clone the repository and download the release carrying `2026-08-22T0815Z.final.manifest.json` — the asset name, not the capture id, identifies the release, because a capture whose metadata was later corrected is published more than once. Any other capture works the same way: the instrument names every manifest it writes `<capture-id>.manifest.json`, so pass whichever one you downloaded. The server reads local bytes, verifies them against the SHA-256 its manifest publishes, and refuses to start if they disagree. It opens no socket."
  },
  "tools": [
    {
      "name": "reachability_lookup",
      "description": "What canireach last measured at a domain: which identities were let through, which were refused, which were never asked, and the detour where one is published (llms.txt, agents.md, a Web Bot Auth directory, a 402 toll). Answers describe a past measurement and carry its capture id, observation slot, vantage and age. A host that has never been probed answers \"unknown\" rather than an inference.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string",
            "description": "Hostname to look up, e.g. \"example.com\". A URL is accepted and its host used. No parent-domain fallback: api.example.com is not example.com."
          }
        },
        "required": [
          "domain"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "dataset_status",
      "description": "Which capture is loaded, how old it is, and how much of it is behavioural evidence rather than doors this instrument chose not to knock on. Call this before trusting any lookup: that split moves with the capture and can be most of the dataset, so read it rather than assuming it.",
      "inputSchema": {
        "type": "object",
        "properties": {},
        "additionalProperties": false
      }
    }
  ],
  "dataset": {
    "capture_id": "2026-08-22T0815Z",
    "capture_class": "pre-2026-09-15-baseline",
    "dataset_sha256": "6d6fe5b62eb7bd4c297fa6428653efa4b01d7abbf92792b7eee6a2cb813f4ca6",
    "observed_from": "2026-08-22T08:28:13.633Z",
    "observed_through": "2026-08-22T08:40:54.831Z",
    "selection": {
      "mode": "pinned",
      "reason": "This surface is pinned to the pre-2026-09-15 baseline so its published figures stay citable and stop moving. Captures after it were taken with a different instrument profile, so the difference between them is not all web. Every capture this project has published is downloadable with its manifest and SHA-256 from the releases page."
    },
    "domains_indexed": 1000,
    "domains_with_any_behavioural_evidence": 974,
    "doors_by_evidence": {
      "behaviour": 4699,
      "robots-declaration": 301,
      "not-attempted": 0
    },
    "comparability_profile": {
      "vantage.class": "unrecorded",
      "observation_window.slot": "unrecorded",
      "input.sha256": "f923fe95bc629b757b8599ad0910fec1f245d5b0355458772e1cffae1b7a1453",
      "instrument_policy.row_schema_version": "unrecorded",
      "instrument_policy.robots_unavailable": "unrecorded",
      "instrument_policy.redirects": "unrecorded",
      "instrument_policy.robots_redirects": "unrecorded",
      "instrument_policy.denial_gate": "unrecorded",
      "instrument_policy.dialects": "unrecorded"
    },
    "robots_unavailable_behaviour": {
      "doors_with_unreadable_robots": 2125,
      "probed_anyway": 2125,
      "skipped": 0,
      "observed_policy": "fail-open",
      "declared_policy": "unrecorded"
    },
    "note": "Doors counted as `not-attempted` had no request sent, because robots.txt could not be read as policy. In this capture 0 of 5000 doors are in that class. robots.txt could not be read at 2125 doors in total; 2125 were probed anyway and 0 were skipped, so this capture behaved as `fail-open` and declares `unrecorded`. Those doors are evidence about the instrument, not about the hosts: under the opposite policy they change class wholesale without a single site having changed its mind, which is why coverage is only meaningful beside the profile that produced it."
  },
  "refusals": [
    "No on-demand probing. The server has no network access and never imports the prober, so it cannot be pointed at a third party as a scanner.",
    "No parent-domain fallback. api.example.com is not example.com, and an unprobed host answers `unknown` rather than an inference.",
    "No present-tense field in any answer. Behaviour is `last_outcome` welded to when it was observed.",
    "A door is only reported as behaviour if a request was actually sent to it."
  ],
  "forbidden_answer_fields": [
    "reachable",
    "blocked",
    "allowed",
    "available",
    "online",
    "can_reach",
    "is_reachable",
    "status",
    "ok"
  ]
}
