@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 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 35 of the 36 tools below, **ingestion included**: the pre-pass script is
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
- 36 tools, generated from one catalogue shared with the remote connector (which
180
- serves the same 33 minus `add_attachment`).
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** (3 tools — what public Swiss registers know about a plot):
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
-
205
- **Dossier** (13 tools — keep what was decided on a project):
206
-
207
- | Tool | Purpose |
208
- | ------------------- | ------------------------------------------------------------------------------------------------------ |
209
- | `list_dossiers` | Your organisation's dossiers, most recently touched first, with open-question counts. |
210
- | `open_dossier` | Open a project's dossier, creating it if needed. Idempotent on the name. |
211
- | `open_question` | Open one question to settle, with optional named options. Idempotent on the title. |
212
- | `save_finding` | Record one piece of evidence: a cited article, a retained value and why, an observation. |
213
- | `record_decision` | Settle a question with a decision the engineer has confirmed, and the retained option. |
214
- | `load_dossier` | Reload everything: questions with their evidence and decisions, open ones first. |
215
- | `resolve_question` | Close a question without a decision, or reopen one. The evidence stays. |
216
- | `list_attachments` | The project attachments of a dossier: site reports, borehole logs, minutes, data sheets. |
217
- | `read_attachment` | Read an attachment's text as Markdown, page by page. |
218
- | `search_in_dossier` | Full-text search over a dossier's attachments, with the page of each hit. |
219
- | `add_attachment` | Upload a file from the user's machine to a dossier (PDF, DOCX, XLSX, images, text). Local server only. |
220
- | `list_templates` | The checklists the organisation wrote for its types of structure. |
221
- | `apply_template` | Open a template's questions in a dossier and file the clauses that resolve in the corpus. |
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
- export declare const CONFIRMED_TOOLS: Record<string, (args: Record<string, unknown>) => string>;
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
- * The confirmation step before a decision is written, on stdio.
3
- *
4
- * Same contract as the remote gate: `confirmed: true` in the arguments means
5
- * the agent already asked the user; otherwise the client is asked through an
6
- * elicitation when it supports one, and told to come back with
7
- * `confirmed: true` when it does not. The Convex function never sees the
8
- * difference.
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: (args) => [
12
- args.title,
13
- args.value ? `= ${String(args.value)}` : null,
14
- args.retainedOption ? `(option ${String(args.retainedOption)})` : null,
15
- ]
16
- .filter(Boolean)
17
- .map(String)
18
- .join(' '),
19
- resolve_question: (args) => args.resolved === false
20
- ? 'reopen this question'
21
- : 'close this question without a decision',
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 summary = CONFIRMED_TOOLS[toolName];
29
- if (!summary || args.confirmed === true)
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: `${toolName === 'record_decision' ? 'Decision' : 'Question'}: ${summary(args)}\n\nRecord it in the dossier?`,
62
+ message: `${confirmation.what}: ${confirmation.summary(args)}\n\n${confirmation.prompt}`,
41
63
  requestedSchema: {
42
64
  type: 'object',
43
- properties: { confirm: { type: 'boolean', title: 'Record it' } },
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.',