{
  "name": "civic-ledger",
  "version": "0.1.0",
  "protocol": "mcp",
  "protocolVersion": "2024-11-05",
  "transport": "stdio",
  "command": "python -m civic_ledger.mcp --state state.json --queue contributions.jsonl",
  "community_server": {
    "status": "built, local run mode only",
    "transport": "streamable-http",
    "module": "civic_ledger.mcp_community",
    "command": "python -m civic_ledger.mcp_community --port 8080 --queue contributions.jsonl",
    "auth": "GitHub OAuth (Bearer token required for writes)",
    "tools": [
      "town_state",
      "coverage_matrix",
      "gap_report",
      "list_gaps",
      "finding_readiness",
      "contribution_status",
      "submit_contribution",
      "propose_source",
      "request_gap_review"
    ],
    "public_endpoint": null,
    "note": "No public endpoint exists. Do not invent a public URL: none exists until one is deployed, and hosting is still undecided (ADR-0041)."
  },
  "tools": [
    {
      "name": "town_state",
      "kind": "read",
      "description": "The town state record: coverage, gaps, counts, publication pointer.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "jurisdiction_id": {
            "type": "string",
            "description": "The town's jurisdiction id."
          }
        },
        "required": [
          "jurisdiction_id"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "coverage_matrix",
      "kind": "read",
      "description": "Expected sources versus what is held, per town.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "jurisdiction_id": {
            "type": "string",
            "description": "The town's jurisdiction id."
          }
        },
        "required": [
          "jurisdiction_id"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "gap_report",
      "kind": "read",
      "description": "The rendered gap report (markdown) for one town.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "jurisdiction_id": {
            "type": "string",
            "description": "The town's jurisdiction id."
          }
        },
        "required": [
          "jurisdiction_id"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "list_gaps",
      "kind": "read",
      "description": "Gap summaries, optionally filtered by intake disposition.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "jurisdiction_id": {
            "type": "string",
            "description": "The town's jurisdiction id."
          },
          "disposition": {
            "type": "string",
            "description": "Optional intake disposition filter, e.g. new_record_need."
          }
        },
        "required": [
          "jurisdiction_id"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "finding_readiness",
      "kind": "read",
      "description": "Findings by readiness (counts only) plus the publication gate outcome.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "jurisdiction_id": {
            "type": "string",
            "description": "The town's jurisdiction id."
          }
        },
        "required": [
          "jurisdiction_id"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "contribution_status",
      "kind": "read",
      "description": "One contribution and its lifecycle state.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "contribution_id": {
            "type": "string",
            "description": "The contribution id."
          }
        },
        "required": [
          "contribution_id"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "submit_contribution",
      "kind": "write",
      "description": "Submit evidence against one gap. Requires gap_id, claim, source_kind (official or user_upload), and evidence (a URI, a digest, or both). The contribution is queued for human review; submission never verifies or publishes.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "contributor": {
            "type": "string",
            "description": "Authenticated contributor id."
          },
          "gap_id": {
            "type": "string",
            "description": "The gap this evidence answers."
          },
          "claim": {
            "type": "string",
            "description": "What the evidence answers, in one neutral sentence."
          },
          "source_kind": {
            "type": "string",
            "description": "official or user_upload."
          },
          "evidence_uri": {
            "type": "string",
            "description": "URL or path to the evidence."
          },
          "evidence_digest": {
            "type": "string",
            "description": "Digest of the evidence bytes."
          },
          "notes": {
            "type": "string",
            "description": "Optional contributor context."
          }
        },
        "required": [
          "contributor",
          "gap_id",
          "claim",
          "source_kind"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "propose_source",
      "kind": "write",
      "description": "Propose a source-inventory entry for a town. Becomes a registry entry only after human verification.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "contributor": {
            "type": "string",
            "description": "Authenticated contributor id."
          },
          "jurisdiction_id": {
            "type": "string",
            "description": "The town's jurisdiction id."
          },
          "agency": {
            "type": "string",
            "description": "The agency that publishes the source."
          },
          "platform": {
            "type": "string",
            "description": "The platform key, e.g. civicplus."
          },
          "base_uri": {
            "type": "string",
            "description": "The source's http(s) base URI."
          },
          "family": {
            "type": "string",
            "description": "The source family, e.g. agenda_center."
          }
        },
        "required": [
          "contributor",
          "jurisdiction_id",
          "agency",
          "platform",
          "base_uri",
          "family"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "request_gap_review",
      "kind": "write",
      "description": "Ask a human to look at one gap.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "contributor": {
            "type": "string",
            "description": "Authenticated contributor id."
          },
          "gap_id": {
            "type": "string",
            "description": "The gap to review."
          },
          "reason": {
            "type": "string",
            "description": "Why the gap needs a human look."
          }
        },
        "required": [
          "contributor",
          "gap_id",
          "reason"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "record_correspondence",
      "kind": "write",
      "description": "Owner-only. Record an agency response as a source assertion on an existing case. Requires case_id, contributor, sender, date, content (summary, subject, or body_text), and an evidence_ref citing the preserved correspondence. Records what the agency said; it never certifies, submits, authorizes a fee, resolves a legal review, publishes, or sends anything.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "case_id": {
            "type": "string",
            "description": "An existing request case id."
          },
          "contributor": {
            "type": "string",
            "description": "Contributor id."
          },
          "sender": {
            "type": "string",
            "description": "Who the correspondence says it is from."
          },
          "date": {
            "type": "string",
            "description": "When the agency responded: an ISO date, or a timezone-aware ISO datetime."
          },
          "kind": {
            "type": "string",
            "description": "Response kind, e.g. acknowledgment, extension, fee_notice, grant, partial_grant, denial, no_records_response, production, other."
          },
          "direction": {
            "type": "string",
            "description": "Must be 'from_agency' when given. Outgoing messages are never recorded here."
          },
          "summary": {
            "type": "string",
            "description": "Our own annotation of the response. Guarded language."
          },
          "subject": {
            "type": "string",
            "description": "Subject line, quoted verbatim."
          },
          "body_text": {
            "type": "string",
            "description": "Body, quoted verbatim."
          },
          "evidence_ref": {
            "type": "string",
            "description": "Citation for the artifact preserving the correspondence."
          },
          "evidence_refs": {
            "type": "array",
            "description": "Additional evidence citations."
          },
          "denied_clauses": {
            "type": "array",
            "description": "For a denial: objects with clause_id and the stated_basis_raw the agency stated."
          },
          "extension": {
            "type": "object",
            "description": "For an extension: announced_on, proposed_due, stated_reason_raw, optional original_projected_due."
          },
          "fee_notice": {
            "type": "object",
            "description": "For a fee notice: stated_on plus the stated amounts. Recording a stated fee never authorizes it."
          },
          "clarification_question": {
            "type": "string",
            "description": "Clarification question, quoted verbatim."
          }
        },
        "required": [
          "case_id",
          "contributor",
          "sender",
          "date",
          "evidence_ref"
        ],
        "additionalProperties": false
      }
    },
    {
      "name": "ingest_production",
      "kind": "write",
      "description": "Owner-only. Record a produced file as an original with its retrieval event and digest, on an existing case. Requires case_id, contributor, supplied_filename, and a digest: supply file_digest, or file_content_base64 to derive and verify it. Supplying bytes verifies the digest; a digest recorded from a bare file_uri is as stated, unverified. This records the receipt; it never alters canonical evidence, never publishes, and never certifies completeness. The bytes enter the canonical evidence store through a separate ingest.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "case_id": {
            "type": "string",
            "description": "An existing request case id."
          },
          "contributor": {
            "type": "string",
            "description": "Contributor id."
          },
          "supplied_filename": {
            "type": "string",
            "description": "The filename exactly as the agency supplied it."
          },
          "file_uri": {
            "type": "string",
            "description": "Reference to where the file lives."
          },
          "file_digest": {
            "type": "string",
            "description": "Canonical digest, sha256:<64 lowercase hex>. Required unless file_content_base64 is given."
          },
          "file_content_base64": {
            "type": "string",
            "description": "Base64 file bytes, hashed to derive or verify the digest. The bytes are not kept."
          },
          "byte_length": {
            "type": "integer",
            "description": "Byte length. Required when no bytes are supplied."
          },
          "declared_media_type": {
            "type": "string",
            "description": "What the production claimed the file was."
          },
          "classification": {
            "type": "string",
            "description": "responsive, partially_responsive, cover_correspondence, unrelated, unclassified, or duplicate."
          },
          "responds_to_clause_ids": {
            "type": "array",
            "description": "Clause ids this file responds to."
          },
          "correspondence_event_id": {
            "type": "string",
            "description": "The covering correspondence event, when recorded."
          },
          "is_partial_as_stated": {
            "type": "boolean",
            "description": "Whether the agency itself described the production as partial."
          },
          "notes": {
            "type": "string",
            "description": "Our own annotation. Guarded language."
          }
        },
        "required": [
          "case_id",
          "contributor",
          "supplied_filename"
        ],
        "additionalProperties": false
      }
    }
  ],
  "never_exposed": [
    "Certification",
    "Submission",
    "Fee authorization",
    "Legal-review resolution",
    "Publication",
    "Any transport that sends a request",
    "Marking a finding closed",
    "Merging an entity",
    "Altering canonical evidence",
    "Auto-publication of contributions"
  ],
  "links": {
    "repo": "https://github.com/Emperiusm/civic-ledger",
    "branch": "master",
    "agents": "agents.md",
    "mcp_page": "mcp.html",
    "methodology": "methodology.html"
  }
}
