wiki-formant 0.1.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.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/entities.d.ts +3 -0
- package/dist/entities.d.ts.map +1 -0
- package/dist/entities.js +30 -0
- package/dist/entities.js.map +1 -0
- package/dist/http.d.ts +25 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +72 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +25 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +107 -0
- package/dist/markdown.js.map +1 -0
- package/dist/mcp.d.ts +123 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +317 -0
- package/dist/mcp.js.map +1 -0
- package/dist/pagination.d.ts +22 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +26 -0
- package/dist/pagination.js.map +1 -0
- package/dist/taxonomy.d.ts +74 -0
- package/dist/taxonomy.d.ts.map +1 -0
- package/dist/taxonomy.js +156 -0
- package/dist/taxonomy.js.map +1 -0
- package/dist/versioning.d.ts +14 -0
- package/dist/versioning.d.ts.map +1 -0
- package/dist/versioning.js +41 -0
- package/dist/versioning.js.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wiki-formant contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# wiki-formant
|
|
2
|
+
|
|
3
|
+
The portable half of a wiki: derived taxonomy, a spec-correct MCP transport, markdown twins, and the conditional-GET plumbing an agent surface needs.
|
|
4
|
+
|
|
5
|
+
Zero runtime dependencies. Web-standard `Request`/`Response`, so it runs unchanged on Next route handlers, Hono, Bun, Deno and workers.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install wiki-formant
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What this is, and what it deliberately is not
|
|
12
|
+
|
|
13
|
+
Every wiki needs a page store, a design system and a set of block types. Those are the parts you must own — they encode your schema, your brand and your content model, and a package that tried to own them would fight you.
|
|
14
|
+
|
|
15
|
+
What every wiki *also* needs, rebuilds by hand, and then lets drift, is the layer above: the second browsing axis derived from metadata you are already storing, the HTML→markdown converter behind every `.md` twin, the JSON-RPC edges that decide whether an MCP client can talk to you at all, and the ETag arithmetic that turns a recrawl into a 304.
|
|
16
|
+
|
|
17
|
+
This package was extracted from three production wikis that had each grown their own copy. By the time it was lifted, the copies had already diverged — one had lost the alphabetical index and the related-page ranking; one had `ToolAnnotations` the others lacked; one had MCP prompts the others lacked. Everything here is the union, with tests pinning the specific bugs that shipped.
|
|
18
|
+
|
|
19
|
+
## Taxonomy
|
|
20
|
+
|
|
21
|
+
`tagPath` puts a page in exactly one place in a tree. The `select`-typed metadata it already carries is a *cross-cutting* axis — and it is almost always stored, rendered once in an infobox as dead text, and never made pressable. The failure mode is a 141-page category rendered as one flat grid while the data to split it sits unread in a JSON column.
|
|
22
|
+
|
|
23
|
+
The tree is yours; supply `getMetadataKeys` and the rest is derived.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createTaxonomy } from 'wiki-formant/taxonomy';
|
|
27
|
+
|
|
28
|
+
const taxonomy = createTaxonomy({
|
|
29
|
+
getMetadataKeys: tagPath => TAGS[tagPath]?.metadataKeys ?? [],
|
|
30
|
+
href: (tagPath, { sort, filters, letter }) => /* your URL contract */,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const filters = taxonomy.facetFilters('ecosystem', searchParams);
|
|
34
|
+
const pages = taxonomy.filterPages(allPages, filters, letter);
|
|
35
|
+
const facets = taxonomy.buildFacets('ecosystem', allPages, filters, letter);
|
|
36
|
+
const index = taxonomy.needsAlphaIndex(allPages.length)
|
|
37
|
+
? taxonomy.alphaIndex(allPages, filters) : [];
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Four behaviours worth knowing, because each replaces a plausible wrong answer:
|
|
41
|
+
|
|
42
|
+
- **Each facet is counted over the set narrowed by every *other* active filter**, so its own options stay switchable instead of collapsing to the one already chosen.
|
|
43
|
+
- **Values come from the data, not from the declared `options`.** A key that declares four values while the pages hold seven would otherwise hide three behind a bar claiming to cover everything. Render what is there and the drift becomes visible.
|
|
44
|
+
- **A single-valued facet hides — unless it is the active one.** An infobox row can set a filter the chips never offered; without its chip the reader lands on a narrowed list with nothing to press to widen it.
|
|
45
|
+
- **`rankRelated` ranks by shared facet values** and returns the shared axis as `{key, value}`, so the *See also* heading can be the link into the filtered set. The behaviour it replaces — `pages.slice(0, 5)` — shows every page in a large category the same five links.
|
|
46
|
+
|
|
47
|
+
One `href` builder is passed in and used by every chip, letter and sort button. A sort button that drops the active filters is the tell that a project grew a second one.
|
|
48
|
+
|
|
49
|
+
## MCP
|
|
50
|
+
|
|
51
|
+
A minimal [Model Context Protocol](https://modelcontextprotocol.io) server over Streamable HTTP, with the transport edges most implementations get wrong.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { mcpResponse, mcpGet, mcpOptions, McpToolError } from 'wiki-formant/mcp';
|
|
55
|
+
|
|
56
|
+
const config = {
|
|
57
|
+
serverInfo: { name: 'my-wiki', version: '1.4.0' },
|
|
58
|
+
instructions: 'Call search_pages first; get_page needs a full path.',
|
|
59
|
+
docsUrl: 'https://example.com/llms.txt',
|
|
60
|
+
tools: [{
|
|
61
|
+
name: 'search_pages',
|
|
62
|
+
description: 'Full-text search across the wiki.',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: { q: { type: 'string', description: 'query' } },
|
|
66
|
+
required: ['q'],
|
|
67
|
+
},
|
|
68
|
+
annotations: { readOnlyHint: true },
|
|
69
|
+
handler: async ({ q }) => search(String(q)),
|
|
70
|
+
}],
|
|
71
|
+
onCall: (req, body) => track(req, body),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const POST = (req: Request) => mcpResponse(req, config);
|
|
75
|
+
export const GET = () => mcpGet(config.docsUrl);
|
|
76
|
+
export const OPTIONS = () => mcpOptions();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
What it gets right:
|
|
80
|
+
|
|
81
|
+
- **A caller-fixable mistake is a tool result with `isError`, never `-32603`.** Bad arguments come back naming every bad field at once, quoting the legal values and appending the schema, so one retry can fix all of them.
|
|
82
|
+
- **Capabilities advertise only what the config populates.** An advertised `resources` whose list comes back empty reads as a bug, not as honesty. The `-32601` method list narrows the same way.
|
|
83
|
+
- **A notification-only POST answers a bare `202`**, not a `200` carrying JSON `null`.
|
|
84
|
+
- **Malformed JSON is `-32700` with a `400`**, never a 500.
|
|
85
|
+
- **`GET` is an explicit 405 with CORS headers.** A framework's automatic 405 carries none, so a browser client cannot even read the refusal.
|
|
86
|
+
- **The preflight allow-list includes `Accept` and `Mcp-Protocol-Version`.** One missing entry fails the preflight rather than the POST, which presents as "the server is down".
|
|
87
|
+
- **Batches are capped** (default 20) with a teaching error, because the rate limiter charges one token per HTTP request before the body is parsed.
|
|
88
|
+
|
|
89
|
+
## Markdown twins
|
|
90
|
+
|
|
91
|
+
`htmlToMarkdown` preserves the structure an agent cites by — headings, lists, tables, code, emphasis — rather than flattening to prose. Tables convert first so the generic rules cannot eat their markup, ordered lists number per list, pipes inside cells are escaped, and a headerless table gets a synthesised header because GFM has no other form.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { htmlToMarkdown, markdownDocument } from 'wiki-formant/markdown';
|
|
95
|
+
|
|
96
|
+
return new Response(
|
|
97
|
+
markdownDocument(
|
|
98
|
+
{ title: page.title, url, updated: page.updatedAt, lastVerified: page.lastVerifiedAt,
|
|
99
|
+
license: { spdx: 'CC-BY-4.0', url: 'https://creativecommons.org/licenses/by/4.0/' } },
|
|
100
|
+
blocksToMarkdown(page.content), // your block types, your function
|
|
101
|
+
),
|
|
102
|
+
{ headers: markdownHeaders(lastModified) },
|
|
103
|
+
);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Block trees stay in your app — every project owns its own type set. Give this module HTML and it gives you markdown.
|
|
107
|
+
|
|
108
|
+
> A trap worth naming: if you serve twins via a rewrite, Next drops the destination query string. The rewrite must carry the `.md` extension through to the destination path, or the twin silently serves JSON.
|
|
109
|
+
|
|
110
|
+
## Conditional GET
|
|
111
|
+
|
|
112
|
+
The `llms.txt` / `llms-index.txt` / `llms-full.txt` trio are the most-recrawled URLs a wiki serves and the most expensive to render. Without a corpus-derived ETag, every AI crawler pays full price on every pass, forever.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { corpusEtag, notModified, textHeaders } from 'wiki-formant/http';
|
|
116
|
+
|
|
117
|
+
const etag = corpusEtag([pageCount, newestUpdatedAt]);
|
|
118
|
+
const lastModified = newestUpdatedAt.toUTCString();
|
|
119
|
+
|
|
120
|
+
export async function GET(request: Request) {
|
|
121
|
+
return notModified(request, etag, lastModified)
|
|
122
|
+
?? new Response(buildCorpus(), { headers: textHeaders(etag, lastModified) });
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Pagination and versioning
|
|
127
|
+
|
|
128
|
+
`parsePagination` clamps `page ≥ 1` and `pageSize` to 1–100; `paginatedResponse` always carries `totalPages`. Reshaping that response is a breaking change to every client that pages, which is why it lives here rather than being re-typed per repo.
|
|
129
|
+
|
|
130
|
+
`parseVersion` / `bump` / `compareVersions` handle revision semver tolerantly — a page always has a version, even when the column holds `null` or junk.
|
|
131
|
+
|
|
132
|
+
## API
|
|
133
|
+
|
|
134
|
+
| Export | From |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `createTaxonomy`, `defaultHref`, `firstLetter`, `toggleFilter` | `wiki-formant/taxonomy` |
|
|
137
|
+
| `mcpResponse`, `mcpGet`, `mcpOptions`, `handleMcp`, `withMcpCors`, `McpToolError`, `MCP_CORS`, `MCP_PROTOCOL_VERSION` | `wiki-formant/mcp` |
|
|
138
|
+
| `htmlToMarkdown`, `inlineToMarkdown`, `tableToMarkdown`, `frontmatter`, `markdownDocument`, `decodeEntities` | `wiki-formant/markdown` |
|
|
139
|
+
| `corpusEtag`, `notModified`, `textHeaders`, `markdownHeaders`, `cleanSnippet`, `pageLine` | `wiki-formant/http` |
|
|
140
|
+
| `parsePagination`, `paginatedResponse`, `toOffset` | `wiki-formant/pagination` |
|
|
141
|
+
| `parseVersion`, `formatVersion`, `incrementVersion`, `bump`, `compareVersions` | `wiki-formant/versioning` |
|
|
142
|
+
|
|
143
|
+
All are also re-exported from the package root.
|
|
144
|
+
|
|
145
|
+
## Licence
|
|
146
|
+
|
|
147
|
+
MIT.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../src/entities.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAejD,CAAC;AAEF,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAOhD"}
|
package/dist/entities.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// entities.ts — HTML entity decoding for text exports.
|
|
2
|
+
//
|
|
3
|
+
// Rich-text editors store typographic punctuation as named entities, so pages
|
|
4
|
+
// carry raw `—` / `“` / `’` into every text export — ampersand
|
|
5
|
+
// soup an agent has to read through. Numeric forms decode generically; the named
|
|
6
|
+
// table covers what wiki corpora actually use (punctuation, arrows, maths,
|
|
7
|
+
// accented Latin, Greek) rather than attempting the full HTML5 list.
|
|
8
|
+
export const NAMED_ENTITIES = {
|
|
9
|
+
amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ',
|
|
10
|
+
mdash: '—', ndash: '–', hellip: '…', middot: '·', bull: '•', sect: '§', para: '¶', dagger: '†',
|
|
11
|
+
ldquo: '“', rdquo: '”', lsquo: '‘', rsquo: '’', laquo: '«', raquo: '»',
|
|
12
|
+
larr: '←', rarr: '→', uarr: '↑', darr: '↓', harr: '↔',
|
|
13
|
+
times: '×', divide: '÷', minus: '−', plusmn: '±', ne: '≠', le: '≤', ge: '≥',
|
|
14
|
+
asymp: '≈', radic: '√', infin: '∞', sum: '∑', prod: '∏', int: '∫', deg: '°', permil: '‰',
|
|
15
|
+
sup2: '²', sup3: '³', frac12: '½', frac14: '¼', frac34: '¾',
|
|
16
|
+
euro: '€', pound: '£', yen: '¥', cent: '¢', copy: '©', reg: '®', trade: '™',
|
|
17
|
+
eacute: 'é', egrave: 'è', ecirc: 'ê', aacute: 'á', agrave: 'à', acirc: 'â', aring: 'å',
|
|
18
|
+
auml: 'ä', ouml: 'ö', uuml: 'ü', iacute: 'í', oacute: 'ó', uacute: 'ú', ntilde: 'ñ',
|
|
19
|
+
ccedil: 'ç', oslash: 'ø', szlig: 'ß', aelig: 'æ',
|
|
20
|
+
alpha: 'α', beta: 'β', gamma: 'γ', delta: 'δ', epsilon: 'ε', theta: 'θ', lambda: 'λ',
|
|
21
|
+
mu: 'μ', nu: 'ν', pi: 'π', rho: 'ρ', sigma: 'σ', tau: 'τ', phi: 'φ', omega: 'ω',
|
|
22
|
+
Delta: 'Δ', Sigma: 'Σ', Omega: 'Ω', Lambda: 'Λ', Phi: 'Φ', Pi: 'Π',
|
|
23
|
+
};
|
|
24
|
+
export function decodeEntities(s) {
|
|
25
|
+
return s
|
|
26
|
+
.replace(/&#x([0-9a-fA-F]{1,6});/g, (_m, hex) => String.fromCodePoint(parseInt(hex, 16)))
|
|
27
|
+
.replace(/&#(\d{1,7});/g, (_m, dec) => String.fromCodePoint(Number(dec)))
|
|
28
|
+
.replace(/&([a-zA-Z][a-zA-Z0-9]{1,9});/g, (m, name) => NAMED_ENTITIES[name] ?? m);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=entities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.js","sourceRoot":"","sources":["../src/entities.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** A stable ETag from whatever the corpus revision is (count + newest stamp). */
|
|
2
|
+
export declare function corpusEtag(parts: Array<string | number | Date | null | undefined>): string;
|
|
3
|
+
/** 304 when the client already holds this revision, else `null` to render. */
|
|
4
|
+
export declare function notModified(request: Request, etag: string, lastModified: string): Response | null;
|
|
5
|
+
/**
|
|
6
|
+
* Headers for a plain-text export. `maxAge` is the edge window — a curated
|
|
7
|
+
* corpus can sit on hours, a projection of live data should pass a short one.
|
|
8
|
+
*/
|
|
9
|
+
export declare function textHeaders(etag: string, lastModified: string, maxAge?: number): Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Headers for a markdown twin. Separate from `textHeaders` because the twin is
|
|
12
|
+
* addressed per page and carries its own `Last-Modified`, and because a client
|
|
13
|
+
* that asked for `.md` should not be handed `text/plain`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function markdownHeaders(lastModified: string, maxAge?: number): Record<string, string>;
|
|
16
|
+
/** Strip URLs and collapse whitespace so an excerpt stays one readable line. */
|
|
17
|
+
export declare function cleanSnippet(text: string, max?: number): string;
|
|
18
|
+
/** One markdown bullet: linked title, excerpt, and the date an agent diffs on. */
|
|
19
|
+
export declare function pageLine(opts: {
|
|
20
|
+
title: string;
|
|
21
|
+
url: string;
|
|
22
|
+
excerpt?: string;
|
|
23
|
+
updated?: Date | string | null;
|
|
24
|
+
}): string;
|
|
25
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAOA,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,MAAM,CAY1F;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAMjG;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,MAAM,SAAO,GACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,SAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAM3F;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAM,GAAG,MAAM,CAQ5D;AAED,kFAAkF;AAClF,wBAAgB,QAAQ,CAAC,IAAI,EAAE;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;CAChC,GAAG,MAAM,CAMT"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// http.ts — conditional-GET plumbing for text corpus endpoints.
|
|
2
|
+
//
|
|
3
|
+
// The three llms depths (llms.txt / llms-index.txt / llms-full.txt) are the
|
|
4
|
+
// most-recrawled URLs a wiki serves and the most expensive to render. With a
|
|
5
|
+
// corpus-derived ETag a recrawl costs a 304 instead of a full corpus build.
|
|
6
|
+
// Without one, every AI crawler pays full price on every pass, forever.
|
|
7
|
+
/** A stable ETag from whatever the corpus revision is (count + newest stamp). */
|
|
8
|
+
export function corpusEtag(parts) {
|
|
9
|
+
const seed = parts
|
|
10
|
+
.map(p => (p instanceof Date ? p.toISOString() : String(p ?? '')))
|
|
11
|
+
.join('|');
|
|
12
|
+
// FNV-1a: short, stable across processes, and no dependency. Collisions do
|
|
13
|
+
// not matter here — the seed already carries the count and the newest stamp.
|
|
14
|
+
let hash = 0x811c9dc5;
|
|
15
|
+
for (let i = 0; i < seed.length; i++) {
|
|
16
|
+
hash ^= seed.charCodeAt(i);
|
|
17
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
18
|
+
}
|
|
19
|
+
return `W/"${hash.toString(36)}-${seed.length.toString(36)}"`;
|
|
20
|
+
}
|
|
21
|
+
/** 304 when the client already holds this revision, else `null` to render. */
|
|
22
|
+
export function notModified(request, etag, lastModified) {
|
|
23
|
+
const inm = request.headers.get('if-none-match');
|
|
24
|
+
const ims = request.headers.get('if-modified-since');
|
|
25
|
+
const fresh = inm ? inm === etag : Boolean(ims && ims === lastModified);
|
|
26
|
+
if (!fresh)
|
|
27
|
+
return null;
|
|
28
|
+
return new Response(null, { status: 304, headers: { ETag: etag, 'Last-Modified': lastModified } });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Headers for a plain-text export. `maxAge` is the edge window — a curated
|
|
32
|
+
* corpus can sit on hours, a projection of live data should pass a short one.
|
|
33
|
+
*/
|
|
34
|
+
export function textHeaders(etag, lastModified, maxAge = 3600) {
|
|
35
|
+
return {
|
|
36
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
37
|
+
'Cache-Control': `public, s-maxage=${maxAge}, stale-while-revalidate=${maxAge * 24}`,
|
|
38
|
+
ETag: etag,
|
|
39
|
+
'Last-Modified': lastModified,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Headers for a markdown twin. Separate from `textHeaders` because the twin is
|
|
44
|
+
* addressed per page and carries its own `Last-Modified`, and because a client
|
|
45
|
+
* that asked for `.md` should not be handed `text/plain`.
|
|
46
|
+
*/
|
|
47
|
+
export function markdownHeaders(lastModified, maxAge = 3600) {
|
|
48
|
+
return {
|
|
49
|
+
'Content-Type': 'text/markdown; charset=utf-8',
|
|
50
|
+
'Cache-Control': `public, s-maxage=${maxAge}, stale-while-revalidate=${maxAge * 24}`,
|
|
51
|
+
'Last-Modified': lastModified,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Strip URLs and collapse whitespace so an excerpt stays one readable line. */
|
|
55
|
+
export function cleanSnippet(text, max = 160) {
|
|
56
|
+
return text
|
|
57
|
+
.replace(/\(https?:\/\/[^)]*\)/g, '')
|
|
58
|
+
.replace(/https?:\/\/\S+/g, '')
|
|
59
|
+
.replace(/\(\s*\)/g, '')
|
|
60
|
+
.replace(/\s{2,}/g, ' ')
|
|
61
|
+
.trim()
|
|
62
|
+
.slice(0, max);
|
|
63
|
+
}
|
|
64
|
+
/** One markdown bullet: linked title, excerpt, and the date an agent diffs on. */
|
|
65
|
+
export function pageLine(opts) {
|
|
66
|
+
const excerpt = opts.excerpt ? `: ${cleanSnippet(opts.excerpt)}` : '';
|
|
67
|
+
const stamp = opts.updated
|
|
68
|
+
? (typeof opts.updated === 'string' ? opts.updated : opts.updated.toISOString()).split('T')[0]
|
|
69
|
+
: '';
|
|
70
|
+
return `- [${opts.title}](${opts.url})${excerpt}${stamp ? ` _(updated ${stamp})_` : ''}`;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// wiki-formant — the portable half of a wiki.
|
|
2
|
+
//
|
|
3
|
+
// Nothing here touches a database, a framework or a design system. Those are
|
|
4
|
+
// the parts every wiki must own; these are the parts every wiki rebuilds and
|
|
5
|
+
// then lets drift.
|
|
6
|
+
export * from './taxonomy.js';
|
|
7
|
+
export * from './mcp.js';
|
|
8
|
+
export * from './markdown.js';
|
|
9
|
+
export * from './entities.js';
|
|
10
|
+
export * from './http.js';
|
|
11
|
+
export * from './pagination.js';
|
|
12
|
+
export * from './versioning.js';
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,mBAAmB;AAEnB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Inline-level HTML → markdown. Applied inside cells, list items, headings. */
|
|
2
|
+
export declare function inlineToMarkdown(html: string): string;
|
|
3
|
+
/** One `<table>` → a GFM table. Falls back to nothing when there are no rows. */
|
|
4
|
+
export declare function tableToMarkdown(html: string): string;
|
|
5
|
+
/** Block-level HTML → markdown. */
|
|
6
|
+
export declare function htmlToMarkdown(html: string): string;
|
|
7
|
+
export interface FrontmatterFields {
|
|
8
|
+
title: string;
|
|
9
|
+
url: string;
|
|
10
|
+
updated?: Date | string | null;
|
|
11
|
+
/** When the page's facts were last checked against sources — the freshness
|
|
12
|
+
* signal an agent actually needs, and the one most twins omit. */
|
|
13
|
+
lastVerified?: Date | string | null;
|
|
14
|
+
license?: {
|
|
15
|
+
spdx: string;
|
|
16
|
+
url: string;
|
|
17
|
+
} | null;
|
|
18
|
+
/** Any additional scalar rows, emitted in insertion order. */
|
|
19
|
+
extra?: Record<string, string | number | undefined>;
|
|
20
|
+
}
|
|
21
|
+
/** YAML frontmatter block, `---` fences included. */
|
|
22
|
+
export declare function frontmatter(fields: FrontmatterFields): string;
|
|
23
|
+
/** A complete markdown document: frontmatter, an H1, and a body you supply. */
|
|
24
|
+
export declare function markdownDocument(fields: FrontmatterFields, body: string): string;
|
|
25
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAcA,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmBrD;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoBpD;AAED,mCAAmC;AACnC,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAwDnD;AAKD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IAC/B;uEACmE;IACnE,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC/C,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;CACrD;AAED,qDAAqD;AACrD,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAgB7D;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhF"}
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// markdown.ts — HTML → markdown for the `.md` twin of a content page.
|
|
2
|
+
//
|
|
3
|
+
// Distinct from a plain text extractor, which flattens everything to prose.
|
|
4
|
+
// This preserves the structure an agent navigates and cites by — headings,
|
|
5
|
+
// lists, tables, code, emphasis — so a fetched page can be quoted precisely
|
|
6
|
+
// instead of re-summarised.
|
|
7
|
+
//
|
|
8
|
+
// Block trees are NOT handled here: every project owns its own block type set,
|
|
9
|
+
// so `blocksToMarkdown` belongs in the app. Give this module HTML and it gives
|
|
10
|
+
// you markdown; give `frontmatter` the fields and it gives you the document
|
|
11
|
+
// head. That is the whole portable half.
|
|
12
|
+
import { decodeEntities } from './entities.js';
|
|
13
|
+
/** Inline-level HTML → markdown. Applied inside cells, list items, headings. */
|
|
14
|
+
export function inlineToMarkdown(html) {
|
|
15
|
+
return decodeEntities(html
|
|
16
|
+
.replace(/<br\s*\/?>/gi, ' ')
|
|
17
|
+
.replace(/<(?:strong|b)\b[^>]*>(.*?)<\/(?:strong|b)>/gi, '**$1**')
|
|
18
|
+
.replace(/<(?:em|i)\b[^>]*>(.*?)<\/(?:em|i)>/gi, '_$1_')
|
|
19
|
+
.replace(/<code\b[^>]*>(.*?)<\/code>/gi, '`$1`')
|
|
20
|
+
.replace(/<a\b[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)')
|
|
21
|
+
.replace(/<img\b[^>]*alt="([^"]*)"[^>]*src="([^"]*)"[^>]*>/gi, '')
|
|
22
|
+
.replace(/<img\b[^>]*src="([^"]*)"[^>]*>/gi, '')
|
|
23
|
+
.replace(/<[^>]+>/g, ''))
|
|
24
|
+
// HTML collapses whitespace in inline context, so the converter has to as
|
|
25
|
+
// well — including newlines. Editor-produced HTML arrives on one line and
|
|
26
|
+
// never exposes this, but hand-authored HTML is indented, and without the
|
|
27
|
+
// newline in this class every wrapped source line reaches the reader with a
|
|
28
|
+
// leading space and markdown renderers start seeing stray indentation.
|
|
29
|
+
.replace(/\s+/g, ' ')
|
|
30
|
+
.trim();
|
|
31
|
+
}
|
|
32
|
+
/** One `<table>` → a GFM table. Falls back to nothing when there are no rows. */
|
|
33
|
+
export function tableToMarkdown(html) {
|
|
34
|
+
const rows = [...html.matchAll(/<tr\b[^>]*>([\s\S]*?)<\/tr>/gi)].map(m => [...m[1].matchAll(/<(t[hd])\b[^>]*>([\s\S]*?)<\/\1>/gi)].map(c => inlineToMarkdown(c[2]).replace(/\|/g, '\\|')));
|
|
35
|
+
if (!rows.length)
|
|
36
|
+
return '';
|
|
37
|
+
const width = Math.max(...rows.map(r => r.length));
|
|
38
|
+
const pad = (r) => [...r, ...Array(width - r.length).fill('')];
|
|
39
|
+
// A table whose first row is all <th> has a header; otherwise synthesise an
|
|
40
|
+
// empty one, since GFM has no headerless table form.
|
|
41
|
+
const headed = /<th\b/i.test(html.slice(0, html.indexOf('</tr>') + 1));
|
|
42
|
+
const [head, ...body] = headed
|
|
43
|
+
? [pad(rows[0]), ...rows.slice(1).map(pad)]
|
|
44
|
+
: [Array(width).fill(''), ...rows.map(pad)];
|
|
45
|
+
return [
|
|
46
|
+
`| ${head.join(' | ')} |`,
|
|
47
|
+
`| ${Array(width).fill('---').join(' | ')} |`,
|
|
48
|
+
...body.map(r => `| ${r.join(' | ')} |`),
|
|
49
|
+
].join('\n');
|
|
50
|
+
}
|
|
51
|
+
/** Block-level HTML → markdown. */
|
|
52
|
+
export function htmlToMarkdown(html) {
|
|
53
|
+
let out = html;
|
|
54
|
+
// Tables first — their inner markup must not be eaten by the generic rules.
|
|
55
|
+
out = out.replace(/<table\b[^>]*>[\s\S]*?<\/table>/gi, m => `\n\n${tableToMarkdown(m)}\n\n`);
|
|
56
|
+
// Lists. `<ol>` numbering is computed per-list, which is why this cannot be a
|
|
57
|
+
// single regex with a `$1` backreference — inside a replace callback `$1` is
|
|
58
|
+
// a literal, not a substitution. (That exact bug shipped once and rendered
|
|
59
|
+
// every ordered list as a column of `1. $1`.)
|
|
60
|
+
out = out.replace(/<ul\b[^>]*>([\s\S]*?)<\/ul>/gi, (_m, body) => {
|
|
61
|
+
const items = [...body.matchAll(/<li\b[^>]*>([\s\S]*?)<\/li>/gi)].map(i => `- ${inlineToMarkdown(i[1])}`);
|
|
62
|
+
return `\n\n${items.join('\n')}\n\n`;
|
|
63
|
+
});
|
|
64
|
+
out = out.replace(/<ol\b[^>]*>([\s\S]*?)<\/ol>/gi, (_m, body) => {
|
|
65
|
+
const items = [...body.matchAll(/<li\b[^>]*>([\s\S]*?)<\/li>/gi)].map((i, n) => `${n + 1}. ${inlineToMarkdown(i[1])}`);
|
|
66
|
+
return `\n\n${items.join('\n')}\n\n`;
|
|
67
|
+
});
|
|
68
|
+
out = out
|
|
69
|
+
.replace(/<pre\b[^>]*>\s*<code\b[^>]*>([\s\S]*?)<\/code>\s*<\/pre>/gi, (_m, code) => `\n\n\`\`\`\n${decodeEntities(code).trim()}\n\`\`\`\n\n`)
|
|
70
|
+
.replace(/<pre\b[^>]*>([\s\S]*?)<\/pre>/gi, (_m, code) => `\n\n\`\`\`\n${decodeEntities(code).trim()}\n\`\`\`\n\n`)
|
|
71
|
+
.replace(/<blockquote\b[^>]*>([\s\S]*?)<\/blockquote>/gi, (_m, body) => `\n\n${inlineToMarkdown(body)
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map(l => `> ${l}`)
|
|
74
|
+
.join('\n')}\n\n`)
|
|
75
|
+
.replace(/<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/gi, (_m, level, body) => `\n\n${'#'.repeat(Number(level))} ${inlineToMarkdown(body)}\n\n`)
|
|
76
|
+
.replace(/<hr\s*\/?>/gi, '\n\n---\n\n')
|
|
77
|
+
.replace(/<p\b[^>]*>([\s\S]*?)<\/p>/gi, (_m, body) => `\n\n${inlineToMarkdown(body)}\n\n`)
|
|
78
|
+
.replace(/<div\b[^>]*>([\s\S]*?)<\/div>/gi, (_m, body) => `\n\n${inlineToMarkdown(body)}\n\n`);
|
|
79
|
+
return decodeEntities(out.replace(/<[^>]+>/g, ''))
|
|
80
|
+
.replace(/[ \t]+$/gm, '')
|
|
81
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
82
|
+
.trim();
|
|
83
|
+
}
|
|
84
|
+
const isoDate = (d) => (typeof d === 'string' ? d : d.toISOString()).split('T')[0];
|
|
85
|
+
/** YAML frontmatter block, `---` fences included. */
|
|
86
|
+
export function frontmatter(fields) {
|
|
87
|
+
const esc = (s) => s.replace(/"/g, '\\"');
|
|
88
|
+
return [
|
|
89
|
+
'---',
|
|
90
|
+
`title: "${esc(fields.title)}"`,
|
|
91
|
+
`url: "${fields.url}"`,
|
|
92
|
+
...(fields.updated ? [`updated: ${isoDate(fields.updated)}`] : []),
|
|
93
|
+
...(fields.lastVerified ? [`last_verified: ${isoDate(fields.lastVerified)}`] : []),
|
|
94
|
+
...(fields.license
|
|
95
|
+
? [`license: ${fields.license.spdx}`, `license_url: "${fields.license.url}"`]
|
|
96
|
+
: []),
|
|
97
|
+
...Object.entries(fields.extra ?? {})
|
|
98
|
+
.filter(([, v]) => v !== undefined && v !== '')
|
|
99
|
+
.map(([k, v]) => (typeof v === 'number' ? `${k}: ${v}` : `${k}: "${esc(String(v))}"`)),
|
|
100
|
+
'---',
|
|
101
|
+
].join('\n');
|
|
102
|
+
}
|
|
103
|
+
/** A complete markdown document: frontmatter, an H1, and a body you supply. */
|
|
104
|
+
export function markdownDocument(fields, body) {
|
|
105
|
+
return `${frontmatter(fields)}\n\n# ${fields.title}\n\n${body.trim()}\n`;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export declare const MCP_PROTOCOL_VERSION = "2025-03-26";
|
|
2
|
+
export type ToolParam = {
|
|
3
|
+
type: 'string' | 'number' | 'boolean' | 'array';
|
|
4
|
+
description: string;
|
|
5
|
+
enum?: string[];
|
|
6
|
+
items?: {
|
|
7
|
+
type: 'string' | 'number';
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export type ToolSchema = {
|
|
11
|
+
type: 'object';
|
|
12
|
+
properties: Record<string, ToolParam>;
|
|
13
|
+
required?: string[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Behavioural hints a client uses to decide what needs a human in the loop.
|
|
17
|
+
* Without them every tool looks alike, so a client that auto-approves read-only
|
|
18
|
+
* calls has no way to tell a lookup from one that reaches a payment processor.
|
|
19
|
+
*/
|
|
20
|
+
export type ToolAnnotations = {
|
|
21
|
+
/** Human-facing label for the tool. */
|
|
22
|
+
title?: string;
|
|
23
|
+
/** Does not modify anything. */
|
|
24
|
+
readOnlyHint?: boolean;
|
|
25
|
+
/** May destroy or overwrite state (only meaningful when not read-only). */
|
|
26
|
+
destructiveHint?: boolean;
|
|
27
|
+
/** Repeating the identical call has no additional effect. */
|
|
28
|
+
idempotentHint?: boolean;
|
|
29
|
+
/** Touches systems beyond this server. */
|
|
30
|
+
openWorldHint?: boolean;
|
|
31
|
+
};
|
|
32
|
+
export interface McpTool {
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
inputSchema: ToolSchema;
|
|
36
|
+
annotations?: ToolAnnotations;
|
|
37
|
+
/** Surfaced as an A2A skill on the agent card. Not sent over MCP. */
|
|
38
|
+
skill?: {
|
|
39
|
+
id: string;
|
|
40
|
+
tags: string[];
|
|
41
|
+
examples?: string[];
|
|
42
|
+
};
|
|
43
|
+
handler: (args: Record<string, unknown>) => Promise<unknown>;
|
|
44
|
+
}
|
|
45
|
+
export interface McpResource {
|
|
46
|
+
uri: string;
|
|
47
|
+
name: string;
|
|
48
|
+
description: string;
|
|
49
|
+
mimeType: string;
|
|
50
|
+
read: () => Promise<string | null>;
|
|
51
|
+
}
|
|
52
|
+
export interface McpPrompt {
|
|
53
|
+
name: string;
|
|
54
|
+
description: string;
|
|
55
|
+
/**
|
|
56
|
+
* The user message the client inserts when someone picks this prompt. Name
|
|
57
|
+
* the tool sequence outright rather than restating the question: the model
|
|
58
|
+
* reading it has the tool list but no reason to prefer one call order over
|
|
59
|
+
* another, which is the same gap `instructions` exists to close.
|
|
60
|
+
*/
|
|
61
|
+
text: string;
|
|
62
|
+
}
|
|
63
|
+
export interface McpServerConfig {
|
|
64
|
+
serverInfo: {
|
|
65
|
+
name: string;
|
|
66
|
+
version: string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Server-level usage guide returned by `initialize`. Say which tool to reach
|
|
70
|
+
* for first and what NOT to do; it is the only text an agent sees before the
|
|
71
|
+
* tool list.
|
|
72
|
+
*/
|
|
73
|
+
instructions: string;
|
|
74
|
+
tools: McpTool[];
|
|
75
|
+
resources?: McpResource[];
|
|
76
|
+
/**
|
|
77
|
+
* The entry point a client offers after install. Tools are what an agent
|
|
78
|
+
* reaches for once it already has a question; prompts are what a person
|
|
79
|
+
* clicks when they do not have one yet.
|
|
80
|
+
*/
|
|
81
|
+
prompts?: McpPrompt[];
|
|
82
|
+
/** Appended to the GET refusal so a browser that lands here learns where to go. */
|
|
83
|
+
docsUrl?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Cap on JSON-RPC batch size. The rate limiter charges one token per HTTP
|
|
86
|
+
* request, before the body is parsed — an unbounded batch would let a single
|
|
87
|
+
* token fan out into thousands of concurrent handler executions, each its own
|
|
88
|
+
* query. Keep this aligned with whatever ceiling the batching tools advertise.
|
|
89
|
+
*/
|
|
90
|
+
maxBatch?: number;
|
|
91
|
+
/** Per-request analytics hook. Runs before dispatch; never blocks the response. */
|
|
92
|
+
onCall?: (request: Request, body: unknown) => void;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* A caller-fixable failure inside a handler (page not found, empty input).
|
|
96
|
+
* Reported as a tool result with `isError` so the model sees the text and can
|
|
97
|
+
* correct itself, per the spec's split between protocol and execution errors.
|
|
98
|
+
*/
|
|
99
|
+
export declare class McpToolError extends Error {
|
|
100
|
+
readonly details?: Record<string, unknown> | undefined;
|
|
101
|
+
constructor(message: string, details?: Record<string, unknown> | undefined);
|
|
102
|
+
}
|
|
103
|
+
type RpcRequest = {
|
|
104
|
+
jsonrpc: '2.0';
|
|
105
|
+
id: string | number | null;
|
|
106
|
+
method: string;
|
|
107
|
+
params?: unknown;
|
|
108
|
+
};
|
|
109
|
+
/** Dispatch a parsed body. `null` means notification-only — answer 202, not 200. */
|
|
110
|
+
export declare function handleMcp(body: RpcRequest | RpcRequest[], config: McpServerConfig): Promise<object | object[] | null>;
|
|
111
|
+
export declare const MCP_CORS: Record<string, string>;
|
|
112
|
+
export declare function withMcpCors<T extends Response>(res: T): T;
|
|
113
|
+
export declare function mcpOptions(): Response;
|
|
114
|
+
/**
|
|
115
|
+
* Streamable HTTP lets a server decline the SSE leg by answering GET with 405.
|
|
116
|
+
* A framework's automatic 405 carries no CORS headers, so a browser client
|
|
117
|
+
* could not even read the refusal — answer it here, and say what to do instead.
|
|
118
|
+
*/
|
|
119
|
+
export declare function mcpGet(docsUrl?: string): Response;
|
|
120
|
+
/** The whole POST leg: parse, track, dispatch, and answer with the right status. */
|
|
121
|
+
export declare function mcpResponse(request: Request, config: McpServerConfig): Promise<Response>;
|
|
122
|
+
export {};
|
|
123
|
+
//# sourceMappingURL=mcp.d.ts.map
|