@stratta/mcp 0.9.8 → 0.12.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.
@@ -4,14 +4,37 @@ export declare const openDossier: import("./define.js").ToolDef<{
4
4
  name: z.ZodString;
5
5
  reference: z.ZodOptional<z.ZodString>;
6
6
  }>;
7
+ export declare const openQuestion: import("./define.js").ToolDef<{
8
+ dossierId: z.ZodString;
9
+ title: z.ZodString;
10
+ body: z.ZodOptional<z.ZodString>;
11
+ options: z.ZodOptional<z.ZodArray<z.ZodString>>;
12
+ }>;
7
13
  export declare const saveFinding: import("./define.js").ToolDef<{
14
+ attachmentId: z.ZodOptional<z.ZodString>;
15
+ attachmentPage: z.ZodOptional<z.ZodNumber>;
16
+ title: z.ZodString;
17
+ detail: z.ZodOptional<z.ZodString>;
18
+ value: z.ZodOptional<z.ZodString>;
19
+ confidence: z.ZodOptional<z.ZodEnum<{
20
+ established: "established";
21
+ judgement: "judgement";
22
+ to_confirm: "to_confirm";
23
+ }>>;
24
+ normCode: z.ZodOptional<z.ZodString>;
25
+ sectionPath: z.ZodOptional<z.ZodString>;
26
+ page: z.ZodOptional<z.ZodNumber>;
27
+ normEdition: z.ZodOptional<z.ZodString>;
8
28
  dossierId: z.ZodString;
29
+ questionId: z.ZodOptional<z.ZodString>;
9
30
  kind: z.ZodEnum<{
10
31
  reference: "reference";
11
32
  hypothesis: "hypothesis";
12
33
  observation: "observation";
13
- question: "question";
14
34
  }>;
35
+ }>;
36
+ export declare const recordDecision: import("./define.js").ToolDef<{
37
+ confirmed: z.ZodOptional<z.ZodBoolean>;
15
38
  title: z.ZodString;
16
39
  detail: z.ZodOptional<z.ZodString>;
17
40
  value: z.ZodOptional<z.ZodString>;
@@ -23,26 +46,74 @@ export declare const saveFinding: import("./define.js").ToolDef<{
23
46
  normCode: z.ZodOptional<z.ZodString>;
24
47
  sectionPath: z.ZodOptional<z.ZodString>;
25
48
  page: z.ZodOptional<z.ZodNumber>;
49
+ normEdition: z.ZodOptional<z.ZodString>;
50
+ questionId: z.ZodString;
51
+ retainedOption: z.ZodOptional<z.ZodString>;
26
52
  }>;
27
53
  export declare const loadDossier: import("./define.js").ToolDef<{
28
54
  dossierId: z.ZodOptional<z.ZodString>;
29
55
  name: z.ZodOptional<z.ZodString>;
30
56
  }>;
31
57
  export declare const resolveQuestion: import("./define.js").ToolDef<{
32
- entryId: z.ZodString;
58
+ questionId: z.ZodString;
33
59
  resolved: z.ZodOptional<z.ZodBoolean>;
60
+ confirmed: z.ZodOptional<z.ZodBoolean>;
61
+ }>;
62
+ export declare const listAttachments: import("./define.js").ToolDef<{
63
+ dossierId: z.ZodString;
64
+ }>;
65
+ export declare const readAttachment: import("./define.js").ToolDef<{
66
+ attachmentId: z.ZodString;
67
+ from: z.ZodOptional<z.ZodNumber>;
68
+ to: z.ZodOptional<z.ZodNumber>;
69
+ maxChars: z.ZodOptional<z.ZodNumber>;
70
+ }>;
71
+ export declare const searchInDossier: import("./define.js").ToolDef<{
72
+ dossierId: z.ZodString;
73
+ keyword: z.ZodString;
74
+ limit: z.ZodOptional<z.ZodNumber>;
75
+ }>;
76
+ export declare const addAttachment: import("./define.js").ToolDef<{
77
+ dossierId: z.ZodString;
78
+ filePath: z.ZodString;
79
+ }>;
80
+ export declare const listTemplates: import("./define.js").ToolDef<{}>;
81
+ export declare const applyTemplate: import("./define.js").ToolDef<{
82
+ dossierId: z.ZodString;
83
+ templateId: z.ZodString;
34
84
  }>;
35
85
  export declare const dossierTools: (import("./define.js").ToolDef<{}> | import("./define.js").ToolDef<{
36
86
  name: z.ZodString;
37
87
  reference: z.ZodOptional<z.ZodString>;
38
88
  }> | import("./define.js").ToolDef<{
39
89
  dossierId: z.ZodString;
90
+ title: z.ZodString;
91
+ body: z.ZodOptional<z.ZodString>;
92
+ options: z.ZodOptional<z.ZodArray<z.ZodString>>;
93
+ }> | import("./define.js").ToolDef<{
94
+ attachmentId: z.ZodOptional<z.ZodString>;
95
+ attachmentPage: z.ZodOptional<z.ZodNumber>;
96
+ title: z.ZodString;
97
+ detail: z.ZodOptional<z.ZodString>;
98
+ value: z.ZodOptional<z.ZodString>;
99
+ confidence: z.ZodOptional<z.ZodEnum<{
100
+ established: "established";
101
+ judgement: "judgement";
102
+ to_confirm: "to_confirm";
103
+ }>>;
104
+ normCode: z.ZodOptional<z.ZodString>;
105
+ sectionPath: z.ZodOptional<z.ZodString>;
106
+ page: z.ZodOptional<z.ZodNumber>;
107
+ normEdition: z.ZodOptional<z.ZodString>;
108
+ dossierId: z.ZodString;
109
+ questionId: z.ZodOptional<z.ZodString>;
40
110
  kind: z.ZodEnum<{
41
111
  reference: "reference";
42
112
  hypothesis: "hypothesis";
43
113
  observation: "observation";
44
- question: "question";
45
114
  }>;
115
+ }> | import("./define.js").ToolDef<{
116
+ confirmed: z.ZodOptional<z.ZodBoolean>;
46
117
  title: z.ZodString;
47
118
  detail: z.ZodOptional<z.ZodString>;
48
119
  value: z.ZodOptional<z.ZodString>;
@@ -54,10 +125,31 @@ export declare const dossierTools: (import("./define.js").ToolDef<{}> | import("
54
125
  normCode: z.ZodOptional<z.ZodString>;
55
126
  sectionPath: z.ZodOptional<z.ZodString>;
56
127
  page: z.ZodOptional<z.ZodNumber>;
128
+ normEdition: z.ZodOptional<z.ZodString>;
129
+ questionId: z.ZodString;
130
+ retainedOption: z.ZodOptional<z.ZodString>;
57
131
  }> | import("./define.js").ToolDef<{
58
132
  dossierId: z.ZodOptional<z.ZodString>;
59
133
  name: z.ZodOptional<z.ZodString>;
60
134
  }> | import("./define.js").ToolDef<{
61
- entryId: z.ZodString;
135
+ questionId: z.ZodString;
62
136
  resolved: z.ZodOptional<z.ZodBoolean>;
137
+ confirmed: z.ZodOptional<z.ZodBoolean>;
138
+ }> | import("./define.js").ToolDef<{
139
+ dossierId: z.ZodString;
140
+ }> | import("./define.js").ToolDef<{
141
+ attachmentId: z.ZodString;
142
+ from: z.ZodOptional<z.ZodNumber>;
143
+ to: z.ZodOptional<z.ZodNumber>;
144
+ maxChars: z.ZodOptional<z.ZodNumber>;
145
+ }> | import("./define.js").ToolDef<{
146
+ dossierId: z.ZodString;
147
+ keyword: z.ZodString;
148
+ limit: z.ZodOptional<z.ZodNumber>;
149
+ }> | import("./define.js").ToolDef<{
150
+ dossierId: z.ZodString;
151
+ filePath: z.ZodString;
152
+ }> | import("./define.js").ToolDef<{
153
+ dossierId: z.ZodString;
154
+ templateId: z.ZodString;
63
155
  }>)[];
@@ -1,32 +1,58 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { basename, extname } from 'node:path';
1
3
  import { z } from 'zod';
2
4
  import { api } from '../client.js';
3
5
  import { requireApiKey } from '../auth.js';
6
+ import { catalogEntry, param } from './catalog.gen.js';
4
7
  import { defineTool } from './define.js';
5
8
  /**
6
- * The five dossier tools: how the work survives the conversation.
9
+ * The seven dossier tools: how the work survives the conversation.
7
10
  *
8
- * Everything else here reads a norm. These write down what was decided with
9
- * it, so the next conversation starts where the last one stopped and a human
10
- * can review it in the app.
11
+ * A dossier is a set of questions. Each question holds the evidence gathered
12
+ * for it, the options on the table and, once the engineer has confirmed it, a
13
+ * decision. The question is the unit of work: it is what gets assigned,
14
+ * reviewed and closed.
11
15
  *
12
- * The descriptions carry more instruction than the read tools do, on purpose.
13
- * An agent left to guess will either record nothing — and the feature is dead
14
- * — or record every sentence it produces, which buries the three decisions
15
- * that mattered. `get_methodology` tells it when to open a dossier; these tell
16
- * it what is worth putting in one.
16
+ * Names, descriptions and annotations come from the generated catalogue,
17
+ * shared with the remote transport. `get_methodology` tells the agent when to
18
+ * open a dossier; the descriptions tell it what is worth putting in one.
17
19
  */
18
- const write = { readOnlyHint: false, openWorldHint: false };
19
- const readOnly = { readOnlyHint: true, openWorldHint: false };
20
- const dossierId = z
21
- .string()
22
- .min(1)
23
- .describe('Dossier id returned by open_dossier or list_dossiers.');
20
+ const headOf = (name) => {
21
+ const entry = catalogEntry(name);
22
+ return {
23
+ name,
24
+ title: entry.title,
25
+ description: entry.description,
26
+ annotations: entry.annotations,
27
+ };
28
+ };
29
+ const dossierId = (tool) => z.string().min(1).describe(param(tool, 'dossierId'));
30
+ const questionId = (tool) => z.string().min(1).describe(param(tool, 'questionId'));
31
+ /** The fields a piece of evidence or a decision carries. */
32
+ const entryFields = (tool) => ({
33
+ title: z.string().min(1).max(200).describe(param(tool, 'title')),
34
+ detail: z.string().max(4000).optional().describe(param(tool, 'detail')),
35
+ value: z.string().max(120).optional().describe(param(tool, 'value')),
36
+ confidence: z
37
+ .enum(['established', 'judgement', 'to_confirm'])
38
+ .optional()
39
+ .describe(param(tool, 'confidence')),
40
+ normCode: z.string().max(40).optional().describe(param(tool, 'normCode')),
41
+ sectionPath: z
42
+ .string()
43
+ .max(60)
44
+ .optional()
45
+ .describe(param(tool, 'sectionPath')),
46
+ page: z.number().int().positive().optional().describe(param(tool, 'page')),
47
+ normEdition: z
48
+ .string()
49
+ .max(40)
50
+ .optional()
51
+ .describe(param(tool, 'normEdition')),
52
+ });
24
53
  export const listDossiers = defineTool({
25
- name: 'list_dossiers',
26
- title: 'List dossiers',
27
- description: 'List the dossiers of YOUR organisation, most recently touched first. A dossier is a project record: what was decided, what it rests on, and what is still open. Call this when the user mentions a project by name, or asks what they were working on. Each row carries the count of entries and of UNRESOLVED questions — a dossier with open questions is the one to resume.',
54
+ ...headOf('list_dossiers'),
28
55
  inputSchema: {},
29
- annotations: readOnly,
30
56
  run: async (client) => ({
31
57
  dossiers: await client.action(api.dossiersApi.listDossiers, {
32
58
  apiKey: requireApiKey(),
@@ -34,107 +60,106 @@ export const listDossiers = defineTool({
34
60
  }),
35
61
  });
36
62
  export const openDossier = defineTool({
37
- name: 'open_dossier',
38
- title: 'Open a dossier',
39
- description: "Open the dossier for a project, creating it if it does not exist yet. IDEMPOTENT on the name: calling it twice with the same name returns the same dossier rather than splitting a project's history in two. Call this when the user starts working on a named project and there is something worth keeping — a retained value, an assumption, a site observation, an unanswered question. Do not open one for a passing lookup. Returns { dossierId, created }.",
63
+ ...headOf('open_dossier'),
40
64
  inputSchema: {
41
- name: z
42
- .string()
43
- .min(1)
44
- .max(120)
45
- .describe('How the user refers to the project, e.g. "Villa Morges — toiture". Reuse their words: this is the name they will search for.'),
65
+ name: z.string().min(1).max(120).describe(param('open_dossier', 'name')),
46
66
  reference: z
47
67
  .string()
48
68
  .max(60)
49
69
  .optional()
50
- .describe("The bureau's own project number, if the user gave one."),
70
+ .describe(param('open_dossier', 'reference')),
51
71
  },
52
- annotations: { ...write, idempotentHint: true },
53
72
  run: (client, args) => client.action(api.dossiersApi.openDossier, {
54
73
  apiKey: requireApiKey(),
55
74
  name: args.name,
56
75
  reference: args.reference,
57
76
  }),
58
77
  });
59
- export const saveFinding = defineTool({
60
- name: 'save_finding',
61
- title: 'Save a finding to a dossier',
62
- description: `Record ONE decision in a dossier. Call it as you work, not in a batch at the end — a finding saved when it is made carries the reasoning that produced it.
63
-
64
- WHAT TO RECORD, by kind:
65
- - reference: what a norm says, that the project relies on. ALWAYS fill normCode + sectionPath (+ page). If you cannot cite it, it is not a reference.
66
- - hypothesis: a value the engineer RETAINS, and why. This is the one that matters most in geotechnics, where a retained value is a judgement between disagreeing measurements rather than the output of a formula. Put the number in \`value\` and the reasoning in \`detail\`.
67
- - observation: what the site, a borehole, or a survey showed. Include the date in \`detail\` when known.
68
- - question: something not settled. These surface FIRST when the dossier is reloaded, so record them even when you cannot answer — especially then.
69
-
70
- WHAT NOT TO RECORD: your own prose, intermediate steps, anything the user did not treat as a decision. A dossier of forty entries where three mattered is worse than a dossier of three.
71
-
72
- Set \`confidence\` whenever the entry is a value: established (computed or read directly), judgement (the engineer chose it), to_confirm (provisional, needs a test or a check). Confusing those three is the professional fault this field exists to prevent.`,
78
+ export const openQuestion = defineTool({
79
+ ...headOf('open_question'),
73
80
  inputSchema: {
74
- dossierId,
75
- kind: z
76
- .enum(['reference', 'hypothesis', 'observation', 'question'])
77
- .describe('See the tool description: pick by what the entry IS.'),
78
- title: z
79
- .string()
80
- .min(1)
81
- .max(200)
82
- .describe('The decision in one line, as an engineer would write it in a report.'),
83
- detail: z
81
+ dossierId: dossierId('open_question'),
82
+ title: z.string().min(1).max(200).describe(param('open_question', 'title')),
83
+ body: z
84
84
  .string()
85
85
  .max(4000)
86
86
  .optional()
87
- .describe('The reasoning: why this value, what it rests on, what disagreed. Required in practice for a hypothesis — without it the entry cannot be reviewed.'),
88
- value: z
89
- .string()
90
- .max(120)
91
- .optional()
92
- .describe('The retained value WITH its unit, e.g. "30°", "1,25 kN/m²".'),
93
- confidence: z
94
- .enum(['established', 'judgement', 'to_confirm'])
87
+ .describe(param('open_question', 'body')),
88
+ options: z
89
+ .array(z.string().min(1).max(120))
90
+ .max(10)
95
91
  .optional()
96
- .describe('How firm the value is. See the tool description.'),
97
- normCode: z
98
- .string()
99
- .max(40)
100
- .optional()
101
- .describe('Norm this comes from, e.g. "SIA 261".'),
102
- sectionPath: z
92
+ .describe(param('open_question', 'options')),
93
+ },
94
+ run: (client, args) => client.action(api.dossiersApi.openQuestion, {
95
+ apiKey: requireApiKey(),
96
+ dossierId: args.dossierId,
97
+ title: args.title,
98
+ body: args.body,
99
+ options: args.options,
100
+ }),
101
+ });
102
+ export const saveFinding = defineTool({
103
+ ...headOf('save_finding'),
104
+ inputSchema: {
105
+ dossierId: dossierId('save_finding'),
106
+ questionId: questionId('save_finding').optional(),
107
+ kind: z
108
+ .enum(['reference', 'hypothesis', 'observation'])
109
+ .describe(param('save_finding', 'kind')),
110
+ ...entryFields('save_finding'),
111
+ attachmentId: z
103
112
  .string()
104
- .max(60)
105
113
  .optional()
106
- .describe('Exact section path, e.g. "14.2".'),
107
- page: z
114
+ .describe(param('save_finding', 'attachmentId')),
115
+ attachmentPage: z
108
116
  .number()
109
117
  .int()
110
- .positive()
118
+ .min(1)
111
119
  .optional()
112
- .describe('Page in the norm, so a human can verify it in their PDF.'),
120
+ .describe(param('save_finding', 'attachmentPage')),
113
121
  },
114
- annotations: write,
115
122
  run: (client, args) => client.action(api.dossiersApi.saveFinding, {
116
123
  apiKey: requireApiKey(),
117
124
  ...args,
118
125
  }),
119
126
  });
127
+ export const recordDecision = defineTool({
128
+ ...headOf('record_decision'),
129
+ inputSchema: {
130
+ questionId: questionId('record_decision'),
131
+ retainedOption: z
132
+ .string()
133
+ .max(120)
134
+ .optional()
135
+ .describe(param('record_decision', 'retainedOption')),
136
+ ...entryFields('record_decision'),
137
+ confirmed: z
138
+ .boolean()
139
+ .optional()
140
+ .describe(param('record_decision', 'confirmed')),
141
+ },
142
+ // `confirmed` is read by the confirmation gate in `index.ts`, never sent.
143
+ run: (client, { confirmed: _confirmed, ...args }) => client.action(api.dossiersApi.recordDecision, {
144
+ apiKey: requireApiKey(),
145
+ ...args,
146
+ }),
147
+ });
120
148
  export const loadDossier = defineTool({
121
- name: 'load_dossier',
122
- title: 'Load a dossier',
123
- description: 'Reload everything a dossier holds: retained values, what they rest on, site observations, open questions and the comments a colleague left. UNRESOLVED QUESTIONS COME FIRST — read them before anything else and tell the user what is still open. Call this whenever the user returns to a project ("reprends le dossier X", "on en était où sur Y") BEFORE answering anything about it, so you build on what was decided instead of deciding it again. Accepts either the id or the name the user uses.',
149
+ ...headOf('load_dossier'),
124
150
  inputSchema: {
125
151
  dossierId: z
126
152
  .string()
127
153
  .min(1)
128
154
  .optional()
129
- .describe('Preferred when you have it.'),
155
+ .describe(param('load_dossier', 'dossierId')),
130
156
  name: z
131
157
  .string()
132
158
  .min(1)
133
159
  .max(120)
134
160
  .optional()
135
- .describe('The project name, when the user names it rather than giving an id. Matched exactly; call list_dossiers first if unsure.'),
161
+ .describe(param('load_dossier', 'name')),
136
162
  },
137
- annotations: readOnly,
138
163
  run: (client, args) => client.action(api.dossiersApi.loadDossier, {
139
164
  apiKey: requireApiKey(),
140
165
  dossierId: args.dossierId,
@@ -142,33 +167,209 @@ export const loadDossier = defineTool({
142
167
  }),
143
168
  });
144
169
  export const resolveQuestion = defineTool({
145
- name: 'resolve_question',
146
- title: 'Close an open question',
147
- description: 'Mark a question entry as settled, once it actually is. The entry stays in the dossier — the trail of what was once uncertain is part of the record — but it stops surfacing at the top on reload. Only call this when the user has confirmed the answer, never on your own reasoning.',
170
+ ...headOf('resolve_question'),
148
171
  inputSchema: {
149
- entryId: z
150
- .string()
151
- .min(1)
152
- .describe('Entry id of the question, from load_dossier.'),
172
+ questionId: questionId('resolve_question'),
153
173
  resolved: z
154
174
  .boolean()
155
175
  .optional()
156
- .describe('Defaults to true. Pass false to reopen a question.'),
176
+ .describe(param('resolve_question', 'resolved')),
177
+ confirmed: z
178
+ .boolean()
179
+ .optional()
180
+ .describe(param('resolve_question', 'confirmed')),
157
181
  },
158
- annotations: { ...write, idempotentHint: true },
159
182
  run: async (client, args) => {
160
183
  await client.action(api.dossiersApi.resolveQuestion, {
161
184
  apiKey: requireApiKey(),
162
- entryId: args.entryId,
185
+ questionId: args.questionId,
163
186
  resolved: args.resolved,
164
187
  });
165
188
  return { ok: true };
166
189
  },
167
190
  });
191
+ const MIME_BY_EXT = {
192
+ '.pdf': 'application/pdf',
193
+ '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
194
+ '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
195
+ '.png': 'image/png',
196
+ '.jpg': 'image/jpeg',
197
+ '.jpeg': 'image/jpeg',
198
+ '.txt': 'text/plain',
199
+ '.md': 'text/markdown',
200
+ '.csv': 'text/csv',
201
+ };
202
+ export const listAttachments = defineTool({
203
+ ...headOf('list_attachments'),
204
+ inputSchema: {
205
+ dossierId: z.string().describe(param('list_attachments', 'dossierId')),
206
+ },
207
+ run: async (client, args) => {
208
+ const attachments = await client.action(api.dossiersApi.listAttachments, {
209
+ apiKey: requireApiKey(),
210
+ dossierId: args.dossierId,
211
+ });
212
+ if (attachments === null)
213
+ return { error: 'Dossier not found in your workspace.' };
214
+ return { dossierId: args.dossierId, attachments };
215
+ },
216
+ });
217
+ export const readAttachment = defineTool({
218
+ ...headOf('read_attachment'),
219
+ inputSchema: {
220
+ attachmentId: z.string().describe(param('read_attachment', 'attachmentId')),
221
+ from: z
222
+ .number()
223
+ .int()
224
+ .min(1)
225
+ .optional()
226
+ .describe(param('read_attachment', 'from')),
227
+ to: z
228
+ .number()
229
+ .int()
230
+ .min(1)
231
+ .optional()
232
+ .describe(param('read_attachment', 'to')),
233
+ maxChars: z
234
+ .number()
235
+ .int()
236
+ .min(500)
237
+ .max(40_000)
238
+ .optional()
239
+ .describe(param('read_attachment', 'maxChars')),
240
+ },
241
+ run: async (client, args) => {
242
+ const read = (await client.action(api.dossiersApi.readAttachment, {
243
+ apiKey: requireApiKey(),
244
+ attachmentId: args.attachmentId,
245
+ from: args.from,
246
+ to: args.to,
247
+ maxChars: args.maxChars,
248
+ }));
249
+ if (read === null) {
250
+ return {
251
+ error: 'Attachment not found in your workspace. Call list_attachments on the dossier.',
252
+ };
253
+ }
254
+ return read;
255
+ },
256
+ toContent: (result) => {
257
+ const r = result;
258
+ if (r.error || !r.markdown) {
259
+ return {
260
+ content: [{ type: 'text', text: r.error ?? 'Attachment unavailable.' }],
261
+ isError: true,
262
+ };
263
+ }
264
+ const { markdown, ...structured } = r;
265
+ return {
266
+ content: [{ type: 'text', text: markdown }],
267
+ structuredContent: { ...structured, markdown },
268
+ };
269
+ },
270
+ });
271
+ export const searchInDossier = defineTool({
272
+ ...headOf('search_in_dossier'),
273
+ inputSchema: {
274
+ dossierId: z.string().describe(param('search_in_dossier', 'dossierId')),
275
+ keyword: z
276
+ .string()
277
+ .min(1)
278
+ .max(200)
279
+ .describe(param('search_in_dossier', 'keyword')),
280
+ limit: z
281
+ .number()
282
+ .int()
283
+ .min(1)
284
+ .max(30)
285
+ .optional()
286
+ .describe(param('search_in_dossier', 'limit')),
287
+ },
288
+ run: async (client, args) => {
289
+ const hits = await client.action(api.dossiersApi.searchInDossier, {
290
+ apiKey: requireApiKey(),
291
+ dossierId: args.dossierId,
292
+ keyword: args.keyword,
293
+ limit: args.limit,
294
+ });
295
+ if (hits === null)
296
+ return { error: 'Dossier not found in your workspace.' };
297
+ return { keyword: args.keyword, hits };
298
+ },
299
+ });
300
+ export const addAttachment = defineTool({
301
+ ...headOf('add_attachment'),
302
+ inputSchema: {
303
+ dossierId: z.string().describe(param('add_attachment', 'dossierId')),
304
+ filePath: z.string().min(1).describe(param('add_attachment', 'filePath')),
305
+ },
306
+ run: async (client, args) => {
307
+ const mimeType = MIME_BY_EXT[extname(args.filePath).toLowerCase()];
308
+ if (!mimeType) {
309
+ return {
310
+ error: `Unsupported file type "${extname(args.filePath)}". Allowed: ${Object.keys(MIME_BY_EXT).join(', ')}.`,
311
+ };
312
+ }
313
+ let bytes;
314
+ try {
315
+ bytes = readFileSync(args.filePath);
316
+ }
317
+ catch {
318
+ return {
319
+ error: `Cannot read ${args.filePath}. Check the path with the user.`,
320
+ };
321
+ }
322
+ if (bytes.byteLength > 25 * 1024 * 1024) {
323
+ return { error: 'A file is limited to 25 MB.' };
324
+ }
325
+ const { attachmentId } = await client.action(api.dossiersApi.uploadAttachment, {
326
+ apiKey: requireApiKey(),
327
+ dossierId: args.dossierId,
328
+ name: basename(args.filePath),
329
+ mimeType,
330
+ base64: bytes.toString('base64'),
331
+ });
332
+ return {
333
+ attachmentId,
334
+ name: basename(args.filePath),
335
+ bytes: bytes.byteLength,
336
+ note: 'Text extraction runs in the background; list_attachments shows when it is ready.',
337
+ };
338
+ },
339
+ });
340
+ export const listTemplates = defineTool({
341
+ ...headOf('list_templates'),
342
+ inputSchema: {},
343
+ run: async (client) => ({
344
+ templates: await client.action(api.dossiersApi.listTemplates, {
345
+ apiKey: requireApiKey(),
346
+ }),
347
+ }),
348
+ });
349
+ export const applyTemplate = defineTool({
350
+ ...headOf('apply_template'),
351
+ inputSchema: {
352
+ dossierId: z.string().describe(param('apply_template', 'dossierId')),
353
+ templateId: z.string().describe(param('apply_template', 'templateId')),
354
+ },
355
+ run: (client, args) => client.action(api.dossiersApi.applyTemplate, {
356
+ apiKey: requireApiKey(),
357
+ dossierId: args.dossierId,
358
+ templateId: args.templateId,
359
+ }),
360
+ });
168
361
  export const dossierTools = [
169
362
  listDossiers,
170
363
  openDossier,
364
+ openQuestion,
171
365
  saveFinding,
366
+ recordDecision,
172
367
  loadDossier,
173
368
  resolveQuestion,
369
+ listAttachments,
370
+ readAttachment,
371
+ searchInDossier,
372
+ addAttachment,
373
+ listTemplates,
374
+ applyTemplate,
174
375
  ];
@@ -13,6 +13,8 @@ export declare const ingestCreateDocument: import("./define.js").ToolDef<{
13
13
  en: "en";
14
14
  }>;
15
15
  totalPages: z.ZodNumber;
16
+ scope: z.ZodOptional<z.ZodString>;
17
+ edition: z.ZodOptional<z.ZodString>;
16
18
  }>;
17
19
  export declare const ingestCreateSections: import("./define.js").ToolDef<{
18
20
  documentId: z.ZodString;
@@ -71,6 +73,7 @@ export declare const ingestNormalizeCrossRefs: import("./define.js").ToolDef<{
71
73
  }>;
72
74
  export declare const ingestPublish: import("./define.js").ToolDef<{
73
75
  documentId: z.ZodString;
76
+ force: z.ZodOptional<z.ZodBoolean>;
74
77
  }>;
75
78
  export declare const ingestDelete: import("./define.js").ToolDef<{
76
79
  documentId: z.ZodString;
@@ -88,6 +91,8 @@ export declare const ingestTools: (import("./define.js").ToolDef<{
88
91
  en: "en";
89
92
  }>;
90
93
  totalPages: z.ZodNumber;
94
+ scope: z.ZodOptional<z.ZodString>;
95
+ edition: z.ZodOptional<z.ZodString>;
91
96
  }> | import("./define.js").ToolDef<{
92
97
  documentId: z.ZodString;
93
98
  sections: z.ZodArray<z.ZodObject<{
@@ -137,4 +142,7 @@ export declare const ingestTools: (import("./define.js").ToolDef<{
137
142
  figureNumber: z.ZodString;
138
143
  }> | import("./define.js").ToolDef<{
139
144
  documentId: z.ZodString;
145
+ }> | import("./define.js").ToolDef<{
146
+ documentId: z.ZodString;
147
+ force: z.ZodOptional<z.ZodBoolean>;
140
148
  }>)[];