@cyanheads/ris-austria-mcp-server 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/AGENTS.md +86 -88
  2. package/CLAUDE.md +86 -88
  3. package/Dockerfile +11 -6
  4. package/README.md +55 -7
  5. package/changelog/0.4.x/0.4.1.md +15 -0
  6. package/changelog/0.4.x/0.4.2.md +26 -0
  7. package/changelog/template.md +55 -16
  8. package/dist/index.js +6 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/mcp-server/resources/definitions/ris-document.resource.d.ts +4 -3
  11. package/dist/mcp-server/resources/definitions/ris-document.resource.d.ts.map +1 -1
  12. package/dist/mcp-server/resources/definitions/ris-document.resource.js +12 -6
  13. package/dist/mcp-server/resources/definitions/ris-document.resource.js.map +1 -1
  14. package/dist/mcp-server/tools/definitions/_shared.d.ts.map +1 -1
  15. package/dist/mcp-server/tools/definitions/_shared.js +0 -2
  16. package/dist/mcp-server/tools/definitions/_shared.js.map +1 -1
  17. package/dist/mcp-server/tools/definitions/ris-get-document.tool.d.ts +95 -34
  18. package/dist/mcp-server/tools/definitions/ris-get-document.tool.d.ts.map +1 -1
  19. package/dist/mcp-server/tools/definitions/ris-get-document.tool.js +198 -46
  20. package/dist/mcp-server/tools/definitions/ris-get-document.tool.js.map +1 -1
  21. package/dist/mcp-server/tools/definitions/ris-lookup-citation.tool.d.ts +3 -3
  22. package/dist/mcp-server/tools/definitions/ris-lookup-citation.tool.d.ts.map +1 -1
  23. package/dist/mcp-server/tools/definitions/ris-lookup-citation.tool.js +5 -6
  24. package/dist/mcp-server/tools/definitions/ris-lookup-citation.tool.js.map +1 -1
  25. package/dist/mcp-server/tools/definitions/ris-track-changes.tool.js +2 -2
  26. package/dist/mcp-server/tools/definitions/ris-track-changes.tool.js.map +1 -1
  27. package/dist/services/ris/normalizer.d.ts +1 -1
  28. package/dist/services/ris/normalizer.d.ts.map +1 -1
  29. package/dist/services/ris/normalizer.js +3 -3
  30. package/dist/services/ris/normalizer.js.map +1 -1
  31. package/dist/services/ris/ris-service.d.ts +1 -1
  32. package/dist/services/ris/ris-service.d.ts.map +1 -1
  33. package/dist/services/ris/ris-service.js +4 -10
  34. package/dist/services/ris/ris-service.js.map +1 -1
  35. package/package.json +7 -7
  36. package/server.json +3 -3
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.4.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/ris-austria-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/ris-austria-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/ris-austria-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.2-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.4.2-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/ris-austria-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/ris-austria-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/ris-austria-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -132,8 +132,8 @@ Read and export a single document.
132
132
  - `format`: `markdown` (default, boilerplate stripped), raw `html`, RIS `xml`, or `urls_only`
133
133
  - Every response carries a binding status: `authentic` (with amtssigniert PDF URL), `consolidated_informational`, `historical_record`, `decision`, `preparatory`, `administrative_directive`, or `translation`
134
134
  - Applications that publish only the signed PDF (district and municipal promulgations, court rules) or only scans (1848–1940 gazettes, ÖNB-hosted) return a `format_unavailable` notice with the usable URLs instead of failing
135
- - Markdown over 40,000 bytes returns a §/Artikel/Anlage section outline (`kind: outline`) instead of truncating. Re-call with `sections:[…]` to pull the sections you need
136
- - Outlining needs those headings. A rendition without them — most court decisions, gazette and announcement bodies, every raw `html`/`xml` — has nothing to outline and returns whole at any size
135
+ - Markdown over 40,000 bytes returns an outline (`kind: outline`) instead of truncating. Re-call with `sections:[…]` to pull the entries you need
136
+ - Outline entries are the document's §/Artikel/Anlage sections, or — for the court decisions, gazette bodies and announcements that carry no such headings — contiguous byte windows named `Part 1 of N` … `Part N of N`, cut at line breaks and covering the text with nothing dropped. Raw `html`/`xml` are never sliced and return whole at any size
137
137
  - Returns content, not fresh metadata. The upstream API has no document-by-number search, so document numbers come from a prior search or lookup result
138
138
 
139
139
  ---
