@burdenoff/mcp-libs 2026.627.6 → 2026.629.1
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/dist/modules/adaptercloud/index.cjs +1841 -0
- package/dist/modules/adaptercloud/index.cjs.map +1 -0
- package/dist/modules/adaptercloud/index.d.cts +8 -0
- package/dist/modules/adaptercloud/index.d.ts +8 -0
- package/dist/modules/adaptercloud/index.js +1780 -0
- package/dist/modules/adaptercloud/index.js.map +1 -0
- package/dist/modules/atheletebridge/index.cjs +1707 -0
- package/dist/modules/atheletebridge/index.cjs.map +1 -0
- package/dist/modules/atheletebridge/index.d.cts +8 -0
- package/dist/modules/atheletebridge/index.d.ts +8 -0
- package/dist/modules/atheletebridge/index.js +1646 -0
- package/dist/modules/atheletebridge/index.js.map +1 -0
- package/dist/modules/coolandlovely/index.cjs +1935 -0
- package/dist/modules/coolandlovely/index.cjs.map +1 -0
- package/dist/modules/coolandlovely/index.d.cts +8 -0
- package/dist/modules/coolandlovely/index.d.ts +8 -0
- package/dist/modules/coolandlovely/index.js +1874 -0
- package/dist/modules/coolandlovely/index.js.map +1 -0
- package/dist/modules/hookmovies/index.cjs +2024 -0
- package/dist/modules/hookmovies/index.cjs.map +1 -0
- package/dist/modules/hookmovies/index.d.cts +8 -0
- package/dist/modules/hookmovies/index.d.ts +8 -0
- package/dist/modules/hookmovies/index.js +1963 -0
- package/dist/modules/hookmovies/index.js.map +1 -0
- package/dist/modules/labsofscience/index.cjs +2039 -0
- package/dist/modules/labsofscience/index.cjs.map +1 -0
- package/dist/modules/labsofscience/index.d.cts +8 -0
- package/dist/modules/labsofscience/index.d.ts +8 -0
- package/dist/modules/labsofscience/index.js +1978 -0
- package/dist/modules/labsofscience/index.js.map +1 -0
- package/dist/modules/subscriberbot/index.cjs +2037 -0
- package/dist/modules/subscriberbot/index.cjs.map +1 -0
- package/dist/modules/subscriberbot/index.d.cts +8 -0
- package/dist/modules/subscriberbot/index.d.ts +8 -0
- package/dist/modules/subscriberbot/index.js +1976 -0
- package/dist/modules/subscriberbot/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,2039 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
require('graphql-request');
|
|
5
|
+
require('cross-fetch');
|
|
6
|
+
require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
7
|
+
require('@modelcontextprotocol/sdk/types.js');
|
|
8
|
+
|
|
9
|
+
// src/modules/labsofscience/tools/projects.ts
|
|
10
|
+
|
|
11
|
+
// src/core/errors.ts
|
|
12
|
+
var McpInternalError = class extends Error {
|
|
13
|
+
cause;
|
|
14
|
+
constructor(message, cause) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "McpInternalError";
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/core/result.ts
|
|
22
|
+
function jsonResult(data, structured) {
|
|
23
|
+
const result = {
|
|
24
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
25
|
+
};
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function errorResult(message, hint) {
|
|
29
|
+
const text = hint ? `${message}
|
|
30
|
+
|
|
31
|
+
Hint: ${hint}` : message;
|
|
32
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/core/tool-name.ts
|
|
36
|
+
var MAX_TOOL_NAME_LENGTH = 63;
|
|
37
|
+
function assertSafeName(name, kind) {
|
|
38
|
+
if (name.length > MAX_TOOL_NAME_LENGTH) {
|
|
39
|
+
throw new McpInternalError(
|
|
40
|
+
`Tool name '${name}' exceeds ${MAX_TOOL_NAME_LENGTH} characters (some MCP clients truncate at 64).`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
function mcpToolName(productName, ...parts) {
|
|
46
|
+
if (!productName) {
|
|
47
|
+
throw new McpInternalError("mcpToolName: productName is required");
|
|
48
|
+
}
|
|
49
|
+
const name = [productName, ...parts].filter(Boolean).join("_");
|
|
50
|
+
return assertSafeName(name);
|
|
51
|
+
}
|
|
52
|
+
function mcpResourceUri(productName, ...parts) {
|
|
53
|
+
if (!productName) {
|
|
54
|
+
throw new McpInternalError("mcpResourceUri: productName is required");
|
|
55
|
+
}
|
|
56
|
+
return `${productName}://${parts.filter(Boolean).join("/")}`;
|
|
57
|
+
}
|
|
58
|
+
function mcpPromptName(productName, ...parts) {
|
|
59
|
+
if (!productName) {
|
|
60
|
+
throw new McpInternalError("mcpPromptName: productName is required");
|
|
61
|
+
}
|
|
62
|
+
return [productName, ...parts].filter(Boolean).join("-");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/modules/labsofscience/operations.ts
|
|
66
|
+
var PROJECT_FIELDS = `
|
|
67
|
+
id code name description field status visibility principalInvestigatorId
|
|
68
|
+
startDate endDate tags workspaceId createdAt updatedAt
|
|
69
|
+
`;
|
|
70
|
+
var EXPERIMENT_FIELDS = `
|
|
71
|
+
id title description hypothesis status projectId protocolId leadResearcherId
|
|
72
|
+
variables tags workspaceId createdAt updatedAt
|
|
73
|
+
`;
|
|
74
|
+
var RUN_FIELDS = `
|
|
75
|
+
id experimentId runNumber status parameters observations notes performedById
|
|
76
|
+
startedAt completedAt workspaceId createdAt updatedAt
|
|
77
|
+
`;
|
|
78
|
+
var PROTOCOL_STEP_FIELDS = `
|
|
79
|
+
id protocolId ordinal instruction kind durationMinutes isCritical
|
|
80
|
+
workspaceId createdAt updatedAt
|
|
81
|
+
`;
|
|
82
|
+
var PROTOCOL_FIELDS = `
|
|
83
|
+
id name summary status version estimatedMinutes materials safetyNotes
|
|
84
|
+
authorId publishedAt tags workspaceId createdAt updatedAt
|
|
85
|
+
`;
|
|
86
|
+
var DATASET_VERSION_FIELDS = `
|
|
87
|
+
id datasetId version sizeBytes rowCount checksum changeNote
|
|
88
|
+
file { id } workspaceId createdAt updatedAt
|
|
89
|
+
`;
|
|
90
|
+
var DATASET_FIELDS = `
|
|
91
|
+
id name description kind status latestVersion experimentId projectId
|
|
92
|
+
tags workspaceId createdAt updatedAt
|
|
93
|
+
`;
|
|
94
|
+
var PIPELINE_FIELDS = `
|
|
95
|
+
id name description engine definition version isActive projectId tags
|
|
96
|
+
workspaceId createdAt updatedAt
|
|
97
|
+
`;
|
|
98
|
+
var PIPELINE_RUN_FIELDS = `
|
|
99
|
+
id pipelineId datasetId datasetVersionId outputDatasetId status parameters
|
|
100
|
+
log triggeredById startedAt finishedAt workspaceId createdAt updatedAt
|
|
101
|
+
`;
|
|
102
|
+
var BOOKING_FIELDS = `
|
|
103
|
+
id instrumentId experimentId bookedById startTime endTime status purpose
|
|
104
|
+
workspaceId createdAt updatedAt
|
|
105
|
+
`;
|
|
106
|
+
var INSTRUMENT_FIELDS = `
|
|
107
|
+
id name model location status requiresApproval custodianId tags
|
|
108
|
+
workspaceId createdAt updatedAt
|
|
109
|
+
`;
|
|
110
|
+
var INVENTORY_FIELDS = `
|
|
111
|
+
id name category status quantity unit reorderLevel location vendor
|
|
112
|
+
catalogNumber lotNumber expiresAt sds { id } tags workspaceId
|
|
113
|
+
createdAt updatedAt
|
|
114
|
+
`;
|
|
115
|
+
var NOTEBOOK_FIELDS = `
|
|
116
|
+
id title body status projectId experimentId runId authorId signedById
|
|
117
|
+
signedAt witnessedById witnessedAt files { id } tags workspaceId
|
|
118
|
+
createdAt updatedAt
|
|
119
|
+
`;
|
|
120
|
+
var PAGE_INFO = `pageInfo { cursor hasMore }`;
|
|
121
|
+
var GET_PROJECT = `
|
|
122
|
+
query LabsofscienceGetProject($id: ID!) {
|
|
123
|
+
labProject(id: $id) {
|
|
124
|
+
${PROJECT_FIELDS}
|
|
125
|
+
experiments { id title status }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
`;
|
|
129
|
+
var LIST_PROJECTS = `
|
|
130
|
+
query LabsofscienceListProjects($status: LabProjectStatus, $pagination: PaginationInput) {
|
|
131
|
+
labListProjects(status: $status, pagination: $pagination) {
|
|
132
|
+
items { ${PROJECT_FIELDS} }
|
|
133
|
+
${PAGE_INFO}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
`;
|
|
137
|
+
var CREATE_PROJECT = `
|
|
138
|
+
mutation LabsofscienceCreateProject($input: LabCreateProjectInput!) {
|
|
139
|
+
labCreateProject(input: $input) {
|
|
140
|
+
${PROJECT_FIELDS}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
`;
|
|
144
|
+
var UPDATE_PROJECT = `
|
|
145
|
+
mutation LabsofscienceUpdateProject($id: ID!, $input: LabUpdateProjectInput!) {
|
|
146
|
+
labUpdateProject(id: $id, input: $input) {
|
|
147
|
+
${PROJECT_FIELDS}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
var ARCHIVE_PROJECT = `
|
|
152
|
+
mutation LabsofscienceArchiveProject($id: ID!) {
|
|
153
|
+
labArchiveProject(id: $id)
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
var GET_EXPERIMENT = `
|
|
157
|
+
query LabsofscienceGetExperiment($id: ID!) {
|
|
158
|
+
labExperiment(id: $id) {
|
|
159
|
+
${EXPERIMENT_FIELDS}
|
|
160
|
+
project { id name }
|
|
161
|
+
runs { id runNumber status }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
165
|
+
var LIST_EXPERIMENTS = `
|
|
166
|
+
query LabsofscienceListExperiments(
|
|
167
|
+
$projectId: ID
|
|
168
|
+
$status: LabExperimentStatus
|
|
169
|
+
$pagination: PaginationInput
|
|
170
|
+
) {
|
|
171
|
+
labListExperiments(projectId: $projectId, status: $status, pagination: $pagination) {
|
|
172
|
+
items { ${EXPERIMENT_FIELDS} }
|
|
173
|
+
${PAGE_INFO}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
`;
|
|
177
|
+
var CREATE_EXPERIMENT = `
|
|
178
|
+
mutation LabsofscienceCreateExperiment($input: LabCreateExperimentInput!) {
|
|
179
|
+
labCreateExperiment(input: $input) {
|
|
180
|
+
${EXPERIMENT_FIELDS}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
`;
|
|
184
|
+
var UPDATE_EXPERIMENT = `
|
|
185
|
+
mutation LabsofscienceUpdateExperiment($id: ID!, $input: LabUpdateExperimentInput!) {
|
|
186
|
+
labUpdateExperiment(id: $id, input: $input) {
|
|
187
|
+
${EXPERIMENT_FIELDS}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
`;
|
|
191
|
+
var TRANSITION_EXPERIMENT = `
|
|
192
|
+
mutation LabsofscienceTransitionExperiment($id: ID!, $status: LabExperimentStatus!) {
|
|
193
|
+
labTransitionExperiment(id: $id, status: $status) {
|
|
194
|
+
${EXPERIMENT_FIELDS}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
198
|
+
var LIST_RUNS = `
|
|
199
|
+
query LabsofscienceListRuns(
|
|
200
|
+
$experimentId: ID
|
|
201
|
+
$status: LabRunStatus
|
|
202
|
+
$pagination: PaginationInput
|
|
203
|
+
) {
|
|
204
|
+
labListRuns(experimentId: $experimentId, status: $status, pagination: $pagination) {
|
|
205
|
+
items { ${RUN_FIELDS} }
|
|
206
|
+
${PAGE_INFO}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
var START_RUN = `
|
|
211
|
+
mutation LabsofscienceStartRun($input: LabCreateRunInput!) {
|
|
212
|
+
labStartRun(input: $input) {
|
|
213
|
+
${RUN_FIELDS}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
`;
|
|
217
|
+
var COMPLETE_RUN = `
|
|
218
|
+
mutation LabsofscienceCompleteRun(
|
|
219
|
+
$id: ID!
|
|
220
|
+
$status: LabRunStatus!
|
|
221
|
+
$input: LabRecordRunResultsInput
|
|
222
|
+
) {
|
|
223
|
+
labCompleteRun(id: $id, status: $status, input: $input) {
|
|
224
|
+
${RUN_FIELDS}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
`;
|
|
228
|
+
var GET_PROTOCOL = `
|
|
229
|
+
query LabsofscienceGetProtocol($id: ID!) {
|
|
230
|
+
labProtocol(id: $id) {
|
|
231
|
+
${PROTOCOL_FIELDS}
|
|
232
|
+
steps { ${PROTOCOL_STEP_FIELDS} }
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
`;
|
|
236
|
+
var LIST_PROTOCOLS = `
|
|
237
|
+
query LabsofscienceListProtocols($status: LabProtocolStatus, $pagination: PaginationInput) {
|
|
238
|
+
labListProtocols(status: $status, pagination: $pagination) {
|
|
239
|
+
items { ${PROTOCOL_FIELDS} }
|
|
240
|
+
${PAGE_INFO}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
`;
|
|
244
|
+
var CREATE_PROTOCOL = `
|
|
245
|
+
mutation LabsofscienceCreateProtocol($input: LabCreateProtocolInput!) {
|
|
246
|
+
labCreateProtocol(input: $input) {
|
|
247
|
+
${PROTOCOL_FIELDS}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
`;
|
|
251
|
+
var UPDATE_PROTOCOL = `
|
|
252
|
+
mutation LabsofscienceUpdateProtocol($id: ID!, $input: LabUpdateProtocolInput!) {
|
|
253
|
+
labUpdateProtocol(id: $id, input: $input) {
|
|
254
|
+
${PROTOCOL_FIELDS}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
`;
|
|
258
|
+
var ADD_PROTOCOL_STEP = `
|
|
259
|
+
mutation LabsofscienceAddProtocolStep($input: LabAddProtocolStepInput!) {
|
|
260
|
+
labAddProtocolStep(input: $input) {
|
|
261
|
+
${PROTOCOL_STEP_FIELDS}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
var PUBLISH_PROTOCOL = `
|
|
266
|
+
mutation LabsofsciencePublishProtocol($id: ID!) {
|
|
267
|
+
labPublishProtocol(id: $id) {
|
|
268
|
+
${PROTOCOL_FIELDS}
|
|
269
|
+
steps { ${PROTOCOL_STEP_FIELDS} }
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
`;
|
|
273
|
+
var GET_DATASET = `
|
|
274
|
+
query LabsofscienceGetDataset($id: ID!) {
|
|
275
|
+
labDataset(id: $id) {
|
|
276
|
+
${DATASET_FIELDS}
|
|
277
|
+
versions { ${DATASET_VERSION_FIELDS} }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
`;
|
|
281
|
+
var LIST_DATASETS = `
|
|
282
|
+
query LabsofscienceListDatasets(
|
|
283
|
+
$kind: LabDatasetKind
|
|
284
|
+
$status: LabDatasetStatus
|
|
285
|
+
$pagination: PaginationInput
|
|
286
|
+
) {
|
|
287
|
+
labListDatasets(kind: $kind, status: $status, pagination: $pagination) {
|
|
288
|
+
items { ${DATASET_FIELDS} }
|
|
289
|
+
${PAGE_INFO}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
`;
|
|
293
|
+
var CREATE_DATASET = `
|
|
294
|
+
mutation LabsofscienceCreateDataset($input: LabCreateDatasetInput!) {
|
|
295
|
+
labCreateDataset(input: $input) {
|
|
296
|
+
${DATASET_FIELDS}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
`;
|
|
300
|
+
var ADD_DATASET_VERSION = `
|
|
301
|
+
mutation LabsofscienceAddDatasetVersion($input: LabAddDatasetVersionInput!) {
|
|
302
|
+
labAddDatasetVersion(input: $input) {
|
|
303
|
+
${DATASET_VERSION_FIELDS}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
`;
|
|
307
|
+
var ARCHIVE_DATASET = `
|
|
308
|
+
mutation LabsofscienceArchiveDataset($id: ID!) {
|
|
309
|
+
labArchiveDataset(id: $id)
|
|
310
|
+
}
|
|
311
|
+
`;
|
|
312
|
+
var LIST_PIPELINES = `
|
|
313
|
+
query LabsofscienceListPipelines {
|
|
314
|
+
labListPipelines {
|
|
315
|
+
${PIPELINE_FIELDS}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
`;
|
|
319
|
+
var CREATE_PIPELINE = `
|
|
320
|
+
mutation LabsofscienceCreatePipeline($input: LabCreatePipelineInput!) {
|
|
321
|
+
labCreatePipeline(input: $input) {
|
|
322
|
+
${PIPELINE_FIELDS}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
`;
|
|
326
|
+
var LIST_PIPELINE_RUNS = `
|
|
327
|
+
query LabsofscienceListPipelineRuns(
|
|
328
|
+
$pipelineId: ID
|
|
329
|
+
$status: LabPipelineRunStatus
|
|
330
|
+
$pagination: PaginationInput
|
|
331
|
+
) {
|
|
332
|
+
labListPipelineRuns(pipelineId: $pipelineId, status: $status, pagination: $pagination) {
|
|
333
|
+
items { ${PIPELINE_RUN_FIELDS} }
|
|
334
|
+
${PAGE_INFO}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
`;
|
|
338
|
+
var RUN_PIPELINE = `
|
|
339
|
+
mutation LabsofscienceRunPipeline($input: LabRunPipelineInput!) {
|
|
340
|
+
labRunPipeline(input: $input) {
|
|
341
|
+
${PIPELINE_RUN_FIELDS}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
`;
|
|
345
|
+
var TRANSITION_PIPELINE_RUN = `
|
|
346
|
+
mutation LabsofscienceTransitionPipelineRun(
|
|
347
|
+
$id: ID!
|
|
348
|
+
$status: LabPipelineRunStatus!
|
|
349
|
+
$log: String
|
|
350
|
+
) {
|
|
351
|
+
labTransitionPipelineRun(id: $id, status: $status, log: $log) {
|
|
352
|
+
${PIPELINE_RUN_FIELDS}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
`;
|
|
356
|
+
var GET_INSTRUMENT = `
|
|
357
|
+
query LabsofscienceGetInstrument($id: ID!) {
|
|
358
|
+
labInstrument(id: $id) {
|
|
359
|
+
${INSTRUMENT_FIELDS}
|
|
360
|
+
bookings { ${BOOKING_FIELDS} }
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
`;
|
|
364
|
+
var LIST_INSTRUMENTS = `
|
|
365
|
+
query LabsofscienceListInstruments($status: LabInstrumentStatus) {
|
|
366
|
+
labListInstruments(status: $status) {
|
|
367
|
+
${INSTRUMENT_FIELDS}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
`;
|
|
371
|
+
var CREATE_INSTRUMENT = `
|
|
372
|
+
mutation LabsofscienceCreateInstrument($input: LabCreateInstrumentInput!) {
|
|
373
|
+
labCreateInstrument(input: $input) {
|
|
374
|
+
${INSTRUMENT_FIELDS}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
`;
|
|
378
|
+
var BOOK_INSTRUMENT = `
|
|
379
|
+
mutation LabsofscienceBookInstrument($input: LabBookInstrumentInput!) {
|
|
380
|
+
labBookInstrument(input: $input) {
|
|
381
|
+
${BOOKING_FIELDS}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
`;
|
|
385
|
+
var CANCEL_BOOKING = `
|
|
386
|
+
mutation LabsofscienceCancelBooking($id: ID!) {
|
|
387
|
+
labCancelBooking(id: $id) {
|
|
388
|
+
${BOOKING_FIELDS}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
`;
|
|
392
|
+
var GET_INVENTORY_ITEM = `
|
|
393
|
+
query LabsofscienceGetInventoryItem($id: ID!) {
|
|
394
|
+
labInventoryItem(id: $id) {
|
|
395
|
+
${INVENTORY_FIELDS}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
`;
|
|
399
|
+
var LIST_INVENTORY_ITEMS = `
|
|
400
|
+
query LabsofscienceListInventoryItems(
|
|
401
|
+
$category: LabInventoryCategory
|
|
402
|
+
$status: LabInventoryStatus
|
|
403
|
+
$pagination: PaginationInput
|
|
404
|
+
) {
|
|
405
|
+
labListInventoryItems(category: $category, status: $status, pagination: $pagination) {
|
|
406
|
+
items { ${INVENTORY_FIELDS} }
|
|
407
|
+
${PAGE_INFO}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
`;
|
|
411
|
+
var CREATE_INVENTORY_ITEM = `
|
|
412
|
+
mutation LabsofscienceCreateInventoryItem($input: LabCreateInventoryItemInput!) {
|
|
413
|
+
labCreateInventoryItem(input: $input) {
|
|
414
|
+
${INVENTORY_FIELDS}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
`;
|
|
418
|
+
var ADJUST_INVENTORY = `
|
|
419
|
+
mutation LabsofscienceAdjustInventory($id: ID!, $input: LabAdjustInventoryInput!) {
|
|
420
|
+
labAdjustInventory(id: $id, input: $input) {
|
|
421
|
+
${INVENTORY_FIELDS}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
`;
|
|
425
|
+
var ARCHIVE_INVENTORY_ITEM = `
|
|
426
|
+
mutation LabsofscienceArchiveInventoryItem($id: ID!) {
|
|
427
|
+
labArchiveInventoryItem(id: $id)
|
|
428
|
+
}
|
|
429
|
+
`;
|
|
430
|
+
var LIST_NOTEBOOK_ENTRIES = `
|
|
431
|
+
query LabsofscienceListNotebookEntries(
|
|
432
|
+
$projectId: ID
|
|
433
|
+
$experimentId: ID
|
|
434
|
+
$status: LabNotebookEntryStatus
|
|
435
|
+
$pagination: PaginationInput
|
|
436
|
+
) {
|
|
437
|
+
labListNotebookEntries(
|
|
438
|
+
projectId: $projectId
|
|
439
|
+
experimentId: $experimentId
|
|
440
|
+
status: $status
|
|
441
|
+
pagination: $pagination
|
|
442
|
+
) {
|
|
443
|
+
items { ${NOTEBOOK_FIELDS} }
|
|
444
|
+
${PAGE_INFO}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
`;
|
|
448
|
+
var CREATE_NOTEBOOK_ENTRY = `
|
|
449
|
+
mutation LabsofscienceCreateNotebookEntry($input: LabCreateNotebookEntryInput!) {
|
|
450
|
+
labCreateNotebookEntry(input: $input) {
|
|
451
|
+
${NOTEBOOK_FIELDS}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
`;
|
|
455
|
+
var SIGN_NOTEBOOK_ENTRY = `
|
|
456
|
+
mutation LabsofscienceSignNotebookEntry($id: ID!) {
|
|
457
|
+
labSignNotebookEntry(id: $id) {
|
|
458
|
+
${NOTEBOOK_FIELDS}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
`;
|
|
462
|
+
var HEALTH = `
|
|
463
|
+
query LabsofscienceHealth {
|
|
464
|
+
labHealth
|
|
465
|
+
}
|
|
466
|
+
`;
|
|
467
|
+
var ProjectStatusEnum = zod.z.enum(["ACTIVE", "ARCHIVED", "COMPLETED", "ON_HOLD", "PLANNING"]).describe("Lifecycle status of a research project");
|
|
468
|
+
var ProjectVisibilityEnum = zod.z.enum(["PRIVATE", "PUBLIC", "WORKSPACE"]).describe("Visibility scope of a research project");
|
|
469
|
+
var ExperimentStatusEnum = zod.z.enum(["ABANDONED", "ANALYSIS", "COMPLETE", "DESIGN", "RUNNING"]).describe("Lifecycle status of an experiment");
|
|
470
|
+
var RunStatusEnum = zod.z.enum(["CANCELLED", "FAILED", "IN_PROGRESS", "PLANNED", "SUCCEEDED"]).describe("Status of an experiment run");
|
|
471
|
+
var ProtocolStatusEnum = zod.z.enum(["DEPRECATED", "DRAFT", "PUBLISHED"]).describe("Lifecycle status of a protocol");
|
|
472
|
+
var StepKindEnum = zod.z.enum(["ANALYSIS", "CLEANUP", "INCUBATION", "MEASUREMENT", "PREPARATION", "SAFETY"]).describe("Kind of a protocol step");
|
|
473
|
+
var DatasetKindEnum = zod.z.enum(["PROCESSED", "RAW", "REFERENCE", "RESULT"]).describe("Kind/classification of a dataset");
|
|
474
|
+
var DatasetStatusEnum = zod.z.enum(["ACTIVE", "ARCHIVED", "LOCKED"]).describe("Lifecycle status of a dataset");
|
|
475
|
+
var PipelineRunStatusEnum = zod.z.enum(["CANCELLED", "FAILED", "QUEUED", "RUNNING", "SUCCEEDED"]).describe("Execution status of an analysis-pipeline run");
|
|
476
|
+
var InstrumentStatusEnum = zod.z.enum(["AVAILABLE", "IN_USE", "MAINTENANCE", "OFFLINE"]).describe("Availability status of a lab instrument");
|
|
477
|
+
var InventoryCategoryEnum = zod.z.enum(["BIOLOGICAL", "CHEMICAL", "CONSUMABLE", "EQUIPMENT_PART", "REAGENT", "SAMPLE"]).describe("Category of an inventory item");
|
|
478
|
+
var InventoryStatusEnum = zod.z.enum(["EXPIRED", "IN_STOCK", "LOW", "OUT_OF_STOCK", "QUARANTINED"]).describe("Stock status of an inventory item");
|
|
479
|
+
var NotebookEntryStatusEnum = zod.z.enum(["DRAFT", "SIGNED", "SUBMITTED", "VOID", "WITNESSED"]).describe("Status of an electronic-lab-notebook (ELN) entry");
|
|
480
|
+
var JsonObject = zod.z.record(zod.z.string(), zod.z.unknown());
|
|
481
|
+
|
|
482
|
+
// src/modules/labsofscience/tools/projects.ts
|
|
483
|
+
function registerProjectTools(server, ctx, track) {
|
|
484
|
+
server.registerTool(
|
|
485
|
+
track(mcpToolName(ctx.productName, "projects_get")),
|
|
486
|
+
{
|
|
487
|
+
title: "Get Research Project",
|
|
488
|
+
description: "Fetch a single research project by ID, including its experiments.",
|
|
489
|
+
inputSchema: { id: zod.z.string().describe("Project ID") }
|
|
490
|
+
},
|
|
491
|
+
async ({ id }) => {
|
|
492
|
+
try {
|
|
493
|
+
const data = await ctx.gql.workspace.request(GET_PROJECT, { id });
|
|
494
|
+
if (!data.labProject) return errorResult(`Project ${id} not found`);
|
|
495
|
+
return jsonResult(data.labProject);
|
|
496
|
+
} catch (error) {
|
|
497
|
+
ctx.logger.error("labsofscience projects_get failed", { error: String(error) });
|
|
498
|
+
return errorResult(
|
|
499
|
+
`Failed to get project: ${error instanceof Error ? error.message : String(error)}`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
);
|
|
504
|
+
server.registerTool(
|
|
505
|
+
track(mcpToolName(ctx.productName, "projects_list")),
|
|
506
|
+
{
|
|
507
|
+
title: "List Research Projects",
|
|
508
|
+
description: "List research projects in the bound workspace with optional status filter and pagination. Returns a connection ({ items, pageInfo }).",
|
|
509
|
+
inputSchema: {
|
|
510
|
+
status: ProjectStatusEnum.optional().describe("Filter by project status"),
|
|
511
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
512
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
513
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
async ({ status, limit, offset, cursor }) => {
|
|
517
|
+
try {
|
|
518
|
+
const data = await ctx.gql.workspace.request(LIST_PROJECTS, {
|
|
519
|
+
status,
|
|
520
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
521
|
+
});
|
|
522
|
+
return jsonResult(data.labListProjects);
|
|
523
|
+
} catch (error) {
|
|
524
|
+
ctx.logger.error("labsofscience projects_list failed", { error: String(error) });
|
|
525
|
+
return errorResult(
|
|
526
|
+
`Failed to list projects: ${error instanceof Error ? error.message : String(error)}`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
);
|
|
531
|
+
server.registerTool(
|
|
532
|
+
track(mcpToolName(ctx.productName, "projects_create")),
|
|
533
|
+
{
|
|
534
|
+
title: "Create Research Project",
|
|
535
|
+
description: "Create a research project. `name` is required; `code` (if provided) should be unique within the workspace.",
|
|
536
|
+
inputSchema: {
|
|
537
|
+
name: zod.z.string().min(1).describe("Project name"),
|
|
538
|
+
code: zod.z.string().optional().describe("Short project code (unique within workspace)"),
|
|
539
|
+
description: zod.z.string().optional().describe("Project description"),
|
|
540
|
+
field: zod.z.string().optional().describe("Scientific field / discipline"),
|
|
541
|
+
principalInvestigatorId: zod.z.string().optional().describe("User ID of the PI"),
|
|
542
|
+
status: ProjectStatusEnum.optional().describe("Initial status (default PLANNING)"),
|
|
543
|
+
visibility: ProjectVisibilityEnum.optional().describe("Visibility (default WORKSPACE)"),
|
|
544
|
+
startDate: zod.z.string().optional().describe("Start date (ISO-8601 DateTime)"),
|
|
545
|
+
endDate: zod.z.string().optional().describe("End date (ISO-8601 DateTime)"),
|
|
546
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Discovery tags")
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
async ({
|
|
550
|
+
name,
|
|
551
|
+
code,
|
|
552
|
+
description,
|
|
553
|
+
field,
|
|
554
|
+
principalInvestigatorId,
|
|
555
|
+
status,
|
|
556
|
+
visibility,
|
|
557
|
+
startDate,
|
|
558
|
+
endDate,
|
|
559
|
+
tags
|
|
560
|
+
}) => {
|
|
561
|
+
try {
|
|
562
|
+
const input = { name };
|
|
563
|
+
if (code !== void 0) input.code = code;
|
|
564
|
+
if (description !== void 0) input.description = description;
|
|
565
|
+
if (field !== void 0) input.field = field;
|
|
566
|
+
if (principalInvestigatorId !== void 0)
|
|
567
|
+
input.principalInvestigatorId = principalInvestigatorId;
|
|
568
|
+
if (status !== void 0) input.status = status;
|
|
569
|
+
if (visibility !== void 0) input.visibility = visibility;
|
|
570
|
+
if (startDate !== void 0) input.startDate = startDate;
|
|
571
|
+
if (endDate !== void 0) input.endDate = endDate;
|
|
572
|
+
if (tags !== void 0) input.tags = tags;
|
|
573
|
+
const data = await ctx.gql.workspace.request(CREATE_PROJECT, { input });
|
|
574
|
+
return jsonResult(data.labCreateProject);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
ctx.logger.error("labsofscience projects_create failed", { error: String(error) });
|
|
577
|
+
return errorResult(
|
|
578
|
+
`Failed to create project: ${error instanceof Error ? error.message : String(error)}`
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
);
|
|
583
|
+
server.registerTool(
|
|
584
|
+
track(mcpToolName(ctx.productName, "projects_update")),
|
|
585
|
+
{
|
|
586
|
+
title: "Update Research Project",
|
|
587
|
+
description: "Update a research project. At least one mutable field must be provided. Use `status` to advance the project lifecycle.",
|
|
588
|
+
inputSchema: {
|
|
589
|
+
id: zod.z.string().describe("Project ID"),
|
|
590
|
+
name: zod.z.string().optional().describe("Updated name"),
|
|
591
|
+
code: zod.z.string().optional().describe("Updated code"),
|
|
592
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
593
|
+
field: zod.z.string().optional().describe("Updated field/discipline"),
|
|
594
|
+
principalInvestigatorId: zod.z.string().optional().describe("Updated PI user ID"),
|
|
595
|
+
status: ProjectStatusEnum.optional().describe("Updated status"),
|
|
596
|
+
visibility: ProjectVisibilityEnum.optional().describe("Updated visibility"),
|
|
597
|
+
startDate: zod.z.string().optional().describe("Updated start date (ISO-8601)"),
|
|
598
|
+
endDate: zod.z.string().optional().describe("Updated end date (ISO-8601)"),
|
|
599
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Updated tags")
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
async ({
|
|
603
|
+
id,
|
|
604
|
+
name,
|
|
605
|
+
code,
|
|
606
|
+
description,
|
|
607
|
+
field,
|
|
608
|
+
principalInvestigatorId,
|
|
609
|
+
status,
|
|
610
|
+
visibility,
|
|
611
|
+
startDate,
|
|
612
|
+
endDate,
|
|
613
|
+
tags
|
|
614
|
+
}) => {
|
|
615
|
+
try {
|
|
616
|
+
const input = {};
|
|
617
|
+
if (name !== void 0) input.name = name;
|
|
618
|
+
if (code !== void 0) input.code = code;
|
|
619
|
+
if (description !== void 0) input.description = description;
|
|
620
|
+
if (field !== void 0) input.field = field;
|
|
621
|
+
if (principalInvestigatorId !== void 0)
|
|
622
|
+
input.principalInvestigatorId = principalInvestigatorId;
|
|
623
|
+
if (status !== void 0) input.status = status;
|
|
624
|
+
if (visibility !== void 0) input.visibility = visibility;
|
|
625
|
+
if (startDate !== void 0) input.startDate = startDate;
|
|
626
|
+
if (endDate !== void 0) input.endDate = endDate;
|
|
627
|
+
if (tags !== void 0) input.tags = tags;
|
|
628
|
+
if (Object.keys(input).length === 0) {
|
|
629
|
+
return errorResult("No updates specified \u2014 provide at least one mutable project field");
|
|
630
|
+
}
|
|
631
|
+
const data = await ctx.gql.workspace.request(UPDATE_PROJECT, { id, input });
|
|
632
|
+
return jsonResult(data.labUpdateProject);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
ctx.logger.error("labsofscience projects_update failed", { error: String(error) });
|
|
635
|
+
return errorResult(
|
|
636
|
+
`Failed to update project: ${error instanceof Error ? error.message : String(error)}`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
);
|
|
641
|
+
server.registerTool(
|
|
642
|
+
track(mcpToolName(ctx.productName, "projects_archive")),
|
|
643
|
+
{
|
|
644
|
+
title: "Archive Research Project",
|
|
645
|
+
description: "Archive a research project. Returns true on success.",
|
|
646
|
+
inputSchema: { id: zod.z.string().describe("Project ID") }
|
|
647
|
+
},
|
|
648
|
+
async ({ id }) => {
|
|
649
|
+
try {
|
|
650
|
+
const data = await ctx.gql.workspace.request(
|
|
651
|
+
ARCHIVE_PROJECT,
|
|
652
|
+
{ id }
|
|
653
|
+
);
|
|
654
|
+
return jsonResult({ archived: data.labArchiveProject, id });
|
|
655
|
+
} catch (error) {
|
|
656
|
+
ctx.logger.error("labsofscience projects_archive failed", { error: String(error) });
|
|
657
|
+
return errorResult(
|
|
658
|
+
`Failed to archive project: ${error instanceof Error ? error.message : String(error)}`
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
function registerExperimentTools(server, ctx, track) {
|
|
665
|
+
server.registerTool(
|
|
666
|
+
track(mcpToolName(ctx.productName, "experiments_get")),
|
|
667
|
+
{
|
|
668
|
+
title: "Get Experiment",
|
|
669
|
+
description: "Fetch a single experiment by ID, including its parent project and runs.",
|
|
670
|
+
inputSchema: { id: zod.z.string().describe("Experiment ID") }
|
|
671
|
+
},
|
|
672
|
+
async ({ id }) => {
|
|
673
|
+
try {
|
|
674
|
+
const data = await ctx.gql.workspace.request(GET_EXPERIMENT, { id });
|
|
675
|
+
if (!data.labExperiment) return errorResult(`Experiment ${id} not found`);
|
|
676
|
+
return jsonResult(data.labExperiment);
|
|
677
|
+
} catch (error) {
|
|
678
|
+
ctx.logger.error("labsofscience experiments_get failed", { error: String(error) });
|
|
679
|
+
return errorResult(
|
|
680
|
+
`Failed to get experiment: ${error instanceof Error ? error.message : String(error)}`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
);
|
|
685
|
+
server.registerTool(
|
|
686
|
+
track(mcpToolName(ctx.productName, "experiments_list")),
|
|
687
|
+
{
|
|
688
|
+
title: "List Experiments",
|
|
689
|
+
description: "List experiments, optionally filtered by project and/or status, with pagination. Returns a connection ({ items, pageInfo }).",
|
|
690
|
+
inputSchema: {
|
|
691
|
+
projectId: zod.z.string().optional().describe("Filter by project ID"),
|
|
692
|
+
status: ExperimentStatusEnum.optional().describe("Filter by experiment status"),
|
|
693
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
694
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
695
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
async ({ projectId, status, limit, offset, cursor }) => {
|
|
699
|
+
try {
|
|
700
|
+
const data = await ctx.gql.workspace.request(LIST_EXPERIMENTS, {
|
|
701
|
+
projectId,
|
|
702
|
+
status,
|
|
703
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
704
|
+
});
|
|
705
|
+
return jsonResult(data.labListExperiments);
|
|
706
|
+
} catch (error) {
|
|
707
|
+
ctx.logger.error("labsofscience experiments_list failed", { error: String(error) });
|
|
708
|
+
return errorResult(
|
|
709
|
+
`Failed to list experiments: ${error instanceof Error ? error.message : String(error)}`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
);
|
|
714
|
+
server.registerTool(
|
|
715
|
+
track(mcpToolName(ctx.productName, "experiments_create")),
|
|
716
|
+
{
|
|
717
|
+
title: "Create Experiment",
|
|
718
|
+
description: "Create an experiment under a project. `projectId` and `title` are required. `variables` is arbitrary JSON capturing the experimental design.",
|
|
719
|
+
inputSchema: {
|
|
720
|
+
projectId: zod.z.string().min(1).describe("Owning project ID"),
|
|
721
|
+
title: zod.z.string().min(1).describe("Experiment title"),
|
|
722
|
+
description: zod.z.string().optional().describe("Experiment description"),
|
|
723
|
+
hypothesis: zod.z.string().optional().describe("Hypothesis under test"),
|
|
724
|
+
protocolId: zod.z.string().optional().describe("Protocol this experiment follows"),
|
|
725
|
+
leadResearcherId: zod.z.string().optional().describe("Lead researcher user ID"),
|
|
726
|
+
variables: JsonObject.optional().describe("Experimental variables JSON"),
|
|
727
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Discovery tags")
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
async ({
|
|
731
|
+
projectId,
|
|
732
|
+
title,
|
|
733
|
+
description,
|
|
734
|
+
hypothesis,
|
|
735
|
+
protocolId,
|
|
736
|
+
leadResearcherId,
|
|
737
|
+
variables,
|
|
738
|
+
tags
|
|
739
|
+
}) => {
|
|
740
|
+
try {
|
|
741
|
+
const input = { projectId, title };
|
|
742
|
+
if (description !== void 0) input.description = description;
|
|
743
|
+
if (hypothesis !== void 0) input.hypothesis = hypothesis;
|
|
744
|
+
if (protocolId !== void 0) input.protocolId = protocolId;
|
|
745
|
+
if (leadResearcherId !== void 0) input.leadResearcherId = leadResearcherId;
|
|
746
|
+
if (variables !== void 0) input.variables = variables;
|
|
747
|
+
if (tags !== void 0) input.tags = tags;
|
|
748
|
+
const data = await ctx.gql.workspace.request(CREATE_EXPERIMENT, { input });
|
|
749
|
+
return jsonResult(data.labCreateExperiment);
|
|
750
|
+
} catch (error) {
|
|
751
|
+
ctx.logger.error("labsofscience experiments_create failed", { error: String(error) });
|
|
752
|
+
return errorResult(
|
|
753
|
+
`Failed to create experiment: ${error instanceof Error ? error.message : String(error)}`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
);
|
|
758
|
+
server.registerTool(
|
|
759
|
+
track(mcpToolName(ctx.productName, "experiments_update")),
|
|
760
|
+
{
|
|
761
|
+
title: "Update Experiment",
|
|
762
|
+
description: "Update an experiment. At least one mutable field must be provided. To change lifecycle status use experiments_transition.",
|
|
763
|
+
inputSchema: {
|
|
764
|
+
id: zod.z.string().describe("Experiment ID"),
|
|
765
|
+
title: zod.z.string().optional().describe("Updated title"),
|
|
766
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
767
|
+
hypothesis: zod.z.string().optional().describe("Updated hypothesis"),
|
|
768
|
+
protocolId: zod.z.string().optional().describe("Updated protocol ID"),
|
|
769
|
+
leadResearcherId: zod.z.string().optional().describe("Updated lead researcher user ID"),
|
|
770
|
+
variables: JsonObject.optional().describe("Updated experimental variables JSON"),
|
|
771
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Updated tags")
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
async ({
|
|
775
|
+
id,
|
|
776
|
+
title,
|
|
777
|
+
description,
|
|
778
|
+
hypothesis,
|
|
779
|
+
protocolId,
|
|
780
|
+
leadResearcherId,
|
|
781
|
+
variables,
|
|
782
|
+
tags
|
|
783
|
+
}) => {
|
|
784
|
+
try {
|
|
785
|
+
const input = {};
|
|
786
|
+
if (title !== void 0) input.title = title;
|
|
787
|
+
if (description !== void 0) input.description = description;
|
|
788
|
+
if (hypothesis !== void 0) input.hypothesis = hypothesis;
|
|
789
|
+
if (protocolId !== void 0) input.protocolId = protocolId;
|
|
790
|
+
if (leadResearcherId !== void 0) input.leadResearcherId = leadResearcherId;
|
|
791
|
+
if (variables !== void 0) input.variables = variables;
|
|
792
|
+
if (tags !== void 0) input.tags = tags;
|
|
793
|
+
if (Object.keys(input).length === 0) {
|
|
794
|
+
return errorResult(
|
|
795
|
+
"No updates specified \u2014 provide at least one mutable experiment field"
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
const data = await ctx.gql.workspace.request(UPDATE_EXPERIMENT, { id, input });
|
|
799
|
+
return jsonResult(data.labUpdateExperiment);
|
|
800
|
+
} catch (error) {
|
|
801
|
+
ctx.logger.error("labsofscience experiments_update failed", { error: String(error) });
|
|
802
|
+
return errorResult(
|
|
803
|
+
`Failed to update experiment: ${error instanceof Error ? error.message : String(error)}`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
);
|
|
808
|
+
server.registerTool(
|
|
809
|
+
track(mcpToolName(ctx.productName, "experiments_transition")),
|
|
810
|
+
{
|
|
811
|
+
title: "Transition Experiment",
|
|
812
|
+
description: "Advance an experiment through its lifecycle (DESIGN \u2192 RUNNING \u2192 ANALYSIS \u2192 COMPLETE, or ABANDONED). Transitions are validated server-side.",
|
|
813
|
+
inputSchema: {
|
|
814
|
+
id: zod.z.string().describe("Experiment ID"),
|
|
815
|
+
status: ExperimentStatusEnum.describe("Target status")
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
async ({ id, status }) => {
|
|
819
|
+
try {
|
|
820
|
+
const data = await ctx.gql.workspace.request(TRANSITION_EXPERIMENT, { id, status });
|
|
821
|
+
return jsonResult(data.labTransitionExperiment);
|
|
822
|
+
} catch (error) {
|
|
823
|
+
ctx.logger.error("labsofscience experiments_transition failed", { error: String(error) });
|
|
824
|
+
return errorResult(
|
|
825
|
+
`Failed to transition experiment: ${error instanceof Error ? error.message : String(error)}`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
function registerRunTools(server, ctx, track) {
|
|
832
|
+
server.registerTool(
|
|
833
|
+
track(mcpToolName(ctx.productName, "runs_list")),
|
|
834
|
+
{
|
|
835
|
+
title: "List Experiment Runs",
|
|
836
|
+
description: "List experiment runs, optionally filtered by experiment and/or status, with pagination. Returns a connection ({ items, pageInfo }).",
|
|
837
|
+
inputSchema: {
|
|
838
|
+
experimentId: zod.z.string().optional().describe("Filter by experiment ID"),
|
|
839
|
+
status: RunStatusEnum.optional().describe("Filter by run status"),
|
|
840
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
841
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
842
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
async ({ experimentId, status, limit, offset, cursor }) => {
|
|
846
|
+
try {
|
|
847
|
+
const data = await ctx.gql.workspace.request(LIST_RUNS, {
|
|
848
|
+
experimentId,
|
|
849
|
+
status,
|
|
850
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
851
|
+
});
|
|
852
|
+
return jsonResult(data.labListRuns);
|
|
853
|
+
} catch (error) {
|
|
854
|
+
ctx.logger.error("labsofscience runs_list failed", { error: String(error) });
|
|
855
|
+
return errorResult(
|
|
856
|
+
`Failed to list runs: ${error instanceof Error ? error.message : String(error)}`
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
);
|
|
861
|
+
server.registerTool(
|
|
862
|
+
track(mcpToolName(ctx.productName, "runs_start")),
|
|
863
|
+
{
|
|
864
|
+
title: "Start Experiment Run",
|
|
865
|
+
description: "Start a new reproducible run of an experiment (runNumber is auto-incremented). `parameters` is arbitrary JSON capturing the run configuration.",
|
|
866
|
+
inputSchema: {
|
|
867
|
+
experimentId: zod.z.string().min(1).describe("Experiment being run"),
|
|
868
|
+
parameters: JsonObject.optional().describe("Run parameters JSON"),
|
|
869
|
+
notes: zod.z.string().optional().describe("Free-text notes for the run")
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
async ({ experimentId, parameters, notes }) => {
|
|
873
|
+
try {
|
|
874
|
+
const input = { experimentId };
|
|
875
|
+
if (parameters !== void 0) input.parameters = parameters;
|
|
876
|
+
if (notes !== void 0) input.notes = notes;
|
|
877
|
+
const data = await ctx.gql.workspace.request(START_RUN, { input });
|
|
878
|
+
return jsonResult(data.labStartRun);
|
|
879
|
+
} catch (error) {
|
|
880
|
+
ctx.logger.error("labsofscience runs_start failed", { error: String(error) });
|
|
881
|
+
return errorResult(
|
|
882
|
+
`Failed to start run: ${error instanceof Error ? error.message : String(error)}`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
);
|
|
887
|
+
server.registerTool(
|
|
888
|
+
track(mcpToolName(ctx.productName, "runs_complete")),
|
|
889
|
+
{
|
|
890
|
+
title: "Complete Experiment Run",
|
|
891
|
+
description: "Record observations and complete a run with a terminal status (SUCCEEDED, FAILED, or CANCELLED).",
|
|
892
|
+
inputSchema: {
|
|
893
|
+
id: zod.z.string().describe("Run ID"),
|
|
894
|
+
status: RunStatusEnum.describe("Terminal run status"),
|
|
895
|
+
observations: JsonObject.optional().describe("Recorded observations JSON"),
|
|
896
|
+
notes: zod.z.string().optional().describe("Closing notes for the run")
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
async ({ id, status, observations, notes }) => {
|
|
900
|
+
try {
|
|
901
|
+
const input = {};
|
|
902
|
+
if (observations !== void 0) input.observations = observations;
|
|
903
|
+
if (notes !== void 0) input.notes = notes;
|
|
904
|
+
const data = await ctx.gql.workspace.request(COMPLETE_RUN, {
|
|
905
|
+
id,
|
|
906
|
+
status,
|
|
907
|
+
input: Object.keys(input).length > 0 ? input : void 0
|
|
908
|
+
});
|
|
909
|
+
return jsonResult(data.labCompleteRun);
|
|
910
|
+
} catch (error) {
|
|
911
|
+
ctx.logger.error("labsofscience runs_complete failed", { error: String(error) });
|
|
912
|
+
return errorResult(
|
|
913
|
+
`Failed to complete run: ${error instanceof Error ? error.message : String(error)}`
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
function registerProtocolTools(server, ctx, track) {
|
|
920
|
+
server.registerTool(
|
|
921
|
+
track(mcpToolName(ctx.productName, "protocols_get")),
|
|
922
|
+
{
|
|
923
|
+
title: "Get Protocol",
|
|
924
|
+
description: "Fetch a single protocol by ID, including its ordered steps.",
|
|
925
|
+
inputSchema: { id: zod.z.string().describe("Protocol ID") }
|
|
926
|
+
},
|
|
927
|
+
async ({ id }) => {
|
|
928
|
+
try {
|
|
929
|
+
const data = await ctx.gql.workspace.request(GET_PROTOCOL, { id });
|
|
930
|
+
if (!data.labProtocol) return errorResult(`Protocol ${id} not found`);
|
|
931
|
+
return jsonResult(data.labProtocol);
|
|
932
|
+
} catch (error) {
|
|
933
|
+
ctx.logger.error("labsofscience protocols_get failed", { error: String(error) });
|
|
934
|
+
return errorResult(
|
|
935
|
+
`Failed to get protocol: ${error instanceof Error ? error.message : String(error)}`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
);
|
|
940
|
+
server.registerTool(
|
|
941
|
+
track(mcpToolName(ctx.productName, "protocols_list")),
|
|
942
|
+
{
|
|
943
|
+
title: "List Protocols",
|
|
944
|
+
description: "List protocols, optionally filtered by status, with pagination. Returns a connection ({ items, pageInfo }).",
|
|
945
|
+
inputSchema: {
|
|
946
|
+
status: ProtocolStatusEnum.optional().describe("Filter by protocol status"),
|
|
947
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
948
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
949
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
async ({ status, limit, offset, cursor }) => {
|
|
953
|
+
try {
|
|
954
|
+
const data = await ctx.gql.workspace.request(LIST_PROTOCOLS, {
|
|
955
|
+
status,
|
|
956
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
957
|
+
});
|
|
958
|
+
return jsonResult(data.labListProtocols);
|
|
959
|
+
} catch (error) {
|
|
960
|
+
ctx.logger.error("labsofscience protocols_list failed", { error: String(error) });
|
|
961
|
+
return errorResult(
|
|
962
|
+
`Failed to list protocols: ${error instanceof Error ? error.message : String(error)}`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
);
|
|
967
|
+
server.registerTool(
|
|
968
|
+
track(mcpToolName(ctx.productName, "protocols_create")),
|
|
969
|
+
{
|
|
970
|
+
title: "Create Protocol",
|
|
971
|
+
description: "Create a draft protocol (standard operating procedure). `name` is required; add steps with protocols_add_step, then protocols_publish to freeze the version.",
|
|
972
|
+
inputSchema: {
|
|
973
|
+
name: zod.z.string().min(1).describe("Protocol name"),
|
|
974
|
+
summary: zod.z.string().optional().describe("Short summary"),
|
|
975
|
+
authorId: zod.z.string().optional().describe("Author user ID"),
|
|
976
|
+
estimatedMinutes: zod.z.number().int().optional().describe("Estimated total duration (minutes)"),
|
|
977
|
+
materials: zod.z.array(zod.z.string()).optional().describe("Required materials list"),
|
|
978
|
+
safetyNotes: zod.z.string().optional().describe("Safety notes"),
|
|
979
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Discovery tags")
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
async ({ name, summary, authorId, estimatedMinutes, materials, safetyNotes, tags }) => {
|
|
983
|
+
try {
|
|
984
|
+
const input = { name };
|
|
985
|
+
if (summary !== void 0) input.summary = summary;
|
|
986
|
+
if (authorId !== void 0) input.authorId = authorId;
|
|
987
|
+
if (estimatedMinutes !== void 0) input.estimatedMinutes = estimatedMinutes;
|
|
988
|
+
if (materials !== void 0) input.materials = materials;
|
|
989
|
+
if (safetyNotes !== void 0) input.safetyNotes = safetyNotes;
|
|
990
|
+
if (tags !== void 0) input.tags = tags;
|
|
991
|
+
const data = await ctx.gql.workspace.request(CREATE_PROTOCOL, { input });
|
|
992
|
+
return jsonResult(data.labCreateProtocol);
|
|
993
|
+
} catch (error) {
|
|
994
|
+
ctx.logger.error("labsofscience protocols_create failed", { error: String(error) });
|
|
995
|
+
return errorResult(
|
|
996
|
+
`Failed to create protocol: ${error instanceof Error ? error.message : String(error)}`
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
);
|
|
1001
|
+
server.registerTool(
|
|
1002
|
+
track(mcpToolName(ctx.productName, "protocols_update")),
|
|
1003
|
+
{
|
|
1004
|
+
title: "Update Protocol",
|
|
1005
|
+
description: "Update a (draft) protocol. At least one mutable field must be provided.",
|
|
1006
|
+
inputSchema: {
|
|
1007
|
+
id: zod.z.string().describe("Protocol ID"),
|
|
1008
|
+
name: zod.z.string().optional().describe("Updated name"),
|
|
1009
|
+
summary: zod.z.string().optional().describe("Updated summary"),
|
|
1010
|
+
estimatedMinutes: zod.z.number().int().optional().describe("Updated estimated duration"),
|
|
1011
|
+
materials: zod.z.array(zod.z.string()).optional().describe("Updated materials list"),
|
|
1012
|
+
safetyNotes: zod.z.string().optional().describe("Updated safety notes"),
|
|
1013
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Updated tags")
|
|
1014
|
+
}
|
|
1015
|
+
},
|
|
1016
|
+
async ({ id, name, summary, estimatedMinutes, materials, safetyNotes, tags }) => {
|
|
1017
|
+
try {
|
|
1018
|
+
const input = {};
|
|
1019
|
+
if (name !== void 0) input.name = name;
|
|
1020
|
+
if (summary !== void 0) input.summary = summary;
|
|
1021
|
+
if (estimatedMinutes !== void 0) input.estimatedMinutes = estimatedMinutes;
|
|
1022
|
+
if (materials !== void 0) input.materials = materials;
|
|
1023
|
+
if (safetyNotes !== void 0) input.safetyNotes = safetyNotes;
|
|
1024
|
+
if (tags !== void 0) input.tags = tags;
|
|
1025
|
+
if (Object.keys(input).length === 0) {
|
|
1026
|
+
return errorResult("No updates specified \u2014 provide at least one mutable protocol field");
|
|
1027
|
+
}
|
|
1028
|
+
const data = await ctx.gql.workspace.request(UPDATE_PROTOCOL, { id, input });
|
|
1029
|
+
return jsonResult(data.labUpdateProtocol);
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
ctx.logger.error("labsofscience protocols_update failed", { error: String(error) });
|
|
1032
|
+
return errorResult(
|
|
1033
|
+
`Failed to update protocol: ${error instanceof Error ? error.message : String(error)}`
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
);
|
|
1038
|
+
server.registerTool(
|
|
1039
|
+
track(mcpToolName(ctx.productName, "protocols_add_step")),
|
|
1040
|
+
{
|
|
1041
|
+
title: "Add Protocol Step",
|
|
1042
|
+
description: "Append a step to a protocol. `instruction` is required; `kind` defaults to PREPARATION. If `ordinal` is omitted the step is appended at the end.",
|
|
1043
|
+
inputSchema: {
|
|
1044
|
+
protocolId: zod.z.string().min(1).describe("Owning protocol ID"),
|
|
1045
|
+
instruction: zod.z.string().min(1).describe("Step instruction text"),
|
|
1046
|
+
kind: StepKindEnum.optional().describe("Step kind (default PREPARATION)"),
|
|
1047
|
+
ordinal: zod.z.number().int().optional().describe("Position in the protocol (1-based)"),
|
|
1048
|
+
durationMinutes: zod.z.number().int().optional().describe("Step duration in minutes"),
|
|
1049
|
+
isCritical: zod.z.boolean().optional().describe("Mark as a critical step (default false)")
|
|
1050
|
+
}
|
|
1051
|
+
},
|
|
1052
|
+
async ({ protocolId, instruction, kind, ordinal, durationMinutes, isCritical }) => {
|
|
1053
|
+
try {
|
|
1054
|
+
const input = { protocolId, instruction };
|
|
1055
|
+
if (kind !== void 0) input.kind = kind;
|
|
1056
|
+
if (ordinal !== void 0) input.ordinal = ordinal;
|
|
1057
|
+
if (durationMinutes !== void 0) input.durationMinutes = durationMinutes;
|
|
1058
|
+
if (isCritical !== void 0) input.isCritical = isCritical;
|
|
1059
|
+
const data = await ctx.gql.workspace.request(ADD_PROTOCOL_STEP, { input });
|
|
1060
|
+
return jsonResult(data.labAddProtocolStep);
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
ctx.logger.error("labsofscience protocols_add_step failed", { error: String(error) });
|
|
1063
|
+
return errorResult(
|
|
1064
|
+
`Failed to add protocol step: ${error instanceof Error ? error.message : String(error)}`
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
);
|
|
1069
|
+
server.registerTool(
|
|
1070
|
+
track(mcpToolName(ctx.productName, "protocols_publish")),
|
|
1071
|
+
{
|
|
1072
|
+
title: "Publish Protocol",
|
|
1073
|
+
description: "Publish a draft protocol, freezing its version for reproducibility. Returns the published protocol with its steps.",
|
|
1074
|
+
inputSchema: { id: zod.z.string().describe("Protocol ID") }
|
|
1075
|
+
},
|
|
1076
|
+
async ({ id }) => {
|
|
1077
|
+
try {
|
|
1078
|
+
const data = await ctx.gql.workspace.request(PUBLISH_PROTOCOL, { id });
|
|
1079
|
+
return jsonResult(data.labPublishProtocol);
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
ctx.logger.error("labsofscience protocols_publish failed", { error: String(error) });
|
|
1082
|
+
return errorResult(
|
|
1083
|
+
`Failed to publish protocol: ${error instanceof Error ? error.message : String(error)}`
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
function registerDatasetTools(server, ctx, track) {
|
|
1090
|
+
server.registerTool(
|
|
1091
|
+
track(mcpToolName(ctx.productName, "datasets_get")),
|
|
1092
|
+
{
|
|
1093
|
+
title: "Get Dataset",
|
|
1094
|
+
description: "Fetch a single dataset by ID, including its version history.",
|
|
1095
|
+
inputSchema: { id: zod.z.string().describe("Dataset ID") }
|
|
1096
|
+
},
|
|
1097
|
+
async ({ id }) => {
|
|
1098
|
+
try {
|
|
1099
|
+
const data = await ctx.gql.workspace.request(GET_DATASET, { id });
|
|
1100
|
+
if (!data.labDataset) return errorResult(`Dataset ${id} not found`);
|
|
1101
|
+
return jsonResult(data.labDataset);
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
ctx.logger.error("labsofscience datasets_get failed", { error: String(error) });
|
|
1104
|
+
return errorResult(
|
|
1105
|
+
`Failed to get dataset: ${error instanceof Error ? error.message : String(error)}`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
);
|
|
1110
|
+
server.registerTool(
|
|
1111
|
+
track(mcpToolName(ctx.productName, "datasets_list")),
|
|
1112
|
+
{
|
|
1113
|
+
title: "List Datasets",
|
|
1114
|
+
description: "List datasets, optionally filtered by kind and/or status, with pagination. Returns a connection ({ items, pageInfo }).",
|
|
1115
|
+
inputSchema: {
|
|
1116
|
+
kind: DatasetKindEnum.optional().describe("Filter by dataset kind"),
|
|
1117
|
+
status: DatasetStatusEnum.optional().describe("Filter by dataset status"),
|
|
1118
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
1119
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
1120
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
async ({ kind, status, limit, offset, cursor }) => {
|
|
1124
|
+
try {
|
|
1125
|
+
const data = await ctx.gql.workspace.request(LIST_DATASETS, {
|
|
1126
|
+
kind,
|
|
1127
|
+
status,
|
|
1128
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
1129
|
+
});
|
|
1130
|
+
return jsonResult(data.labListDatasets);
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
ctx.logger.error("labsofscience datasets_list failed", { error: String(error) });
|
|
1133
|
+
return errorResult(
|
|
1134
|
+
`Failed to list datasets: ${error instanceof Error ? error.message : String(error)}`
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
);
|
|
1139
|
+
server.registerTool(
|
|
1140
|
+
track(mcpToolName(ctx.productName, "datasets_create")),
|
|
1141
|
+
{
|
|
1142
|
+
title: "Create Dataset",
|
|
1143
|
+
description: "Create a dataset record. `name` is required; `kind` defaults to RAW. Attach the actual bytes later via datasets_add_version.",
|
|
1144
|
+
inputSchema: {
|
|
1145
|
+
name: zod.z.string().min(1).describe("Dataset name"),
|
|
1146
|
+
kind: DatasetKindEnum.optional().describe("Dataset kind (default RAW)"),
|
|
1147
|
+
description: zod.z.string().optional().describe("Dataset description"),
|
|
1148
|
+
experimentId: zod.z.string().optional().describe("Source experiment ID"),
|
|
1149
|
+
projectId: zod.z.string().optional().describe("Owning project ID"),
|
|
1150
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Discovery tags")
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
async ({ name, kind, description, experimentId, projectId, tags }) => {
|
|
1154
|
+
try {
|
|
1155
|
+
const input = { name };
|
|
1156
|
+
if (kind !== void 0) input.kind = kind;
|
|
1157
|
+
if (description !== void 0) input.description = description;
|
|
1158
|
+
if (experimentId !== void 0) input.experimentId = experimentId;
|
|
1159
|
+
if (projectId !== void 0) input.projectId = projectId;
|
|
1160
|
+
if (tags !== void 0) input.tags = tags;
|
|
1161
|
+
const data = await ctx.gql.workspace.request(CREATE_DATASET, { input });
|
|
1162
|
+
return jsonResult(data.labCreateDataset);
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
ctx.logger.error("labsofscience datasets_create failed", { error: String(error) });
|
|
1165
|
+
return errorResult(
|
|
1166
|
+
`Failed to create dataset: ${error instanceof Error ? error.message : String(error)}`
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
);
|
|
1171
|
+
server.registerTool(
|
|
1172
|
+
track(mcpToolName(ctx.productName, "datasets_add_version")),
|
|
1173
|
+
{
|
|
1174
|
+
title: "Add Dataset Version",
|
|
1175
|
+
description: "Append an immutable version to a dataset and bump its latestVersion. `fileId` references the snapshot bytes stored in wspace-files-svc.",
|
|
1176
|
+
inputSchema: {
|
|
1177
|
+
datasetId: zod.z.string().min(1).describe("Target dataset ID"),
|
|
1178
|
+
fileId: zod.z.string().optional().describe("wspace-files-svc File ID for the snapshot bytes"),
|
|
1179
|
+
sizeBytes: zod.z.string().optional().describe("Snapshot size in bytes (string for large values)"),
|
|
1180
|
+
rowCount: zod.z.number().int().optional().describe("Number of rows in the snapshot"),
|
|
1181
|
+
checksum: zod.z.string().optional().describe("Content checksum"),
|
|
1182
|
+
changeNote: zod.z.string().optional().describe("What changed in this version")
|
|
1183
|
+
}
|
|
1184
|
+
},
|
|
1185
|
+
async ({ datasetId, fileId, sizeBytes, rowCount, checksum, changeNote }) => {
|
|
1186
|
+
try {
|
|
1187
|
+
const input = { datasetId };
|
|
1188
|
+
if (fileId !== void 0) input.fileId = fileId;
|
|
1189
|
+
if (sizeBytes !== void 0) input.sizeBytes = sizeBytes;
|
|
1190
|
+
if (rowCount !== void 0) input.rowCount = rowCount;
|
|
1191
|
+
if (checksum !== void 0) input.checksum = checksum;
|
|
1192
|
+
if (changeNote !== void 0) input.changeNote = changeNote;
|
|
1193
|
+
const data = await ctx.gql.workspace.request(ADD_DATASET_VERSION, { input });
|
|
1194
|
+
return jsonResult(data.labAddDatasetVersion);
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
ctx.logger.error("labsofscience datasets_add_version failed", { error: String(error) });
|
|
1197
|
+
return errorResult(
|
|
1198
|
+
`Failed to add dataset version: ${error instanceof Error ? error.message : String(error)}`
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
);
|
|
1203
|
+
server.registerTool(
|
|
1204
|
+
track(mcpToolName(ctx.productName, "datasets_archive")),
|
|
1205
|
+
{
|
|
1206
|
+
title: "Archive Dataset",
|
|
1207
|
+
description: "Archive a dataset. Returns true on success.",
|
|
1208
|
+
inputSchema: { id: zod.z.string().describe("Dataset ID") }
|
|
1209
|
+
},
|
|
1210
|
+
async ({ id }) => {
|
|
1211
|
+
try {
|
|
1212
|
+
const data = await ctx.gql.workspace.request(
|
|
1213
|
+
ARCHIVE_DATASET,
|
|
1214
|
+
{ id }
|
|
1215
|
+
);
|
|
1216
|
+
return jsonResult({ archived: data.labArchiveDataset, id });
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
ctx.logger.error("labsofscience datasets_archive failed", { error: String(error) });
|
|
1219
|
+
return errorResult(
|
|
1220
|
+
`Failed to archive dataset: ${error instanceof Error ? error.message : String(error)}`
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
function registerPipelineTools(server, ctx, track) {
|
|
1227
|
+
server.registerTool(
|
|
1228
|
+
track(mcpToolName(ctx.productName, "pipelines_list")),
|
|
1229
|
+
{
|
|
1230
|
+
title: "List Analysis Pipelines",
|
|
1231
|
+
description: "List all analysis pipelines in the bound workspace.",
|
|
1232
|
+
inputSchema: {}
|
|
1233
|
+
},
|
|
1234
|
+
async () => {
|
|
1235
|
+
try {
|
|
1236
|
+
const data = await ctx.gql.workspace.request(LIST_PIPELINES, {});
|
|
1237
|
+
return jsonResult(data.labListPipelines);
|
|
1238
|
+
} catch (error) {
|
|
1239
|
+
ctx.logger.error("labsofscience pipelines_list failed", { error: String(error) });
|
|
1240
|
+
return errorResult(
|
|
1241
|
+
`Failed to list pipelines: ${error instanceof Error ? error.message : String(error)}`
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
);
|
|
1246
|
+
server.registerTool(
|
|
1247
|
+
track(mcpToolName(ctx.productName, "pipelines_create")),
|
|
1248
|
+
{
|
|
1249
|
+
title: "Create Analysis Pipeline",
|
|
1250
|
+
description: "Create a reproducible analysis pipeline. `name` is required; `definition` is arbitrary JSON describing the pipeline graph/steps.",
|
|
1251
|
+
inputSchema: {
|
|
1252
|
+
name: zod.z.string().min(1).describe("Pipeline name"),
|
|
1253
|
+
engine: zod.z.string().optional().describe('Execution engine (e.g. "python", "nextflow")'),
|
|
1254
|
+
description: zod.z.string().optional().describe("Pipeline description"),
|
|
1255
|
+
definition: JsonObject.optional().describe("Pipeline definition JSON (graph/steps)"),
|
|
1256
|
+
projectId: zod.z.string().optional().describe("Owning project ID"),
|
|
1257
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Discovery tags")
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
async ({ name, engine, description, definition, projectId, tags }) => {
|
|
1261
|
+
try {
|
|
1262
|
+
const input = { name };
|
|
1263
|
+
if (engine !== void 0) input.engine = engine;
|
|
1264
|
+
if (description !== void 0) input.description = description;
|
|
1265
|
+
if (definition !== void 0) input.definition = definition;
|
|
1266
|
+
if (projectId !== void 0) input.projectId = projectId;
|
|
1267
|
+
if (tags !== void 0) input.tags = tags;
|
|
1268
|
+
const data = await ctx.gql.workspace.request(CREATE_PIPELINE, { input });
|
|
1269
|
+
return jsonResult(data.labCreatePipeline);
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
ctx.logger.error("labsofscience pipelines_create failed", { error: String(error) });
|
|
1272
|
+
return errorResult(
|
|
1273
|
+
`Failed to create pipeline: ${error instanceof Error ? error.message : String(error)}`
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
);
|
|
1278
|
+
server.registerTool(
|
|
1279
|
+
track(mcpToolName(ctx.productName, "pipelines_run")),
|
|
1280
|
+
{
|
|
1281
|
+
title: "Run Analysis Pipeline",
|
|
1282
|
+
description: "Queue a reproducible analysis-pipeline run over a dataset version. `pipelineId` is required; provide `datasetVersionId` (or `datasetId`) for the input data.",
|
|
1283
|
+
inputSchema: {
|
|
1284
|
+
pipelineId: zod.z.string().min(1).describe("Pipeline to run"),
|
|
1285
|
+
datasetId: zod.z.string().optional().describe("Input dataset ID"),
|
|
1286
|
+
datasetVersionId: zod.z.string().optional().describe("Specific input dataset-version ID"),
|
|
1287
|
+
parameters: JsonObject.optional().describe("Run parameters JSON")
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
async ({ pipelineId, datasetId, datasetVersionId, parameters }) => {
|
|
1291
|
+
try {
|
|
1292
|
+
const input = { pipelineId };
|
|
1293
|
+
if (datasetId !== void 0) input.datasetId = datasetId;
|
|
1294
|
+
if (datasetVersionId !== void 0) input.datasetVersionId = datasetVersionId;
|
|
1295
|
+
if (parameters !== void 0) input.parameters = parameters;
|
|
1296
|
+
const data = await ctx.gql.workspace.request(RUN_PIPELINE, { input });
|
|
1297
|
+
return jsonResult(data.labRunPipeline);
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
ctx.logger.error("labsofscience pipelines_run failed", { error: String(error) });
|
|
1300
|
+
return errorResult(
|
|
1301
|
+
`Failed to run pipeline: ${error instanceof Error ? error.message : String(error)}`
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
);
|
|
1306
|
+
server.registerTool(
|
|
1307
|
+
track(mcpToolName(ctx.productName, "pipeline_runs_list")),
|
|
1308
|
+
{
|
|
1309
|
+
title: "List Pipeline Runs",
|
|
1310
|
+
description: "List analysis-pipeline runs, optionally filtered by pipeline and/or status, with pagination. Returns a connection ({ items, pageInfo }).",
|
|
1311
|
+
inputSchema: {
|
|
1312
|
+
pipelineId: zod.z.string().optional().describe("Filter by pipeline ID"),
|
|
1313
|
+
status: PipelineRunStatusEnum.optional().describe("Filter by run status"),
|
|
1314
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
1315
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
1316
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
1317
|
+
}
|
|
1318
|
+
},
|
|
1319
|
+
async ({ pipelineId, status, limit, offset, cursor }) => {
|
|
1320
|
+
try {
|
|
1321
|
+
const data = await ctx.gql.workspace.request(LIST_PIPELINE_RUNS, {
|
|
1322
|
+
pipelineId,
|
|
1323
|
+
status,
|
|
1324
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
1325
|
+
});
|
|
1326
|
+
return jsonResult(data.labListPipelineRuns);
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
ctx.logger.error("labsofscience pipeline_runs_list failed", { error: String(error) });
|
|
1329
|
+
return errorResult(
|
|
1330
|
+
`Failed to list pipeline runs: ${error instanceof Error ? error.message : String(error)}`
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
);
|
|
1335
|
+
server.registerTool(
|
|
1336
|
+
track(mcpToolName(ctx.productName, "pipeline_runs_transition")),
|
|
1337
|
+
{
|
|
1338
|
+
title: "Transition Pipeline Run",
|
|
1339
|
+
description: "Advance a pipeline run through its execution lifecycle (QUEUED \u2192 RUNNING \u2192 SUCCEEDED/FAILED, or CANCELLED). Transitions are validated; `log` appends execution output.",
|
|
1340
|
+
inputSchema: {
|
|
1341
|
+
id: zod.z.string().describe("Pipeline run ID"),
|
|
1342
|
+
status: PipelineRunStatusEnum.describe("Target run status"),
|
|
1343
|
+
log: zod.z.string().optional().describe("Execution log to append")
|
|
1344
|
+
}
|
|
1345
|
+
},
|
|
1346
|
+
async ({ id, status, log }) => {
|
|
1347
|
+
try {
|
|
1348
|
+
const data = await ctx.gql.workspace.request(TRANSITION_PIPELINE_RUN, { id, status, log });
|
|
1349
|
+
return jsonResult(data.labTransitionPipelineRun);
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
ctx.logger.error("labsofscience pipeline_runs_transition failed", {
|
|
1352
|
+
error: String(error)
|
|
1353
|
+
});
|
|
1354
|
+
return errorResult(
|
|
1355
|
+
`Failed to transition pipeline run: ${error instanceof Error ? error.message : String(error)}`
|
|
1356
|
+
);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
);
|
|
1360
|
+
}
|
|
1361
|
+
function registerInstrumentTools(server, ctx, track) {
|
|
1362
|
+
server.registerTool(
|
|
1363
|
+
track(mcpToolName(ctx.productName, "instruments_get")),
|
|
1364
|
+
{
|
|
1365
|
+
title: "Get Instrument",
|
|
1366
|
+
description: "Fetch a single lab instrument by ID, including its bookings.",
|
|
1367
|
+
inputSchema: { id: zod.z.string().describe("Instrument ID") }
|
|
1368
|
+
},
|
|
1369
|
+
async ({ id }) => {
|
|
1370
|
+
try {
|
|
1371
|
+
const data = await ctx.gql.workspace.request(GET_INSTRUMENT, { id });
|
|
1372
|
+
if (!data.labInstrument) return errorResult(`Instrument ${id} not found`);
|
|
1373
|
+
return jsonResult(data.labInstrument);
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
ctx.logger.error("labsofscience instruments_get failed", { error: String(error) });
|
|
1376
|
+
return errorResult(
|
|
1377
|
+
`Failed to get instrument: ${error instanceof Error ? error.message : String(error)}`
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
);
|
|
1382
|
+
server.registerTool(
|
|
1383
|
+
track(mcpToolName(ctx.productName, "instruments_list")),
|
|
1384
|
+
{
|
|
1385
|
+
title: "List Instruments",
|
|
1386
|
+
description: "List lab instruments, optionally filtered by availability status.",
|
|
1387
|
+
inputSchema: {
|
|
1388
|
+
status: InstrumentStatusEnum.optional().describe("Filter by instrument status")
|
|
1389
|
+
}
|
|
1390
|
+
},
|
|
1391
|
+
async ({ status }) => {
|
|
1392
|
+
try {
|
|
1393
|
+
const data = await ctx.gql.workspace.request(LIST_INSTRUMENTS, { status });
|
|
1394
|
+
return jsonResult(data.labListInstruments);
|
|
1395
|
+
} catch (error) {
|
|
1396
|
+
ctx.logger.error("labsofscience instruments_list failed", { error: String(error) });
|
|
1397
|
+
return errorResult(
|
|
1398
|
+
`Failed to list instruments: ${error instanceof Error ? error.message : String(error)}`
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
);
|
|
1403
|
+
server.registerTool(
|
|
1404
|
+
track(mcpToolName(ctx.productName, "instruments_create")),
|
|
1405
|
+
{
|
|
1406
|
+
title: "Create Instrument",
|
|
1407
|
+
description: "Register a lab instrument. `name` is required. Set `requiresApproval` if bookings must be approved before use.",
|
|
1408
|
+
inputSchema: {
|
|
1409
|
+
name: zod.z.string().min(1).describe("Instrument name"),
|
|
1410
|
+
model: zod.z.string().optional().describe("Make/model"),
|
|
1411
|
+
location: zod.z.string().optional().describe("Physical location"),
|
|
1412
|
+
custodianId: zod.z.string().optional().describe("Custodian user ID"),
|
|
1413
|
+
requiresApproval: zod.z.boolean().optional().describe("Bookings need approval (default false)"),
|
|
1414
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Discovery tags")
|
|
1415
|
+
}
|
|
1416
|
+
},
|
|
1417
|
+
async ({ name, model, location, custodianId, requiresApproval, tags }) => {
|
|
1418
|
+
try {
|
|
1419
|
+
const input = { name };
|
|
1420
|
+
if (model !== void 0) input.model = model;
|
|
1421
|
+
if (location !== void 0) input.location = location;
|
|
1422
|
+
if (custodianId !== void 0) input.custodianId = custodianId;
|
|
1423
|
+
if (requiresApproval !== void 0) input.requiresApproval = requiresApproval;
|
|
1424
|
+
if (tags !== void 0) input.tags = tags;
|
|
1425
|
+
const data = await ctx.gql.workspace.request(CREATE_INSTRUMENT, { input });
|
|
1426
|
+
return jsonResult(data.labCreateInstrument);
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
ctx.logger.error("labsofscience instruments_create failed", { error: String(error) });
|
|
1429
|
+
return errorResult(
|
|
1430
|
+
`Failed to create instrument: ${error instanceof Error ? error.message : String(error)}`
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
);
|
|
1435
|
+
server.registerTool(
|
|
1436
|
+
track(mcpToolName(ctx.productName, "instruments_book")),
|
|
1437
|
+
{
|
|
1438
|
+
title: "Book Instrument",
|
|
1439
|
+
description: "Reserve an instrument for a time window. Overlapping bookings are rejected server-side. `startTime`/`endTime` are ISO-8601 DateTimes.",
|
|
1440
|
+
inputSchema: {
|
|
1441
|
+
instrumentId: zod.z.string().min(1).describe("Instrument to book"),
|
|
1442
|
+
startTime: zod.z.string().min(1).describe("Booking start (ISO-8601 DateTime)"),
|
|
1443
|
+
endTime: zod.z.string().min(1).describe("Booking end (ISO-8601 DateTime)"),
|
|
1444
|
+
experimentId: zod.z.string().optional().describe("Associated experiment ID"),
|
|
1445
|
+
purpose: zod.z.string().optional().describe("Purpose of the booking")
|
|
1446
|
+
}
|
|
1447
|
+
},
|
|
1448
|
+
async ({ instrumentId, startTime, endTime, experimentId, purpose }) => {
|
|
1449
|
+
try {
|
|
1450
|
+
const input = { instrumentId, startTime, endTime };
|
|
1451
|
+
if (experimentId !== void 0) input.experimentId = experimentId;
|
|
1452
|
+
if (purpose !== void 0) input.purpose = purpose;
|
|
1453
|
+
const data = await ctx.gql.workspace.request(BOOK_INSTRUMENT, { input });
|
|
1454
|
+
return jsonResult(data.labBookInstrument);
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
ctx.logger.error("labsofscience instruments_book failed", { error: String(error) });
|
|
1457
|
+
return errorResult(
|
|
1458
|
+
`Failed to book instrument: ${error instanceof Error ? error.message : String(error)}`
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
);
|
|
1463
|
+
server.registerTool(
|
|
1464
|
+
track(mcpToolName(ctx.productName, "bookings_cancel")),
|
|
1465
|
+
{
|
|
1466
|
+
title: "Cancel Instrument Booking",
|
|
1467
|
+
description: "Cancel an instrument booking by ID. Returns the cancelled booking.",
|
|
1468
|
+
inputSchema: { id: zod.z.string().describe("Booking ID") }
|
|
1469
|
+
},
|
|
1470
|
+
async ({ id }) => {
|
|
1471
|
+
try {
|
|
1472
|
+
const data = await ctx.gql.workspace.request(CANCEL_BOOKING, { id });
|
|
1473
|
+
return jsonResult(data.labCancelBooking);
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
ctx.logger.error("labsofscience bookings_cancel failed", { error: String(error) });
|
|
1476
|
+
return errorResult(
|
|
1477
|
+
`Failed to cancel booking: ${error instanceof Error ? error.message : String(error)}`
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
function registerInventoryTools(server, ctx, track) {
|
|
1484
|
+
server.registerTool(
|
|
1485
|
+
track(mcpToolName(ctx.productName, "inventory_get")),
|
|
1486
|
+
{
|
|
1487
|
+
title: "Get Inventory Item",
|
|
1488
|
+
description: "Fetch a single inventory item (reagent/consumable/sample/etc.) by ID.",
|
|
1489
|
+
inputSchema: { id: zod.z.string().describe("Inventory item ID") }
|
|
1490
|
+
},
|
|
1491
|
+
async ({ id }) => {
|
|
1492
|
+
try {
|
|
1493
|
+
const data = await ctx.gql.workspace.request(GET_INVENTORY_ITEM, { id });
|
|
1494
|
+
if (!data.labInventoryItem) return errorResult(`Inventory item ${id} not found`);
|
|
1495
|
+
return jsonResult(data.labInventoryItem);
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
ctx.logger.error("labsofscience inventory_get failed", { error: String(error) });
|
|
1498
|
+
return errorResult(
|
|
1499
|
+
`Failed to get inventory item: ${error instanceof Error ? error.message : String(error)}`
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
);
|
|
1504
|
+
server.registerTool(
|
|
1505
|
+
track(mcpToolName(ctx.productName, "inventory_list")),
|
|
1506
|
+
{
|
|
1507
|
+
title: "List Inventory Items",
|
|
1508
|
+
description: "List inventory items, optionally filtered by category and/or stock status, with pagination. Returns a connection ({ items, pageInfo }).",
|
|
1509
|
+
inputSchema: {
|
|
1510
|
+
category: InventoryCategoryEnum.optional().describe("Filter by category"),
|
|
1511
|
+
status: InventoryStatusEnum.optional().describe("Filter by stock status"),
|
|
1512
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
1513
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
1514
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
1515
|
+
}
|
|
1516
|
+
},
|
|
1517
|
+
async ({ category, status, limit, offset, cursor }) => {
|
|
1518
|
+
try {
|
|
1519
|
+
const data = await ctx.gql.workspace.request(LIST_INVENTORY_ITEMS, {
|
|
1520
|
+
category,
|
|
1521
|
+
status,
|
|
1522
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
1523
|
+
});
|
|
1524
|
+
return jsonResult(data.labListInventoryItems);
|
|
1525
|
+
} catch (error) {
|
|
1526
|
+
ctx.logger.error("labsofscience inventory_list failed", { error: String(error) });
|
|
1527
|
+
return errorResult(
|
|
1528
|
+
`Failed to list inventory items: ${error instanceof Error ? error.message : String(error)}`
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
);
|
|
1533
|
+
server.registerTool(
|
|
1534
|
+
track(mcpToolName(ctx.productName, "inventory_create")),
|
|
1535
|
+
{
|
|
1536
|
+
title: "Create Inventory Item",
|
|
1537
|
+
description: "Add an inventory item. `name` is required; `category` defaults to REAGENT. Set `reorderLevel` to drive LOW/OUT_OF_STOCK status.",
|
|
1538
|
+
inputSchema: {
|
|
1539
|
+
name: zod.z.string().min(1).describe("Item name"),
|
|
1540
|
+
category: InventoryCategoryEnum.optional().describe("Category (default REAGENT)"),
|
|
1541
|
+
quantity: zod.z.number().optional().describe("Initial quantity"),
|
|
1542
|
+
unit: zod.z.string().optional().describe('Unit of measure (e.g. "mL", "units")'),
|
|
1543
|
+
reorderLevel: zod.z.number().optional().describe("Reorder threshold"),
|
|
1544
|
+
location: zod.z.string().optional().describe("Storage location"),
|
|
1545
|
+
vendor: zod.z.string().optional().describe("Vendor/supplier"),
|
|
1546
|
+
catalogNumber: zod.z.string().optional().describe("Vendor catalog number"),
|
|
1547
|
+
lotNumber: zod.z.string().optional().describe("Lot/batch number"),
|
|
1548
|
+
expiresAt: zod.z.string().optional().describe("Expiry date (ISO-8601 DateTime)"),
|
|
1549
|
+
sdsFileId: zod.z.string().optional().describe("wspace-files-svc File ID for the SDS sheet"),
|
|
1550
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Discovery tags")
|
|
1551
|
+
}
|
|
1552
|
+
},
|
|
1553
|
+
async ({
|
|
1554
|
+
name,
|
|
1555
|
+
category,
|
|
1556
|
+
quantity,
|
|
1557
|
+
unit,
|
|
1558
|
+
reorderLevel,
|
|
1559
|
+
location,
|
|
1560
|
+
vendor,
|
|
1561
|
+
catalogNumber,
|
|
1562
|
+
lotNumber,
|
|
1563
|
+
expiresAt,
|
|
1564
|
+
sdsFileId,
|
|
1565
|
+
tags
|
|
1566
|
+
}) => {
|
|
1567
|
+
try {
|
|
1568
|
+
const input = { name };
|
|
1569
|
+
if (category !== void 0) input.category = category;
|
|
1570
|
+
if (quantity !== void 0) input.quantity = quantity;
|
|
1571
|
+
if (unit !== void 0) input.unit = unit;
|
|
1572
|
+
if (reorderLevel !== void 0) input.reorderLevel = reorderLevel;
|
|
1573
|
+
if (location !== void 0) input.location = location;
|
|
1574
|
+
if (vendor !== void 0) input.vendor = vendor;
|
|
1575
|
+
if (catalogNumber !== void 0) input.catalogNumber = catalogNumber;
|
|
1576
|
+
if (lotNumber !== void 0) input.lotNumber = lotNumber;
|
|
1577
|
+
if (expiresAt !== void 0) input.expiresAt = expiresAt;
|
|
1578
|
+
if (sdsFileId !== void 0) input.sdsFileId = sdsFileId;
|
|
1579
|
+
if (tags !== void 0) input.tags = tags;
|
|
1580
|
+
const data = await ctx.gql.workspace.request(CREATE_INVENTORY_ITEM, { input });
|
|
1581
|
+
return jsonResult(data.labCreateInventoryItem);
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
ctx.logger.error("labsofscience inventory_create failed", { error: String(error) });
|
|
1584
|
+
return errorResult(
|
|
1585
|
+
`Failed to create inventory item: ${error instanceof Error ? error.message : String(error)}`
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
);
|
|
1590
|
+
server.registerTool(
|
|
1591
|
+
track(mcpToolName(ctx.productName, "inventory_adjust")),
|
|
1592
|
+
{
|
|
1593
|
+
title: "Adjust Inventory",
|
|
1594
|
+
description: "Apply a signed quantity delta to an item and recompute stock status (positive = restock, negative = consume).",
|
|
1595
|
+
inputSchema: {
|
|
1596
|
+
id: zod.z.string().describe("Inventory item ID"),
|
|
1597
|
+
delta: zod.z.number().describe("Signed quantity delta (positive = restock)"),
|
|
1598
|
+
reason: zod.z.string().optional().describe("Reason for the adjustment")
|
|
1599
|
+
}
|
|
1600
|
+
},
|
|
1601
|
+
async ({ id, delta, reason }) => {
|
|
1602
|
+
try {
|
|
1603
|
+
const input = { delta };
|
|
1604
|
+
if (reason !== void 0) input.reason = reason;
|
|
1605
|
+
const data = await ctx.gql.workspace.request(ADJUST_INVENTORY, { id, input });
|
|
1606
|
+
return jsonResult(data.labAdjustInventory);
|
|
1607
|
+
} catch (error) {
|
|
1608
|
+
ctx.logger.error("labsofscience inventory_adjust failed", { error: String(error) });
|
|
1609
|
+
return errorResult(
|
|
1610
|
+
`Failed to adjust inventory: ${error instanceof Error ? error.message : String(error)}`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
);
|
|
1615
|
+
server.registerTool(
|
|
1616
|
+
track(mcpToolName(ctx.productName, "inventory_archive")),
|
|
1617
|
+
{
|
|
1618
|
+
title: "Archive Inventory Item",
|
|
1619
|
+
description: "Archive an inventory item. Returns true on success.",
|
|
1620
|
+
inputSchema: { id: zod.z.string().describe("Inventory item ID") }
|
|
1621
|
+
},
|
|
1622
|
+
async ({ id }) => {
|
|
1623
|
+
try {
|
|
1624
|
+
const data = await ctx.gql.workspace.request(
|
|
1625
|
+
ARCHIVE_INVENTORY_ITEM,
|
|
1626
|
+
{ id }
|
|
1627
|
+
);
|
|
1628
|
+
return jsonResult({ archived: data.labArchiveInventoryItem, id });
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
ctx.logger.error("labsofscience inventory_archive failed", { error: String(error) });
|
|
1631
|
+
return errorResult(
|
|
1632
|
+
`Failed to archive inventory item: ${error instanceof Error ? error.message : String(error)}`
|
|
1633
|
+
);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
function registerNotebookTools(server, ctx, track) {
|
|
1639
|
+
server.registerTool(
|
|
1640
|
+
track(mcpToolName(ctx.productName, "notebook_list")),
|
|
1641
|
+
{
|
|
1642
|
+
title: "List Notebook Entries",
|
|
1643
|
+
description: "List ELN entries, optionally filtered by project, experiment, and/or status, with pagination. Returns a connection ({ items, pageInfo }).",
|
|
1644
|
+
inputSchema: {
|
|
1645
|
+
projectId: zod.z.string().optional().describe("Filter by project ID"),
|
|
1646
|
+
experimentId: zod.z.string().optional().describe("Filter by experiment ID"),
|
|
1647
|
+
status: NotebookEntryStatusEnum.optional().describe("Filter by entry status"),
|
|
1648
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
1649
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)"),
|
|
1650
|
+
cursor: zod.z.string().optional().describe("Opaque pagination cursor")
|
|
1651
|
+
}
|
|
1652
|
+
},
|
|
1653
|
+
async ({ projectId, experimentId, status, limit, offset, cursor }) => {
|
|
1654
|
+
try {
|
|
1655
|
+
const data = await ctx.gql.workspace.request(LIST_NOTEBOOK_ENTRIES, {
|
|
1656
|
+
projectId,
|
|
1657
|
+
experimentId,
|
|
1658
|
+
status,
|
|
1659
|
+
pagination: { limit: limit ?? 50, offset: offset ?? 0, cursor }
|
|
1660
|
+
});
|
|
1661
|
+
return jsonResult(data.labListNotebookEntries);
|
|
1662
|
+
} catch (error) {
|
|
1663
|
+
ctx.logger.error("labsofscience notebook_list failed", { error: String(error) });
|
|
1664
|
+
return errorResult(
|
|
1665
|
+
`Failed to list notebook entries: ${error instanceof Error ? error.message : String(error)}`
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
);
|
|
1670
|
+
server.registerTool(
|
|
1671
|
+
track(mcpToolName(ctx.productName, "notebook_create")),
|
|
1672
|
+
{
|
|
1673
|
+
title: "Create Notebook Entry",
|
|
1674
|
+
description: "Create an ELN entry under a project. `projectId` and `title` are required. Attach files via `fileIds` (wspace-files-svc File IDs).",
|
|
1675
|
+
inputSchema: {
|
|
1676
|
+
projectId: zod.z.string().min(1).describe("Owning project ID"),
|
|
1677
|
+
title: zod.z.string().min(1).describe("Entry title"),
|
|
1678
|
+
body: zod.z.string().optional().describe("Entry body (rich text / markdown)"),
|
|
1679
|
+
experimentId: zod.z.string().optional().describe("Associated experiment ID"),
|
|
1680
|
+
runId: zod.z.string().optional().describe("Associated run ID"),
|
|
1681
|
+
fileIds: zod.z.array(zod.z.string()).optional().describe("Attached wspace-files-svc File IDs"),
|
|
1682
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Discovery tags")
|
|
1683
|
+
}
|
|
1684
|
+
},
|
|
1685
|
+
async ({ projectId, title, body, experimentId, runId, fileIds, tags }) => {
|
|
1686
|
+
try {
|
|
1687
|
+
const input = { projectId, title };
|
|
1688
|
+
if (body !== void 0) input.body = body;
|
|
1689
|
+
if (experimentId !== void 0) input.experimentId = experimentId;
|
|
1690
|
+
if (runId !== void 0) input.runId = runId;
|
|
1691
|
+
if (fileIds !== void 0) input.fileIds = fileIds;
|
|
1692
|
+
if (tags !== void 0) input.tags = tags;
|
|
1693
|
+
const data = await ctx.gql.workspace.request(CREATE_NOTEBOOK_ENTRY, { input });
|
|
1694
|
+
return jsonResult(data.labCreateNotebookEntry);
|
|
1695
|
+
} catch (error) {
|
|
1696
|
+
ctx.logger.error("labsofscience notebook_create failed", { error: String(error) });
|
|
1697
|
+
return errorResult(
|
|
1698
|
+
`Failed to create notebook entry: ${error instanceof Error ? error.message : String(error)}`
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
);
|
|
1703
|
+
server.registerTool(
|
|
1704
|
+
track(mcpToolName(ctx.productName, "notebook_sign")),
|
|
1705
|
+
{
|
|
1706
|
+
title: "Sign Notebook Entry",
|
|
1707
|
+
description: "Sign an ELN entry for record integrity (locks editable content). Returns the signed entry.",
|
|
1708
|
+
inputSchema: { id: zod.z.string().describe("Notebook entry ID") }
|
|
1709
|
+
},
|
|
1710
|
+
async ({ id }) => {
|
|
1711
|
+
try {
|
|
1712
|
+
const data = await ctx.gql.workspace.request(SIGN_NOTEBOOK_ENTRY, { id });
|
|
1713
|
+
return jsonResult(data.labSignNotebookEntry);
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
ctx.logger.error("labsofscience notebook_sign failed", { error: String(error) });
|
|
1716
|
+
return errorResult(
|
|
1717
|
+
`Failed to sign notebook entry: ${error instanceof Error ? error.message : String(error)}`
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
// src/modules/labsofscience/tools/health.ts
|
|
1725
|
+
function registerHealthTools(server, ctx, track) {
|
|
1726
|
+
server.registerTool(
|
|
1727
|
+
track(mcpToolName(ctx.productName, "health")),
|
|
1728
|
+
{
|
|
1729
|
+
title: "Labsofscience Health Check",
|
|
1730
|
+
description: "Public health probe for the Labsofscience subgraph (labHealth). Requires no authentication.",
|
|
1731
|
+
inputSchema: {}
|
|
1732
|
+
},
|
|
1733
|
+
async () => {
|
|
1734
|
+
try {
|
|
1735
|
+
const data = await ctx.gql.workspace.request(HEALTH, {});
|
|
1736
|
+
return jsonResult({ ok: true, status: data.labHealth });
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
ctx.logger.error("labsofscience health failed", { error: String(error) });
|
|
1739
|
+
return errorResult(
|
|
1740
|
+
`Labsofscience health check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// src/modules/labsofscience/resources.ts
|
|
1748
|
+
function registerLabsofscienceResources(server, ctx, track) {
|
|
1749
|
+
const schemaUri = track(mcpResourceUri(ctx.productName, "schema"));
|
|
1750
|
+
const projectsUri = track(mcpResourceUri(ctx.productName, "projects"));
|
|
1751
|
+
const instrumentsUri = track(mcpResourceUri(ctx.productName, "instruments"));
|
|
1752
|
+
const pipelinesUri = track(mcpResourceUri(ctx.productName, "pipelines"));
|
|
1753
|
+
server.registerResource(
|
|
1754
|
+
"labsofscience-schema",
|
|
1755
|
+
schemaUri,
|
|
1756
|
+
{
|
|
1757
|
+
title: "Labsofscience Domain Schema",
|
|
1758
|
+
description: "Overview of the Labsofscience research domain \u2014 entities, enums, and the per-entity tool surface.",
|
|
1759
|
+
mimeType: "text/plain"
|
|
1760
|
+
},
|
|
1761
|
+
async () => {
|
|
1762
|
+
ctx.logger.debug(`Resource ${schemaUri} requested`);
|
|
1763
|
+
const schema = `# Labsofscience Domain (Overview)
|
|
1764
|
+
|
|
1765
|
+
Science & discovery platform \u2014 a research LIMS / ELN.
|
|
1766
|
+
|
|
1767
|
+
## Entities
|
|
1768
|
+
- LabProject \u2014 a research project (has experiments)
|
|
1769
|
+
- LabExperiment \u2014 an experiment under a project (has runs)
|
|
1770
|
+
- LabExperimentRun \u2014 a reproducible run of an experiment
|
|
1771
|
+
- LabProtocol (+LabProtocolStep) \u2014 a versioned SOP with ordered steps
|
|
1772
|
+
- LabDataset (+LabDatasetVersion) \u2014 a dataset with immutable version snapshots
|
|
1773
|
+
- LabAnalysisPipeline (+LabPipelineRun) \u2014 reproducible analysis pipelines and runs
|
|
1774
|
+
- LabInstrument (+LabInstrumentBooking) \u2014 lab instruments and time-window bookings
|
|
1775
|
+
- LabInventoryItem \u2014 reagents/consumables/samples with stock tracking
|
|
1776
|
+
- LabNotebookEntry \u2014 electronic-lab-notebook (ELN) entries (signable)
|
|
1777
|
+
|
|
1778
|
+
## Enums
|
|
1779
|
+
- LabProjectStatus: PLANNING, ACTIVE, ON_HOLD, COMPLETED, ARCHIVED
|
|
1780
|
+
- LabProjectVisibility: PRIVATE, WORKSPACE, PUBLIC
|
|
1781
|
+
- LabExperimentStatus: DESIGN, RUNNING, ANALYSIS, COMPLETE, ABANDONED
|
|
1782
|
+
- LabRunStatus: PLANNED, IN_PROGRESS, SUCCEEDED, FAILED, CANCELLED
|
|
1783
|
+
- LabProtocolStatus: DRAFT, PUBLISHED, DEPRECATED
|
|
1784
|
+
- LabStepKind: PREPARATION, INCUBATION, MEASUREMENT, ANALYSIS, CLEANUP, SAFETY
|
|
1785
|
+
- LabDatasetKind: RAW, PROCESSED, RESULT, REFERENCE
|
|
1786
|
+
- LabDatasetStatus: ACTIVE, LOCKED, ARCHIVED
|
|
1787
|
+
- LabPipelineRunStatus: QUEUED, RUNNING, SUCCEEDED, FAILED, CANCELLED
|
|
1788
|
+
- LabInstrumentStatus: AVAILABLE, IN_USE, MAINTENANCE, OFFLINE
|
|
1789
|
+
- LabBookingStatus: REQUESTED, CONFIRMED, COMPLETED, CANCELLED
|
|
1790
|
+
- LabInventoryCategory: REAGENT, CHEMICAL, BIOLOGICAL, CONSUMABLE, SAMPLE, EQUIPMENT_PART
|
|
1791
|
+
- LabInventoryStatus: IN_STOCK, LOW, OUT_OF_STOCK, EXPIRED, QUARANTINED
|
|
1792
|
+
- LabNotebookEntryStatus: DRAFT, SUBMITTED, SIGNED, WITNESSED, VOID
|
|
1793
|
+
|
|
1794
|
+
## Tool groups
|
|
1795
|
+
projects, experiments, runs, protocols, datasets, pipelines, instruments, inventory, notebook, health.
|
|
1796
|
+
List queries return { items, pageInfo } connections and accept a PaginationInput { limit, offset, cursor };
|
|
1797
|
+
instruments_list and pipelines_list return plain arrays. All run against the WORKSPACE gateway
|
|
1798
|
+
(workspace-scoped) except the public health probe.
|
|
1799
|
+
`;
|
|
1800
|
+
return {
|
|
1801
|
+
contents: [{ uri: schemaUri, text: schema, mimeType: "text/plain" }]
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
);
|
|
1805
|
+
server.registerResource(
|
|
1806
|
+
"labsofscience-projects",
|
|
1807
|
+
projectsUri,
|
|
1808
|
+
{
|
|
1809
|
+
title: "Labsofscience Project Directory",
|
|
1810
|
+
description: "The first 50 research projects in the bound workspace.",
|
|
1811
|
+
mimeType: "application/json"
|
|
1812
|
+
},
|
|
1813
|
+
async () => {
|
|
1814
|
+
ctx.logger.debug(`Resource ${projectsUri} requested`);
|
|
1815
|
+
try {
|
|
1816
|
+
const data = await ctx.gql.workspace.request(LIST_PROJECTS, {
|
|
1817
|
+
pagination: { limit: 50, offset: 0 }
|
|
1818
|
+
});
|
|
1819
|
+
return {
|
|
1820
|
+
contents: [
|
|
1821
|
+
{
|
|
1822
|
+
uri: projectsUri,
|
|
1823
|
+
text: JSON.stringify(data.labListProjects, null, 2),
|
|
1824
|
+
mimeType: "application/json"
|
|
1825
|
+
}
|
|
1826
|
+
]
|
|
1827
|
+
};
|
|
1828
|
+
} catch (error) {
|
|
1829
|
+
return {
|
|
1830
|
+
contents: [
|
|
1831
|
+
{
|
|
1832
|
+
uri: projectsUri,
|
|
1833
|
+
text: JSON.stringify({
|
|
1834
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1835
|
+
}),
|
|
1836
|
+
mimeType: "application/json"
|
|
1837
|
+
}
|
|
1838
|
+
]
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
);
|
|
1843
|
+
server.registerResource(
|
|
1844
|
+
"labsofscience-instruments",
|
|
1845
|
+
instrumentsUri,
|
|
1846
|
+
{
|
|
1847
|
+
title: "Labsofscience Instrument Catalog",
|
|
1848
|
+
description: "All lab instruments in the bound workspace.",
|
|
1849
|
+
mimeType: "application/json"
|
|
1850
|
+
},
|
|
1851
|
+
async () => {
|
|
1852
|
+
ctx.logger.debug(`Resource ${instrumentsUri} requested`);
|
|
1853
|
+
try {
|
|
1854
|
+
const data = await ctx.gql.workspace.request(
|
|
1855
|
+
LIST_INSTRUMENTS,
|
|
1856
|
+
{}
|
|
1857
|
+
);
|
|
1858
|
+
return {
|
|
1859
|
+
contents: [
|
|
1860
|
+
{
|
|
1861
|
+
uri: instrumentsUri,
|
|
1862
|
+
text: JSON.stringify(data.labListInstruments, null, 2),
|
|
1863
|
+
mimeType: "application/json"
|
|
1864
|
+
}
|
|
1865
|
+
]
|
|
1866
|
+
};
|
|
1867
|
+
} catch (error) {
|
|
1868
|
+
return {
|
|
1869
|
+
contents: [
|
|
1870
|
+
{
|
|
1871
|
+
uri: instrumentsUri,
|
|
1872
|
+
text: JSON.stringify({
|
|
1873
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1874
|
+
}),
|
|
1875
|
+
mimeType: "application/json"
|
|
1876
|
+
}
|
|
1877
|
+
]
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
);
|
|
1882
|
+
server.registerResource(
|
|
1883
|
+
"labsofscience-pipelines",
|
|
1884
|
+
pipelinesUri,
|
|
1885
|
+
{
|
|
1886
|
+
title: "Labsofscience Analysis Pipelines",
|
|
1887
|
+
description: "All analysis pipelines in the bound workspace.",
|
|
1888
|
+
mimeType: "application/json"
|
|
1889
|
+
},
|
|
1890
|
+
async () => {
|
|
1891
|
+
ctx.logger.debug(`Resource ${pipelinesUri} requested`);
|
|
1892
|
+
try {
|
|
1893
|
+
const data = await ctx.gql.workspace.request(
|
|
1894
|
+
LIST_PIPELINES,
|
|
1895
|
+
{}
|
|
1896
|
+
);
|
|
1897
|
+
return {
|
|
1898
|
+
contents: [
|
|
1899
|
+
{
|
|
1900
|
+
uri: pipelinesUri,
|
|
1901
|
+
text: JSON.stringify(data.labListPipelines, null, 2),
|
|
1902
|
+
mimeType: "application/json"
|
|
1903
|
+
}
|
|
1904
|
+
]
|
|
1905
|
+
};
|
|
1906
|
+
} catch (error) {
|
|
1907
|
+
return {
|
|
1908
|
+
contents: [
|
|
1909
|
+
{
|
|
1910
|
+
uri: pipelinesUri,
|
|
1911
|
+
text: JSON.stringify({
|
|
1912
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1913
|
+
}),
|
|
1914
|
+
mimeType: "application/json"
|
|
1915
|
+
}
|
|
1916
|
+
]
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
function registerLabsofsciencePrompts(server, ctx, track) {
|
|
1923
|
+
const p = ctx.productName;
|
|
1924
|
+
const setupName = track(mcpPromptName(p, "experiment-setup"));
|
|
1925
|
+
const opsName = track(mcpPromptName(p, "lab-operations"));
|
|
1926
|
+
server.registerPrompt(
|
|
1927
|
+
setupName,
|
|
1928
|
+
{
|
|
1929
|
+
title: "Experiment Setup",
|
|
1930
|
+
description: "Guide for standing up a reproducible experiment end to end: create the project, author + publish a protocol, create the experiment, start a run, and register a dataset.",
|
|
1931
|
+
argsSchema: {
|
|
1932
|
+
projectName: zod.z.string().optional().describe("Research project name"),
|
|
1933
|
+
field: zod.z.string().optional().describe("Scientific field / discipline")
|
|
1934
|
+
}
|
|
1935
|
+
},
|
|
1936
|
+
({ projectName, field }) => {
|
|
1937
|
+
ctx.logger.debug(`Prompt ${setupName} called`, { projectName, field });
|
|
1938
|
+
const nameNote = projectName ? `Use the project name "${projectName}".` : "Ask the user for the project name.";
|
|
1939
|
+
const fieldNote = field ? `Field: ${field}.` : "Optionally capture the scientific field.";
|
|
1940
|
+
return {
|
|
1941
|
+
messages: [
|
|
1942
|
+
{
|
|
1943
|
+
role: "user",
|
|
1944
|
+
content: {
|
|
1945
|
+
type: "text",
|
|
1946
|
+
text: `Help me set up a reproducible Labsofscience experiment.
|
|
1947
|
+
|
|
1948
|
+
1. **Create Project**: Use ${p}_projects_create. ${nameNote} ${fieldNote} Optionally set principalInvestigatorId and visibility.
|
|
1949
|
+
2. **Author Protocol**: Use ${p}_protocols_create, then add ordered steps with ${p}_protocols_add_step (set kind per step), and ${p}_protocols_publish to freeze the version.
|
|
1950
|
+
3. **Create Experiment**: Use ${p}_experiments_create under the new projectId, referencing the published protocolId and a hypothesis + variables JSON.
|
|
1951
|
+
4. **Start a Run**: Use ${p}_runs_start with the experimentId and parameters; later close it with ${p}_runs_complete (status SUCCEEDED/FAILED) and observations.
|
|
1952
|
+
5. **Register Dataset**: Use ${p}_datasets_create for the run output, then ${p}_datasets_add_version with the fileId snapshot.
|
|
1953
|
+
6. **Advance**: Use ${p}_experiments_transition to move DESIGN \u2192 RUNNING \u2192 ANALYSIS \u2192 COMPLETE.
|
|
1954
|
+
|
|
1955
|
+
Show results at each step and confirm before publishing the protocol.`
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
]
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
);
|
|
1962
|
+
server.registerPrompt(
|
|
1963
|
+
opsName,
|
|
1964
|
+
{
|
|
1965
|
+
title: "Lab Operations",
|
|
1966
|
+
description: "Daily lab operations workflow: check instrument availability + bookings, restock low inventory, run analysis pipelines, and sign off notebook entries.",
|
|
1967
|
+
argsSchema: {
|
|
1968
|
+
projectId: zod.z.string().optional().describe("Project ID to focus on")
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
({ projectId }) => {
|
|
1972
|
+
ctx.logger.debug(`Prompt ${opsName} called`, { projectId });
|
|
1973
|
+
const projectNote = projectId ? `Focus on project "${projectId}".` : `First list projects with ${p}_projects_list and pick one.`;
|
|
1974
|
+
return {
|
|
1975
|
+
messages: [
|
|
1976
|
+
{
|
|
1977
|
+
role: "user",
|
|
1978
|
+
content: {
|
|
1979
|
+
type: "text",
|
|
1980
|
+
text: `Run daily Labsofscience lab operations. ${projectNote}
|
|
1981
|
+
|
|
1982
|
+
1. **Instruments**: Use ${p}_instruments_list (status AVAILABLE) to see free instruments; book one with ${p}_instruments_book for an upcoming run, or cancel stale holds with ${p}_bookings_cancel.
|
|
1983
|
+
2. **Inventory**: Use ${p}_inventory_list with status=LOW (and OUT_OF_STOCK) to find items to restock, then ${p}_inventory_adjust with a positive delta to record receipts.
|
|
1984
|
+
3. **Pipelines**: Use ${p}_pipelines_list and ${p}_pipeline_runs_list (status QUEUED/RUNNING) to monitor analysis; trigger new work with ${p}_pipelines_run and advance with ${p}_pipeline_runs_transition.
|
|
1985
|
+
4. **Notebook**: Use ${p}_notebook_list (status SUBMITTED) for the project and sign approved entries with ${p}_notebook_sign.
|
|
1986
|
+
5. **Experiments**: Use ${p}_experiments_list filtered by projectId to review status and surface runs needing completion via ${p}_runs_list.
|
|
1987
|
+
|
|
1988
|
+
Summarize what needs attention after each step.`
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
]
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
);
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// src/modules/labsofscience/index.ts
|
|
1998
|
+
var labsofscienceModule = {
|
|
1999
|
+
id: "labsofscience",
|
|
2000
|
+
version: "0.0.1",
|
|
2001
|
+
displayName: "Labsofscience",
|
|
2002
|
+
dependsOn: ["auth"],
|
|
2003
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
2004
|
+
register(server, ctx) {
|
|
2005
|
+
const tools = [];
|
|
2006
|
+
const resources = [];
|
|
2007
|
+
const prompts = [];
|
|
2008
|
+
const trackTool = (n) => {
|
|
2009
|
+
tools.push(n);
|
|
2010
|
+
return n;
|
|
2011
|
+
};
|
|
2012
|
+
const trackResource = (uri) => {
|
|
2013
|
+
resources.push(uri);
|
|
2014
|
+
return uri;
|
|
2015
|
+
};
|
|
2016
|
+
const trackPrompt = (n) => {
|
|
2017
|
+
prompts.push(n);
|
|
2018
|
+
return n;
|
|
2019
|
+
};
|
|
2020
|
+
registerProjectTools(server, ctx, trackTool);
|
|
2021
|
+
registerExperimentTools(server, ctx, trackTool);
|
|
2022
|
+
registerRunTools(server, ctx, trackTool);
|
|
2023
|
+
registerProtocolTools(server, ctx, trackTool);
|
|
2024
|
+
registerDatasetTools(server, ctx, trackTool);
|
|
2025
|
+
registerPipelineTools(server, ctx, trackTool);
|
|
2026
|
+
registerInstrumentTools(server, ctx, trackTool);
|
|
2027
|
+
registerInventoryTools(server, ctx, trackTool);
|
|
2028
|
+
registerNotebookTools(server, ctx, trackTool);
|
|
2029
|
+
registerHealthTools(server, ctx, trackTool);
|
|
2030
|
+
registerLabsofscienceResources(server, ctx, trackResource);
|
|
2031
|
+
registerLabsofsciencePrompts(server, ctx, trackPrompt);
|
|
2032
|
+
return { tools, resources, prompts };
|
|
2033
|
+
}
|
|
2034
|
+
};
|
|
2035
|
+
var labsofscience_default = labsofscienceModule;
|
|
2036
|
+
|
|
2037
|
+
module.exports = labsofscience_default;
|
|
2038
|
+
//# sourceMappingURL=index.cjs.map
|
|
2039
|
+
//# sourceMappingURL=index.cjs.map
|