@stratta/mcp 1.20.1 → 1.22.0
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/README.md +27 -25
- package/dist/index.js +3 -0
- package/dist/prompts.d.ts +9 -2
- package/dist/prompts.js +5 -15
- package/dist/tools/catalog.gen.d.ts +5 -1
- package/dist/tools/catalog.gen.js +58 -19
- package/dist/tools/claude-meta.gen.d.ts +28 -0
- package/dist/tools/claude-meta.gen.js +48 -0
- package/dist/tools/read.d.ts +11 -0
- package/dist/tools/read.js +17 -0
- package/dist/tools/toolsets.gen.js +1 -1
- package/package.json +1 -1
- package/skills/consult-stratta/SKILL.md +1 -0
package/README.md
CHANGED
|
@@ -49,8 +49,8 @@ This package is what you want when:
|
|
|
49
49
|
|
|
50
50
|
## Lighter setup: `--simple`
|
|
51
51
|
|
|
52
|
-
By default the server announces all
|
|
53
|
-
schemas). `--simple` announces only the
|
|
52
|
+
By default the server announces all 60 tools (about 67,000 characters of
|
|
53
|
+
schemas). `--simple` announces only the 11 norm-reading tools (about 11,000),
|
|
54
54
|
which is enough for an agent that just reads norms:
|
|
55
55
|
|
|
56
56
|
```bash
|
|
@@ -193,23 +193,24 @@ named `E2E dossier <timestamp>` and does not delete it.
|
|
|
193
193
|
## Tools exposed
|
|
194
194
|
|
|
195
195
|
62 tools, generated from one catalogue shared with the remote connector. This
|
|
196
|
-
package announces
|
|
197
|
-
`add_attachment`, plus the
|
|
196
|
+
package announces 60 of them; the remote connector serves 61: everything minus
|
|
197
|
+
`add_attachment`, plus the two tools a host calls for the person, listed last.
|
|
198
198
|
|
|
199
199
|
**Read** (10 tools — query the norms of your workspace):
|
|
200
200
|
|
|
201
|
-
| Tool | Purpose
|
|
202
|
-
| ----------------- |
|
|
203
|
-
| `get_methodology` | Behavioural contract: persona, workflow, meta-routing hints, answer rules. Call first.
|
|
204
|
-
| `whoami` | Which organisation, role and plan this connection reads as, and how many norms it sees.
|
|
205
|
-
| `list_norms` | List the norms of your workspace, filtered by `query` (code or title words), bounded by `limit`.
|
|
206
|
-
| `get_toc` | Hierarchical TOC for a norm (default `maxDepth=1` = chapters).
|
|
207
|
-
| `get_subtree` | Drill into a chapter/section subtree (`path` + `maxDepth`).
|
|
208
|
-
| `get_section` | Full enriched content of a section (formulas, tables, figures, cross-refs).
|
|
209
|
-
| `search_in_norm` | Keyword search inside a norm.
|
|
210
|
-
| `search_corpus` | Keyword search across every norm of the workspace, grouped by norm.
|
|
211
|
-
| `get_figure` | Retrieve a figure inline (base64 ImageContent).
|
|
212
|
-
| `get_cross_refs` | Outgoing cross-refs from a section to other norms.
|
|
201
|
+
| Tool | Purpose |
|
|
202
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
203
|
+
| `get_methodology` | Behavioural contract: persona, workflow, meta-routing hints, answer rules. Call first. |
|
|
204
|
+
| `whoami` | Which organisation, role and plan this connection reads as, and how many norms it sees. |
|
|
205
|
+
| `list_norms` | List the norms of your workspace, filtered by `query` (code or title words), bounded by `limit`. |
|
|
206
|
+
| `get_toc` | Hierarchical TOC for a norm (default `maxDepth=1` = chapters). |
|
|
207
|
+
| `get_subtree` | Drill into a chapter/section subtree (`path` + `maxDepth`). |
|
|
208
|
+
| `get_section` | Full enriched content of a section (formulas, tables, figures, cross-refs). |
|
|
209
|
+
| `search_in_norm` | Keyword search inside a norm. |
|
|
210
|
+
| `search_corpus` | Keyword search across every norm of the workspace, grouped by norm. |
|
|
211
|
+
| `get_figure` | Retrieve a figure inline (base64 ImageContent). |
|
|
212
|
+
| `get_cross_refs` | Outgoing cross-refs from a section to other norms. |
|
|
213
|
+
| `verify_citation` | Check that a sentence is printed word for word in a clause before quoting it; returns the passage and the citation. |
|
|
213
214
|
|
|
214
215
|
**Site** (10 tools — what public Swiss registers know about a plot, and the site sheet as the app shows it):
|
|
215
216
|
|
|
@@ -293,13 +294,12 @@ too, and its file is the layout the Word export is rendered into.
|
|
|
293
294
|
| `ingest_publish` | Score the document and flip it to published; refuses below 30/100 unless forced. |
|
|
294
295
|
| `ingest_delete` | Delete a document and all its children. |
|
|
295
296
|
|
|
296
|
-
**Remote only** (
|
|
297
|
+
**Remote only** (2 tools, served by `https://stratta.ch/mcp` and not by this package, which draws no interface):
|
|
297
298
|
|
|
298
|
-
| Tool
|
|
299
|
-
|
|
|
300
|
-
| `show_citations`
|
|
301
|
-
| `
|
|
302
|
-
| `get_profile` | An opaque id and the organization of the connection, so a host tells two connected accounts apart. |
|
|
299
|
+
| Tool | Purpose |
|
|
300
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
301
|
+
| `show_citations` | Show the clauses an answer cites as cards (an MCP App), each quote checked word for word against the clause. |
|
|
302
|
+
| `get_profile` | An opaque id and the organization of the connection, so a host tells two connected accounts apart. |
|
|
303
303
|
|
|
304
304
|
## How agents should use it
|
|
305
305
|
|
|
@@ -333,9 +333,11 @@ contents to depth 2, code URL-encoded, e.g. `stratta://norm/SIA%20267/toc`),
|
|
|
333
333
|
`stratta://skills/{name}` (one skill as Markdown, the office version when there
|
|
334
334
|
is one). The same five are served by the remote connector.
|
|
335
335
|
|
|
336
|
-
|
|
337
|
-
`investigate-question` (question,
|
|
338
|
-
`check-value` (value, norm?)
|
|
336
|
+
Four prompts appear as slash commands in clients that support them, on the
|
|
337
|
+
local and the remote server alike: `investigate-question` (question,
|
|
338
|
+
project?), `resume-dossier` (name), `check-value` (value, norm?) and
|
|
339
|
+
`apply-method` (task, project?), which follows the method your office wrote
|
|
340
|
+
for the task.
|
|
339
341
|
|
|
340
342
|
For depositing your own documents, see the bundled **`ingest-norm` skill** —
|
|
341
343
|
a 2-phase hybrid pipeline (since 0.4.0): a Python pre-pass
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { SESSION_ID, usageDigest } from './usage.js';
|
|
|
18
18
|
import { SERVER_INSTRUCTIONS } from './tools/catalog.gen.js';
|
|
19
19
|
import { registerResources } from './resources.js';
|
|
20
20
|
import { registerPrompts } from './prompts.js';
|
|
21
|
+
import { claudeMeta } from './tools/claude-meta.gen.js';
|
|
21
22
|
import { TOOLSET_CHOICE } from './mode.js';
|
|
22
23
|
import { toolInChoice } from './tools/toolsets.gen.js';
|
|
23
24
|
/**
|
|
@@ -124,6 +125,8 @@ for (const def of [
|
|
|
124
125
|
description: def.description,
|
|
125
126
|
inputSchema: def.inputSchema,
|
|
126
127
|
annotations: def.annotations,
|
|
128
|
+
// Claude Code keeps the norm reader loaded and takes a long clause whole.
|
|
129
|
+
_meta: claudeMeta(def.name),
|
|
127
130
|
},
|
|
128
131
|
// The SDK validates `args` against `inputSchema` before this runs, so the
|
|
129
132
|
// handler receives values that already match the declared shape.
|
package/dist/prompts.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Prompts: the ways a user starts a conversation with Stratta, as slash
|
|
4
|
+
* commands in the clients that support them (Claude Code, Claude Desktop,
|
|
5
|
+
* Cursor). The remote server serves the same ones (`convex/mcpPrompts.ts`).
|
|
6
|
+
*
|
|
7
|
+
* Text and arguments come from the catalogue, so a wording change is made
|
|
8
|
+
* once. `{{arg}}` is filled with the argument; an `optional` line is appended
|
|
9
|
+
* only when its argument was given.
|
|
10
|
+
*/
|
|
4
11
|
export declare function registerPrompts(server: McpServer): void;
|
package/dist/prompts.js
CHANGED
|
@@ -1,31 +1,21 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { PROMPTS } from './tools/catalog.gen.js';
|
|
2
|
+
import { PROMPTS, renderPrompt } from './tools/catalog.gen.js';
|
|
3
3
|
import { choiceHas } from './tools/toolsets.gen.js';
|
|
4
4
|
import { TOOLSET_CHOICE } from './mode.js';
|
|
5
5
|
/**
|
|
6
|
-
* Prompts: the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Prompts: the ways a user starts a conversation with Stratta, as slash
|
|
7
|
+
* commands in the clients that support them (Claude Code, Claude Desktop,
|
|
8
|
+
* Cursor). The remote server serves the same ones (`convex/mcpPrompts.ts`).
|
|
9
9
|
*
|
|
10
10
|
* Text and arguments come from the catalogue, so a wording change is made
|
|
11
11
|
* once. `{{arg}}` is filled with the argument; an `optional` line is appended
|
|
12
12
|
* only when its argument was given.
|
|
13
13
|
*/
|
|
14
|
-
const fill = (template, args) => template.replace(/\{\{(\w+)\}\}/g, (_match, key) => String(args[key] ?? ''));
|
|
15
|
-
export function renderPrompt(p, args) {
|
|
16
|
-
let text = fill(p.text, args);
|
|
17
|
-
for (const [arg, line] of Object.entries(p.optional ?? {})) {
|
|
18
|
-
if (args[arg])
|
|
19
|
-
text += `\n${fill(line, args)}`;
|
|
20
|
-
}
|
|
21
|
-
return text;
|
|
22
|
-
}
|
|
23
14
|
export function registerPrompts(server) {
|
|
24
15
|
for (const p of PROMPTS) {
|
|
25
16
|
// A prompt that drives dossier tools is not offered without them.
|
|
26
|
-
if (p.
|
|
17
|
+
if (p.needs && !choiceHas(TOOLSET_CHOICE, p.needs))
|
|
27
18
|
continue;
|
|
28
|
-
}
|
|
29
19
|
const shape = {};
|
|
30
20
|
for (const a of p.args) {
|
|
31
21
|
const s = z.string().describe(a.description);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Toolset } from './toolsets.gen.js';
|
|
1
2
|
export type Transport = 'stdio' | 'remote';
|
|
2
3
|
export type ToolAnnotations = {
|
|
3
4
|
readOnlyHint: boolean;
|
|
@@ -13,7 +14,7 @@ export type CatalogEntry = {
|
|
|
13
14
|
transports: Transport[];
|
|
14
15
|
params: Record<string, string>;
|
|
15
16
|
};
|
|
16
|
-
export declare const SERVER_INSTRUCTIONS = "Stratta serves the norms and reference documents of the signed-in organization (technical standards, laws, ordinances, regulations, accounting standards, internal references), and only those.
|
|
17
|
+
export declare const SERVER_INSTRUCTIONS = "Stratta serves the norms and reference documents of the signed-in organization (technical standards, laws, ordinances, regulations, accounting standards, internal references), and only those. `get_methodology` returns how these documents are organized, how one is walked down to a clause and how a passage is cited; it is meant for the start of a conversation that relies on them. A norm absent from `list_norms` is not readable here.";
|
|
17
18
|
export declare const CATALOG: CatalogEntry[];
|
|
18
19
|
export type PromptArg = {
|
|
19
20
|
name: string;
|
|
@@ -27,8 +28,11 @@ export type PromptEntry = {
|
|
|
27
28
|
args: PromptArg[];
|
|
28
29
|
text: string;
|
|
29
30
|
optional?: Record<string, string>;
|
|
31
|
+
needs?: Toolset;
|
|
30
32
|
};
|
|
31
33
|
export declare const PROMPTS: PromptEntry[];
|
|
32
34
|
export declare function catalogEntry(name: string): CatalogEntry;
|
|
35
|
+
/** The message a prompt starts the conversation with, arguments filled in. */
|
|
36
|
+
export declare function renderPrompt(p: PromptEntry, args: Record<string, unknown>): string;
|
|
33
37
|
/** Description of one parameter, as the catalogue words it. */
|
|
34
38
|
export declare function param(tool: string, name: string): string;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Generated by scripts/build-mcp-catalog.mjs from convex/mcpCatalog.ts.
|
|
2
2
|
// Do not edit: change the source and run the script.
|
|
3
|
-
export const SERVER_INSTRUCTIONS = "Stratta serves the norms and reference documents of the signed-in organization (technical standards, laws, ordinances, regulations, accounting standards, internal references), and only those.
|
|
3
|
+
export const SERVER_INSTRUCTIONS = "Stratta serves the norms and reference documents of the signed-in organization (technical standards, laws, ordinances, regulations, accounting standards, internal references), and only those. `get_methodology` returns how these documents are organized, how one is walked down to a clause and how a passage is cited; it is meant for the start of a conversation that relies on them. A norm absent from `list_norms` is not readable here.";
|
|
4
4
|
export const CATALOG = [
|
|
5
5
|
{
|
|
6
6
|
"name": "get_methodology",
|
|
7
7
|
"title": "Working method",
|
|
8
|
-
"description": "
|
|
8
|
+
"description": "Return the conventions of this server: how a norm is walked from its table of contents down to a clause, how a passage is cited, and what a project dossier records. Use this at the start of a conversation that will rely on the documents of the organization.",
|
|
9
9
|
"annotations": {
|
|
10
10
|
"readOnlyHint": true,
|
|
11
11
|
"destructiveHint": false,
|
|
@@ -178,9 +178,9 @@ export const CATALOG = [
|
|
|
178
178
|
}
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
|
-
"name": "
|
|
182
|
-
"title": "
|
|
183
|
-
"description": "
|
|
181
|
+
"name": "verify_citation",
|
|
182
|
+
"title": "Verify a quote",
|
|
183
|
+
"description": "Use this before quoting a clause word for word: checks that the sentence is printed in the clause, whatever its case, accents and line breaks. Returns verified, the passage as the norm prints it and the citation to write after it. When the sentence is not in the clause, verified is false and passage is the closest sentence of the clause, to quote from instead; a changed number makes a quote fail.",
|
|
184
184
|
"annotations": {
|
|
185
185
|
"readOnlyHint": true,
|
|
186
186
|
"destructiveHint": false,
|
|
@@ -194,13 +194,14 @@ export const CATALOG = [
|
|
|
194
194
|
"params": {
|
|
195
195
|
"code": "Norm code exactly as list_norms returns it, e.g. \"SIA 261\", \"EN 1997-1\", \"CO\", \"IFRS 15\".",
|
|
196
196
|
"edition": "Edition of the norm when the organization holds several (\"2013\", \"2020\"). Default: the most recent published edition; list_norms names them.",
|
|
197
|
-
"
|
|
197
|
+
"path": "Section path as it appears in the table of contents, e.g. \"4.2.1\" or \"Annexe A\".",
|
|
198
|
+
"quote": "The sentence as it will be quoted, at least 8 characters and one sentence or two at most."
|
|
198
199
|
}
|
|
199
200
|
},
|
|
200
201
|
{
|
|
201
|
-
"name": "
|
|
202
|
-
"title": "
|
|
203
|
-
"description": "
|
|
202
|
+
"name": "get_figure",
|
|
203
|
+
"title": "Figure",
|
|
204
|
+
"description": "Return one figure of a norm as an image: diagrams, 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.",
|
|
204
205
|
"annotations": {
|
|
205
206
|
"readOnlyHint": true,
|
|
206
207
|
"destructiveHint": false,
|
|
@@ -208,16 +209,19 @@ export const CATALOG = [
|
|
|
208
209
|
"openWorldHint": false
|
|
209
210
|
},
|
|
210
211
|
"transports": [
|
|
212
|
+
"stdio",
|
|
211
213
|
"remote"
|
|
212
214
|
],
|
|
213
215
|
"params": {
|
|
214
|
-
"
|
|
216
|
+
"code": "Norm code exactly as list_norms returns it, e.g. \"SIA 261\", \"EN 1997-1\", \"CO\", \"IFRS 15\".",
|
|
217
|
+
"edition": "Edition of the norm when the organization holds several (\"2013\", \"2020\"). Default: the most recent published edition; list_norms names them.",
|
|
218
|
+
"figureId": "Figure identifier from the figures array of get_section."
|
|
215
219
|
}
|
|
216
220
|
},
|
|
217
221
|
{
|
|
218
|
-
"name": "
|
|
219
|
-
"title": "
|
|
220
|
-
"description": "
|
|
222
|
+
"name": "show_citations",
|
|
223
|
+
"title": "Show cited clauses",
|
|
224
|
+
"description": "Use this when an answer rests on clauses read with get_section and the person should be able to check them: shows those clauses as cards they can open against the norm, once per answer, in the order they were cited. It reads nothing new and does not replace get_section; only clauses actually read belong in it.",
|
|
221
225
|
"annotations": {
|
|
222
226
|
"readOnlyHint": true,
|
|
223
227
|
"destructiveHint": false,
|
|
@@ -227,7 +231,9 @@ export const CATALOG = [
|
|
|
227
231
|
"transports": [
|
|
228
232
|
"remote"
|
|
229
233
|
],
|
|
230
|
-
"params": {
|
|
234
|
+
"params": {
|
|
235
|
+
"citations": "The cited clauses, one to eight: code and path as get_section took them, the edition when the norm has several, and optionally the exact sentence quoted from the clause."
|
|
236
|
+
}
|
|
231
237
|
},
|
|
232
238
|
{
|
|
233
239
|
"name": "get_profile",
|
|
@@ -267,7 +273,7 @@ export const CATALOG = [
|
|
|
267
273
|
{
|
|
268
274
|
"name": "get_boreholes",
|
|
269
275
|
"title": "Read the boreholes of a site",
|
|
270
|
-
"description": "Read the boreholes a site scan found, nearest first, with their logged strata (depth interval, description, period), depth, year, SIA 261 ground class when logged, water table when logged, and links to the cantonal documents. Call it after get_site_context when the question turns on what the ground is actually made of. Each borehole is one observation at one point
|
|
276
|
+
"description": "Read the boreholes a site scan found, nearest first, with their logged strata (depth interval, description, period), depth, year, SIA 261 ground class when logged, water table when logged, and links to the cantonal documents. Call it after get_site_context when the question turns on what the ground is actually made of. Each borehole is one observation at one point, at the distance given; the ground between two boreholes and the design values of a layer are not in this data.",
|
|
271
277
|
"annotations": {
|
|
272
278
|
"readOnlyHint": true,
|
|
273
279
|
"destructiveHint": false,
|
|
@@ -287,7 +293,7 @@ export const CATALOG = [
|
|
|
287
293
|
{
|
|
288
294
|
"name": "get_site_context",
|
|
289
295
|
"title": "Read a site scan",
|
|
290
|
-
"description": "Read what a site scan found, grouped by topic, each value with its source and the date THE DATA was current. Returns two separate lists: what the registers settled, and what they could NOT settle, with the reason
|
|
296
|
+
"description": "Read what a site scan found, grouped by topic, each value with its source and the date THE DATA was current. Returns two separate lists: what the registers settled, and what they could NOT settle, with the reason; the second list is what a bid has to budget for. A geological description returned here is not a design value (friction angle, bearing capacity, modulus): those come from a local investigation.",
|
|
291
297
|
"annotations": {
|
|
292
298
|
"readOnlyHint": true,
|
|
293
299
|
"destructiveHint": false,
|
|
@@ -722,7 +728,7 @@ export const CATALOG = [
|
|
|
722
728
|
{
|
|
723
729
|
"name": "write_section",
|
|
724
730
|
"title": "Write a section",
|
|
725
|
-
"description": "Write one section of a deliverable: the text as it would be printed, and what it rests on. Every sentence of substance must come from a source the section lists: a decision or piece of evidence of the dossier (kind entry, ref the entry id), a fact of the site (kind fact, ref the fact key such as groundwater.level), a clause read in a norm (kind clause, ref the norm code and the section path as in \"SIA 267 s. 9.5.2\"), a page of an attachment (kind attachment, ref the id with #p12), or a reading recorded by the dossier (kind reading, ref the reading id). A section without a source is refused; when the dossier has nothing for it, pass gap true with a body that says what is missing, and the section is printed as something to establish. Replaces what the section held.
|
|
731
|
+
"description": "Write one section of a deliverable: the text as it would be printed, and what it rests on. Every sentence of substance must come from a source the section lists: a decision or piece of evidence of the dossier (kind entry, ref the entry id), a fact of the site (kind fact, ref the fact key such as groundwater.level), a clause read in a norm (kind clause, ref the norm code and the section path as in \"SIA 267 s. 9.5.2\"), a page of an attachment (kind attachment, ref the id with #p12), or a reading recorded by the dossier (kind reading, ref the reading id). A section without a source is refused; when the dossier has nothing for it, pass gap true with a body that says what is missing, and the section is printed as something to establish. Replaces what the section held.",
|
|
726
732
|
"annotations": {
|
|
727
733
|
"readOnlyHint": false,
|
|
728
734
|
"destructiveHint": false,
|
|
@@ -979,7 +985,7 @@ export const CATALOG = [
|
|
|
979
985
|
{
|
|
980
986
|
"name": "update_entry",
|
|
981
987
|
"title": "Correct an entry",
|
|
982
|
-
"description": "Correct a piece of evidence or a decision in place: its wording, its value, its confidence, or the citation (norm, section path, page, edition) when the wrong clause was cited. Pass only the fields to change; pass page null to clear it. The kind and the question do not change here (attach_entry moves an entry).
|
|
988
|
+
"description": "Correct a piece of evidence or a decision in place: its wording, its value, its confidence, or the citation (norm, section path, page, edition) when the wrong clause was cited. Pass only the fields to change; pass page null to clear it. The kind and the question do not change here (attach_entry moves an entry). Use this when the user asks for the correction.",
|
|
983
989
|
"annotations": {
|
|
984
990
|
"readOnlyHint": false,
|
|
985
991
|
"destructiveHint": false,
|
|
@@ -1260,7 +1266,8 @@ export const PROMPTS = [
|
|
|
1260
1266
|
"required": true
|
|
1261
1267
|
}
|
|
1262
1268
|
],
|
|
1263
|
-
"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."
|
|
1269
|
+
"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.",
|
|
1270
|
+
"needs": "dossiers"
|
|
1264
1271
|
},
|
|
1265
1272
|
{
|
|
1266
1273
|
"name": "check-value",
|
|
@@ -1282,6 +1289,28 @@ export const PROMPTS = [
|
|
|
1282
1289
|
"optional": {
|
|
1283
1290
|
"norm": "Check against {{norm}} first."
|
|
1284
1291
|
}
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
"name": "apply-method",
|
|
1295
|
+
"title": "Do a task the way the office does it",
|
|
1296
|
+
"description": "Carry out a task (a project basis, a verification note, a site chapter) by following the method the office has written for it.",
|
|
1297
|
+
"args": [
|
|
1298
|
+
{
|
|
1299
|
+
"name": "task",
|
|
1300
|
+
"description": "What to produce, e.g. \"the project basis for the retaining wall\".",
|
|
1301
|
+
"required": true
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
"name": "project",
|
|
1305
|
+
"description": "The project or dossier the task belongs to, if any.",
|
|
1306
|
+
"required": false
|
|
1307
|
+
}
|
|
1308
|
+
],
|
|
1309
|
+
"text": "Do this task the way the office does it: {{task}}\n\nCall list_skills and pick the method that fits the task, then get_skill on it and follow its steps in order. If no method fits, say so, name the closest one, and ask before going on without one. Every value taken from a norm keeps its citation [CODE section path, p. N].",
|
|
1310
|
+
"optional": {
|
|
1311
|
+
"project": "The task belongs to the project \"{{project}}\": reload its dossier first (list_dossiers, then load_dossier) and work from what it already holds."
|
|
1312
|
+
},
|
|
1313
|
+
"needs": "dossiers"
|
|
1285
1314
|
}
|
|
1286
1315
|
];
|
|
1287
1316
|
export function catalogEntry(name) {
|
|
@@ -1290,6 +1319,16 @@ export function catalogEntry(name) {
|
|
|
1290
1319
|
throw new Error(`No catalogue entry for tool "${name}"`);
|
|
1291
1320
|
return entry;
|
|
1292
1321
|
}
|
|
1322
|
+
const fill = (template, args) => template.replace(/\{\{(\w+)\}\}/g, (_match, key) => String(args[key] ?? ''));
|
|
1323
|
+
/** The message a prompt starts the conversation with, arguments filled in. */
|
|
1324
|
+
export function renderPrompt(p, args) {
|
|
1325
|
+
let text = fill(p.text, args);
|
|
1326
|
+
for (const [arg, line] of Object.entries(p.optional ?? {})) {
|
|
1327
|
+
if (args[arg])
|
|
1328
|
+
text += `\n${fill(line, args)}`;
|
|
1329
|
+
}
|
|
1330
|
+
return text;
|
|
1331
|
+
}
|
|
1293
1332
|
/** Description of one parameter, as the catalogue words it. */
|
|
1294
1333
|
export function param(tool, name) {
|
|
1295
1334
|
const text = catalogEntry(tool).params[name];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What Claude reads in a tool's `_meta`, on both transports.
|
|
3
|
+
*
|
|
4
|
+
* This module imports nothing: `scripts/build-mcp-catalog.mjs` copies it
|
|
5
|
+
* verbatim into the npm package (`claude-meta.gen.ts`), which is published
|
|
6
|
+
* alone and cannot import from here. A test holds the copy in step.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The tools Claude Code keeps loaded instead of deferring them.
|
|
10
|
+
*
|
|
11
|
+
* Claude Code defers every MCP tool and finds them by search: only the tool
|
|
12
|
+
* names and the server instructions load when a session starts
|
|
13
|
+
* (code.claude.com/docs/en/mcp, « Scale with MCP tool search »). The norm
|
|
14
|
+
* reader is what every conversation starts with, so it stays at hand; three
|
|
15
|
+
* to five tools, as the guidance asks, or deferral buys nothing.
|
|
16
|
+
*/
|
|
17
|
+
export declare const ALWAYS_LOADED_TOOLS: ReadonlyArray<string>;
|
|
18
|
+
/**
|
|
19
|
+
* The reads that may legitimately run long, and how long.
|
|
20
|
+
*
|
|
21
|
+
* Past 50 000 characters Claude Code writes a result to a file and hands the
|
|
22
|
+
* model a reference; a long clause with its tables is exactly that case, and
|
|
23
|
+
* the agent then cites a text it has not read. The value raises that
|
|
24
|
+
* threshold for the tool (ceiling 500 000).
|
|
25
|
+
*/
|
|
26
|
+
export declare const LARGE_RESULT_TOOLS: Readonly<Record<string, number>>;
|
|
27
|
+
/** The `_meta` of a tool, or nothing; other hosts ignore the keys. */
|
|
28
|
+
export declare function claudeMeta(name: string): Record<string, boolean | number> | undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Generated by scripts/build-mcp-catalog.mjs from convex/mcpClaudeMeta.ts.
|
|
2
|
+
// Do not edit: change the source and run the script.
|
|
3
|
+
/**
|
|
4
|
+
* What Claude reads in a tool's `_meta`, on both transports.
|
|
5
|
+
*
|
|
6
|
+
* This module imports nothing: `scripts/build-mcp-catalog.mjs` copies it
|
|
7
|
+
* verbatim into the npm package (`claude-meta.gen.ts`), which is published
|
|
8
|
+
* alone and cannot import from here. A test holds the copy in step.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The tools Claude Code keeps loaded instead of deferring them.
|
|
12
|
+
*
|
|
13
|
+
* Claude Code defers every MCP tool and finds them by search: only the tool
|
|
14
|
+
* names and the server instructions load when a session starts
|
|
15
|
+
* (code.claude.com/docs/en/mcp, « Scale with MCP tool search »). The norm
|
|
16
|
+
* reader is what every conversation starts with, so it stays at hand; three
|
|
17
|
+
* to five tools, as the guidance asks, or deferral buys nothing.
|
|
18
|
+
*/
|
|
19
|
+
export const ALWAYS_LOADED_TOOLS = [
|
|
20
|
+
'get_methodology',
|
|
21
|
+
'list_norms',
|
|
22
|
+
'search_corpus',
|
|
23
|
+
'get_toc',
|
|
24
|
+
'get_section',
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* The reads that may legitimately run long, and how long.
|
|
28
|
+
*
|
|
29
|
+
* Past 50 000 characters Claude Code writes a result to a file and hands the
|
|
30
|
+
* model a reference; a long clause with its tables is exactly that case, and
|
|
31
|
+
* the agent then cites a text it has not read. The value raises that
|
|
32
|
+
* threshold for the tool (ceiling 500 000).
|
|
33
|
+
*/
|
|
34
|
+
export const LARGE_RESULT_TOOLS = {
|
|
35
|
+
get_section: 200_000,
|
|
36
|
+
get_subtree: 200_000,
|
|
37
|
+
read_attachment: 200_000,
|
|
38
|
+
};
|
|
39
|
+
/** The `_meta` of a tool, or nothing; other hosts ignore the keys. */
|
|
40
|
+
export function claudeMeta(name) {
|
|
41
|
+
const meta = {};
|
|
42
|
+
if (ALWAYS_LOADED_TOOLS.includes(name))
|
|
43
|
+
meta['anthropic/alwaysLoad'] = true;
|
|
44
|
+
const chars = LARGE_RESULT_TOOLS[name];
|
|
45
|
+
if (chars !== undefined)
|
|
46
|
+
meta['anthropic/maxResultSizeChars'] = chars;
|
|
47
|
+
return Object.keys(meta).length > 0 ? meta : undefined;
|
|
48
|
+
}
|
package/dist/tools/read.d.ts
CHANGED
|
@@ -44,6 +44,12 @@ export declare const getFigure: import("./define.js").ToolDef<{
|
|
|
44
44
|
edition: z.ZodOptional<z.ZodString>;
|
|
45
45
|
figureId: z.ZodString;
|
|
46
46
|
}>;
|
|
47
|
+
export declare const verifyCitation: import("./define.js").ToolDef<{
|
|
48
|
+
code: z.ZodString;
|
|
49
|
+
edition: z.ZodOptional<z.ZodString>;
|
|
50
|
+
path: z.ZodString;
|
|
51
|
+
quote: z.ZodString;
|
|
52
|
+
}>;
|
|
47
53
|
export declare const readTools: (import("./define.js").ToolDef<{}> | import("./define.js").ToolDef<{
|
|
48
54
|
query: z.ZodOptional<z.ZodString>;
|
|
49
55
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
@@ -79,4 +85,9 @@ export declare const readTools: (import("./define.js").ToolDef<{}> | import("./d
|
|
|
79
85
|
code: z.ZodString;
|
|
80
86
|
edition: z.ZodOptional<z.ZodString>;
|
|
81
87
|
figureId: z.ZodString;
|
|
88
|
+
}> | import("./define.js").ToolDef<{
|
|
89
|
+
code: z.ZodString;
|
|
90
|
+
edition: z.ZodOptional<z.ZodString>;
|
|
91
|
+
path: z.ZodString;
|
|
92
|
+
quote: z.ZodString;
|
|
82
93
|
}>)[];
|
package/dist/tools/read.js
CHANGED
|
@@ -327,6 +327,22 @@ export const getFigure = defineTool({
|
|
|
327
327
|
};
|
|
328
328
|
},
|
|
329
329
|
});
|
|
330
|
+
export const verifyCitation = defineTool({
|
|
331
|
+
...headOf('verify_citation'),
|
|
332
|
+
inputSchema: {
|
|
333
|
+
code: code('verify_citation'),
|
|
334
|
+
edition: edition('verify_citation'),
|
|
335
|
+
path: path('verify_citation'),
|
|
336
|
+
quote: z.string().min(8).describe(param('verify_citation', 'quote')),
|
|
337
|
+
},
|
|
338
|
+
run: (client, args) => client.action(api._mcp.verifyCitation, {
|
|
339
|
+
apiKey: requireApiKey(),
|
|
340
|
+
code: args.code,
|
|
341
|
+
edition: args.edition,
|
|
342
|
+
path: args.path,
|
|
343
|
+
quote: args.quote,
|
|
344
|
+
}),
|
|
345
|
+
});
|
|
330
346
|
export const readTools = [
|
|
331
347
|
getMethodology,
|
|
332
348
|
whoami,
|
|
@@ -337,5 +353,6 @@ export const readTools = [
|
|
|
337
353
|
searchInNorm,
|
|
338
354
|
searchCorpus,
|
|
339
355
|
getCrossRefs,
|
|
356
|
+
verifyCitation,
|
|
340
357
|
getFigure,
|
|
341
358
|
];
|
|
@@ -22,10 +22,10 @@ export const TOOLSET_OF = {
|
|
|
22
22
|
search_in_norm: 'norms',
|
|
23
23
|
search_corpus: 'norms',
|
|
24
24
|
get_cross_refs: 'norms',
|
|
25
|
+
verify_citation: 'norms',
|
|
25
26
|
get_figure: 'norms',
|
|
26
27
|
// Shown to the person rather than read by the model (ADR 59): remote only.
|
|
27
28
|
show_citations: 'norms',
|
|
28
|
-
open_norm_reader: 'norms',
|
|
29
29
|
get_profile: 'norms',
|
|
30
30
|
list_dossiers: 'dossiers',
|
|
31
31
|
open_dossier: 'dossiers',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stratta/mcp",
|
|
3
3
|
"mcpName": "ch.stratta/mcp",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.22.0",
|
|
5
5
|
"description": "MCP server exposing your organization’s norms and reference documents (standards, laws, regulations) to any MCP client, with exact citations, via Stratta TreeRAG.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"author": "SmartFlow <hello@stratta.ch>",
|
|
@@ -27,6 +27,7 @@ When the question names a norm absent from the corpus but a norm you hold refers
|
|
|
27
27
|
## Answer contract
|
|
28
28
|
|
|
29
29
|
- Every technical statement rests on a section you read in this conversation and cites it as `[CODE § path, p. N]`, for example `[SIA 267 § 9.5.2.1, p. 63]`. A value with no clause is not an answer.
|
|
30
|
+
- A sentence quoted word for word is checked with `verify_citation` before it is written; when it is refused, quote the passage it returns.
|
|
30
31
|
- A calculation shows its inputs, each with its citation, its formula in LaTeX (`$$...$$`) with every symbol defined and its unit (kN, MPa, CHF, %).
|
|
31
32
|
- Before answering, check: each value has its clause; each clause was read, not remembered from a summary; the resolvable references were followed; what the corpus does not cover is named, with the norm or clause where it is likely to be found.
|
|
32
33
|
- When several norms are cited, group by norm under headings: principle, then method, then conditions of use.
|