# MuniPro citations for Rogo and other MCP clients

Status: Active

## Production origin and browser authentication

The production origin is **https://munipro.com**. For example, the returned
path `/muni/debtDetails/1471/` becomes
https://munipro.com/muni/debtDetails/1471/.

These are authenticated MuniPro browser pages. A reader already signed in to
MuniPro in that browser can open an authorized record with their existing
session. Otherwise MuniPro redirects to `/accounts/login/` with a `next`
destination. The account must still have access to the borrower and workspace.
Rogo's MCP OAuth token does not establish a browser session or grant access
to someone else opening the citation. A Rogo embedded browser may have a
different cookie session; opening in the user's normal browser is supported.
Do not put an access token, API key or session cookie in a citation URL.

## Source links and series identity

Use the response's **`citation.url`** as the clickable source. Debt summaries
and banker dataset results carry this object, including the downloaded JSON:

```json
{
  "citation": {
    "url": "https://munipro.com/muni/debtDetails/1471/?view=canonical",
    "title": "Issuer name from the response",
    "record_type": "borrower",
    "record_id": "1471",
    "link_granularity": "issuer",
    "requires_login": true
  }
}
```

The URL uses the **returned effective borrower ID**. `view=canonical` prevents
a source-record citation from silently redirecting to a reader's working copy.
A URL whose ID already identifies the firm's working copy stays on that copy;
it does not grant another company access. Preserve any `workspace`,
`source_borrower_id` and `fork_borrower_id` annotations in the answer.

For older responses without `citation`, prepend the production origin to
the returned debt-details `borrower.human_pages[].path`. Such ordinary navigation
links can resolve to the reader's active working copy. For exact-record
citations use the effective returned borrower ID with `?view=canonical`.
Treat MCP references such as `debt-summary://1471` as tool references for
`fetch`, not as web links.

| Surface | Series identity |
|---|---|
| `get_debt_summary` | Each `series[]` group has `series_records[]`, containing actual `series_id`, `entity_key` and `designation`. A group may contain multiple legal issues. |
| `get_debt_details_dataset` with `outstanding` | `outstanding.rows[].issue_id` is the Issue ID; join to the file's `series_records[]` on `series_id` after normalizing string/integer representation. |
| Broader-profile `list_outstanding_series` / `get_verified_debt_profile` | `series[].series_id` is the Issue record ID. These tools are outside the initial banker profile. |

An Issue's numeric ID is stable for that stored record. Its UUID `entity_key`
survives a source-to-working-copy clone, while the clone has a different numeric
ID. Use `(returned borrower ID, series_id)` for a row reference in a specific
workspace. Use the entity key only to correlate explicitly related graphs;
it is not a globally unique row ID, a public URL, or an immutable data version.
Deleted and recreated records need not keep the same identity.

`get_debt_summary` groups by issuer/series display names. Names are mutable and
not unique. `series_identity_complete: false` means at least one contributing
security has no valid Issue association in this borrower; the returned record
list is partial. Never infer an Issue from its name or assign an entire grouped
amount to one Issue. Empty legacy `issue_id` values mean identity is unavailable.
Grid `_rowId` and `orgHierarchy` are display identities, not permanent record IDs.

**There is currently no supported series-row browser permalink.** The citation
opens the issuer page, not a selected/highlighted series. Display the series
designation and Issue ID alongside that link for row-level auditability. Do not
invent `/series/<id>/`, `?series_id=`, or `#series-<id>` browser targets.

## Evidence to retain and acceptance in Rogo

A record link opens current data; it is not a historical snapshot. Preserve the
tool name, arguments, returned IDs, source dates, UTC retrieval time and relevant
workspace annotations with the answer. For a dataset, retain the JSON, its
SHA-256, `generated_at`, `parameters` and all provenance/coverage warnings.
Keep calculation assumptions separate from source observation dates.

Use the default Rogo custom MCP connector first. Before signing off:

1. Produce an answer with a clickable `citation.url` and visible series IDs.
2. Open it while signed in to MuniPro; verify the returned borrower/workspace.
3. Open it in a signed-out browser; complete login and verify the destination.
4. Confirm a user without borrower permission cannot open the private record.
5. Confirm a multi-Issue summary group retains every returned ID and any
   incomplete-identity warning. Reconcile the saved dataset with its parameters.

Rogo controls citation rendering and browser/session handling. MuniPro-side
protocol tests do not prove that Rogo displays a clickable citation correctly;
record this acceptance in the Rogo team's workspace.

