@cyanheads/ris-austria-mcp-server 0.3.0 → 0.4.1
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.
- package/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +5 -5
- package/changelog/0.4.x/0.4.0.md +17 -0
- package/changelog/0.4.x/0.4.1.md +15 -0
- package/dist/mcp-server/resources/definitions/ris-document.resource.d.ts +4 -3
- package/dist/mcp-server/resources/definitions/ris-document.resource.d.ts.map +1 -1
- package/dist/mcp-server/resources/definitions/ris-document.resource.js +11 -6
- package/dist/mcp-server/resources/definitions/ris-document.resource.js.map +1 -1
- package/dist/mcp-server/tools/definitions/ris-get-document.tool.d.ts +100 -38
- package/dist/mcp-server/tools/definitions/ris-get-document.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/ris-get-document.tool.js +271 -75
- package/dist/mcp-server/tools/definitions/ris-get-document.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/ris-search-drafts.tool.d.ts +4 -8
- package/dist/mcp-server/tools/definitions/ris-search-drafts.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/ris-search-drafts.tool.js +24 -16
- package/dist/mcp-server/tools/definitions/ris-search-drafts.tool.js.map +1 -1
- package/dist/services/ris/ris-service.d.ts +4 -3
- package/dist/services/ris/ris-service.d.ts.map +1 -1
- package/dist/services/ris/ris-service.js +4 -3
- package/dist/services/ris/ris-service.js.map +1 -1
- package/package.json +1 -1
- package/server.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** ris-austria-mcp-server
|
|
4
|
-
**Version:** 0.
|
|
4
|
+
**Version:** 0.4.1
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
|
|
6
6
|
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
|
package/CLAUDE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** ris-austria-mcp-server
|
|
4
|
-
**Version:** 0.
|
|
4
|
+
**Version:** 0.4.1
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
|
|
6
6
|
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/ris-austria-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/ris-austria-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -97,7 +97,7 @@ Search federal law before it is enacted.
|
|
|
97
97
|
- `stage: government_bills` covers bills adopted by the council of ministers (Regierungsvorlagen, 2004+), filtered by adoption date
|
|
98
98
|
- `ministry` accepts the abbreviation ("BMF") and the server expands it to RIS's exact designation
|
|
99
99
|
- Output includes review deadlines, council adoption dates, and the RIS web view
|
|
100
|
-
- `materials` lists the companion documents filed with the draft — Erläuterungen (the drafting reasoning the bill text omits), Textgegenüberstellung, Vorblatt/WFA, covering letter, annexes. Their filenames are opaque and per-record, so passing a `materials[].
|
|
100
|
+
- `materials` lists the companion documents filed with the draft — Erläuterungen (the drafting reasoning the bill text omits), Textgegenüberstellung, Vorblatt/WFA, covering letter, annexes. Their filenames are opaque and per-record, so passing a `materials[].url` to `ris_get_document` is the only way to read one; `format` there picks the rendition, so the one URL reaches every text rendition the companion has. Where RIS files no HTML rendition — about one companion in eight, nearly all covering letters — the URL is the PDF, which is a download rather than a text rendition
|
|
101
101
|
|
|
102
102
|
---
|
|
103
103
|
|
|
@@ -128,12 +128,12 @@ Resolve a citation to its canonical document.
|
|
|
128
128
|
|
|
129
129
|
Read and export a single document.
|
|
130
130
|
|
|
131
|
-
- Addresses documents by `document_number` plus `application` (from any search or lookup result), or by a passed-through `ris.bka.gv.at` document URL (host and path allowlisted) — including a draft's companion documents from `ris_search_drafts`'s `materials`, which nothing else can reach
|
|
131
|
+
- Addresses documents by `document_number` plus `application` (from any search or lookup result), or by a passed-through `ris.bka.gv.at` document URL (host and path allowlisted) — including a draft's companion documents from a `ris_search_drafts` record's `materials[].url`, which nothing else can reach. `format` selects the rendition for a companion exactly as for a main 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
|
|
136
|
-
-
|
|
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
|
---
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "ris_search_drafts materials collapses to one URL per companion; ris_get_document now accepts every companion filename shape RIS publishes, not only its newest one."
|
|
3
|
+
breaking: true
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.4.0 — 2026-07-31
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **`ris_search_drafts` `materials[].url`** — `materials[].urls` (an object of up to four rendition URLs) collapses to a single `materials[].url`: the companion's HTML rendition, or its PDF where RIS files no HTML one (roughly one companion in eight, nearly all Begut covering letters). `ris_get_document` validates a companion URL's extension and then discards it, rebuilding the rendition from its own `format` input, so one URL already reached every rendition the companion has — the four-URL set was duplication. Cuts `structuredContent` 38–46% across sampled page sizes. **Breaking** for anything reading `materials[].urls.html` (or `.xml`/`.pdf`/`.rtf`). ([#34](https://github.com/cyanheads/ris-austria-mcp-server/issues/34))
|
|
12
|
+
- **`document_url` refusal messages** — split into their three distinct causes (unrenderable extension, path nested below the document folder, unrecognized filename) instead of one message that told a caller who had copied a URL verbatim to copy a URL verbatim.
|
|
13
|
+
|
|
14
|
+
## Fixed
|
|
15
|
+
|
|
16
|
+
- **`ris_get_document` companion filename acceptance** — `document_url` now accepts every companion filename shape RIS publishes, not only the `Materialien_`/`Schreiben_`/`Anlagen_` + UUID shape used on 2026 records. Adds the `COO_{n}_{n}_{n}_{n}` Fabasoft object address (79.7% of the live corpus, every companion filed before 2026) and the prefixless `{NNNN}_{UUID}` shape. Each is matched as a whole anchored alternative over hex digits, `_`, and its own literal prefix — never a prefix or substring test, which would also match the per-record inline formula images RIS names `Material-COO_…`/`Anlage-COO_…`. ([#35](https://github.com/cyanheads/ris-austria-mcp-server/issues/35))
|
|
17
|
+
- **Companion 404 recovery** — a companion document RIS publishes with no rendition in the requested format now gets a recovery hint pointing at `format: urls_only` and `content_urls.pdf`, instead of the generic `document_not_found` recovery written for `document_number` + `application` addressing.
|
|
@@ -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.
|
|
@@ -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
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
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;;;;;;;;;;;;;;;;;;;;;;GA6F9B,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
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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}', {
|
|
@@ -79,9 +80,13 @@ export const risDocumentResource = resource('ris://document/{application}/{docum
|
|
|
79
80
|
return rendition.unavailableNotice;
|
|
80
81
|
if (rendition.text === undefined)
|
|
81
82
|
return '';
|
|
82
|
-
// Oversized markdown degrades to
|
|
83
|
+
// Oversized markdown degrades to an outline plus a notice pointing at the
|
|
83
84
|
// ris_get_document tool — this bare-string surface carries no section selector.
|
|
84
|
-
const
|
|
85
|
+
const addressable = addressableSections(rendition.text, 'markdown');
|
|
86
|
+
const size = rendition.byteSize !== undefined ? ` (${rendition.byteSize} bytes)` : '';
|
|
87
|
+
const decision = outlineDocument(rendition.text, addressable, (sections) => addressable[0]?.kind === 'window'
|
|
88
|
+
? `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.`
|
|
89
|
+
: `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
90
|
return decision.kind === 'full'
|
|
86
91
|
? decision.text
|
|
87
92
|
: `${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":"AAAA
|
|
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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"}
|
|
@@ -3,17 +3,18 @@
|
|
|
3
3
|
* or its rendition URLs, with explicit binding status and the amtssigniert Authentisch PDF
|
|
4
4
|
* surfaced wherever it exists. Two addressing modes route to the same fetch path:
|
|
5
5
|
* `document_number` + `application`, or a `document_url` from a result's content_urls — the
|
|
6
|
-
* latter also carrying the one route to a draft's companion documents
|
|
7
|
-
*
|
|
6
|
+
* latter also carrying the one route to a draft's companion documents, whose per-record
|
|
7
|
+
* opaque filenames no document number reaches (see {@link COMPANION_STEMS} for the shapes
|
|
8
|
+
* RIS files them under).
|
|
8
9
|
* Format availability varies by application (full text · authentic-PDF-only · PDF-only ·
|
|
9
10
|
* metadata-only) — a text-format request against a non-text application degrades to a
|
|
10
11
|
* `format_unavailable` notice on a success result, never an error. Markdown conversion drops
|
|
11
12
|
* the `.sr-only` screen-reader twin RIS ships beside every abbreviated citation, keeping the
|
|
12
|
-
* visible form; `html` and `xml` pass through untouched. Oversized markdown
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
17
18
|
* {@link renderDocument} helper backs both this tool and the `ris://document/…` resource.
|
|
18
19
|
* @module mcp-server/tools/definitions/ris-get-document
|
|
19
20
|
*/
|
|
@@ -23,20 +24,22 @@ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
|
23
24
|
import { type OutlineResult, type SectionMeta } from '@cyanheads/mcp-ts-core/utils';
|
|
24
25
|
import type { RisBindingStatus } from '../../../services/ris/reference/index.js';
|
|
25
26
|
/** Requested output format. `markdown` and `html` both read the HTML rendition. */
|
|
26
|
-
type DocumentFormat = 'markdown' | 'html' | 'xml' | 'urls_only';
|
|
27
|
+
export type DocumentFormat = 'markdown' | 'html' | 'xml' | 'urls_only';
|
|
27
28
|
/**
|
|
28
|
-
* Serialized-length budget for inlining document text
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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).
|
|
34
36
|
*
|
|
35
37
|
* Sized against what one tool result can carry into a working context (≈10K tokens of legal
|
|
36
38
|
* prose), not against what counts as a pathological document: across live samples the
|
|
37
39
|
* documents callers routinely read land well under it — consolidated-law sections at 2–3 KB,
|
|
38
40
|
* drafts and bills at 6–36 KB — while the multi-act bills and long translations that motivated
|
|
39
|
-
* 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.
|
|
40
43
|
*
|
|
41
44
|
* Handed to `outlineOnOverflow`, which measures `JSON.stringify` length (≈ UTF-8 bytes for
|
|
42
45
|
* the mostly-Latin legal text); the exact `byte_size` is reported separately.
|
|
@@ -90,16 +93,22 @@ type ParsedDocumentUrl = {
|
|
|
90
93
|
*/
|
|
91
94
|
export declare function parseDocumentUrl(url: string, contentBaseUrl: string): ParsedDocumentUrl;
|
|
92
95
|
/**
|
|
93
|
-
* One addressable
|
|
94
|
-
* heading up to the next
|
|
95
|
-
* 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.
|
|
96
100
|
*/
|
|
97
101
|
export interface DocumentSection {
|
|
98
|
-
/** UTF-8 byte size of the
|
|
102
|
+
/** UTF-8 byte size of the entry's text. */
|
|
99
103
|
readonly bytes: number;
|
|
100
|
-
/**
|
|
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
|
+
*/
|
|
101
110
|
readonly name: string;
|
|
102
|
-
/** The
|
|
111
|
+
/** The entry's text: heading to next heading for a section, line to line for a window. */
|
|
103
112
|
readonly text: string;
|
|
104
113
|
}
|
|
105
114
|
/**
|
|
@@ -111,40 +120,93 @@ export interface DocumentSection {
|
|
|
111
120
|
* html/xml rendition) — the caller reads that as "nothing to outline".
|
|
112
121
|
*/
|
|
113
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[];
|
|
114
161
|
/** Outcome of resolving a `sections:[…]` selector against a rendered document. */
|
|
115
162
|
export interface SectionSelection {
|
|
116
|
-
/**
|
|
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
|
+
*/
|
|
117
167
|
readonly available: SectionMeta[];
|
|
118
|
-
/** Concatenated text of the matched
|
|
168
|
+
/** Concatenated text of the matched entries, in document order; `''` when none matched. */
|
|
119
169
|
readonly text: string;
|
|
120
|
-
/** Requested names that matched no
|
|
170
|
+
/** Requested names that matched no entry, in the order requested. */
|
|
121
171
|
readonly unmatched: string[];
|
|
172
|
+
/** Whether `available` lists byte windows rather than §/Artikel/Anlage sections. */
|
|
173
|
+
readonly windowed: boolean;
|
|
122
174
|
}
|
|
123
175
|
/**
|
|
124
176
|
* Resolve a `sections:[…]` selector against rendered document text — the selective-retrieval
|
|
125
|
-
* counterpart to the outline
|
|
126
|
-
*
|
|
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.
|
|
127
180
|
* Reports the unmatched names and the full roster alongside the matched text so the handler
|
|
128
181
|
* can disclose a mismatch instead of silently returning something the caller didn't ask for.
|
|
129
182
|
*/
|
|
130
|
-
export declare function selectDocumentSections(text: string, want: readonly string[]): SectionSelection;
|
|
183
|
+
export declare function selectDocumentSections(text: string, want: readonly string[], format: DocumentFormat): SectionSelection;
|
|
131
184
|
/**
|
|
132
185
|
* Apply the outline-on-overflow contract to rendered document text: whole under the byte
|
|
133
|
-
* budget (or with fewer than two
|
|
134
|
-
*
|
|
135
|
-
*
|
|
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.
|
|
136
195
|
*/
|
|
137
|
-
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<{
|
|
138
197
|
text: string;
|
|
139
198
|
}>;
|
|
140
199
|
/** Name the three largest sections, quoted, as examples for a re-call notice. */
|
|
141
200
|
export declare function exampleSectionNames(sections: readonly SectionMeta[]): string;
|
|
142
201
|
/**
|
|
143
|
-
* Render an outline — the
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
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.
|
|
148
210
|
*/
|
|
149
211
|
export declare function renderOutlineSections(sections: readonly SectionMeta[]): string;
|
|
150
212
|
/**
|
|
@@ -215,8 +277,8 @@ export declare const risGetDocument: import("@cyanheads/mcp-ts-core").ToolDefini
|
|
|
215
277
|
}, {
|
|
216
278
|
readonly reason: "unsupported_url";
|
|
217
279
|
readonly code: JsonRpcErrorCode.ValidationError;
|
|
218
|
-
readonly when: "document_url fails the host + /Dokumente/ path-prefix allowlist,
|
|
219
|
-
readonly recovery: "Only ris.bka.gv.at /Dokumente/ URLs are fetchable
|
|
280
|
+
readonly when: "document_url fails the host + /Dokumente/ path-prefix allowlist, its path segment is not a recognized RIS application, or its trailing filename addresses neither the document’s own rendition nor one of its companion documents — thrown locally, nothing fetched.";
|
|
281
|
+
readonly recovery: "Only ris.bka.gv.at /Dokumente/ URLs are fetchable, and only a document’s own rendition or a companion filed in the same folder — copy the URL verbatim from a result’s content_urls or a ris_search_drafts record’s materials, or switch to document_number + application.";
|
|
220
282
|
}, {
|
|
221
283
|
readonly reason: "document_not_found";
|
|
222
284
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ris-get-document.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/ris-get-document.tool.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ris-get-document.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/ris-get-document.tool.ts"],"names":[],"mappings":"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"}
|