@thingd/sdk 0.70.0 → 0.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -3
- package/dist/client/http-thing-store.d.ts +0 -55
- package/dist/client/http-thing-store.d.ts.map +0 -1
- package/dist/client/http-thing-store.js +0 -233
- package/dist/client/in-memory-thing-store.d.ts +0 -41
- package/dist/client/in-memory-thing-store.d.ts.map +0 -1
- package/dist/client/in-memory-thing-store.js +0 -292
- package/dist/client/index.d.ts +0 -5
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js +0 -3
- package/dist/client/thingd.d.ts +0 -24
- package/dist/client/thingd.d.ts.map +0 -1
- package/dist/client/thingd.js +0 -27
- package/dist/constants.d.ts +0 -2
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -1
- package/dist/index.d.ts +0 -13
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -11
- package/dist/mcp/audit.d.ts +0 -27
- package/dist/mcp/audit.d.ts.map +0 -1
- package/dist/mcp/audit.js +0 -36
- package/dist/mcp/config.d.ts +0 -22
- package/dist/mcp/config.d.ts.map +0 -1
- package/dist/mcp/config.js +0 -52
- package/dist/mcp/index.d.ts +0 -6
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -5
- package/dist/mcp/result.d.ts +0 -3
- package/dist/mcp/result.d.ts.map +0 -1
- package/dist/mcp/result.js +0 -10
- package/dist/mcp/server.d.ts +0 -19
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -51
- package/dist/mcp/tools.d.ts +0 -10
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/mcp/tools.js +0 -1033
- package/dist/memory/index.d.ts +0 -7
- package/dist/memory/index.d.ts.map +0 -1
- package/dist/memory/index.js +0 -7
- package/dist/rest/helpers.d.ts +0 -17
- package/dist/rest/helpers.d.ts.map +0 -1
- package/dist/rest/helpers.js +0 -60
- package/dist/rest/index.d.ts +0 -3
- package/dist/rest/index.d.ts.map +0 -1
- package/dist/rest/index.js +0 -2
- package/dist/rest/server.d.ts +0 -4
- package/dist/rest/server.d.ts.map +0 -1
- package/dist/rest/server.js +0 -472
- package/dist/scheduler.d.ts +0 -28
- package/dist/scheduler.d.ts.map +0 -1
- package/dist/scheduler.js +0 -451
- package/dist/stores/cloud-thing-store.d.ts +0 -63
- package/dist/stores/cloud-thing-store.d.ts.map +0 -1
- package/dist/stores/cloud-thing-store.js +0 -396
- package/dist/stores/in-memory-thing-store.d.ts +0 -62
- package/dist/stores/in-memory-thing-store.d.ts.map +0 -1
- package/dist/stores/in-memory-thing-store.js +0 -709
- package/dist/stores/native-thing-store.d.ts +0 -62
- package/dist/stores/native-thing-store.d.ts.map +0 -1
- package/dist/stores/native-thing-store.js +0 -374
- package/dist/thingd.d.ts +0 -87
- package/dist/thingd.d.ts.map +0 -1
- package/dist/thingd.js +0 -236
- package/dist/types/index.d.ts +0 -2
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types.d.ts +0 -435
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- package/dist/version.d.ts +0 -6
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js +0 -5
package/dist/mcp/tools.js
DELETED
|
@@ -1,1033 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { appendMcpAuditEvent, resolveThingdMcpAuditOptions, } from "./audit.js";
|
|
3
|
-
import { jsonResult } from "./result.js";
|
|
4
|
-
const memoryObjectSchema = z.object({ id: z.string().min(1) }).catchall(z.unknown());
|
|
5
|
-
const memoryEventSchema = z.object({ type: z.string().min(1) }).catchall(z.unknown());
|
|
6
|
-
const objectPayloadSchema = z.record(z.string(), z.unknown());
|
|
7
|
-
const auditInputSchema = {
|
|
8
|
-
actor: z.string().min(1).optional(),
|
|
9
|
-
source: z.string().min(1).optional(),
|
|
10
|
-
};
|
|
11
|
-
export function registerThingdTools(server, db, options = {}) {
|
|
12
|
-
const audit = resolveThingdMcpAuditOptions(options.audit);
|
|
13
|
-
const allowlist = options.hardening?.collectionAllowlist;
|
|
14
|
-
const readOnly = options.hardening?.readOnly ?? false;
|
|
15
|
-
/** Throw a tool-level error if the collection is not in the allowlist. */
|
|
16
|
-
function assertCollectionAllowed(collection) {
|
|
17
|
-
if (allowlist && !allowlist.has(collection)) {
|
|
18
|
-
throw new Error(`Collection "${collection}" is not permitted by this thingd MCP server. Allowed: ${[...allowlist].join(", ")}.`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
/** Throw a tool-level error if the server is in read-only mode. */
|
|
22
|
-
function assertWriteAllowed() {
|
|
23
|
-
if (readOnly) {
|
|
24
|
-
throw new Error("This thingd MCP server is configured in read-only mode. Write operations are not permitted.");
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
server.registerTool("thing_search", {
|
|
28
|
-
title: "Search Memory",
|
|
29
|
-
description: "Search thingd objects and events by full-text query. Returns matching items ranked by relevance using SQLite FTS5 with Porter word stemming. Use this to find previously stored memories, notes, or events by keyword or phrase. Accepts a query string and optional filter by collection names, metadata key-value pairs, and a result limit. Returns an array of matching objects with relevance scores.",
|
|
30
|
-
inputSchema: {
|
|
31
|
-
query: z.string().min(1),
|
|
32
|
-
collections: z.array(z.string().min(1)).optional(),
|
|
33
|
-
limit: z.number().int().positive().max(100).optional(),
|
|
34
|
-
filter: z.record(z.string(), z.unknown()).optional(),
|
|
35
|
-
},
|
|
36
|
-
annotations: {
|
|
37
|
-
readOnlyHint: true,
|
|
38
|
-
destructiveHint: false,
|
|
39
|
-
idempotentHint: true,
|
|
40
|
-
openWorldHint: false,
|
|
41
|
-
},
|
|
42
|
-
}, async ({ query, collections, limit, filter }) => {
|
|
43
|
-
if (collections) {
|
|
44
|
-
for (const c of collections) {
|
|
45
|
-
assertCollectionAllowed(c);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return jsonResult(await db.search(query, { collections, limit, filter }));
|
|
49
|
-
});
|
|
50
|
-
server.registerTool("thing_get", {
|
|
51
|
-
title: "Get Object",
|
|
52
|
-
description: "Read one thingd object by collection name and id. Returns the full object if found, or null if not found. Use this to retrieve a specific stored record when you know its exact collection and id. Returns a single object or null.",
|
|
53
|
-
inputSchema: {
|
|
54
|
-
collection: z.string().min(1),
|
|
55
|
-
id: z.string().min(1),
|
|
56
|
-
},
|
|
57
|
-
annotations: {
|
|
58
|
-
readOnlyHint: true,
|
|
59
|
-
destructiveHint: false,
|
|
60
|
-
idempotentHint: true,
|
|
61
|
-
openWorldHint: false,
|
|
62
|
-
},
|
|
63
|
-
}, async ({ collection, id }) => {
|
|
64
|
-
assertCollectionAllowed(collection);
|
|
65
|
-
return jsonResult(await db.get(collection, id));
|
|
66
|
-
});
|
|
67
|
-
server.registerTool("thing_put", {
|
|
68
|
-
title: "Put Object",
|
|
69
|
-
description: "Create or replace one object-shaped memory record in a collection. The object must have an 'id' field. If an object with the same id already exists in the collection, it is replaced. Use this to store memories, notes, tasks, or any structured data. Returns the stored object with collection, version, and timestamps.",
|
|
70
|
-
inputSchema: {
|
|
71
|
-
collection: z.string().min(1),
|
|
72
|
-
object: memoryObjectSchema,
|
|
73
|
-
expectedVersion: z.number().int().nonnegative().optional(),
|
|
74
|
-
...auditInputSchema,
|
|
75
|
-
},
|
|
76
|
-
annotations: {
|
|
77
|
-
readOnlyHint: false,
|
|
78
|
-
destructiveHint: false,
|
|
79
|
-
idempotentHint: false,
|
|
80
|
-
openWorldHint: false,
|
|
81
|
-
},
|
|
82
|
-
}, async ({ collection, object, actor, source, expectedVersion }) => {
|
|
83
|
-
assertWriteAllowed();
|
|
84
|
-
assertCollectionAllowed(collection);
|
|
85
|
-
const stored = await db.put(collection, object, { expectedVersion });
|
|
86
|
-
await appendMcpAuditEvent(db, audit, {
|
|
87
|
-
action: "objects.put",
|
|
88
|
-
target: {
|
|
89
|
-
collection,
|
|
90
|
-
id: stored.id,
|
|
91
|
-
},
|
|
92
|
-
metadata: auditMetadata(actor, source),
|
|
93
|
-
result: {
|
|
94
|
-
collection: stored.collection,
|
|
95
|
-
id: stored.id,
|
|
96
|
-
version: stored.version,
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
return jsonResult(stored);
|
|
100
|
-
});
|
|
101
|
-
server.registerTool("thing_delete", {
|
|
102
|
-
title: "Delete Object",
|
|
103
|
-
description: "Delete one thingd object by collection name and id. Permanently removes the object from the store. Returns a result indicating whether the deletion was successful. Use this to remove outdated or incorrect records.",
|
|
104
|
-
inputSchema: {
|
|
105
|
-
collection: z.string().min(1),
|
|
106
|
-
id: z.string().min(1),
|
|
107
|
-
...auditInputSchema,
|
|
108
|
-
},
|
|
109
|
-
annotations: {
|
|
110
|
-
readOnlyHint: false,
|
|
111
|
-
destructiveHint: true,
|
|
112
|
-
idempotentHint: true,
|
|
113
|
-
openWorldHint: false,
|
|
114
|
-
},
|
|
115
|
-
}, async ({ collection, id, actor, source }) => {
|
|
116
|
-
assertWriteAllowed();
|
|
117
|
-
assertCollectionAllowed(collection);
|
|
118
|
-
const result = await db.delete(collection, id);
|
|
119
|
-
await appendMcpAuditEvent(db, audit, {
|
|
120
|
-
action: "objects.delete",
|
|
121
|
-
target: {
|
|
122
|
-
collection,
|
|
123
|
-
id,
|
|
124
|
-
},
|
|
125
|
-
metadata: auditMetadata(actor, source),
|
|
126
|
-
result,
|
|
127
|
-
});
|
|
128
|
-
return jsonResult(result);
|
|
129
|
-
});
|
|
130
|
-
server.registerTool("thing_events_append", {
|
|
131
|
-
title: "Append Event",
|
|
132
|
-
description: "Append an event to a named event stream. Events are append-only, ordered records with a 'type' field and arbitrary payload. Use this to record occurrences, state changes, or audit entries. Each event gets an auto-incremented sequence id. Returns the stored event with id, stream, and timestamp.",
|
|
133
|
-
inputSchema: {
|
|
134
|
-
stream: z.string().min(1),
|
|
135
|
-
event: memoryEventSchema,
|
|
136
|
-
...auditInputSchema,
|
|
137
|
-
},
|
|
138
|
-
annotations: {
|
|
139
|
-
readOnlyHint: false,
|
|
140
|
-
destructiveHint: false,
|
|
141
|
-
idempotentHint: false,
|
|
142
|
-
openWorldHint: false,
|
|
143
|
-
},
|
|
144
|
-
}, async ({ stream, event, actor, source }) => {
|
|
145
|
-
assertWriteAllowed();
|
|
146
|
-
// Prevent direct writes to the protected audit stream
|
|
147
|
-
if (stream === audit.stream) {
|
|
148
|
-
throw new Error(`Stream '${stream}' is protected and cannot be written to directly`);
|
|
149
|
-
}
|
|
150
|
-
const stored = await db.events.append(stream, event);
|
|
151
|
-
await appendMcpAuditEvent(db, audit, {
|
|
152
|
-
action: "events.append",
|
|
153
|
-
target: {
|
|
154
|
-
stream,
|
|
155
|
-
eventType: stored.type,
|
|
156
|
-
eventId: stored.id,
|
|
157
|
-
},
|
|
158
|
-
metadata: auditMetadata(actor, source),
|
|
159
|
-
result: {
|
|
160
|
-
id: stored.id,
|
|
161
|
-
stream: stored.stream,
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
return jsonResult(stored);
|
|
165
|
-
});
|
|
166
|
-
server.registerTool("thing_events_list", {
|
|
167
|
-
title: "List Events",
|
|
168
|
-
description: "List events from a thingd stream, optionally filtered by stream name, starting from a specific sequence number, with a configurable limit, or filtered by timestamp. Use this to review recent activity, replay events, or audit changes. Returns an array of events ordered by sequence.",
|
|
169
|
-
inputSchema: {
|
|
170
|
-
stream: z.string().min(1).optional(),
|
|
171
|
-
fromSequence: z.number().int().positive().optional(),
|
|
172
|
-
limit: z.number().int().positive().optional(),
|
|
173
|
-
since: z.string().optional(),
|
|
174
|
-
},
|
|
175
|
-
annotations: {
|
|
176
|
-
readOnlyHint: true,
|
|
177
|
-
destructiveHint: false,
|
|
178
|
-
idempotentHint: true,
|
|
179
|
-
openWorldHint: false,
|
|
180
|
-
},
|
|
181
|
-
}, async ({ stream, fromSequence, limit, since }) => jsonResult(await db.events.list(stream, { fromSequence, limit, since })));
|
|
182
|
-
server.registerTool("thing_queue_push", {
|
|
183
|
-
title: "Push Queue Job",
|
|
184
|
-
description: "Push a durable job onto a named queue. Jobs have a JSON payload and can include an idempotency key, max retry attempts, and a delay before the job becomes ready. Use this to schedule background work like processing, notifications, or data pipelines. Returns the created job with id, queue, and status.",
|
|
185
|
-
inputSchema: {
|
|
186
|
-
queue: z.string().min(1),
|
|
187
|
-
payload: objectPayloadSchema,
|
|
188
|
-
idempotencyKey: z.string().min(1).optional(),
|
|
189
|
-
maxAttempts: z.number().int().positive().max(100).optional(),
|
|
190
|
-
delayMs: z.number().int().min(0).optional(),
|
|
191
|
-
priority: z.number().int().min(0).optional(),
|
|
192
|
-
...auditInputSchema,
|
|
193
|
-
},
|
|
194
|
-
annotations: {
|
|
195
|
-
readOnlyHint: false,
|
|
196
|
-
destructiveHint: false,
|
|
197
|
-
idempotentHint: false,
|
|
198
|
-
openWorldHint: false,
|
|
199
|
-
},
|
|
200
|
-
}, async ({ queue, payload, idempotencyKey, maxAttempts, delayMs, priority, actor, source }) => {
|
|
201
|
-
assertWriteAllowed();
|
|
202
|
-
const job = await db.queue(queue).push(payload, {
|
|
203
|
-
idempotencyKey,
|
|
204
|
-
maxAttempts,
|
|
205
|
-
delayMs,
|
|
206
|
-
priority,
|
|
207
|
-
});
|
|
208
|
-
await appendMcpAuditEvent(db, audit, {
|
|
209
|
-
action: "queue.push",
|
|
210
|
-
target: {
|
|
211
|
-
queue,
|
|
212
|
-
id: job.id,
|
|
213
|
-
},
|
|
214
|
-
metadata: auditMetadata(actor, source),
|
|
215
|
-
result: {
|
|
216
|
-
id: job.id,
|
|
217
|
-
queue: job.queue,
|
|
218
|
-
status: job.status,
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
return jsonResult(job);
|
|
222
|
-
});
|
|
223
|
-
server.registerTool("thing_queue_claim", {
|
|
224
|
-
title: "Claim Queue Job",
|
|
225
|
-
description: "Claim the next ready job from a queue. The job is leased for a configurable duration (default 30s). If not acked or nacked before the lease expires, it returns to the ready state. Returns the claimed job with payload, or null if no jobs are ready. Use this to process queue work in a worker loop.",
|
|
226
|
-
inputSchema: {
|
|
227
|
-
queue: z.string().min(1),
|
|
228
|
-
leaseMs: z.number().int().optional(),
|
|
229
|
-
...auditInputSchema,
|
|
230
|
-
},
|
|
231
|
-
annotations: {
|
|
232
|
-
readOnlyHint: false,
|
|
233
|
-
destructiveHint: false,
|
|
234
|
-
idempotentHint: false,
|
|
235
|
-
openWorldHint: false,
|
|
236
|
-
},
|
|
237
|
-
}, async ({ queue, leaseMs, actor, source }) => {
|
|
238
|
-
assertWriteAllowed();
|
|
239
|
-
const job = await db.queue(queue).claim({ leaseMs });
|
|
240
|
-
if (job) {
|
|
241
|
-
await appendMcpAuditEvent(db, audit, {
|
|
242
|
-
action: "queue.claim",
|
|
243
|
-
target: {
|
|
244
|
-
queue,
|
|
245
|
-
id: job.id,
|
|
246
|
-
},
|
|
247
|
-
metadata: auditMetadata(actor, source),
|
|
248
|
-
result: {
|
|
249
|
-
id: job.id,
|
|
250
|
-
queue: job.queue,
|
|
251
|
-
status: job.status,
|
|
252
|
-
attempts: job.attempts,
|
|
253
|
-
},
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
return jsonResult(job);
|
|
257
|
-
});
|
|
258
|
-
server.registerTool("thing_queue_ack", {
|
|
259
|
-
title: "Acknowledge Queue Job",
|
|
260
|
-
description: "Mark one leased queue job as completed. This removes the job from the queue permanently. Call this after successfully processing a claimed job. Returns a result with ok status and the final job status.",
|
|
261
|
-
inputSchema: {
|
|
262
|
-
queue: z.string().min(1),
|
|
263
|
-
id: z.string().min(1),
|
|
264
|
-
...auditInputSchema,
|
|
265
|
-
},
|
|
266
|
-
annotations: {
|
|
267
|
-
readOnlyHint: false,
|
|
268
|
-
destructiveHint: false,
|
|
269
|
-
idempotentHint: false,
|
|
270
|
-
openWorldHint: false,
|
|
271
|
-
},
|
|
272
|
-
}, async ({ queue, id, actor, source }) => {
|
|
273
|
-
assertWriteAllowed();
|
|
274
|
-
const result = await db.queue(queue).ack(id);
|
|
275
|
-
if (result.ok) {
|
|
276
|
-
await appendMcpAuditEvent(db, audit, {
|
|
277
|
-
action: "queue.ack",
|
|
278
|
-
target: {
|
|
279
|
-
queue,
|
|
280
|
-
id,
|
|
281
|
-
},
|
|
282
|
-
metadata: auditMetadata(actor, source),
|
|
283
|
-
result: {
|
|
284
|
-
ok: true,
|
|
285
|
-
status: result.job.status,
|
|
286
|
-
},
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
return jsonResult(result);
|
|
290
|
-
});
|
|
291
|
-
server.registerTool("thing_queue_nack", {
|
|
292
|
-
title: "Reject Queue Job",
|
|
293
|
-
description: "Reject a leased queue job for retry or dead-letter routing. If the job has remaining attempts, it goes back to ready (optionally after a delay). If attempts are exhausted, it moves to the dead-letter list. Optionally attach an error message. Returns a result with ok status and the updated job status.",
|
|
294
|
-
inputSchema: {
|
|
295
|
-
queue: z.string().min(1),
|
|
296
|
-
id: z.string().min(1),
|
|
297
|
-
delayMs: z.number().int().min(0).optional(),
|
|
298
|
-
error: z.string().optional(),
|
|
299
|
-
...auditInputSchema,
|
|
300
|
-
},
|
|
301
|
-
annotations: {
|
|
302
|
-
readOnlyHint: false,
|
|
303
|
-
destructiveHint: false,
|
|
304
|
-
idempotentHint: false,
|
|
305
|
-
openWorldHint: false,
|
|
306
|
-
},
|
|
307
|
-
}, async ({ queue, id, delayMs, error, actor, source }) => {
|
|
308
|
-
assertWriteAllowed();
|
|
309
|
-
const result = await db.queue(queue).nack(id, { delayMs, error });
|
|
310
|
-
if (result.ok) {
|
|
311
|
-
await appendMcpAuditEvent(db, audit, {
|
|
312
|
-
action: "queue.nack",
|
|
313
|
-
target: {
|
|
314
|
-
queue,
|
|
315
|
-
id,
|
|
316
|
-
},
|
|
317
|
-
metadata: auditMetadata(actor, source),
|
|
318
|
-
result: {
|
|
319
|
-
ok: true,
|
|
320
|
-
status: result.job.status,
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
}
|
|
324
|
-
return jsonResult(result);
|
|
325
|
-
});
|
|
326
|
-
server.registerTool("thing_queue_list", {
|
|
327
|
-
title: "List Queue Jobs",
|
|
328
|
-
description: "List all jobs in a queue across all states (ready, leased, dead-letter). Use this to inspect queue contents, monitor backlog, or debug stuck jobs. Returns an array of job objects with id, payload, status, attempts, and timestamps.",
|
|
329
|
-
inputSchema: {
|
|
330
|
-
queue: z.string().min(1),
|
|
331
|
-
},
|
|
332
|
-
annotations: {
|
|
333
|
-
readOnlyHint: true,
|
|
334
|
-
destructiveHint: false,
|
|
335
|
-
idempotentHint: true,
|
|
336
|
-
openWorldHint: false,
|
|
337
|
-
},
|
|
338
|
-
}, async ({ queue }) => jsonResult(await db.queue(queue).list()));
|
|
339
|
-
server.registerTool("thing_queue_dead", {
|
|
340
|
-
title: "List Dead Queue Jobs",
|
|
341
|
-
description: "List dead-letter jobs in a queue. These are jobs that exhausted all retry attempts. Use this to inspect failed work, diagnose errors, or decide whether to retry or discard. Returns an array of dead-letter job objects.",
|
|
342
|
-
inputSchema: {
|
|
343
|
-
queue: z.string().min(1),
|
|
344
|
-
},
|
|
345
|
-
annotations: {
|
|
346
|
-
readOnlyHint: true,
|
|
347
|
-
destructiveHint: false,
|
|
348
|
-
idempotentHint: true,
|
|
349
|
-
openWorldHint: false,
|
|
350
|
-
},
|
|
351
|
-
}, async ({ queue }) => jsonResult(await db.queue(queue).dead()));
|
|
352
|
-
server.registerTool("thing_objects_list", {
|
|
353
|
-
title: "List Objects",
|
|
354
|
-
description: "List objects in a collection with optional filtering, sorting, limit, and offset. Returns an array of objects. Use sortBy.field to sort by id, collection, created_at, updated_at, or version.",
|
|
355
|
-
inputSchema: {
|
|
356
|
-
collection: z.string().min(1),
|
|
357
|
-
filter: z.record(z.string(), z.unknown()).optional(),
|
|
358
|
-
sortBy: z
|
|
359
|
-
.object({
|
|
360
|
-
field: z.enum(["id", "collection", "created_at", "updated_at", "version"]),
|
|
361
|
-
direction: z.enum(["asc", "desc"]).default("asc"),
|
|
362
|
-
})
|
|
363
|
-
.optional(),
|
|
364
|
-
limit: z.number().int().positive().optional(),
|
|
365
|
-
offset: z.number().int().nonnegative().optional(),
|
|
366
|
-
},
|
|
367
|
-
annotations: {
|
|
368
|
-
readOnlyHint: true,
|
|
369
|
-
destructiveHint: false,
|
|
370
|
-
idempotentHint: true,
|
|
371
|
-
openWorldHint: false,
|
|
372
|
-
},
|
|
373
|
-
}, async ({ collection, filter, sortBy, limit, offset }) => {
|
|
374
|
-
assertCollectionAllowed(collection);
|
|
375
|
-
return jsonResult(await db.listObjects(collection, { filter, sortBy, limit, offset }));
|
|
376
|
-
});
|
|
377
|
-
server.registerTool("thing_count_objects", {
|
|
378
|
-
title: "Count Objects",
|
|
379
|
-
description: "Count all objects stored across all collections. Returns a single number representing the total object count. Use this for quick inventory checks or monitoring storage usage.",
|
|
380
|
-
inputSchema: {},
|
|
381
|
-
annotations: {
|
|
382
|
-
readOnlyHint: true,
|
|
383
|
-
destructiveHint: false,
|
|
384
|
-
idempotentHint: true,
|
|
385
|
-
openWorldHint: false,
|
|
386
|
-
},
|
|
387
|
-
}, async () => jsonResult(await db.countObjects()));
|
|
388
|
-
server.registerTool("thing_count_objects_in_collection", {
|
|
389
|
-
title: "Count Objects in Collection",
|
|
390
|
-
description: "Count objects in a specific collection. Returns a single number. Uses an indexed query for O(log n) performance.",
|
|
391
|
-
inputSchema: {
|
|
392
|
-
collection: z.string().describe("Collection name to count"),
|
|
393
|
-
},
|
|
394
|
-
annotations: {
|
|
395
|
-
readOnlyHint: true,
|
|
396
|
-
destructiveHint: false,
|
|
397
|
-
idempotentHint: true,
|
|
398
|
-
openWorldHint: false,
|
|
399
|
-
},
|
|
400
|
-
}, async ({ collection }) => jsonResult(await db.countObjectsInCollection(collection)));
|
|
401
|
-
server.registerTool("thing_count_events", {
|
|
402
|
-
title: "Count Events",
|
|
403
|
-
description: "Count all events across all streams. Returns a single number representing the total event count. Use this to monitor event volume or check stream activity.",
|
|
404
|
-
inputSchema: {},
|
|
405
|
-
annotations: {
|
|
406
|
-
readOnlyHint: true,
|
|
407
|
-
destructiveHint: false,
|
|
408
|
-
idempotentHint: true,
|
|
409
|
-
openWorldHint: false,
|
|
410
|
-
},
|
|
411
|
-
}, async () => jsonResult(await db.countEvents()));
|
|
412
|
-
server.registerTool("thing_count_active_jobs", {
|
|
413
|
-
title: "Count Active Jobs",
|
|
414
|
-
description: "Count all active (non-dead) queue jobs across all queues. Includes ready, leased, and delayed jobs. Use this to monitor queue depth and worker load.",
|
|
415
|
-
inputSchema: {},
|
|
416
|
-
annotations: {
|
|
417
|
-
readOnlyHint: true,
|
|
418
|
-
destructiveHint: false,
|
|
419
|
-
idempotentHint: true,
|
|
420
|
-
openWorldHint: false,
|
|
421
|
-
},
|
|
422
|
-
}, async () => jsonResult(await db.countActiveJobs()));
|
|
423
|
-
server.registerTool("thing_count_dead_jobs", {
|
|
424
|
-
title: "Count Dead Jobs",
|
|
425
|
-
description: "Count all dead-letter queue jobs across all queues. These are jobs that failed all retry attempts. Use this to monitor failure rates and decide when to investigate or discard.",
|
|
426
|
-
inputSchema: {},
|
|
427
|
-
annotations: {
|
|
428
|
-
readOnlyHint: true,
|
|
429
|
-
destructiveHint: false,
|
|
430
|
-
idempotentHint: true,
|
|
431
|
-
openWorldHint: false,
|
|
432
|
-
},
|
|
433
|
-
}, async () => jsonResult(await db.countDeadJobs()));
|
|
434
|
-
server.registerTool("thing_list_collections", {
|
|
435
|
-
title: "List Collections",
|
|
436
|
-
description: "List all object collection names in the store. Returns an array of collection name strings. Use this to discover what data is stored before searching or querying.",
|
|
437
|
-
inputSchema: {},
|
|
438
|
-
annotations: {
|
|
439
|
-
readOnlyHint: true,
|
|
440
|
-
destructiveHint: false,
|
|
441
|
-
idempotentHint: true,
|
|
442
|
-
openWorldHint: false,
|
|
443
|
-
},
|
|
444
|
-
}, async () => jsonResult(await db.listCollections()));
|
|
445
|
-
server.registerTool("thing_list_streams", {
|
|
446
|
-
title: "List Streams",
|
|
447
|
-
description: "List all event stream names in the store. Returns an array of stream name strings. Use this to discover available event streams before listing or appending events.",
|
|
448
|
-
inputSchema: {},
|
|
449
|
-
annotations: {
|
|
450
|
-
readOnlyHint: true,
|
|
451
|
-
destructiveHint: false,
|
|
452
|
-
idempotentHint: true,
|
|
453
|
-
openWorldHint: false,
|
|
454
|
-
},
|
|
455
|
-
}, async () => jsonResult(await db.listStreams()));
|
|
456
|
-
server.registerTool("thing_list_queues", {
|
|
457
|
-
title: "List Queues",
|
|
458
|
-
description: "List all queue names in the store. Returns an array of queue name strings. Use this to discover available queues before pushing or claiming jobs.",
|
|
459
|
-
inputSchema: {},
|
|
460
|
-
annotations: {
|
|
461
|
-
readOnlyHint: true,
|
|
462
|
-
destructiveHint: false,
|
|
463
|
-
idempotentHint: true,
|
|
464
|
-
openWorldHint: false,
|
|
465
|
-
},
|
|
466
|
-
}, async () => jsonResult(await db.listQueues()));
|
|
467
|
-
server.registerTool("thing_create_index", {
|
|
468
|
-
title: "Create Index",
|
|
469
|
-
description: "Create a functional index on a JSON body field for a collection. Subsequent listObjects calls with filter on this field will use the index for O(log n) lookups instead of full table scans. Idempotent — recreating an existing index is a no-op.",
|
|
470
|
-
inputSchema: {
|
|
471
|
-
collection: z.string().describe("Collection name"),
|
|
472
|
-
field: z.string().describe("JSON body field name to index"),
|
|
473
|
-
},
|
|
474
|
-
annotations: {
|
|
475
|
-
readOnlyHint: false,
|
|
476
|
-
destructiveHint: false,
|
|
477
|
-
idempotentHint: true,
|
|
478
|
-
openWorldHint: false,
|
|
479
|
-
},
|
|
480
|
-
}, async ({ collection, field }) => {
|
|
481
|
-
await db.createIndex(collection, field);
|
|
482
|
-
return jsonResult({ created: true });
|
|
483
|
-
});
|
|
484
|
-
server.registerTool("thing_list_indexes", {
|
|
485
|
-
title: "List Indexes",
|
|
486
|
-
description: "List all custom functional indexes. Returns an array of [collection, field] pairs.",
|
|
487
|
-
inputSchema: {},
|
|
488
|
-
annotations: {
|
|
489
|
-
readOnlyHint: true,
|
|
490
|
-
destructiveHint: false,
|
|
491
|
-
idempotentHint: true,
|
|
492
|
-
openWorldHint: false,
|
|
493
|
-
},
|
|
494
|
-
}, async () => jsonResult(await db.listIndexes()));
|
|
495
|
-
server.registerTool("thing_objects_put_batch", {
|
|
496
|
-
title: "Put Objects Batch",
|
|
497
|
-
description: "Create or replace multiple objects in a collection in a single operation. More efficient than calling thing_put repeatedly. Returns the array of stored objects.",
|
|
498
|
-
inputSchema: {
|
|
499
|
-
collection: z.string().min(1),
|
|
500
|
-
objects: z
|
|
501
|
-
.array(z.object({ id: z.string() }).passthrough())
|
|
502
|
-
.min(1)
|
|
503
|
-
.max(1000),
|
|
504
|
-
...auditInputSchema,
|
|
505
|
-
},
|
|
506
|
-
annotations: {
|
|
507
|
-
readOnlyHint: false,
|
|
508
|
-
destructiveHint: false,
|
|
509
|
-
idempotentHint: false,
|
|
510
|
-
openWorldHint: false,
|
|
511
|
-
},
|
|
512
|
-
}, async ({ collection, objects, actor, source }) => {
|
|
513
|
-
assertWriteAllowed();
|
|
514
|
-
assertCollectionAllowed(collection);
|
|
515
|
-
const result = await db.putBatch(collection, objects);
|
|
516
|
-
await appendMcpAuditEvent(db, audit, {
|
|
517
|
-
action: "objects.put_batch",
|
|
518
|
-
target: { collection, count: objects.length },
|
|
519
|
-
metadata: auditMetadata(actor, source),
|
|
520
|
-
result: { count: result.length },
|
|
521
|
-
});
|
|
522
|
-
return jsonResult(result);
|
|
523
|
-
});
|
|
524
|
-
server.registerTool("thing_objects_delete_batch", {
|
|
525
|
-
title: "Delete Objects Batch",
|
|
526
|
-
description: "Delete multiple objects by ID in a single operation. Returns the count of deleted objects.",
|
|
527
|
-
inputSchema: {
|
|
528
|
-
collection: z.string().min(1),
|
|
529
|
-
ids: z.array(z.string().min(1)).min(1).max(1000),
|
|
530
|
-
...auditInputSchema,
|
|
531
|
-
},
|
|
532
|
-
annotations: {
|
|
533
|
-
readOnlyHint: false,
|
|
534
|
-
destructiveHint: true,
|
|
535
|
-
idempotentHint: true,
|
|
536
|
-
openWorldHint: false,
|
|
537
|
-
},
|
|
538
|
-
}, async ({ collection, ids, actor, source }) => {
|
|
539
|
-
assertWriteAllowed();
|
|
540
|
-
assertCollectionAllowed(collection);
|
|
541
|
-
const deleted = await db.deleteBatch(collection, ids);
|
|
542
|
-
await appendMcpAuditEvent(db, audit, {
|
|
543
|
-
action: "objects.delete_batch",
|
|
544
|
-
target: { collection, count: ids.length },
|
|
545
|
-
metadata: auditMetadata(actor, source),
|
|
546
|
-
result: { deleted },
|
|
547
|
-
});
|
|
548
|
-
return jsonResult({ deleted });
|
|
549
|
-
});
|
|
550
|
-
server.registerTool("thing_objects_get_batch", {
|
|
551
|
-
title: "Get Objects Batch",
|
|
552
|
-
description: "Read multiple objects by ID in a single operation. Returns an array of objects (null for missing IDs). More efficient than calling thing_get repeatedly.",
|
|
553
|
-
inputSchema: {
|
|
554
|
-
collection: z.string().min(1),
|
|
555
|
-
ids: z.array(z.string().min(1)).min(1).max(1000),
|
|
556
|
-
},
|
|
557
|
-
annotations: {
|
|
558
|
-
readOnlyHint: true,
|
|
559
|
-
destructiveHint: false,
|
|
560
|
-
idempotentHint: true,
|
|
561
|
-
openWorldHint: false,
|
|
562
|
-
},
|
|
563
|
-
}, async ({ collection, ids }) => jsonResult(await db.getBatch(collection, ids)));
|
|
564
|
-
server.registerTool("thing_link_create", {
|
|
565
|
-
title: "Create Link",
|
|
566
|
-
description: "Create a directed graph link between two references (e.g., thingd objects, external URLs). You can optionally assign a linkType (e.g., 'parent', 'related_to'), weight, and metadata JSON string.",
|
|
567
|
-
inputSchema: {
|
|
568
|
-
fromRef: z.string().min(1),
|
|
569
|
-
linkType: z.string().min(1),
|
|
570
|
-
toRef: z.string().min(1),
|
|
571
|
-
weight: z.number().optional(),
|
|
572
|
-
metadataJson: z.string().optional(),
|
|
573
|
-
...auditInputSchema,
|
|
574
|
-
},
|
|
575
|
-
annotations: {
|
|
576
|
-
readOnlyHint: false,
|
|
577
|
-
destructiveHint: false,
|
|
578
|
-
idempotentHint: false,
|
|
579
|
-
openWorldHint: false,
|
|
580
|
-
},
|
|
581
|
-
}, async ({ fromRef, linkType, toRef, weight, metadataJson, actor, source }) => {
|
|
582
|
-
assertWriteAllowed();
|
|
583
|
-
const link = await db.links.create(fromRef, linkType, toRef, weight, metadataJson);
|
|
584
|
-
await appendMcpAuditEvent(db, audit, {
|
|
585
|
-
action: "link.create",
|
|
586
|
-
target: { fromRef, linkType, toRef },
|
|
587
|
-
metadata: auditMetadata(actor, source),
|
|
588
|
-
result: { id: link.id },
|
|
589
|
-
});
|
|
590
|
-
return jsonResult(link);
|
|
591
|
-
});
|
|
592
|
-
server.registerTool("thing_link_delete", {
|
|
593
|
-
title: "Delete Link",
|
|
594
|
-
description: "Delete a graph link by its id. Returns an object with a deleted boolean.",
|
|
595
|
-
inputSchema: {
|
|
596
|
-
id: z.string().min(1),
|
|
597
|
-
...auditInputSchema,
|
|
598
|
-
},
|
|
599
|
-
annotations: {
|
|
600
|
-
readOnlyHint: false,
|
|
601
|
-
destructiveHint: true,
|
|
602
|
-
idempotentHint: true,
|
|
603
|
-
openWorldHint: false,
|
|
604
|
-
},
|
|
605
|
-
}, async ({ id, actor, source }) => {
|
|
606
|
-
assertWriteAllowed();
|
|
607
|
-
const deleted = await db.links.delete(id);
|
|
608
|
-
await appendMcpAuditEvent(db, audit, {
|
|
609
|
-
action: "link.delete",
|
|
610
|
-
target: { id },
|
|
611
|
-
metadata: auditMetadata(actor, source),
|
|
612
|
-
result: { deleted },
|
|
613
|
-
});
|
|
614
|
-
return jsonResult({ deleted });
|
|
615
|
-
});
|
|
616
|
-
server.registerTool("thing_link_get", {
|
|
617
|
-
title: "Get Link",
|
|
618
|
-
description: "Get a graph link by its id.",
|
|
619
|
-
inputSchema: {
|
|
620
|
-
id: z.string().min(1),
|
|
621
|
-
},
|
|
622
|
-
annotations: {
|
|
623
|
-
readOnlyHint: true,
|
|
624
|
-
destructiveHint: false,
|
|
625
|
-
idempotentHint: true,
|
|
626
|
-
openWorldHint: false,
|
|
627
|
-
},
|
|
628
|
-
}, async ({ id }) => jsonResult(await db.links.get(id)));
|
|
629
|
-
server.registerTool("thing_link_neighbors", {
|
|
630
|
-
title: "Get Link Neighbors",
|
|
631
|
-
description: "Get all links connected to a specific reference. You can filter by direction (Outgoing, Incoming, Both) and linkType.",
|
|
632
|
-
inputSchema: {
|
|
633
|
-
reference: z.string().min(1),
|
|
634
|
-
direction: z.enum(["Outgoing", "Incoming", "Both"]).optional(),
|
|
635
|
-
linkType: z.string().optional(),
|
|
636
|
-
limit: z.number().int().positive().optional(),
|
|
637
|
-
},
|
|
638
|
-
annotations: {
|
|
639
|
-
readOnlyHint: true,
|
|
640
|
-
destructiveHint: false,
|
|
641
|
-
idempotentHint: true,
|
|
642
|
-
openWorldHint: false,
|
|
643
|
-
},
|
|
644
|
-
}, async ({ reference, direction, linkType, limit }) => jsonResult(await db.links.neighbors(reference, direction, { linkType, limit })));
|
|
645
|
-
server.registerTool("thing_link_count", {
|
|
646
|
-
title: "Count Links",
|
|
647
|
-
description: "Count all graph links in the store.",
|
|
648
|
-
inputSchema: {},
|
|
649
|
-
annotations: {
|
|
650
|
-
readOnlyHint: true,
|
|
651
|
-
destructiveHint: false,
|
|
652
|
-
idempotentHint: true,
|
|
653
|
-
openWorldHint: false,
|
|
654
|
-
},
|
|
655
|
-
}, async () => jsonResult(await db.countLinks()));
|
|
656
|
-
server.registerTool("thing_aggregate", {
|
|
657
|
-
title: "Aggregate Objects",
|
|
658
|
-
description: "Run aggregate queries on objects in a collection. Supports count, sum, avg, min, max with optional groupBy and filter.",
|
|
659
|
-
inputSchema: {
|
|
660
|
-
collection: z.string().min(1),
|
|
661
|
-
function: z.enum(["count", "sum", "avg", "min", "max"]),
|
|
662
|
-
field: z.string().min(1).optional(),
|
|
663
|
-
groupBy: z.string().min(1).optional(),
|
|
664
|
-
filter: z.record(z.string(), z.unknown()).optional(),
|
|
665
|
-
...auditInputSchema,
|
|
666
|
-
},
|
|
667
|
-
annotations: {
|
|
668
|
-
readOnlyHint: true,
|
|
669
|
-
destructiveHint: false,
|
|
670
|
-
idempotentHint: true,
|
|
671
|
-
openWorldHint: false,
|
|
672
|
-
},
|
|
673
|
-
}, async ({ collection, function: fn, field, groupBy, filter, actor, source }) => {
|
|
674
|
-
assertCollectionAllowed(collection);
|
|
675
|
-
let result;
|
|
676
|
-
switch (fn) {
|
|
677
|
-
case "sum":
|
|
678
|
-
result = await db.aggregate.sum(collection, field ?? "", { groupBy, filter });
|
|
679
|
-
break;
|
|
680
|
-
case "avg":
|
|
681
|
-
result = await db.aggregate.avg(collection, field ?? "", { groupBy, filter });
|
|
682
|
-
break;
|
|
683
|
-
case "min":
|
|
684
|
-
result = await db.aggregate.min(collection, field ?? "", { groupBy, filter });
|
|
685
|
-
break;
|
|
686
|
-
case "max":
|
|
687
|
-
result = await db.aggregate.max(collection, field ?? "", { groupBy, filter });
|
|
688
|
-
break;
|
|
689
|
-
default:
|
|
690
|
-
result = await db.aggregate.count(collection, { groupBy, filter });
|
|
691
|
-
break;
|
|
692
|
-
}
|
|
693
|
-
await appendMcpAuditEvent(db, audit, {
|
|
694
|
-
action: "thing_aggregate",
|
|
695
|
-
target: { collection, function: fn },
|
|
696
|
-
metadata: { actor, source },
|
|
697
|
-
});
|
|
698
|
-
return jsonResult(result);
|
|
699
|
-
});
|
|
700
|
-
server.registerTool("thing_timeseries", {
|
|
701
|
-
title: "Time Series Aggregation",
|
|
702
|
-
description: "Run time-series aggregation on objects. Buckets objects by hour/day/week/month and applies an aggregate function.",
|
|
703
|
-
inputSchema: {
|
|
704
|
-
collection: z.string().min(1),
|
|
705
|
-
function: z.enum(["count", "sum", "avg", "min", "max"]),
|
|
706
|
-
bucket: z.enum(["hour", "day", "week", "month"]),
|
|
707
|
-
field: z.string().min(1).optional(),
|
|
708
|
-
filter: z.record(z.string(), z.unknown()).optional(),
|
|
709
|
-
from: z.string().optional(),
|
|
710
|
-
to: z.string().optional(),
|
|
711
|
-
...auditInputSchema,
|
|
712
|
-
},
|
|
713
|
-
annotations: {
|
|
714
|
-
readOnlyHint: true,
|
|
715
|
-
destructiveHint: false,
|
|
716
|
-
idempotentHint: true,
|
|
717
|
-
openWorldHint: false,
|
|
718
|
-
},
|
|
719
|
-
}, async ({ collection, function: fn, bucket, field, filter, from, to, actor, source }) => {
|
|
720
|
-
assertCollectionAllowed(collection);
|
|
721
|
-
const result = await db.timeseries(collection, {
|
|
722
|
-
function: fn,
|
|
723
|
-
bucket,
|
|
724
|
-
field,
|
|
725
|
-
filter,
|
|
726
|
-
from,
|
|
727
|
-
to,
|
|
728
|
-
});
|
|
729
|
-
await appendMcpAuditEvent(db, audit, {
|
|
730
|
-
action: "thing_timeseries",
|
|
731
|
-
target: { collection, function: fn, bucket },
|
|
732
|
-
metadata: { actor, source },
|
|
733
|
-
});
|
|
734
|
-
return jsonResult(result);
|
|
735
|
-
});
|
|
736
|
-
server.registerTool("thing_vector_search", {
|
|
737
|
-
title: "Vector Search",
|
|
738
|
-
description: "Search objects by vector similarity (cosine similarity). Provide a query vector as an array of floats (e.g. [0.1, 0.2, 0.3]) and optionally set topK and a metadata filter. Returns matching objects ranked by similarity score.",
|
|
739
|
-
inputSchema: {
|
|
740
|
-
collection: z.string().min(1),
|
|
741
|
-
vector: z.array(z.number()),
|
|
742
|
-
topK: z.number().int().positive().optional(),
|
|
743
|
-
filter: z.record(z.string(), z.unknown()).optional(),
|
|
744
|
-
},
|
|
745
|
-
annotations: {
|
|
746
|
-
readOnlyHint: true,
|
|
747
|
-
destructiveHint: false,
|
|
748
|
-
idempotentHint: true,
|
|
749
|
-
openWorldHint: false,
|
|
750
|
-
},
|
|
751
|
-
}, async ({ collection, vector, topK, filter }) => {
|
|
752
|
-
assertCollectionAllowed(collection);
|
|
753
|
-
return jsonResult(await db.vectorSearch(collection, vector, { topK, filter }));
|
|
754
|
-
});
|
|
755
|
-
server.registerTool("thing_schema", {
|
|
756
|
-
title: "Reflect Schema",
|
|
757
|
-
description: "Reflect the schema of one or all collections by sampling stored objects. Returns inferred field names, types, and sample values.",
|
|
758
|
-
inputSchema: {
|
|
759
|
-
collection: z.string().min(1).optional(),
|
|
760
|
-
},
|
|
761
|
-
annotations: {
|
|
762
|
-
readOnlyHint: true,
|
|
763
|
-
destructiveHint: false,
|
|
764
|
-
idempotentHint: true,
|
|
765
|
-
openWorldHint: false,
|
|
766
|
-
},
|
|
767
|
-
}, async ({ collection }) => {
|
|
768
|
-
if (collection) {
|
|
769
|
-
assertCollectionAllowed(collection);
|
|
770
|
-
}
|
|
771
|
-
return jsonResult(await db.schema(collection));
|
|
772
|
-
});
|
|
773
|
-
server.registerTool("thing_nlq", {
|
|
774
|
-
title: "Natural Language Query",
|
|
775
|
-
description: "Ask a natural language question about your data. Requires NLQ configuration with an LLM endpoint.",
|
|
776
|
-
inputSchema: {
|
|
777
|
-
question: z.string().min(1),
|
|
778
|
-
collection: z.string().min(1).optional(),
|
|
779
|
-
},
|
|
780
|
-
annotations: {
|
|
781
|
-
readOnlyHint: true,
|
|
782
|
-
destructiveHint: false,
|
|
783
|
-
idempotentHint: true,
|
|
784
|
-
openWorldHint: false,
|
|
785
|
-
},
|
|
786
|
-
}, async ({ question, collection }) => {
|
|
787
|
-
return jsonResult(await db.nlq.query(question, { collection }));
|
|
788
|
-
});
|
|
789
|
-
// ── Scheduler tools ──
|
|
790
|
-
server.registerTool("thing_scheduler_schedule", {
|
|
791
|
-
title: "Create Cron Schedule",
|
|
792
|
-
description: "Create or update a recurring schedule with a cron expression. Schedules persist across restarts and emit lifecycle events (started, completed, failed, disabled). The handler executes when the cron triggers. Returns the created schedule with nextRunAt.",
|
|
793
|
-
inputSchema: {
|
|
794
|
-
id: z.string().min(1),
|
|
795
|
-
expression: z.string().min(1),
|
|
796
|
-
timezone: z.string().optional(),
|
|
797
|
-
payload: z.record(z.string(), z.unknown()).optional(),
|
|
798
|
-
enabled: z.boolean().optional(),
|
|
799
|
-
maxConsecutiveFails: z.number().int().positive().optional(),
|
|
800
|
-
...auditInputSchema,
|
|
801
|
-
},
|
|
802
|
-
annotations: {
|
|
803
|
-
readOnlyHint: false,
|
|
804
|
-
destructiveHint: false,
|
|
805
|
-
idempotentHint: true,
|
|
806
|
-
openWorldHint: false,
|
|
807
|
-
},
|
|
808
|
-
}, async ({ id, expression, timezone, payload, enabled, maxConsecutiveFails, actor, source }) => {
|
|
809
|
-
assertWriteAllowed();
|
|
810
|
-
const schedule = await db.scheduler.schedule(id, {
|
|
811
|
-
expression,
|
|
812
|
-
timezone,
|
|
813
|
-
payload,
|
|
814
|
-
enabled,
|
|
815
|
-
maxConsecutiveFails,
|
|
816
|
-
handler: async () => { },
|
|
817
|
-
});
|
|
818
|
-
await appendMcpAuditEvent(db, audit, {
|
|
819
|
-
action: "scheduler.schedule",
|
|
820
|
-
target: { id: schedule.id },
|
|
821
|
-
metadata: auditMetadata(actor, source),
|
|
822
|
-
result: { id: schedule.id, expression: schedule.expression, nextRunAt: schedule.nextRunAt },
|
|
823
|
-
});
|
|
824
|
-
return jsonResult(schedule);
|
|
825
|
-
});
|
|
826
|
-
server.registerTool("thing_scheduler_schedule_interval", {
|
|
827
|
-
title: "Create Interval Schedule",
|
|
828
|
-
description: "Create a schedule that runs at a fixed interval (e.g. every 5 minutes). Accepts intervalMs for the delay between runs. Returns the created schedule.",
|
|
829
|
-
inputSchema: {
|
|
830
|
-
id: z.string().min(1),
|
|
831
|
-
intervalMs: z.number().int().positive(),
|
|
832
|
-
payload: z.record(z.string(), z.unknown()).optional(),
|
|
833
|
-
enabled: z.boolean().optional(),
|
|
834
|
-
maxConsecutiveFails: z.number().int().positive().optional(),
|
|
835
|
-
...auditInputSchema,
|
|
836
|
-
},
|
|
837
|
-
annotations: {
|
|
838
|
-
readOnlyHint: false,
|
|
839
|
-
destructiveHint: false,
|
|
840
|
-
idempotentHint: true,
|
|
841
|
-
openWorldHint: false,
|
|
842
|
-
},
|
|
843
|
-
}, async ({ id, intervalMs, payload, enabled, maxConsecutiveFails, actor, source }) => {
|
|
844
|
-
assertWriteAllowed();
|
|
845
|
-
const schedule = await db.scheduler.scheduleInterval(id, {
|
|
846
|
-
intervalMs,
|
|
847
|
-
payload,
|
|
848
|
-
enabled,
|
|
849
|
-
maxConsecutiveFails,
|
|
850
|
-
handler: async () => { },
|
|
851
|
-
});
|
|
852
|
-
await appendMcpAuditEvent(db, audit, {
|
|
853
|
-
action: "scheduler.schedule_interval",
|
|
854
|
-
target: { id: schedule.id },
|
|
855
|
-
metadata: auditMetadata(actor, source),
|
|
856
|
-
result: { id: schedule.id, intervalMs, nextRunAt: schedule.nextRunAt },
|
|
857
|
-
});
|
|
858
|
-
return jsonResult(schedule);
|
|
859
|
-
});
|
|
860
|
-
server.registerTool("thing_scheduler_schedule_once", {
|
|
861
|
-
title: "Create One-Time Schedule",
|
|
862
|
-
description: "Create a schedule that runs once at a specific ISO timestamp. The schedule auto-disables after execution. Returns the created schedule.",
|
|
863
|
-
inputSchema: {
|
|
864
|
-
id: z.string().min(1),
|
|
865
|
-
runAt: z.string().min(1),
|
|
866
|
-
payload: z.record(z.string(), z.unknown()).optional(),
|
|
867
|
-
...auditInputSchema,
|
|
868
|
-
},
|
|
869
|
-
annotations: {
|
|
870
|
-
readOnlyHint: false,
|
|
871
|
-
destructiveHint: false,
|
|
872
|
-
idempotentHint: true,
|
|
873
|
-
openWorldHint: false,
|
|
874
|
-
},
|
|
875
|
-
}, async ({ id, runAt, payload, actor, source }) => {
|
|
876
|
-
assertWriteAllowed();
|
|
877
|
-
const schedule = await db.scheduler.scheduleOnce(id, {
|
|
878
|
-
runAt,
|
|
879
|
-
payload,
|
|
880
|
-
handler: async () => { },
|
|
881
|
-
});
|
|
882
|
-
await appendMcpAuditEvent(db, audit, {
|
|
883
|
-
action: "scheduler.schedule_once",
|
|
884
|
-
target: { id: schedule.id },
|
|
885
|
-
metadata: auditMetadata(actor, source),
|
|
886
|
-
result: { id: schedule.id, runAt: schedule.nextRunAt },
|
|
887
|
-
});
|
|
888
|
-
return jsonResult(schedule);
|
|
889
|
-
});
|
|
890
|
-
server.registerTool("thing_scheduler_list", {
|
|
891
|
-
title: "List Schedules",
|
|
892
|
-
description: "List all registered schedules with their current status, next run time, and run/fail counts. Returns an array of schedule objects.",
|
|
893
|
-
inputSchema: {},
|
|
894
|
-
annotations: {
|
|
895
|
-
readOnlyHint: true,
|
|
896
|
-
destructiveHint: false,
|
|
897
|
-
idempotentHint: true,
|
|
898
|
-
openWorldHint: false,
|
|
899
|
-
},
|
|
900
|
-
}, async () => {
|
|
901
|
-
return jsonResult(await db.scheduler.list());
|
|
902
|
-
});
|
|
903
|
-
server.registerTool("thing_scheduler_get", {
|
|
904
|
-
title: "Get Schedule",
|
|
905
|
-
description: "Get details of a single schedule by ID including its expression, next run, last status, and run/fail counts. Returns null if not found.",
|
|
906
|
-
inputSchema: {
|
|
907
|
-
id: z.string().min(1),
|
|
908
|
-
},
|
|
909
|
-
annotations: {
|
|
910
|
-
readOnlyHint: true,
|
|
911
|
-
destructiveHint: false,
|
|
912
|
-
idempotentHint: true,
|
|
913
|
-
openWorldHint: false,
|
|
914
|
-
},
|
|
915
|
-
}, async ({ id }) => {
|
|
916
|
-
return jsonResult(await db.scheduler.get(id));
|
|
917
|
-
});
|
|
918
|
-
server.registerTool("thing_scheduler_stats", {
|
|
919
|
-
title: "Scheduler Statistics",
|
|
920
|
-
description: "Get aggregate statistics for the scheduler: total schedules, enabled, disabled, currently running, and the next scheduled run.",
|
|
921
|
-
inputSchema: {},
|
|
922
|
-
annotations: {
|
|
923
|
-
readOnlyHint: true,
|
|
924
|
-
destructiveHint: false,
|
|
925
|
-
idempotentHint: true,
|
|
926
|
-
openWorldHint: false,
|
|
927
|
-
},
|
|
928
|
-
}, async () => {
|
|
929
|
-
return jsonResult(await db.scheduler.stats());
|
|
930
|
-
});
|
|
931
|
-
server.registerTool("thing_scheduler_pause", {
|
|
932
|
-
title: "Pause Schedule",
|
|
933
|
-
description: "Pause a schedule so it stops triggering. The schedule remains stored and can be resumed later. Returns the updated schedule.",
|
|
934
|
-
inputSchema: {
|
|
935
|
-
id: z.string().min(1),
|
|
936
|
-
...auditInputSchema,
|
|
937
|
-
},
|
|
938
|
-
annotations: {
|
|
939
|
-
readOnlyHint: false,
|
|
940
|
-
destructiveHint: false,
|
|
941
|
-
idempotentHint: false,
|
|
942
|
-
openWorldHint: false,
|
|
943
|
-
},
|
|
944
|
-
}, async ({ id, actor, source }) => {
|
|
945
|
-
assertWriteAllowed();
|
|
946
|
-
const schedule = await db.scheduler.pause(id);
|
|
947
|
-
await appendMcpAuditEvent(db, audit, {
|
|
948
|
-
action: "scheduler.pause",
|
|
949
|
-
target: { id: schedule.id },
|
|
950
|
-
metadata: auditMetadata(actor, source),
|
|
951
|
-
result: { id: schedule.id, enabled: schedule.enabled },
|
|
952
|
-
});
|
|
953
|
-
return jsonResult(schedule);
|
|
954
|
-
});
|
|
955
|
-
server.registerTool("thing_scheduler_resume", {
|
|
956
|
-
title: "Resume Schedule",
|
|
957
|
-
description: "Resume a paused schedule. The next run time is recalculated from the current time. Returns the updated schedule.",
|
|
958
|
-
inputSchema: {
|
|
959
|
-
id: z.string().min(1),
|
|
960
|
-
...auditInputSchema,
|
|
961
|
-
},
|
|
962
|
-
annotations: {
|
|
963
|
-
readOnlyHint: false,
|
|
964
|
-
destructiveHint: false,
|
|
965
|
-
idempotentHint: false,
|
|
966
|
-
openWorldHint: false,
|
|
967
|
-
},
|
|
968
|
-
}, async ({ id, actor, source }) => {
|
|
969
|
-
assertWriteAllowed();
|
|
970
|
-
const schedule = await db.scheduler.resume(id);
|
|
971
|
-
await appendMcpAuditEvent(db, audit, {
|
|
972
|
-
action: "scheduler.resume",
|
|
973
|
-
target: { id: schedule.id },
|
|
974
|
-
metadata: auditMetadata(actor, source),
|
|
975
|
-
result: { id: schedule.id, enabled: schedule.enabled, nextRunAt: schedule.nextRunAt },
|
|
976
|
-
});
|
|
977
|
-
return jsonResult(schedule);
|
|
978
|
-
});
|
|
979
|
-
server.registerTool("thing_scheduler_run", {
|
|
980
|
-
title: "Manually Trigger Schedule",
|
|
981
|
-
description: "Immediately execute a schedule's handler, regardless of its next run time. Useful for testing or manual triggers. The schedule must have a registered handler.",
|
|
982
|
-
inputSchema: {
|
|
983
|
-
id: z.string().min(1),
|
|
984
|
-
...auditInputSchema,
|
|
985
|
-
},
|
|
986
|
-
annotations: {
|
|
987
|
-
readOnlyHint: false,
|
|
988
|
-
destructiveHint: false,
|
|
989
|
-
idempotentHint: false,
|
|
990
|
-
openWorldHint: false,
|
|
991
|
-
},
|
|
992
|
-
}, async ({ id, actor, source }) => {
|
|
993
|
-
assertWriteAllowed();
|
|
994
|
-
await db.scheduler.run(id);
|
|
995
|
-
await appendMcpAuditEvent(db, audit, {
|
|
996
|
-
action: "scheduler.run",
|
|
997
|
-
target: { id },
|
|
998
|
-
metadata: auditMetadata(actor, source),
|
|
999
|
-
result: { triggered: true },
|
|
1000
|
-
});
|
|
1001
|
-
return jsonResult({ triggered: true, id });
|
|
1002
|
-
});
|
|
1003
|
-
server.registerTool("thing_scheduler_remove", {
|
|
1004
|
-
title: "Delete Schedule",
|
|
1005
|
-
description: "Permanently delete a schedule and its handler. This cannot be undone. Returns whether the schedule was deleted.",
|
|
1006
|
-
inputSchema: {
|
|
1007
|
-
id: z.string().min(1),
|
|
1008
|
-
...auditInputSchema,
|
|
1009
|
-
},
|
|
1010
|
-
annotations: {
|
|
1011
|
-
readOnlyHint: false,
|
|
1012
|
-
destructiveHint: true,
|
|
1013
|
-
idempotentHint: false,
|
|
1014
|
-
openWorldHint: false,
|
|
1015
|
-
},
|
|
1016
|
-
}, async ({ id, actor, source }) => {
|
|
1017
|
-
assertWriteAllowed();
|
|
1018
|
-
const deleted = await db.scheduler.remove(id);
|
|
1019
|
-
await appendMcpAuditEvent(db, audit, {
|
|
1020
|
-
action: "scheduler.remove",
|
|
1021
|
-
target: { id },
|
|
1022
|
-
metadata: auditMetadata(actor, source),
|
|
1023
|
-
result: { deleted },
|
|
1024
|
-
});
|
|
1025
|
-
return jsonResult({ deleted, id });
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
function auditMetadata(actor, source) {
|
|
1029
|
-
return {
|
|
1030
|
-
actor,
|
|
1031
|
-
source,
|
|
1032
|
-
};
|
|
1033
|
-
}
|