Rogo's [Cite component documentation](https://design.rogo.ai/product/inline/cite)
describes a host-owned mapping from citation IDs to sources. It is not a published
external MCP citation-registration schema. MuniPro's `citation.url` supplies the
source URL; James must confirm how the default connector registers it and renders
an auditable link. Do not manufacture `cite:` IDs or promise automatic citation
pills from the presence of a JSON URL alone.

## Financial figures and excerpts

This section applies when a connection reaches issuer financial figures through
the investor research surface, which is off unless MuniPro turns it on for the
connection. There, `get_issuer_income_statement_series` and
`get_issuer_financial_statement_detail` return only figures from accepted,
human-verified income statements whose source document is public. Every figure
carries `citations`: one entry for a figure read from one cell, and one per
contributing cell for a total MuniPro added up, which also carries
`derivation {formula, inputs}`. A figure that cannot be cited is never
returned: its year is listed with reason `source_not_public` or
`provenance_unresolved`. Never fill or interpolate a withheld year. Excerpt
text is never part of a figure payload.

Each citation is a `financial_citation` (version `financial_citation.v1`):

| Key | Meaning |
|---|---|
| `url` | The clickable source: the EMMA document with `#page=N` when the page is known, else the EMMA document, else the issuer's public BondGov page. It never needs a login and never carries a token. |
| `title` | Issuer name, fiscal year and document title. |
| `record_type` | `financial_statement` for a whole statement, `statement_line` for one cell. |
| `record_id` | The statement id, or `<statement_id>:<row_id>:<column_key>` for a cell. |
| `link_granularity` | `page` when `url` carries a page anchor, else `document`. |
| `requires_login` | Always `false`. |
| `kind` | `verified_figure`. |
| `source` | `emma` or `issuer_upload`: where the public copy of the document comes from. |
| `emma_url` | The EMMA document URL, or null when the source is not EMMA. |
| `pdf_page` | The PDF page a viewer shows, counted from 1. |
| `printed_page` | The page number printed on the page, or null when the page prints none. |
| `heading_path` | The statement line's labels from its section heading down. |
| `document_title` | The source document's title. |
| `document_family` | The source document's family, for example an annual financial report. |
| `fiscal_year` | The statement's fiscal year. |
| `issuer` | `{slug, name, profile_id}` of the issuer that filed the statement. |
| `statement_id` | The statement the figure was read from. |
| `row_id` | The statement line, or null for a whole statement. |
| `column_key` | The statement column, or null for a whole statement. |
| `verification_label` | `human_verified`. |
| `issuer_confirmed` | Whether the issuer confirmed this statement. |
| `mark` | Present only when the figure carries a mark: `{kind, label}`. |
| `retrieved_at` | The UTC time of the call. |

Show a figure's mark as its words, exactly:

| `mark.kind` | `mark.label` | Meaning |
|---|---|---|
| `issuer_confirmed` | Issuer-confirmed | The issuer confirmed this statement, and its public BondGov page prints this figure as is. |
| `confirmed_statement` | From a confirmed statement | The issuer confirmed the statement this figure comes from. It does not say the issuer checked this figure. |
| `calculated` | Calculated by MuniPro | MuniPro computed this value from cited figures. |

A figure with no `mark` carries none; do not add one. A confirmation covers
only the latest income statement on the issuer's public page, so other years
read `issuer_confirmed: false`. False means not confirmed, never that the
issuer declined. A statement's `confirmation` names the public modules it
covers (`covers_modules`), who confirmed (`role`, and `name_public`, which is
the role when no name is shown) and when (`confirmed_at` for machines,
`confirmed_on_display` such as 9/22/2026 for readers). A confirmation can lapse
when the public page's figures change; the statement then reads false until the
issuer confirms again.

A restated figure is cited from the later statement, or withheld with
`restated_value_withheld`. A restatement difference (`restatement_difference`,
`restatement_percent`) is served only when both of its figures are cited on
the same point; otherwise it is null and `restatement_difference_withheld`
says why. The statement detail never serves a cell's `restated_delta`
(`restated_delta_withheld`). `restatements` lists only restatements between
public statements; when one involving a statement that is not public is left
out, `has_restatements` stays true and `restatement_scan` reads
`complete: false` with `restatements_withheld`.

When an issuer turns off its figures for AI assistants, the two tools answer
differently. `get_issuer_income_statement_series` returns the suppressed shape
with `suppression.channel: 'ai'` and `suppression.message`; show that message
as is. `get_issuer_financial_statement_detail` returns the same error as for an
unknown statement, 404 `statement_not_found`, with no suppression fields. Either
way the figures remain on the issuer's BondGov page.

Connection setup: [Rogo guide](rogo-connection-guide.md).
Dataset fields: [banker data guide](banker-data-guide.md).
