@stratta/mcp 0.13.1 → 0.15.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 +47 -28
- package/dist/confirm.d.ts +10 -1
- package/dist/confirm.js +46 -24
- package/dist/errors.js +40 -0
- package/dist/tools/catalog.gen.js +380 -2
- package/dist/tools/dossier.d.ts +136 -0
- package/dist/tools/dossier.js +282 -0
- package/dist/tools/ingest.d.ts +4 -0
- package/dist/tools/ingest.js +12 -3
- package/dist/tools/site.d.ts +46 -0
- package/dist/tools/site.js +121 -1
- package/package.json +1 -1
- package/skills/consult-stratta/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ https://stratta.ch/mcp
|
|
|
34
34
|
That is the shorter path, the one the dashboard walks you through for Claude,
|
|
35
35
|
ChatGPT, Claude Code, Codex, Cursor, VS Code, Gemini CLI and Windsurf, and the
|
|
36
36
|
only one that works in an agent running in the cloud (claude.ai, ChatGPT). It
|
|
37
|
-
serves
|
|
37
|
+
serves 54 of the 55 tools below, **ingestion included**: the pre-pass script is
|
|
38
38
|
downloaded from https://stratta.ch/ingest-prepass.py when it is not on disk.
|
|
39
39
|
See https://stratta.ch/docs/en/guides/connect-remote.
|
|
40
40
|
|
|
@@ -176,8 +176,8 @@ named `E2E dossier <timestamp>` and does not delete it.
|
|
|
176
176
|
|
|
177
177
|
## Tools exposed
|
|
178
178
|
|
|
179
|
-
|
|
180
|
-
serves the same
|
|
179
|
+
55 tools, generated from one catalogue shared with the remote connector (which
|
|
180
|
+
serves the same 54, everything minus `add_attachment`).
|
|
181
181
|
|
|
182
182
|
**Read** (10 tools — query the norms of your workspace):
|
|
183
183
|
|
|
@@ -194,31 +194,50 @@ serves the same 33 minus `add_attachment`).
|
|
|
194
194
|
| `get_figure` | Retrieve a figure inline (base64 ImageContent). |
|
|
195
195
|
| `get_cross_refs` | Outgoing cross-refs from a section to other norms. |
|
|
196
196
|
|
|
197
|
-
**Site** (
|
|
198
|
-
|
|
199
|
-
| Tool
|
|
200
|
-
|
|
|
201
|
-
| `scan_site`
|
|
202
|
-
| `get_site_context`
|
|
203
|
-
| `get_boreholes`
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
|
208
|
-
|
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
|
215
|
-
|
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
219
|
-
| `
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
197
|
+
**Site** (10 tools — what public Swiss registers know about a plot, and the site sheet as the app shows it):
|
|
198
|
+
|
|
199
|
+
| Tool | Purpose |
|
|
200
|
+
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
201
|
+
| `scan_site` | Collect municipality, parcel, elevation, geology, nearby boreholes, polluted sites, hazards and noise class around a Swiss address. |
|
|
202
|
+
| `get_site_context` | Read what the scan settled, and — separately — what it could not, with the reason. |
|
|
203
|
+
| `get_boreholes` | Read the boreholes nearest the site with their logged strata, SIA 261 ground class, water table, and links to cantonal documents. |
|
|
204
|
+
| `list_sites` | The site sheets of the organisation, with their dossier, canton and scan status. |
|
|
205
|
+
| `get_site` | The sheet as the app shows it: every fact with its key, status, source, notice and attribution, and the scan with its sources. |
|
|
206
|
+
| `get_site_section` | The section along the axis: terrain, modelled bedrock, geology bands and the boreholes of the corridor with their logs. |
|
|
207
|
+
| `list_site_overlays` | The map areas around the point (polluted sites, hazards, water protection), with distance and coverage of the site point. |
|
|
208
|
+
| `get_groundwater_contours` | The cantonal piezometric contours stored by the scan, with the aquifer name and elevation. |
|
|
209
|
+
| `list_site_photos` | The photos uploaded on the sheet, with position, direction, instant and note. No image, no link. |
|
|
210
|
+
| `list_site_neighbours` | The organisation's other sheets within a radius, with the distance and the geological unit their scan established. |
|
|
211
|
+
|
|
212
|
+
**Dossier** (25 tools — keep what was decided on a project, and everything the dossier page can do to a question):
|
|
213
|
+
|
|
214
|
+
| Tool | Purpose |
|
|
215
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
216
|
+
| `list_dossiers` | Your organisation's dossiers, most recently touched first, with open-question counts and site id. |
|
|
217
|
+
| `open_dossier` | Open a project's dossier, creating it if needed. Idempotent on the name. |
|
|
218
|
+
| `open_question` | Open one question to settle, with optional named options. Idempotent on the title. |
|
|
219
|
+
| `save_finding` | Record one piece of evidence: a cited article, a retained value and why, an observation. |
|
|
220
|
+
| `record_decision` | Settle a question with a decision the engineer has confirmed, and the retained option. |
|
|
221
|
+
| `load_dossier` | Reload everything: questions with their evidence and decisions, open ones first. |
|
|
222
|
+
| `resolve_question` | Close a question without a decision, or reopen one. The evidence stays. |
|
|
223
|
+
| `list_attachments` | The project attachments of a dossier: site reports, borehole logs, minutes, data sheets. |
|
|
224
|
+
| `read_attachment` | Read an attachment's text as Markdown, page by page. |
|
|
225
|
+
| `search_in_dossier` | Full-text search over a dossier's attachments, with the page of each hit. |
|
|
226
|
+
| `add_attachment` | Upload a file from the user's machine to a dossier (PDF, DOCX, XLSX, images, text). Local server only. |
|
|
227
|
+
| `list_templates` | The checklists the organisation wrote for its types of structure. |
|
|
228
|
+
| `apply_template` | Open a template's questions in a dossier and file the clauses that resolve in the corpus. |
|
|
229
|
+
| `list_questions` | The questions of a dossier, paginated, with status, assignee, due date, options and decision title. |
|
|
230
|
+
| `get_question` | One question in full: options, decision, evidence with every citation field, comments with authors. |
|
|
231
|
+
| `get_dossier_activity` | The history of a dossier, most recent first, paginated: who did what, from the app or an agent. |
|
|
232
|
+
| `list_exports` | The verification notes and journals exported from a dossier, with hash and trusted timestamp. |
|
|
233
|
+
| `update_question` | Reword a question, assign it by e-mail address, set or clear its due date. |
|
|
234
|
+
| `add_option` | Add one way of settling a question. Idempotent on the name. |
|
|
235
|
+
| `update_option` | Rename or describe an option, or mark it retained (the others are released). |
|
|
236
|
+
| `delete_option` | Remove an option; its evidence stays on the question. Asks the user first. |
|
|
237
|
+
| `delete_question` | Delete a question opened by mistake; its evidence stays, unfiled. Asks the user first. |
|
|
238
|
+
| `add_comment` | Leave a signed remark on a dossier, a question or an entry. |
|
|
239
|
+
| `update_entry` | Correct an entry's wording, value, confidence or citation in place. |
|
|
240
|
+
| `attach_entry` | File an entry under a question and optionally an option, or unfile it. |
|
|
222
241
|
|
|
223
242
|
A dossier is read, annotated, reviewed and exported from
|
|
224
243
|
[stratta.ch/dossiers](https://stratta.ch/dossiers).
|
package/dist/confirm.d.ts
CHANGED
|
@@ -9,7 +9,16 @@ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
|
9
9
|
* `confirmed: true` when it does not. The Convex function never sees the
|
|
10
10
|
* difference.
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
type Confirmation = {
|
|
13
|
+
/** What is at stake, as the dialog names it. */
|
|
14
|
+
what: string;
|
|
15
|
+
/** The one line the user reads, built from the arguments. */
|
|
16
|
+
summary: (args: Record<string, unknown>) => string;
|
|
17
|
+
/** The question asked. A removal never asks "Record it". */
|
|
18
|
+
prompt: string;
|
|
19
|
+
};
|
|
20
|
+
export declare const CONFIRMED_TOOLS: Record<string, Confirmation>;
|
|
13
21
|
export declare const CONFIRMATION_HINT: string;
|
|
14
22
|
/** `null` when the call may proceed, a tool result to return otherwise. */
|
|
15
23
|
export declare function confirmWithUser(server: Pick<Server, 'getClientCapabilities' | 'elicitInput'>, toolName: string, args: Record<string, unknown>): Promise<CallToolResult | null>;
|
|
24
|
+
export {};
|
package/dist/confirm.js
CHANGED
|
@@ -1,32 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*/
|
|
1
|
+
const decisionSummary = (args) => [
|
|
2
|
+
args.title,
|
|
3
|
+
args.value ? `= ${String(args.value)}` : null,
|
|
4
|
+
args.retainedOption ? `(option ${String(args.retainedOption)})` : null,
|
|
5
|
+
]
|
|
6
|
+
.filter(Boolean)
|
|
7
|
+
.map(String)
|
|
8
|
+
.join(' ');
|
|
10
9
|
export const CONFIRMED_TOOLS = {
|
|
11
|
-
record_decision:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
: '
|
|
10
|
+
record_decision: {
|
|
11
|
+
what: 'Decision',
|
|
12
|
+
summary: decisionSummary,
|
|
13
|
+
prompt: 'Record it in the dossier?',
|
|
14
|
+
},
|
|
15
|
+
resolve_question: {
|
|
16
|
+
what: 'Question',
|
|
17
|
+
summary: (args) => args.resolved === false
|
|
18
|
+
? 'reopen this question'
|
|
19
|
+
: 'close this question without a decision',
|
|
20
|
+
prompt: 'Record it in the dossier?',
|
|
21
|
+
},
|
|
22
|
+
// The two deletions of 0.14.0. The gate only sees ids, so the line says
|
|
23
|
+
// what goes and what stays rather than naming the row.
|
|
24
|
+
delete_question: {
|
|
25
|
+
what: 'Question',
|
|
26
|
+
summary: () => 'delete this question with its options, its comments and its decision; ' +
|
|
27
|
+
'its evidence stays in the dossier, unfiled',
|
|
28
|
+
prompt: 'Delete it?',
|
|
29
|
+
},
|
|
30
|
+
delete_option: {
|
|
31
|
+
what: 'Option',
|
|
32
|
+
summary: () => 'remove this option; the evidence filed under it stays on the question',
|
|
33
|
+
prompt: 'Remove it?',
|
|
34
|
+
},
|
|
35
|
+
// The third deletion, gated since 0.15.0 on both transports: it is the one
|
|
36
|
+
// that empties a corpus rather than a dossier.
|
|
37
|
+
ingest_delete: {
|
|
38
|
+
what: 'Document',
|
|
39
|
+
summary: () => 'delete this document with all its sections, figures, tables, formulas ' +
|
|
40
|
+
'and cross-references; the dossiers that cite it keep their citations, ' +
|
|
41
|
+
'pointing at a document that no longer exists',
|
|
42
|
+
prompt: 'Delete it?',
|
|
43
|
+
},
|
|
22
44
|
};
|
|
23
45
|
export const CONFIRMATION_HINT = 'Not recorded: this client cannot ask the user directly. Confirm the ' +
|
|
24
46
|
'decision with the user in the conversation, then call again with ' +
|
|
25
47
|
'confirmed: true.';
|
|
26
48
|
/** `null` when the call may proceed, a tool result to return otherwise. */
|
|
27
49
|
export async function confirmWithUser(server, toolName, args) {
|
|
28
|
-
const
|
|
29
|
-
if (!
|
|
50
|
+
const confirmation = CONFIRMED_TOOLS[toolName];
|
|
51
|
+
if (!confirmation || args.confirmed === true)
|
|
30
52
|
return null;
|
|
31
53
|
if (!server.getClientCapabilities()?.elicitation) {
|
|
32
54
|
return {
|
|
@@ -37,10 +59,10 @@ export async function confirmWithUser(server, toolName, args) {
|
|
|
37
59
|
}
|
|
38
60
|
const result = await server.elicitInput({
|
|
39
61
|
mode: 'form',
|
|
40
|
-
message: `${
|
|
62
|
+
message: `${confirmation.what}: ${confirmation.summary(args)}\n\n${confirmation.prompt}`,
|
|
41
63
|
requestedSchema: {
|
|
42
64
|
type: 'object',
|
|
43
|
-
properties: { confirm: { type: 'boolean', title: '
|
|
65
|
+
properties: { confirm: { type: 'boolean', title: 'Confirm' } },
|
|
44
66
|
required: ['confirm'],
|
|
45
67
|
},
|
|
46
68
|
});
|
package/dist/errors.js
CHANGED
|
@@ -47,6 +47,7 @@ const HELP = {
|
|
|
47
47
|
RATE_LIMITED: `${DOCS}/resources/troubleshooting`,
|
|
48
48
|
DOSSIER_NOT_FOUND: `${DOCS}/mcp/dossier-tools`,
|
|
49
49
|
DOSSIER_CLOSED: `${DOCS}/guides/suivre-un-projet`,
|
|
50
|
+
READ_ONLY: `${DOCS}/account/plans`,
|
|
50
51
|
ENTRY_NOT_FOUND: `${DOCS}/mcp/dossier-tools`,
|
|
51
52
|
TOO_MANY_DOSSIERS: `${DOCS}/mcp/dossier-tools`,
|
|
52
53
|
TOO_MANY_ENTRIES: `${DOCS}/mcp/dossier-tools`,
|
|
@@ -129,6 +130,45 @@ export function formatToolError(err) {
|
|
|
129
130
|
"Cette entrée n'existe pas, ou elle appartient à un autre dossier.",
|
|
130
131
|
'Rechargez le dossier avec `load_dossier` pour obtenir des identifiants à jour.',
|
|
131
132
|
]);
|
|
133
|
+
case 'QUESTION_NOT_FOUND':
|
|
134
|
+
return withHelp(data, [
|
|
135
|
+
"Cette question n'existe pas dans votre organisation, ou elle appartient à un autre dossier.",
|
|
136
|
+
'Appelez `list_questions` sur le dossier pour obtenir des identifiants à jour.',
|
|
137
|
+
]);
|
|
138
|
+
case 'OPTION_NOT_FOUND':
|
|
139
|
+
return withHelp(data, [
|
|
140
|
+
"Cette option n'existe pas, ou elle appartient à une autre question.",
|
|
141
|
+
'Appelez `get_question` pour lire les options et leurs identifiants.',
|
|
142
|
+
]);
|
|
143
|
+
case 'OPTION_WITHOUT_QUESTION':
|
|
144
|
+
return withHelp(data, [
|
|
145
|
+
'Une option se rattache à une question : donnez `questionId` avec `optionId`.',
|
|
146
|
+
]);
|
|
147
|
+
case 'DECISION_IS_BOUND':
|
|
148
|
+
return withHelp(data, [
|
|
149
|
+
'Une décision appartient à sa question et ne se déplace pas. Corrigez-la avec `update_entry`, ou rouvrez la question avec `resolve_question` (resolved: false).',
|
|
150
|
+
]);
|
|
151
|
+
case 'NOT_A_MEMBER':
|
|
152
|
+
return withHelp(data, [
|
|
153
|
+
"Cette adresse ne correspond à aucun membre de l'organisation. Demandez à l'utilisateur l'adresse exacte, ou laissez la question sans responsable.",
|
|
154
|
+
]);
|
|
155
|
+
case 'INVALID_DATE':
|
|
156
|
+
return withHelp(data, [
|
|
157
|
+
'La date attendue est un jour ISO, par exemple « 2026-10-15 ».',
|
|
158
|
+
]);
|
|
159
|
+
case 'SITE_NOT_FOUND':
|
|
160
|
+
return withHelp(data, [
|
|
161
|
+
"Cette fiche de site n'existe pas dans votre organisation.",
|
|
162
|
+
'Appelez `list_sites`, ou lisez `siteId` sur la ligne du dossier dans `list_dossiers`.',
|
|
163
|
+
]);
|
|
164
|
+
case 'TOO_MANY_QUESTIONS':
|
|
165
|
+
return withHelp(data, [
|
|
166
|
+
'Ce dossier a atteint son plafond de questions. Ne réessayez pas ; closez ou supprimez celles qui ne comptent plus.',
|
|
167
|
+
]);
|
|
168
|
+
case 'TOO_MANY_OPTIONS':
|
|
169
|
+
return withHelp(data, [
|
|
170
|
+
'Cette question a déjà dix options, le maximum. Ne réessayez pas ; retirez-en une avec `delete_option` si une nouvelle est nécessaire.',
|
|
171
|
+
]);
|
|
132
172
|
case 'TOO_MANY_DOSSIERS':
|
|
133
173
|
return withHelp(data, [
|
|
134
174
|
'Cette organisation a atteint son plafond de dossiers.',
|