@stratta/mcp 1.20.0 → 1.21.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 +5 -3
- 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 +40 -7
- package/dist/tools/claude-meta.gen.d.ts +28 -0
- package/dist/tools/claude-meta.gen.js +48 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -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,
|
|
@@ -267,7 +267,7 @@ export const CATALOG = [
|
|
|
267
267
|
{
|
|
268
268
|
"name": "get_boreholes",
|
|
269
269
|
"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
|
|
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, at the distance given; the ground between two boreholes and the design values of a layer are not in this data.",
|
|
271
271
|
"annotations": {
|
|
272
272
|
"readOnlyHint": true,
|
|
273
273
|
"destructiveHint": false,
|
|
@@ -287,7 +287,7 @@ export const CATALOG = [
|
|
|
287
287
|
{
|
|
288
288
|
"name": "get_site_context",
|
|
289
289
|
"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
|
|
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; 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
291
|
"annotations": {
|
|
292
292
|
"readOnlyHint": true,
|
|
293
293
|
"destructiveHint": false,
|
|
@@ -722,7 +722,7 @@ export const CATALOG = [
|
|
|
722
722
|
{
|
|
723
723
|
"name": "write_section",
|
|
724
724
|
"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.
|
|
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.",
|
|
726
726
|
"annotations": {
|
|
727
727
|
"readOnlyHint": false,
|
|
728
728
|
"destructiveHint": false,
|
|
@@ -979,7 +979,7 @@ export const CATALOG = [
|
|
|
979
979
|
{
|
|
980
980
|
"name": "update_entry",
|
|
981
981
|
"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).
|
|
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). Use this when the user asks for the correction.",
|
|
983
983
|
"annotations": {
|
|
984
984
|
"readOnlyHint": false,
|
|
985
985
|
"destructiveHint": false,
|
|
@@ -1260,7 +1260,8 @@ export const PROMPTS = [
|
|
|
1260
1260
|
"required": true
|
|
1261
1261
|
}
|
|
1262
1262
|
],
|
|
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."
|
|
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.",
|
|
1264
|
+
"needs": "dossiers"
|
|
1264
1265
|
},
|
|
1265
1266
|
{
|
|
1266
1267
|
"name": "check-value",
|
|
@@ -1282,6 +1283,28 @@ export const PROMPTS = [
|
|
|
1282
1283
|
"optional": {
|
|
1283
1284
|
"norm": "Check against {{norm}} first."
|
|
1284
1285
|
}
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
"name": "apply-method",
|
|
1289
|
+
"title": "Do a task the way the office does it",
|
|
1290
|
+
"description": "Carry out a task (a project basis, a verification note, a site chapter) by following the method the office has written for it.",
|
|
1291
|
+
"args": [
|
|
1292
|
+
{
|
|
1293
|
+
"name": "task",
|
|
1294
|
+
"description": "What to produce, e.g. \"the project basis for the retaining wall\".",
|
|
1295
|
+
"required": true
|
|
1296
|
+
},
|
|
1297
|
+
{
|
|
1298
|
+
"name": "project",
|
|
1299
|
+
"description": "The project or dossier the task belongs to, if any.",
|
|
1300
|
+
"required": false
|
|
1301
|
+
}
|
|
1302
|
+
],
|
|
1303
|
+
"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].",
|
|
1304
|
+
"optional": {
|
|
1305
|
+
"project": "The task belongs to the project \"{{project}}\": reload its dossier first (list_dossiers, then load_dossier) and work from what it already holds."
|
|
1306
|
+
},
|
|
1307
|
+
"needs": "dossiers"
|
|
1285
1308
|
}
|
|
1286
1309
|
];
|
|
1287
1310
|
export function catalogEntry(name) {
|
|
@@ -1290,6 +1313,16 @@ export function catalogEntry(name) {
|
|
|
1290
1313
|
throw new Error(`No catalogue entry for tool "${name}"`);
|
|
1291
1314
|
return entry;
|
|
1292
1315
|
}
|
|
1316
|
+
const fill = (template, args) => template.replace(/\{\{(\w+)\}\}/g, (_match, key) => String(args[key] ?? ''));
|
|
1317
|
+
/** The message a prompt starts the conversation with, arguments filled in. */
|
|
1318
|
+
export function renderPrompt(p, args) {
|
|
1319
|
+
let text = fill(p.text, args);
|
|
1320
|
+
for (const [arg, line] of Object.entries(p.optional ?? {})) {
|
|
1321
|
+
if (args[arg])
|
|
1322
|
+
text += `\n${fill(line, args)}`;
|
|
1323
|
+
}
|
|
1324
|
+
return text;
|
|
1325
|
+
}
|
|
1293
1326
|
/** Description of one parameter, as the catalogue words it. */
|
|
1294
1327
|
export function param(tool, name) {
|
|
1295
1328
|
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/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.21.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>",
|
|
@@ -42,14 +42,14 @@
|
|
|
42
42
|
"prepublishOnly": "npm run build"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
46
46
|
"convex": "^1.42.3",
|
|
47
|
-
"zod": "^4.
|
|
47
|
+
"zod": "^4.6.7"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^26.1.1",
|
|
51
51
|
"typescript": "^6.0.3",
|
|
52
|
-
"vitest": "^4.1.
|
|
52
|
+
"vitest": "^4.1.12"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=20"
|