@stratta/mcp 0.10.0 → 0.12.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.
@@ -0,0 +1,734 @@
1
+ // Generated by scripts/build-mcp-catalog.mjs from convex/mcpCatalog.ts.
2
+ // Do not edit: change the source and run the script.
3
+ export const SERVER_INSTRUCTIONS = "Stratta serves the construction norms of the signed-in organization, and only those. Call `get_methodology` before the first technical question of a conversation: it carries the navigation order, the citation rules and the dossier policy. Never answer a technical question from memory; a norm absent from `list_norms` cannot be read.";
4
+ export const CATALOG = [
5
+ {
6
+ "name": "get_methodology",
7
+ "title": "Working method",
8
+ "description": "Read the working contract this server expects an agent to follow: how to walk a norm from its table of contents down to a clause, how to cite what it reads, and when to record something in a project dossier. Call it once, before the first technical answer of a conversation, and follow it for the rest of the conversation.",
9
+ "annotations": {
10
+ "readOnlyHint": true,
11
+ "destructiveHint": false,
12
+ "idempotentHint": true,
13
+ "openWorldHint": false
14
+ },
15
+ "transports": [
16
+ "stdio",
17
+ "remote"
18
+ ],
19
+ "params": {}
20
+ },
21
+ {
22
+ "name": "whoami",
23
+ "title": "Connected account",
24
+ "description": "Report which organization this connection reads from, the signed-in role, the plan name and how many norms are available. Use it to confirm the connection points at the intended workspace before starting, or when a norm the user expects is missing.",
25
+ "annotations": {
26
+ "readOnlyHint": true,
27
+ "destructiveHint": false,
28
+ "idempotentHint": true,
29
+ "openWorldHint": false
30
+ },
31
+ "transports": [
32
+ "stdio",
33
+ "remote"
34
+ ],
35
+ "params": {}
36
+ },
37
+ {
38
+ "name": "list_norms",
39
+ "title": "Available norms",
40
+ "description": "List the norms published in the signed-in organization, with code, year, title, language and coverage (how much of the norm was ingested). This is the first call of any search: a norm absent from this list cannot be read and must not be answered from memory. A demo entry is fictional and never a technical reference; a sparse entry carries a notice to relay.",
41
+ "annotations": {
42
+ "readOnlyHint": true,
43
+ "destructiveHint": false,
44
+ "idempotentHint": true,
45
+ "openWorldHint": false
46
+ },
47
+ "transports": [
48
+ "stdio",
49
+ "remote"
50
+ ],
51
+ "params": {}
52
+ },
53
+ {
54
+ "name": "get_toc",
55
+ "title": "Table of contents",
56
+ "description": "Return the table of contents of one norm as a tree of path, title, summary and page range. Pass maxDepth 1 for the chapter list, which is enough to choose where to descend; deeper levels return the whole outline and fill the context. Descend with get_subtree, read with get_section.",
57
+ "annotations": {
58
+ "readOnlyHint": true,
59
+ "destructiveHint": false,
60
+ "idempotentHint": true,
61
+ "openWorldHint": false
62
+ },
63
+ "transports": [
64
+ "stdio",
65
+ "remote"
66
+ ],
67
+ "params": {
68
+ "code": "Norm code exactly as list_norms returns it, e.g. \"SIA 261\", \"SIA 267\", \"EN 1997-1\".",
69
+ "edition": "Edition of the norm when the organization holds several (\"2013\", \"2020\"). Default: the most recent published edition; list_norms names them.",
70
+ "maxDepth": "Nesting depth to include. 1 (default) returns chapters only; 2 adds X.Y sections. Large norms grow fast beyond 2."
71
+ }
72
+ },
73
+ {
74
+ "name": "get_subtree",
75
+ "title": "Section tree",
76
+ "description": "Return the section tree below one path, each node with its summary and page range. This is the step between the table of contents and reading: it shows what a chapter contains without loading its text. Use maxDepth to bound it; a tree larger than maxNodes loses its deepest levels and says so with truncated: true.",
77
+ "annotations": {
78
+ "readOnlyHint": true,
79
+ "destructiveHint": false,
80
+ "idempotentHint": true,
81
+ "openWorldHint": false
82
+ },
83
+ "transports": [
84
+ "stdio",
85
+ "remote"
86
+ ],
87
+ "params": {
88
+ "code": "Norm code exactly as list_norms returns it, e.g. \"SIA 261\", \"SIA 267\", \"EN 1997-1\".",
89
+ "edition": "Edition of the norm when the organization holds several (\"2013\", \"2020\"). Default: the most recent published edition; list_norms names them.",
90
+ "path": "Path to root the subtree at, e.g. \"9\" or \"9.5\". A clause number below the last heading resolves to that heading.",
91
+ "maxDepth": "Depth relative to the root. 1 returns the root and its direct children; omit for the whole subtree.",
92
+ "maxNodes": "Cap on the nodes returned, 20 to 2000. Default 400."
93
+ }
94
+ },
95
+ {
96
+ "name": "get_section",
97
+ "title": "Section",
98
+ "description": "Return one section as a Markdown document: its text with the LaTeX formulas and tables where the norm prints them, its figures, the cross-references it carries and the sections below it, with the exact path and page range. This is what gets quoted: call it before stating any technical claim taken from a norm, and cite [CODE section path, p. N]. A clause number that is not a node (9.5.2.1) resolves to its nearest heading, whose text carries the clause. A long section comes in windows of maxChars; when truncated is true, call again with the offset given.",
99
+ "annotations": {
100
+ "readOnlyHint": true,
101
+ "destructiveHint": false,
102
+ "idempotentHint": true,
103
+ "openWorldHint": false
104
+ },
105
+ "transports": [
106
+ "stdio",
107
+ "remote"
108
+ ],
109
+ "params": {
110
+ "code": "Norm code exactly as list_norms returns it, e.g. \"SIA 261\", \"SIA 267\", \"EN 1997-1\".",
111
+ "edition": "Edition of the norm when the organization holds several (\"2013\", \"2020\"). Default: the most recent published edition; list_norms names them.",
112
+ "path": "Section path as it appears in the table of contents, e.g. \"4.2.1\" or \"Annexe A\".",
113
+ "maxChars": "Characters of text per call, 500 to 40000. Default 12000, which fits any client. Raise it only when one section is needed whole.",
114
+ "offset": "Where to resume in the text, from the nextOffset of a truncated call."
115
+ }
116
+ },
117
+ {
118
+ "name": "search_in_norm",
119
+ "title": "Search a norm",
120
+ "description": "Full-text search inside one norm, ranked, a hit in a heading outranking a hit in the body. Use it when the table of contents suggests no obvious section. Matching is per term, not per substring, in the language of the norm: search \"charges\" in a French norm, not \"loads\"; accents and case do not matter. A clause number as keyword returns that clause. Returns paths, titles and snippets; read the promising ones with get_section.",
121
+ "annotations": {
122
+ "readOnlyHint": true,
123
+ "destructiveHint": false,
124
+ "idempotentHint": true,
125
+ "openWorldHint": false
126
+ },
127
+ "transports": [
128
+ "stdio",
129
+ "remote"
130
+ ],
131
+ "params": {
132
+ "code": "Norm code exactly as list_norms returns it, e.g. \"SIA 261\", \"SIA 267\", \"EN 1997-1\".",
133
+ "edition": "Edition of the norm when the organization holds several (\"2013\", \"2020\"). Default: the most recent published edition; list_norms names them.",
134
+ "keyword": "Search terms in the language of the norm, accents optional. Several words find sections containing all of them; a clause number finds that clause.",
135
+ "limit": "Maximum number of hits, 1 to 50. Default 20."
136
+ }
137
+ },
138
+ {
139
+ "name": "search_corpus",
140
+ "title": "Search all norms",
141
+ "description": "Full-text search across every norm the organization can read, grouped by norm, heading hits from any norm ahead of body hits. Use it first when the question does not name its norm: it tells which norms speak of the subject before any table of contents is opened. Same matching as search_in_norm. Returns, per norm, its code and title and a few hits; continue with get_section on the paths that look relevant.",
142
+ "annotations": {
143
+ "readOnlyHint": true,
144
+ "destructiveHint": false,
145
+ "idempotentHint": true,
146
+ "openWorldHint": false
147
+ },
148
+ "transports": [
149
+ "stdio",
150
+ "remote"
151
+ ],
152
+ "params": {
153
+ "keyword": "Search terms in the language of the norms, accents optional. Several words find sections containing all of them.",
154
+ "limit": "Maximum number of hits across all norms, 1 to 40. Default 20."
155
+ }
156
+ },
157
+ {
158
+ "name": "get_cross_refs",
159
+ "title": "Cross-references",
160
+ "description": "Return the references one section makes: to other clauses, annexes, figures and tables of the same norm, and to other norms. SIA norms deliberately spread the rule, the coefficient and the action across separate clauses and norms, so following a reference is often what separates a correct answer from an incomplete one. Each reference says whether it is resolvable in this corpus (listed first) and names the target section; read each relevant target with get_section.",
161
+ "annotations": {
162
+ "readOnlyHint": true,
163
+ "destructiveHint": false,
164
+ "idempotentHint": true,
165
+ "openWorldHint": false
166
+ },
167
+ "transports": [
168
+ "stdio",
169
+ "remote"
170
+ ],
171
+ "params": {
172
+ "code": "Norm code exactly as list_norms returns it, e.g. \"SIA 261\", \"SIA 267\", \"EN 1997-1\".",
173
+ "edition": "Edition of the norm when the organization holds several (\"2013\", \"2020\"). Default: the most recent published edition; list_norms names them.",
174
+ "path": "Section path as it appears in the table of contents, e.g. \"4.2.1\" or \"Annexe A\"."
175
+ }
176
+ },
177
+ {
178
+ "name": "get_figure",
179
+ "title": "Figure",
180
+ "description": "Return one figure of a norm as an image: load diagrams, design charts, standard cross-sections. Call it when the figure carries the rule and describing it in words would lose the content. The figure identifier comes from the figures array of get_section.",
181
+ "annotations": {
182
+ "readOnlyHint": true,
183
+ "destructiveHint": false,
184
+ "idempotentHint": true,
185
+ "openWorldHint": false
186
+ },
187
+ "transports": [
188
+ "stdio",
189
+ "remote"
190
+ ],
191
+ "params": {
192
+ "code": "Norm code exactly as list_norms returns it, e.g. \"SIA 261\", \"SIA 267\", \"EN 1997-1\".",
193
+ "edition": "Edition of the norm when the organization holds several (\"2013\", \"2020\"). Default: the most recent published edition; list_norms names them.",
194
+ "figureId": "Figure identifier from the figures array of get_section."
195
+ }
196
+ },
197
+ {
198
+ "name": "list_dossiers",
199
+ "title": "Project dossiers",
200
+ "description": "List the project dossiers of the organization, most recently touched first, with the count of entries and of open questions. Call it when the user names a project or asks what they were working on: a dossier that already exists is reloaded, never recreated.",
201
+ "annotations": {
202
+ "readOnlyHint": true,
203
+ "destructiveHint": false,
204
+ "idempotentHint": true,
205
+ "openWorldHint": false
206
+ },
207
+ "transports": [
208
+ "stdio",
209
+ "remote"
210
+ ],
211
+ "params": {}
212
+ },
213
+ {
214
+ "name": "open_dossier",
215
+ "title": "Open a dossier",
216
+ "description": "Open a project dossier, or return the one that already carries that name: calling it twice with the same name never creates a duplicate. Open one when the user works on a named project and there is something worth keeping (a question to settle, a retained value, an assumption, a site observation); not for a passing lookup. Returns dossierId and created.",
217
+ "annotations": {
218
+ "readOnlyHint": false,
219
+ "destructiveHint": false,
220
+ "idempotentHint": true,
221
+ "openWorldHint": false
222
+ },
223
+ "transports": [
224
+ "stdio",
225
+ "remote"
226
+ ],
227
+ "params": {
228
+ "name": "How the user refers to the project, in their own words: this is the name they will search for.",
229
+ "reference": "The office's own project number, if the user gave one."
230
+ }
231
+ },
232
+ {
233
+ "name": "open_question",
234
+ "title": "Open a question",
235
+ "description": "Open one question to settle in a dossier, before gathering evidence for it: \"can pile P38 be kept?\", \"which friction angle do we retain?\". A question is the unit of work; the evidence saved with save_finding and the eventual record_decision hang off it. One question per thing to settle, never a list of eight in one body. Idempotent on the title: the same wording returns the question already open. Name the options on the table when there are several. Returns questionId and created.",
236
+ "annotations": {
237
+ "readOnlyHint": false,
238
+ "destructiveHint": false,
239
+ "idempotentHint": true,
240
+ "openWorldHint": false
241
+ },
242
+ "transports": [
243
+ "stdio",
244
+ "remote"
245
+ ],
246
+ "params": {
247
+ "dossierId": "Dossier id returned by open_dossier or list_dossiers.",
248
+ "title": "The question in one line, phrased as a question.",
249
+ "body": "What is at stake and why it is not settled, in two or three sentences. Not a list of sub-questions.",
250
+ "options": "The ways this could be settled, one short name each."
251
+ }
252
+ },
253
+ {
254
+ "name": "save_finding",
255
+ "title": "Record evidence",
256
+ "description": "Record one piece of evidence in a dossier, as you work rather than in a batch at the end. Kinds: reference (what a norm says; always give normCode, sectionPath and page, or it is not a reference), hypothesis (the value the engineer retains and why, value in value and reasoning in detail), observation (what the site, a borehole or a survey showed, with the date). One clause, one value or one fact per call, in one or two sentences. Pass questionId to file it under the question it serves; without it the entry waits unclassified. Set confidence whenever a value is at stake: established, judgement or to_confirm. Questions go through open_question and decisions through record_decision, not here.",
257
+ "annotations": {
258
+ "readOnlyHint": false,
259
+ "destructiveHint": false,
260
+ "idempotentHint": false,
261
+ "openWorldHint": false
262
+ },
263
+ "transports": [
264
+ "stdio",
265
+ "remote"
266
+ ],
267
+ "params": {
268
+ "dossierId": "Dossier id returned by open_dossier or list_dossiers.",
269
+ "questionId": "The question this evidence serves, from open_question or load_dossier. Omit only when you do not know yet.",
270
+ "kind": "reference, hypothesis or observation: pick by what the entry is.",
271
+ "title": "The fact in one line, as an engineer would write it in a report. One clause, one value or one observation; not a theme.",
272
+ "detail": "The reasoning in one or two sentences: why this value, what it rests on, what disagreed. Expected for a hypothesis.",
273
+ "value": "The retained value with its unit, e.g. \"30 deg\", \"1.25 kN/m2\".",
274
+ "confidence": "established (computed or read directly), judgement (the engineer chose it), to_confirm (provisional, pending a test).",
275
+ "normCode": "Norm this comes from, e.g. \"SIA 267\".",
276
+ "sectionPath": "Exact section path, e.g. \"9.5.2.1\".",
277
+ "page": "Page in the norm, so a human can verify it in their PDF.",
278
+ "normEdition": "The edition cited, e.g. \"2013\" or \"2020 + rect. 2022\", when list_norms gives it.",
279
+ "attachmentId": "The project attachment the evidence comes from, when it is not a norm.",
280
+ "attachmentPage": "The page of that attachment."
281
+ }
282
+ },
283
+ {
284
+ "name": "record_decision",
285
+ "title": "Record a decision",
286
+ "description": "Settle a question with a decision the engineer has confirmed: what was decided, why, and the clause it rests on. Name the retained option when the question listed some; put the number in value and set confidence when the decision is a value. Only after the user confirmed the decision, never on your own reasoning. The question leaves the open items and keeps its evidence. Returns entryId.",
287
+ "annotations": {
288
+ "readOnlyHint": false,
289
+ "destructiveHint": false,
290
+ "idempotentHint": false,
291
+ "openWorldHint": false
292
+ },
293
+ "transports": [
294
+ "stdio",
295
+ "remote"
296
+ ],
297
+ "params": {
298
+ "questionId": "Question id returned by open_question or load_dossier.",
299
+ "retainedOption": "The option that was retained, by the name it was given. An option not listed yet is created.",
300
+ "title": "The decision in one line, as it would be written in a report.",
301
+ "detail": "Why, in one or two sentences, and what it rests on.",
302
+ "value": "The decided value with its unit, when the decision is a value.",
303
+ "confidence": "established, judgement or to_confirm.",
304
+ "normCode": "Norm the decision rests on, e.g. \"SIA 267\".",
305
+ "sectionPath": "Exact section path, e.g. \"9.5.2.1\".",
306
+ "page": "Page in the norm.",
307
+ "normEdition": "The edition cited, when known.",
308
+ "confirmed": "true when the user has already confirmed this decision in the conversation. Otherwise the user is asked through the client before anything is written."
309
+ }
310
+ },
311
+ {
312
+ "name": "load_dossier",
313
+ "title": "Load a dossier",
314
+ "description": "Reload a dossier: its questions with their evidence, options and decisions, open questions first, then the evidence not yet filed under a question, then the comments a colleague left. Do this before answering whenever the user returns to a project already named, otherwise a decision already taken gets taken again. The name is enough; the id is optional.",
315
+ "annotations": {
316
+ "readOnlyHint": true,
317
+ "destructiveHint": false,
318
+ "idempotentHint": true,
319
+ "openWorldHint": false
320
+ },
321
+ "transports": [
322
+ "stdio",
323
+ "remote"
324
+ ],
325
+ "params": {
326
+ "dossierId": "Dossier id, preferred when you have it.",
327
+ "name": "The project name as the user says it. Matched exactly; call list_dossiers first if unsure."
328
+ }
329
+ },
330
+ {
331
+ "name": "resolve_question",
332
+ "title": "Close a question",
333
+ "description": "Close a question that stopped mattering without a decision (the variant was abandoned, the request withdrawn), or reopen one with resolved false. The question stays in the dossier with its evidence. To settle a question with an answer, call record_decision instead. Only when the user says so.",
334
+ "annotations": {
335
+ "readOnlyHint": false,
336
+ "destructiveHint": false,
337
+ "idempotentHint": true,
338
+ "openWorldHint": false
339
+ },
340
+ "transports": [
341
+ "stdio",
342
+ "remote"
343
+ ],
344
+ "params": {
345
+ "questionId": "Question id returned by open_question or load_dossier.",
346
+ "resolved": "Defaults to true (close). Pass false to reopen.",
347
+ "confirmed": "true when the user has already confirmed in the conversation that the question is settled. Otherwise the user is asked through the client first."
348
+ }
349
+ },
350
+ {
351
+ "name": "list_attachments",
352
+ "title": "Project attachments",
353
+ "description": "List the project attachments of one dossier: the site reports, borehole logs, meeting minutes and data sheets the engineer uploaded. Returns for each its id, name, type, page count and extraction status. Read one with read_attachment before answering on the project; a piece is evidence like a clause and is cited the same way.",
354
+ "annotations": {
355
+ "readOnlyHint": true,
356
+ "destructiveHint": false,
357
+ "idempotentHint": true,
358
+ "openWorldHint": false
359
+ },
360
+ "transports": [
361
+ "stdio",
362
+ "remote"
363
+ ],
364
+ "params": {
365
+ "dossierId": "The dossier, from list_dossiers or open_dossier."
366
+ }
367
+ },
368
+ {
369
+ "name": "read_attachment",
370
+ "title": "Read an attachment",
371
+ "description": "Read the text of a project attachment as a Markdown document, page by page. from and to bound the pages, maxChars the characters; when truncated is true, call again with from set to nextPage. A scan without a text layer says so. Cite what you read as [name, p. N] and file it with save_finding using attachmentId and attachmentPage.",
372
+ "annotations": {
373
+ "readOnlyHint": true,
374
+ "destructiveHint": false,
375
+ "idempotentHint": true,
376
+ "openWorldHint": false
377
+ },
378
+ "transports": [
379
+ "stdio",
380
+ "remote"
381
+ ],
382
+ "params": {
383
+ "attachmentId": "The attachment, from list_attachments or search_in_dossier.",
384
+ "from": "First page to read. Default 1.",
385
+ "to": "Last page to read. Default: the last page.",
386
+ "maxChars": "Characters of text per call, 500 to 40000. Default 12000."
387
+ }
388
+ },
389
+ {
390
+ "name": "search_in_dossier",
391
+ "title": "Search the attachments",
392
+ "description": "Full-text search over the attachments of one dossier, accents and case ignored, matching per term. Returns the attachment, the page and a snippet for each hit; read the page with read_attachment before quoting it.",
393
+ "annotations": {
394
+ "readOnlyHint": true,
395
+ "destructiveHint": false,
396
+ "idempotentHint": true,
397
+ "openWorldHint": false
398
+ },
399
+ "transports": [
400
+ "stdio",
401
+ "remote"
402
+ ],
403
+ "params": {
404
+ "dossierId": "The dossier whose attachments to search.",
405
+ "keyword": "Search terms in the language of the documents.",
406
+ "limit": "Maximum number of hits, 1 to 30. Default 10."
407
+ }
408
+ },
409
+ {
410
+ "name": "add_attachment",
411
+ "title": "Add an attachment",
412
+ "description": "Upload a file from the user's machine to a dossier as a project attachment: PDF, DOCX, XLSX, PNG, JPEG, TXT, MD or CSV, 25 MB at most, 20 per dossier. Its text is extracted for read_attachment and search_in_dossier. Only upload a file the user named; never look for one.",
413
+ "annotations": {
414
+ "readOnlyHint": false,
415
+ "destructiveHint": false,
416
+ "idempotentHint": false,
417
+ "openWorldHint": false
418
+ },
419
+ "transports": [
420
+ "stdio"
421
+ ],
422
+ "params": {
423
+ "dossierId": "The dossier to attach the file to.",
424
+ "filePath": "Absolute path of the file on the machine running this server."
425
+ }
426
+ },
427
+ {
428
+ "name": "list_templates",
429
+ "title": "Dossier templates",
430
+ "description": "List the checklists the organization wrote for its types of structure: each is a set of questions with the clauses they rest on. When a new project matches one, apply it with apply_template instead of opening the questions one by one.",
431
+ "annotations": {
432
+ "readOnlyHint": true,
433
+ "destructiveHint": false,
434
+ "idempotentHint": true,
435
+ "openWorldHint": false
436
+ },
437
+ "transports": [
438
+ "stdio",
439
+ "remote"
440
+ ],
441
+ "params": {}
442
+ },
443
+ {
444
+ "name": "apply_template",
445
+ "title": "Apply a template",
446
+ "description": "Open the questions of a template in a dossier, with their options, and file every clause that resolves in the corpus as a reference under its question. A clause the organization cannot read is written into the question as something to establish. Applying twice opens nothing twice. Tell the user which questions were opened and which clauses are missing.",
447
+ "annotations": {
448
+ "readOnlyHint": false,
449
+ "destructiveHint": false,
450
+ "idempotentHint": true,
451
+ "openWorldHint": false
452
+ },
453
+ "transports": [
454
+ "stdio",
455
+ "remote"
456
+ ],
457
+ "params": {
458
+ "dossierId": "The dossier to apply the template to.",
459
+ "templateId": "The template, from list_templates."
460
+ }
461
+ },
462
+ {
463
+ "name": "ingest_status",
464
+ "title": "Check ingestion status",
465
+ "description": "Check whether a norm (by code) already exists in the workspace, how many sections it has and its coverage score (how much of the norm actually made it in). Call it before creating a document, to avoid duplicates.",
466
+ "annotations": {
467
+ "readOnlyHint": true,
468
+ "destructiveHint": false,
469
+ "idempotentHint": true,
470
+ "openWorldHint": false
471
+ },
472
+ "transports": [
473
+ "stdio",
474
+ "remote"
475
+ ],
476
+ "params": {
477
+ "code": "Norm code, e.g. \"SIA 261\"."
478
+ }
479
+ },
480
+ {
481
+ "name": "ingest_create_document",
482
+ "title": "Create norm document",
483
+ "description": "Create a draft norm document in the workspace. Returns the documentId every later ingest_* call needs. The organization must hold a licence for the norm it ingests.",
484
+ "annotations": {
485
+ "readOnlyHint": false,
486
+ "destructiveHint": false,
487
+ "idempotentHint": false,
488
+ "openWorldHint": false
489
+ },
490
+ "transports": [
491
+ "stdio",
492
+ "remote"
493
+ ],
494
+ "params": {
495
+ "code": "Norm code as printed on the cover, e.g. \"SIA 118/262\".",
496
+ "year": "Edition year.",
497
+ "title": "Full title of the norm.",
498
+ "language": "fr, de, it or en.",
499
+ "totalPages": "Page count of the source PDF.",
500
+ "edition": "Edition label as printed on the cover (\"2013\", \"2020+C1\"). Default: the year. Lets several editions of one code coexist.",
501
+ "scope": "One sentence on what the norm covers, shown in the served methodology, e.g. \"Geotechnical design: foundations, piles, anchors, retaining structures\"."
502
+ }
503
+ },
504
+ {
505
+ "name": "ingest_create_sections",
506
+ "title": "Create sections",
507
+ "description": "Insert sections into a draft document, up to 200 per call. Parent links resolve through parentNodeId within the batch and the sections already inserted. Returns a map of nodeId to sectionId; use those ids to attach formulas, tables, figures and cross-references.",
508
+ "annotations": {
509
+ "readOnlyHint": false,
510
+ "destructiveHint": false,
511
+ "idempotentHint": false,
512
+ "openWorldHint": false
513
+ },
514
+ "transports": [
515
+ "stdio",
516
+ "remote"
517
+ ],
518
+ "params": {
519
+ "documentId": "Document id from ingest_create_document.",
520
+ "sections": "The sections to insert, as the pre-pass produced them."
521
+ }
522
+ },
523
+ {
524
+ "name": "ingest_attach_formula",
525
+ "title": "Attach formula",
526
+ "description": "Attach a LaTeX formula to a section. Idempotent on the formula number: attaching the same number twice updates the formula instead of doubling it.",
527
+ "annotations": {
528
+ "readOnlyHint": false,
529
+ "destructiveHint": false,
530
+ "idempotentHint": true,
531
+ "openWorldHint": false
532
+ },
533
+ "transports": [
534
+ "stdio",
535
+ "remote"
536
+ ],
537
+ "params": {
538
+ "sectionId": "Section id from ingest_create_sections.",
539
+ "latex": "The formula in LaTeX, without dollar signs.",
540
+ "description": "What the formula computes, in one sentence.",
541
+ "formulaNumber": "The number as printed, e.g. \"(9.12)\". Empty when the norm prints none."
542
+ }
543
+ },
544
+ {
545
+ "name": "ingest_attach_table",
546
+ "title": "Attach table",
547
+ "description": "Attach a structured table to a section: headers plus rows of cells. Idempotent on the table number.",
548
+ "annotations": {
549
+ "readOnlyHint": false,
550
+ "destructiveHint": false,
551
+ "idempotentHint": true,
552
+ "openWorldHint": false
553
+ },
554
+ "transports": [
555
+ "stdio",
556
+ "remote"
557
+ ],
558
+ "params": {
559
+ "sectionId": "Section id from ingest_create_sections.",
560
+ "data": "An object with headers (string[]) and rows (string[][]).",
561
+ "caption": "The caption as printed.",
562
+ "tableNumber": "The number as printed, e.g. \"Tableau 3\"."
563
+ }
564
+ },
565
+ {
566
+ "name": "ingest_attach_cross_ref",
567
+ "title": "Attach cross-reference",
568
+ "description": "Attach a cross-reference from a section to another norm or clause. Idempotent on the target and the reference text.",
569
+ "annotations": {
570
+ "readOnlyHint": false,
571
+ "destructiveHint": false,
572
+ "idempotentHint": true,
573
+ "openWorldHint": false
574
+ },
575
+ "transports": [
576
+ "stdio",
577
+ "remote"
578
+ ],
579
+ "params": {
580
+ "sourceSectionId": "Section id the reference is made from.",
581
+ "targetDocumentCode": "Code of the norm referred to, e.g. \"SIA 263\".",
582
+ "targetSectionPath": "Section path in the target norm, when the text names one.",
583
+ "refText": "The reference as written in the text.",
584
+ "refType": "explicit when the text names the norm, implicit otherwise."
585
+ }
586
+ },
587
+ {
588
+ "name": "ingest_upload_figure",
589
+ "title": "Upload figure",
590
+ "description": "Upload a figure image (base64) and attach it to a section in one call. PNG, JPEG or WebP, 8 MB at most.",
591
+ "annotations": {
592
+ "readOnlyHint": false,
593
+ "destructiveHint": false,
594
+ "idempotentHint": false,
595
+ "openWorldHint": false
596
+ },
597
+ "transports": [
598
+ "stdio",
599
+ "remote"
600
+ ],
601
+ "params": {
602
+ "sectionId": "Section id from ingest_create_sections.",
603
+ "base64": "Base64-encoded image bytes; a data: prefix is tolerated.",
604
+ "mimeType": "image/png, image/jpeg or image/webp.",
605
+ "caption": "The caption as printed.",
606
+ "figureNumber": "The number as printed, e.g. \"Figure 12\"."
607
+ }
608
+ },
609
+ {
610
+ "name": "ingest_normalize_cross_refs",
611
+ "title": "Normalize cross-references",
612
+ "description": "Scan every section of a document and rebuild the cross-references to other norms found in its text. Idempotent; run it after the sections are in.",
613
+ "annotations": {
614
+ "readOnlyHint": false,
615
+ "destructiveHint": false,
616
+ "idempotentHint": true,
617
+ "openWorldHint": false
618
+ },
619
+ "transports": [
620
+ "stdio",
621
+ "remote"
622
+ ],
623
+ "params": {
624
+ "documentId": "Document id from ingest_create_document."
625
+ }
626
+ },
627
+ {
628
+ "name": "ingest_publish",
629
+ "title": "Publish document",
630
+ "description": "Publish a draft document so the read tools can serve it. The server first scores how much of the norm was ingested (text per page, pages under a chapter, empty leaves, enrichment) and refuses below 30/100 with the reasons: fix the ingestion rather than retry. Pass force true only when the user confirms the document really is that short. Returns the score and its flags; show them to the user.",
631
+ "annotations": {
632
+ "readOnlyHint": false,
633
+ "destructiveHint": false,
634
+ "idempotentHint": true,
635
+ "openWorldHint": false
636
+ },
637
+ "transports": [
638
+ "stdio",
639
+ "remote"
640
+ ],
641
+ "params": {
642
+ "documentId": "Document id from ingest_create_document.",
643
+ "force": "Publish even below the coverage floor. Only after the user confirmed the document is legitimately thin."
644
+ }
645
+ },
646
+ {
647
+ "name": "ingest_delete",
648
+ "title": "Delete document",
649
+ "description": "Delete a document with all its sections, figures, tables, formulas and cross-references. Irreversible; use it before re-ingesting a norm.",
650
+ "annotations": {
651
+ "readOnlyHint": false,
652
+ "destructiveHint": true,
653
+ "idempotentHint": false,
654
+ "openWorldHint": false
655
+ },
656
+ "transports": [
657
+ "stdio",
658
+ "remote"
659
+ ],
660
+ "params": {
661
+ "documentId": "Document id from ingest_create_document."
662
+ }
663
+ }
664
+ ];
665
+ export const PROMPTS = [
666
+ {
667
+ "name": "investigate-question",
668
+ "title": "Investigate a question in the norms",
669
+ "description": "Answer a technical question from the norms of this workspace, with a citation for every value, and file what is retained in the project dossier when there is one.",
670
+ "args": [
671
+ {
672
+ "name": "question",
673
+ "description": "The engineering question, as the user would ask a colleague.",
674
+ "required": true
675
+ },
676
+ {
677
+ "name": "project",
678
+ "description": "The project or dossier the question belongs to, if any.",
679
+ "required": false
680
+ }
681
+ ],
682
+ "text": "Investigate this question from the norms available in Stratta: {{question}}\n\nCall get_methodology first if it has not been called in this conversation. Use search_corpus when the norm is not obvious, then get_toc and get_subtree to find the clause, get_section to read it, and follow the references marked resolvable. Answer with a citation [CODE section path, p. N] for every value, show any calculation with its inputs, and say what the corpus does not cover.",
683
+ "optional": {
684
+ "project": "The question concerns the project \"{{project}}\": reload its dossier first (list_dossiers, then load_dossier), open a question for it if none fits, and file what is retained under that question."
685
+ }
686
+ },
687
+ {
688
+ "name": "resume-dossier",
689
+ "title": "Resume a project dossier",
690
+ "description": "Reload a project dossier, summarise its open questions and decisions, and ask what to work on next without re-deciding anything.",
691
+ "args": [
692
+ {
693
+ "name": "name",
694
+ "description": "The dossier name or reference as the user calls it.",
695
+ "required": true
696
+ }
697
+ ],
698
+ "text": "Resume the project dossier \"{{name}}\": call list_dossiers, then load_dossier on the matching one. Summarise the open questions first, then the decisions taken with the clauses they rest on, then the evidence not yet filed. Do not re-decide what is decided. Ask which open question to work on next."
699
+ },
700
+ {
701
+ "name": "check-value",
702
+ "title": "Check a value against the norms",
703
+ "description": "Find the clause that defines a value, read it, and say whether the value is right and under which conditions.",
704
+ "args": [
705
+ {
706
+ "name": "value",
707
+ "description": "The value and what it is, e.g. \"gamma_M = 1.35 for pile shaft friction\".",
708
+ "required": true
709
+ },
710
+ {
711
+ "name": "norm",
712
+ "description": "The norm code to check against, if known.",
713
+ "required": false
714
+ }
715
+ ],
716
+ "text": "Check this value against the norms available in Stratta: {{value}}\n\nFind the clause that defines it (search_corpus, or search_in_norm when the norm is known), read it with get_section, and answer whether the value is right, with the citation and the conditions the clause attaches. If the corpus does not define it, say so and name the norm where it is likely to be found.",
717
+ "optional": {
718
+ "norm": "Check against {{norm}} first."
719
+ }
720
+ }
721
+ ];
722
+ export function catalogEntry(name) {
723
+ const entry = CATALOG.find((e) => e.name === name);
724
+ if (!entry)
725
+ throw new Error(`No catalogue entry for tool "${name}"`);
726
+ return entry;
727
+ }
728
+ /** Description of one parameter, as the catalogue words it. */
729
+ export function param(tool, name) {
730
+ const text = catalogEntry(tool).params[name];
731
+ if (!text)
732
+ throw new Error(`No catalogue description for ${tool}.${name}`);
733
+ return text;
734
+ }