@@ -207,22 +207,56 @@ A public instance is available at `https://ris-austria.caseyjhand.com/mcp` — n
207
207
  - [Bun v1.3](https://bun.sh/) or higher, or Node.js v24+ (needed for `npx`/`bunx`)
208
208
  - No API key — the RIS OGD API is keyless
209
209
 
210
- ### Install from npm (recommended)
210
+ ### Self-hosted / local
211
211
 
212
- Add the server to your MCP client configuration:
212
+ Add the server to your MCP client configuration with Bun:
213
213
 
214
214
  ```json
215
215
  {
216
216
  "mcpServers": {
217
217
  "ris-austria-mcp-server": {
218
+ "type": "stdio",
219
+ "command": "bunx",
220
+ "args": ["@cyanheads/ris-austria-mcp-server@latest"]
221
+ }
222
+ }
223
+ }
224
+ ```
225
+
226
+ Or with npx (no Bun required):
227
+
228
+ ```json
229
+ {
230
+ "mcpServers": {
231
+ "ris-austria-mcp-server": {
232
+ "type": "stdio",
218
233
  "command": "npx",
219
- "args": ["-y", "@cyanheads/ris-austria-mcp-server"]
234
+ "args": ["-y", "@cyanheads/ris-austria-mcp-server@latest"]
235
+ }
236
+ }
237
+ }
238
+ ```
239
+
240
+ Or with Docker:
241
+
242
+ ```json
243
+ {
244
+ "mcpServers": {
245
+ "ris-austria-mcp-server": {
246
+ "type": "stdio",
247
+ "command": "docker",
248
+ "args": [
249
+ "run", "-i", "--rm",
250
+ "-e", "MCP_TRANSPORT_TYPE=stdio",
251
+ "ghcr.io/cyanheads/ris-austria-mcp-server:latest"
252
+ ]
220
253
  }
221
254
  }
222
255
  }
223
256
  ```
224
257
 
225
- `bunx @cyanheads/ris-austria-mcp-server` works the same way if you prefer Bun. Add any [configuration](#configuration) variables under an `"env"` key.
258
+ Add any [configuration](#configuration) variables under an `"env"` key for bunx/npx, or as
259
+ additional `-e NAME=value` arguments for Docker.
226
260
 
227
261
  ### Install from source
228
262
 
@@ -281,6 +315,7 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
281
315
  | `RIS_CONTACT` | Contact string appended to the User-Agent (RIS netiquette asks integrators to be identifiable). | none |
282
316
  | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
283
317
  | `MCP_HTTP_PORT` | HTTP server port. | `3010` |
318
+ | `MCP_SESSION_MODE` | HTTP session handling: `stateful`, `stateless`, or `auto`. This server pins stateless serving explicitly. | `stateless` |
284
319
  | `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
285
320
  | `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, …). | `info` |
286
321
  | `STORAGE_PROVIDER_TYPE` | Storage backend. | `in-memory` |
@@ -312,13 +347,26 @@ See [`.env.example`](./.env.example) for the full list of optional overrides.
312
347
  bun run lint:mcp # Validate MCP definitions against spec
313
348
  ```
314
349
 
350
+ ### Docker
351
+
352
+ ```sh
353
+ docker build -t ris-austria-mcp-server .
354
+ docker run --rm -p 3010:3010 ris-austria-mcp-server
355
+ ```
356
+
357
+ The Dockerfile defaults to HTTP transport, stateless session mode, and logs to
358
+ `/var/log/ris-austria-mcp-server`. OpenTelemetry peer dependencies are installed by default;
359
+ build with `--build-arg OTEL_ENABLED=false` to omit them.
360
+
315
361
  ## Project structure
316
362
 
317
363
  | Directory | Purpose |
318
364
  |:---|:---|
319
365
  | `src/index.ts` | `createApp()` entry point — registers tools and resources. |
366
+ | `src/config` | Optional RIS endpoint and contact configuration, parsed with Zod. |
320
367
  | `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). |
321
368
  | `src/mcp-server/resources` | Resource definitions (`*.resource.ts`). |
369
+ | `src/services/ris` | RIS request building, HTTP access, normalization, and static reference data. |
322
370
  | `docs/design.md` | Settled v1 design — tool surface, service spec, live-confirmed RIS API reference. |
323
371
  | `tests/` | Unit and integration tests mirroring `src/`. |
324
372
  | `skills/` | Framework skills synced from `@cyanheads/mcp-ts-core`. |
@@ -0,0 +1,15 @@
1
+ ---
2
+ summary: "ris_get_document now outlines heading-free markdown that overflows the byte budget into retrievable byte windows, instead of returning it whole at any size; also fixes a join bug in multi-section retrieval."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.4.1 — 2026-07-31
8
+
9
+ ## Changed
10
+
11
+ - **`ris_get_document` / `ris://document/…`** — markdown that overflows the 40,000-byte budget with no §/Artikel/Anlage headings (most court decisions, gazette and announcement bodies, `§ 0` promulgation records) no longer returns whole at any size. `windowDocument()` cuts it into near-even byte windows snapped to line boundaries, named `Part n of N` in document order; `addressableSections()` resolves both the outline and the `sections:[…]` selector to §/Artikel/Anlage sections where the markdown carries at least two, else to windows. `html` and `xml` remain untouched passthroughs regardless of size. ([#33](https://github.com/cyanheads/ris-austria-mcp-server/issues/33))
12
+
13
+ ## Fixed
14
+
15
+ - **`selectDocumentSections` multi-entry join** — a `sections:[…]` call naming several entries joined their text with `\n\n`, bytes not present in the source, inflating the returned size past the sum of the advertised entries and, in a table-heavy document, splitting a table at the seam. Entries now join with nothing between them and reassemble byte-for-byte. Predates the windowing change above; affects any multi-section retrieval.
@@ -0,0 +1,26 @@
1
+ ---
2
+ summary: "Adopts MCP SDK v2 with 2026-07-28 compatibility, strict root tool inputs, cache hints, and current error envelopes."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.4.2 — 2026-08-24
8
+
9
+ ## Added
10
+
11
+ - **MCP 2026-07-28 compatibility** — the server now supports the current protocol alongside 2025-era clients, with one-hour public caching for immutable catalogs and 24-hour public caching for RIS document resources.
12
+
13
+ ## Changed
14
+
15
+ - **Tool contracts** — undeclared root input fields now reject instead of being stripped, and output schemas advertise the structured error envelope so validating clients can accept both success and failure results.
16
+ - **Error recovery** — RIS client rejections now use `ValidationError`; request deadlines preserve `Timeout` recovery while correlated service requests inherit the handler context.
17
+ - **HTTP sessions** — source runs, containers, and documented configuration now resolve explicitly to stateless mode. MCP 2026-07-28 requests remain sessionless in either mode.
18
+ - **Maintenance surface** — Bun 1.4 container and package-manager pins, production peer omission, test typechecking, framework skills, community health files, and generated documentation are current.
19
+
20
+ ## Dependencies
21
+
22
+ - `@cyanheads/mcp-ts-core` ^0.11.0 → ^0.12.3
23
+ - `@biomejs/biome` 2.5.5 → 2.5.10
24
+ - `@types/node` 26.1.1 → 26.2.0
25
+ - `tsc-alias` ^1.9.1 → ^1.9.2
26
+ - `vitest` ^4.1.10 → ^4.1.11
@@ -4,10 +4,11 @@
4
4
  # to author a new release. Set that file's H1 to `# <version> — YYYY-MM-DD`
5
5
  # with a concrete date.
6
6
 
7
- # Required. One-line GitHub Release-style headline. 350 character cap.
8
- # Default short and scannable. Don't pad, don't stitch unrelated changes with
9
- # semicolons — pick the headline. Quotes required: unquoted YAML treats `: `
10
- # inside the value as a key separator and fails GitHub's strict parser.
7
+ # Required. One-line GitHub Release-style headline. 350 character cap — a
8
+ # ceiling, not a target. Default short and scannable. Don't pad, don't stitch
9
+ # unrelated changes with commas/semicolons into an inventory — pick the
10
+ # headline, like a tag's theme line. Quotes required: unquoted YAML treats
11
+ # `: ` inside the value as a key separator and fails GitHub's strict parser.
11
12
  summary: ""
12
13
 
13
14
  # Set `true` when consumers must change code to upgrade: API removals,
@@ -24,9 +25,10 @@ security: false
24
25
 
25
26
  # Optional free-form notes for maintenance agents processing this release.
26
27
  # Not rendered in CHANGELOG — consumed by agents running `maintenance` on
27
- # downstream servers. Use for adoption instructions that don't fit the
28
- # human-facing sections: new files to create, fields to populate, one-time
29
- # migration steps. Omit the field entirely when there's nothing to say.
28
+ # downstream servers. ADOPTION STEPS ONLY — new files to create, fields to
29
+ # populate, one-time migration steps. Never a second rendering of the body:
30
+ # if a body bullet already says it, name the bullet's symbol instead of
31
+ # re-explaining. Omit the field entirely when there's nothing to say.
30
32
  # agent-notes: |
31
33
  # <instructions for downstream maintenance agents>
32
34
  ---
@@ -41,17 +43,54 @@ security: false
41
43
  each bullet with the symbol or concept name in **bold** so they can skip
42
44
  what's irrelevant and zoom in on what's not.
43
45
 
44
- Tone: terse, fact-dense, not verbose. Default to one sentence per bullet —
45
- name the symbol, state what changed, stop. Use a second sentence only when
46
- it carries weight. If a bullet feels long, it is.
47
-
48
- Cut: mechanism walkthroughs (those belong in JSDoc, CLAUDE.md/AGENTS.md, or the
49
- relevant skill), ceremonial framings ("This release introduces…",
50
- backwards-compat paragraphs), file-by-file test enumerations, internal
51
- implementation notes. Prefer code/symbol names over English re-explanations.
46
+ Tone: terse, fact-dense, not verbose. Bullet shape: **symbol** + what
47
+ changed + at most one consumer-facing caveat. One sentence by default, two
48
+ when the second carries weight — a bullet past ~40 words or three sentences
49
+ is wrong. The depth lives one hop away: the linked issue carries the why,
50
+ the commit diff carries the how. The changelog names what changed and what
51
+ a consumer does about it; a reader who wants mechanism opens the link.
52
+
53
+ Model length on THIS guide, never on the previous entry — entries modeled
54
+ on entries compound.
55
+
56
+ Cut (each has shipped as a wall of text; these are the cruft):
57
+ - History/justification narration — how the bug worked, why the old
58
+ behavior was wrong. One short clause at most; the issue carries the story.
59
+ - Design-rationale defense — "chosen over Y because…", "guarding the
60
+ getter is not enough…". That is the author arguing with a reviewer;
61
+ reviewers read the PR, not the changelog.
62
+ - Defensive unchanged-clauses — "X is unchanged", "byte-identical to
63
+ <prev>". Keep one only where its absence would cause a real misread,
64
+ as a short parenthetical.
65
+ - Edge-case inventories — marker lists, not-flagged lists, escape tables.
66
+ Tests and the issue carry those.
67
+ - Mechanism walkthroughs (JSDoc, CLAUDE.md/AGENTS.md, or the relevant
68
+ skill own those), ceremonial framings ("This release introduces…"),
69
+ backwards-compat paragraphs, file-by-file test enumerations. Prefer
70
+ code/symbol names over English re-explanations.
71
+
72
+ Verified ≠ included: the every-claim-verified-from-the-diff rule bounds
73
+ the TRUTH of what you write, never the AMOUNT.
74
+
75
+ Example — same fact, right size:
76
+
77
+ TOO LONG: **`fetchWithTimeout`'s `timeoutMs` bounds the whole exchange**
78
+ (#341). `fetch` resolves once headers arrive and the deadline was
79
+ cleared as the helper returned, so a peer that answered promptly and
80
+ then stalled the stream held the request open indefinitely. A 2xx
81
+ carrying a body now comes back as a passthrough wrapper that disarms
82
+ the deadline when the body closes, errors, or is cancelled; …
83
+ [+90 more words of mechanism and edge cases]
84
+
85
+ RIGHT: **`fetchWithTimeout`'s `timeoutMs` now bounds the whole
86
+ exchange, not just the headers** (#341). A stalled body aborts with
87
+ the same `Timeout` error; the returned `Response` is a wrapper, so
88
+ identity assertions (`toBe(response)`) no longer hold.
52
89
 
53
90
  Narrative intro: skip by default. Add one short sentence only when the
54
- release theme genuinely needs framing the bullets can't carry.
91
+ release theme genuinely needs framing the bullets can't carry. When many
92
+ bullets share one upgrade consequence, state it ONCE — intro line or
93
+ agent-notes — never per bullet.
55
94
 
56
95
  Sections: Keep a Changelog order — Added, Changed, Deprecated, Removed,
57
96
  Fixed, Security. Include only sections with entries; delete the rest
package/dist/index.js CHANGED
@@ -18,6 +18,12 @@ import { initRisService } from './services/ris/ris-service.js';
18
18
  await createApp({
19
19
  name: 'ris-austria-mcp-server',
20
20
  title: 'ris-austria-mcp-server',
21
+ cacheHints: {
22
+ 'tools/list': { ttlMs: 3_600_000, cacheScope: 'public' },
23
+ 'resources/list': { ttlMs: 3_600_000, cacheScope: 'public' },
24
+ 'resources/templates/list': { ttlMs: 3_600_000, cacheScope: 'public' },
25
+ 'server/discover': { ttlMs: 3_600_000, cacheScope: 'public' },
26
+ },
21
27
  tools: [
22
28
  risSearchLegislation,
23
29
  risSearchCaseLaw,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6DAA6D,CAAC;AAClG,OAAO,EAAE,cAAc,EAAE,MAAM,yDAAyD,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,2DAA2D,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,gBAAgB,EAAE,MAAM,4DAA4D,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,2DAA2D,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,+DAA+D,CAAC;AACrG,OAAO,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,wBAAwB;IAC9B,KAAK,EAAE,wBAAwB;IAC/B,KAAK,EAAE;QACL,oBAAoB;QACpB,gBAAgB;QAChB,gBAAgB;QAChB,eAAe;QACf,sBAAsB;QACtB,iBAAiB;QACjB,cAAc;QACd,eAAe;QACf,gBAAgB;KACjB;IACD,SAAS,EAAE,CAAC,mBAAmB,CAAC;IAChC,KAAK,CAAC,IAAI;QACR,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6DAA6D,CAAC;AAClG,OAAO,EAAE,cAAc,EAAE,MAAM,yDAAyD,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,2DAA2D,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,gBAAgB,EAAE,MAAM,4DAA4D,CAAC;AAC9F,OAAO,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,2DAA2D,CAAC;AAC7F,OAAO,EAAE,oBAAoB,EAAE,MAAM,+DAA+D,CAAC;AACrG,OAAO,EAAE,eAAe,EAAE,MAAM,0DAA0D,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE/D,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,wBAAwB;IAC9B,KAAK,EAAE,wBAAwB;IAC/B,UAAU,EAAE;QACV,YAAY,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE;QACxD,gBAAgB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE;QAC5D,0BAA0B,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE;QACtE,iBAAiB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE;KAC9D;IACD,KAAK,EAAE;QACL,oBAAoB;QACpB,gBAAgB;QAChB,gBAAgB;QAChB,eAAe;QACf,sBAAsB;QACtB,iBAAiB;QACjB,cAAc;QACd,eAAe;QACf,gBAAgB;KACjB;IACD,SAAS,EAAE,CAAC,mBAAmB,CAAC;IAChC,KAAK,CAAC,IAAI;QACR,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;CACF,CAAC,CAAC"}
@@ -3,9 +3,10 @@
3
3
  * of ris_get_document. Reads the same content path (HTML rendition converted to markdown) for
4
4
  * an (application, documentNumber) pair. Applications with no text rendition (authentic-PDF-only,
5
5
  * PDF-only, or metadata-only) return a short note pointing at the usable artifact instead of
6
- * text. Oversized text degrades to the same §/Artikel/Anlage section outline the tool emits,
7
- * plus a note pointing at ris_get_document (which carries the sections selector this bare-string
8
- * resource cannot). Backed by the shared renderDocument helper from the tool definition.
6
+ * text. Oversized text degrades to the same outline the tool emits — §/Artikel/Anlage sections,
7
+ * or `Part n of N` byte windows where the rendition carries no such headings — plus a note
8
+ * pointing at ris_get_document (which carries the sections selector this bare-string resource
9
+ * cannot). Backed by the shared renderDocument helper from the tool definition.
9
10
  * @module mcp-server/resources/definitions/ris-document
10
11
  */
11
12
  import { z } from '@cyanheads/mcp-ts-core';
@@ -1 +1 @@
1
- {"version":3,"file":"ris-document.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/ris-document.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAY3E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;GA2F9B,CAAC"}
1
+ {"version":3,"file":"ris-document.resource.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/ris-document.resource.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAY,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAa3E,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;GA8F9B,CAAC"}
@@ -3,14 +3,15 @@
3
3
  * of ris_get_document. Reads the same content path (HTML rendition converted to markdown) for
4
4
  * an (application, documentNumber) pair. Applications with no text rendition (authentic-PDF-only,
5
5
  * PDF-only, or metadata-only) return a short note pointing at the usable artifact instead of
6
- * text. Oversized text degrades to the same §/Artikel/Anlage section outline the tool emits,
7
- * plus a note pointing at ris_get_document (which carries the sections selector this bare-string
8
- * resource cannot). Backed by the shared renderDocument helper from the tool definition.
6
+ * text. Oversized text degrades to the same outline the tool emits — §/Artikel/Anlage sections,
7
+ * or `Part n of N` byte windows where the rendition carries no such headings — plus a note
8
+ * pointing at ris_get_document (which carries the sections selector this bare-string resource
9
+ * cannot). Backed by the shared renderDocument helper from the tool definition.
9
10
  * @module mcp-server/resources/definitions/ris-document
10
11
  */
11
12
  import { resource, z } from '@cyanheads/mcp-ts-core';
12
13
  import { JsonRpcErrorCode, McpError } from '@cyanheads/mcp-ts-core/errors';
13
- import { exampleSectionNames, outlineDocument, renderDocument, renderOutlineSections, } from '../../../mcp-server/tools/definitions/ris-get-document.tool.js';
14
+ import { addressableSections, exampleSectionNames, outlineDocument, renderDocument, renderOutlineSections, } from '../../../mcp-server/tools/definitions/ris-get-document.tool.js';
14
15
  import { RIS_APPLICATIONS } from '../../../services/ris/reference/index.js';
15
16
  const APPLICATION_CODES = RIS_APPLICATIONS.map((app) => app.code);
16
17
  export const risDocumentResource = resource('ris://document/{application}/{documentNumber}', {
@@ -18,6 +19,7 @@ export const risDocumentResource = resource('ris://document/{application}/{docum
18
19
  title: 'RIS Document (Markdown)',
19
20
  description: 'Markdown text of one RIS document — the injectable twin of ris_get_document (markdown format). Addressed by application code and technical document number, both copied verbatim from a ris_search_* or ris_lookup_citation result. Applications that publish no text rendition (district/municipal promulgations, court rules, party-transparency decisions, council minutes, and the 1848–1940 imperial gazettes) return a short note pointing at the authentic PDF or scan instead.',
20
21
  mimeType: 'text/markdown',
22
+ cacheHint: { ttlMs: 86_400_000, cacheScope: 'public' },
21
23
  params: z.object({
22
24
  application: z
23
25
  .enum(APPLICATION_CODES)
@@ -79,9 +81,13 @@ export const risDocumentResource = resource('ris://document/{application}/{docum
79
81
  return rendition.unavailableNotice;
80
82
  if (rendition.text === undefined)
81
83
  return '';
82
- // Oversized markdown degrades to a section outline plus a notice pointing at the
84
+ // Oversized markdown degrades to an outline plus a notice pointing at the
83
85
  // ris_get_document tool — this bare-string surface carries no section selector.
84
- const decision = outlineDocument(rendition.text, (sections) => `Document too large to inline${rendition.byteSize !== undefined ? ` (${rendition.byteSize} bytes)` : ''}. Use the ris_get_document tool with sections:[…] to retrieve specific sections — e.g. ${exampleSectionNames(sections)}. This resource carries no section selector.`);
86
+ const addressable = addressableSections(rendition.text, 'markdown');
87
+ const size = rendition.byteSize !== undefined ? ` (${rendition.byteSize} bytes)` : '';
88
+ const decision = outlineDocument(rendition.text, addressable, (sections) => addressable[0]?.kind === 'window'
89
+ ? `Document too large to inline${size}. It carries no §/Artikel/Anlage headings, so it is listed as ${sections.length} contiguous windows cut at line breaks — read them in the order they are named. Use the ris_get_document tool with sections:[…] to retrieve one — e.g. ${exampleSectionNames(addressable)}. This resource carries no selector.`
90
+ : `Document too large to inline${size}. Use the ris_get_document tool with sections:[…] to retrieve specific sections — e.g. ${exampleSectionNames(sections)}. This resource carries no section selector.`);
85
91
  return decision.kind === 'full'
86
92
  ? decision.text
87
93
  : `${renderOutlineSections(decision.sections)}\n\n${decision.notice}`;
@@ -1 +1 @@
1
- {"version":3,"file":"ris-document.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/ris-document.resource.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ris-document.resource.js","sourceRoot":"","sources":["../../../../src/mcp-server/resources/definitions/ris-document.resource.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/_shared.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAsB3C;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,aAMwE,CAAC;AAEnG;;;;;GAKG;AACH,KAAK,mBAAmB,GAAG,eAAe,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;AAEnF,kEAAkE;AAClE,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAC9C,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;CAC7D;AAoBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAMlF;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,MAAM,GAAG,SAAS,GAC1D,OAAO,CAWT"}
1
+ {"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/_shared.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAsB3C;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,aAMwE,CAAC;AAEnG;;;;;GAKG;AACH,KAAK,mBAAmB,GAAG,eAAe,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;AAEnF,kEAAkE;AAClE,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,mBAAmB,CAAC,CAAC;IAC9C,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;CAC7D;AAkBD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAMlF;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,MAAM,GAAG,SAAS,GAC1D,OAAO,CAWT"}
@@ -41,7 +41,6 @@ export const isoDateString = z
41
41
  *
42
42
  * - `ValidationError` — a schema-valid but unsupported parameter combination the request
43
43
  * builder rejected locally, before any upstream call.
44
- * - `InvalidParams` — RIS rejected a parameter value in its in-band error envelope.
45
44
  * - `ServiceUnavailable` — RIS unreachable, degraded, or serving an HTML error page.
46
45
  * - `Timeout` — the request deadline elapsed. Its own reason rather than a widened
47
46
  * `upstream_error` guard: `ctx.fail` resolves the code from the contract entry, so folding
@@ -49,7 +48,6 @@ export const isoDateString = z
49
48
  */
50
49
  const REASON_BY_CODE = new Map([
51
50
  [JsonRpcErrorCode.ValidationError, 'invalid_query'],
52
- [JsonRpcErrorCode.InvalidParams, 'invalid_query'],
53
51
  [JsonRpcErrorCode.ServiceUnavailable, 'upstream_error'],
54
52
  [JsonRpcErrorCode.Timeout, 'upstream_timeout'],
55
53
  ]);
@@ -1 +1 @@
1
- {"version":3,"file":"_shared.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/_shared.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"_shared.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/_shared.ts"],"names":[],"mappings":"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"}
@@ -10,11 +10,11 @@
10
10
  * metadata-only) — a text-format request against a non-text application degrades to a
11
11
  * `format_unavailable` notice on a success result, never an error. Markdown conversion drops
12
12
  * the `.sr-only` screen-reader twin RIS ships beside every abbreviated citation, keeping the
13
- * visible form; `html` and `xml` pass through untouched. Oversized markdown carrying
14
- * §/Artikel/Anlage structure overflows to a retrievable section outline (never a silent
15
- * truncation, and never for a rendition with no such headings); a follow-up call
16
- * with `sections:[…]` returns just the chosen sections, and a selector matching nothing gets
17
- * the outline back with a notice rather than the whole document. The shared
13
+ * visible form; `html` and `xml` pass through untouched. Oversized markdown overflows to a
14
+ * retrievable outline rather than a silent truncation — its §/Artikel/Anlage sections, or
15
+ * `Part n of N` byte windows where it carries no such headings; a follow-up call with
16
+ * `sections:[…]` returns just the chosen entries, and a selector matching nothing gets the
17
+ * outline back with a notice rather than the whole document. The shared
18
18
  * {@link renderDocument} helper backs both this tool and the `ris://document/…` resource.
19
19
  * @module mcp-server/tools/definitions/ris-get-document
20
20
  */
@@ -24,20 +24,22 @@ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
24
24
  import { type OutlineResult, type SectionMeta } from '@cyanheads/mcp-ts-core/utils';
25
25
  import type { RisBindingStatus } from '../../../services/ris/reference/index.js';
26
26
  /** Requested output format. `markdown` and `html` both read the HTML rendition. */
27
- type DocumentFormat = 'markdown' | 'html' | 'xml' | 'urls_only';
27
+ export type DocumentFormat = 'markdown' | 'html' | 'xml' | 'urls_only';
28
28
  /**
29
- * Serialized-length budget for inlining document text. Above it, markdown overflows to a
30
- * §/Artikel/Anlage section outline instead of returning whole — but only when the rendition
31
- * segments into at least two of those sections. A rendition carrying none of them has nothing
32
- * to outline and returns whole however large: court decisions, gazette and announcement
33
- * bodies, and consolidated `§ 0` promulgation records all do, measured live to 399,972 bytes
34
- * on a Bvwg decision.
29
+ * Serialized-length budget for inlining document text, and the size of one addressable
30
+ * window. Above it, markdown overflows to an outline instead of returning whole — its
31
+ * §/Artikel/Anlage sections where it carries at least two such headings, else the byte
32
+ * windows {@link windowDocument} cuts, so a rendition the segmenter cannot split is
33
+ * retrievable rather than returned whole at any size (court decisions, gazette and
34
+ * announcement bodies, and consolidated `§ 0` promulgation records all carry no headings,
35
+ * measured live to 399,972 bytes on a Bvwg decision).
35
36
  *
36
37
  * Sized against what one tool result can carry into a working context (≈10K tokens of legal
37
38
  * prose), not against what counts as a pathological document: across live samples the
38
39
  * documents callers routinely read land well under it — consolidated-law sections at 2–3 KB,
39
40
  * drafts and bills at 6–36 KB — while the multi-act bills and long translations that motivated
40
- * the outline (71 KB–199 KB) route through it.
41
+ * the outline (71 KB–199 KB) route through it. One number serves both roles because a window
42
+ * is by definition one result's worth, which is what the budget already means.
41
43
  *
42
44
  * Handed to `outlineOnOverflow`, which measures `JSON.stringify` length (≈ UTF-8 bytes for
43
45
  * the mostly-Latin legal text); the exact `byte_size` is reported separately.
@@ -91,16 +93,22 @@ type ParsedDocumentUrl = {
91
93
  */
92
94
  export declare function parseDocumentUrl(url: string, contentBaseUrl: string): ParsedDocumentUrl;
93
95
  /**
94
- * One addressable section of a rendered RIS document — a structural unit sliced from its
95
- * heading up to the next, plus the leading `Präambel` when the document opens with text
96
- * before its first heading.
96
+ * One addressable entry of a rendered RIS document. Two kinds, never mixed in one roster:
97
+ * a `section` is a structural unit sliced from its heading up to the next (plus the leading
98
+ * `Präambel` when the document opens with text before its first heading); a `window` is a
99
+ * contiguous byte slice of a document carrying no such headings.
97
100
  */
98
101
  export interface DocumentSection {
99
- /** UTF-8 byte size of the section text. */
102
+ /** UTF-8 byte size of the entry's text. */
100
103
  readonly bytes: number;
101
- /** Section identifier — a §/Artikel/Anlage marker, article-qualified so names stay unique. */
104
+ /** Which roster this entry belongs to — see {@link addressableSections}. */
105
+ readonly kind: 'section' | 'window';
106
+ /**
107
+ * Entry identifier — a §/Artikel/Anlage marker (article-qualified so names stay unique)
108
+ * for a section, `Part n of N` for a window.
109
+ */
102
110
  readonly name: string;
103
- /** The section's text, from its heading up to the next heading. */
111
+ /** The entry's text: heading to next heading for a section, line to line for a window. */
104
112
  readonly text: string;
105
113
  }
106
114
  /**
@@ -112,40 +120,93 @@ export interface DocumentSection {
112
120
  * html/xml rendition) — the caller reads that as "nothing to outline".
113
121
  */
114
122
  export declare function segmentDocument(text: string): DocumentSection[];
123
+ /**
124
+ * Cut rendered document text into near-even windows snapped to line boundaries, named
125
+ * `Part 1 of N` … `Part N of N` in document order. The retrieval path for a rendition
126
+ * {@link segmentDocument} cannot split; returns `[]` under the budget and for text that
127
+ * carries no line break to cut on, which the caller reads as "nothing to outline".
128
+ *
129
+ * Even windows rather than fill-the-budget windows: filling leaves a runt tail (a 40,440-byte
130
+ * decision splits 39,602 + 838, buying a round-trip for a scrap) while an even split of the
131
+ * same document is 20,196 + 20,244. Each window's target is the bytes still unassigned over
132
+ * the windows still to cut, so the remainder that snapping to the next line break carries
133
+ * forward is spread across the rest instead of piling onto the tail.
134
+ *
135
+ * Two rules keep the budget a ceiling rather than an aspiration. A window never takes a line
136
+ * that would carry it past the budget, so snapping to the next line break cannot overshoot;
137
+ * and when the tail still lands over — the target sitting just under the budget leaves the
138
+ * last window everything the earlier ones declined — the window count rises until every
139
+ * window fits. A line longer than the budget is the exception, and the reason the count stops
140
+ * rising rather than climbing to one window per line: splitting inside a line would put the
141
+ * cut where the caller cannot see it, so such a line sits alone in a window that goes over.
142
+ *
143
+ * Window bytes sum to the document's byte size exactly — the windows concatenate back to the
144
+ * input, so nothing is dropped or double-counted.
145
+ */
146
+ export declare function windowDocument(text: string, budget: number): DocumentSection[];
147
+ /**
148
+ * The one roster of what a caller can address in a rendition — §/Artikel/Anlage sections
149
+ * where the markdown carries at least two of them, else the byte windows an over-budget
150
+ * document is cut into. Both the outline and the `sections:[…]` selector resolve through
151
+ * here, so every entry the outline advertises is retrievable by name.
152
+ *
153
+ * Markdown only. `html` and `xml` are byte-identical passthroughs of the published artifact
154
+ * and a mid-document slice of either is not well-formed, so they stay whole at any size and
155
+ * a caller who needs one has `content_urls`.
156
+ *
157
+ * The two-entry floor matches `outlineOnOverflow`'s own short-circuit: an outline whose only
158
+ * possible `sections` argument returns the same bytes costs a round-trip and buys nothing.
159
+ */
160
+ export declare function addressableSections(text: string, format: DocumentFormat): DocumentSection[];
115
161
  /** Outcome of resolving a `sections:[…]` selector against a rendered document. */
116
162
  export interface SectionSelection {
117
- /** The document's whole section roster, largest first — what the caller can re-pick from. */
163
+ /**
164
+ * The document's whole entry roster — what the caller can re-pick from. Sections come
165
+ * largest first; windows keep document order (see {@link outlineDocument}).
166
+ */
118
167
  readonly available: SectionMeta[];
119
- /** Concatenated text of the matched sections, in document order; `''` when none matched. */
168
+ /** Concatenated text of the matched entries, in document order; `''` when none matched. */
120
169
  readonly text: string;
121
- /** Requested names that matched no section, in the order requested. */
170
+ /** Requested names that matched no entry, in the order requested. */
122
171
  readonly unmatched: string[];
172
+ /** Whether `available` lists byte windows rather than §/Artikel/Anlage sections. */
173
+ readonly windowed: boolean;
123
174
  }
124
175
  /**
125
176
  * Resolve a `sections:[…]` selector against rendered document text — the selective-retrieval
126
- * counterpart to the outline. The handler re-fetches the document (the upstream query is
127
- * deterministic, so it reproduces the same text) and slices it to the requested sections.
177
+ * counterpart to the outline, resolving through the same {@link addressableSections} roster
178
+ * so every advertised name retrieves. The handler re-fetches the document (the upstream query
179
+ * is deterministic, so it reproduces the same text) and slices it to the requested entries.
128
180
  * Reports the unmatched names and the full roster alongside the matched text so the handler
129
181
  * can disclose a mismatch instead of silently returning something the caller didn't ask for.
130
182
  */
131
- export declare function selectDocumentSections(text: string, want: readonly string[]): SectionSelection;
183
+ export declare function selectDocumentSections(text: string, want: readonly string[], format: DocumentFormat): SectionSelection;
132
184
  /**
133
185
  * Apply the outline-on-overflow contract to rendered document text: whole under the byte
134
- * budget (or with fewer than two structural sections), else a section outline. Shares
135
- * {@link segmentDocument} as the section extractor so the tool and the resource segment
136
- * identically; the `notice` builder is caller-specific (each points at its own re-call).
186
+ * budget (or with fewer than two addressable entries), else an outline of `roster`. The
187
+ * caller passes the roster it already resolved through {@link addressableSections}, so the
188
+ * outline it advertises and the wording of its notice describe the same entries; the `notice`
189
+ * builder is caller-specific (each points at its own re-call).
190
+ *
191
+ * `outlineOnOverflow` sorts entries largest first and offers no opt-out. That reads as a menu
192
+ * for a section roster, where the names say what each entry is. A window roster is a reading
193
+ * sequence, and a caller that walks `sections` in array order — the obvious way to read one
194
+ * — would take the document out of order, so document order is restored for it here.
137
195
  */
138
- export declare function outlineDocument(text: string, notice: (sections: SectionMeta[]) => string): OutlineResult<{
196
+ export declare function outlineDocument(text: string, roster: readonly DocumentSection[], notice: (sections: SectionMeta[]) => string): OutlineResult<{
139
197
  text: string;
140
198
  }>;
141
199
  /** Name the three largest sections, quoted, as examples for a re-call notice. */
142
200
  export declare function exampleSectionNames(sections: readonly SectionMeta[]): string;
143
201
  /**
144
- * Render an outline — the section roster the agent picks from — to markdown. Shared by the
145
- * tool's `format()` (as a content block) and the resource (as its body), so both client
146
- * surfaces list identical sections. Deliberately says nothing about *why* the outline was
147
- * returned: the tool emits one both on a byte overflow and on an unmatched `sections`
148
- * selector, and the accompanying notice is what separates the two.
202
+ * Render an outline — the roster the agent picks from — to markdown. Shared by the tool's
203
+ * `format()` (as a content block) and the resource (as its body), so both client surfaces
204
+ * list identical entries. A roster is all sections or all windows ({@link addressableSections}
205
+ * never mixes them), and the heading names which, since this surface carries only names and
206
+ * byte sizes: windows are a reading sequence listed in document order, sections a menu listed
207
+ * largest first. Deliberately says nothing about *why* the outline was returned: the tool
208
+ * emits one both on a byte overflow and on an unmatched `sections` selector, and the
209
+ * accompanying notice is what separates the two.
149
210
  */
150
211
  export declare function renderOutlineSections(sections: readonly SectionMeta[]): string;
151
212
  /**