@alfe.ai/openclaw-database 0.0.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.
@@ -0,0 +1,2 @@
1
+ import plugin from "./plugin.js";
2
+ export { plugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { t as plugin } from "./plugin2.js";
2
+ export { plugin as default };
@@ -0,0 +1,25 @@
1
+ //#region src/plugin.d.ts
2
+ interface PluginApi {
3
+ logger: {
4
+ info: (...args: unknown[]) => void;
5
+ error: (...args: unknown[]) => void;
6
+ debug: (...args: unknown[]) => void;
7
+ };
8
+ config?: {
9
+ plugins?: {
10
+ entries?: Record<string, {
11
+ config?: Record<string, string>;
12
+ }>;
13
+ };
14
+ };
15
+ registerTool(tool: unknown): void;
16
+ }
17
+ declare const plugin: {
18
+ id: string;
19
+ name: string;
20
+ version: string;
21
+ activate(api: PluginApi): Promise<void>;
22
+ deactivate(api: PluginApi): Promise<void>;
23
+ };
24
+ //#endregion
25
+ export { plugin as default };
package/dist/plugin.js ADDED
@@ -0,0 +1,2 @@
1
+ import { t as plugin } from "./plugin2.js";
2
+ export { plugin as default };
@@ -0,0 +1,486 @@
1
+ import { MongoClient } from "mongodb";
2
+ //#region src/tools.ts
3
+ const DEFAULT_DB = "org_default";
4
+ function str(required = true) {
5
+ return {
6
+ type: "string",
7
+ ...required ? {} : { default: "" }
8
+ };
9
+ }
10
+ function optStr() {
11
+ return {
12
+ type: "string",
13
+ default: ""
14
+ };
15
+ }
16
+ function registerTools(api, client, databases, audit) {
17
+ function db(name) {
18
+ const dbName = name ?? DEFAULT_DB;
19
+ if (!databases.includes(dbName)) throw new Error(`Access denied: database "${dbName}" is not in the allowed list`);
20
+ return client.db(dbName);
21
+ }
22
+ api.registerTool({
23
+ name: "db_find",
24
+ description: "Find documents matching a filter",
25
+ label: "db_find",
26
+ parameters: {
27
+ type: "object",
28
+ properties: {
29
+ database: optStr(),
30
+ collection: str(),
31
+ filter: {
32
+ type: "object",
33
+ default: {}
34
+ },
35
+ sort: {
36
+ type: "object",
37
+ default: {}
38
+ },
39
+ limit: {
40
+ type: "number",
41
+ default: 20
42
+ },
43
+ skip: {
44
+ type: "number",
45
+ default: 0
46
+ },
47
+ projection: {
48
+ type: "object",
49
+ default: {}
50
+ }
51
+ },
52
+ required: ["collection"]
53
+ },
54
+ execute: async (_id, p) => {
55
+ const docs = await db(p.database).collection(p.collection).find(p.filter).sort(p.sort).project(p.projection).skip(p.skip || 0).limit(p.limit || 20).toArray();
56
+ return {
57
+ documents: docs,
58
+ count: docs.length
59
+ };
60
+ }
61
+ });
62
+ api.registerTool({
63
+ name: "db_find_one",
64
+ description: "Find a single document matching a filter",
65
+ label: "db_find_one",
66
+ parameters: {
67
+ type: "object",
68
+ properties: {
69
+ database: optStr(),
70
+ collection: str(),
71
+ filter: {
72
+ type: "object",
73
+ default: {}
74
+ },
75
+ projection: {
76
+ type: "object",
77
+ default: {}
78
+ }
79
+ },
80
+ required: ["collection"]
81
+ },
82
+ execute: async (_id, p) => {
83
+ return await db(p.database).collection(p.collection).findOne(p.filter, { projection: p.projection }) ?? { _not_found: true };
84
+ }
85
+ });
86
+ api.registerTool({
87
+ name: "db_insert",
88
+ description: "Insert one or more documents",
89
+ label: "db_insert",
90
+ parameters: {
91
+ type: "object",
92
+ properties: {
93
+ database: optStr(),
94
+ collection: str(),
95
+ documents: {
96
+ type: "array",
97
+ items: { type: "object" }
98
+ }
99
+ },
100
+ required: ["collection", "documents"]
101
+ },
102
+ execute: async (_id, p) => {
103
+ const docs = p.documents;
104
+ const result = await db(p.database).collection(p.collection).insertMany(docs);
105
+ audit({
106
+ database: p.database || DEFAULT_DB,
107
+ collection: p.collection,
108
+ operation: "insert",
109
+ summary: `Inserted ${String(docs.length)} document(s)`
110
+ });
111
+ return {
112
+ insertedCount: result.insertedCount,
113
+ insertedIds: Object.values(result.insertedIds).map(String)
114
+ };
115
+ }
116
+ });
117
+ api.registerTool({
118
+ name: "db_update",
119
+ description: "Update documents matching a filter",
120
+ label: "db_update",
121
+ parameters: {
122
+ type: "object",
123
+ properties: {
124
+ database: optStr(),
125
+ collection: str(),
126
+ filter: { type: "object" },
127
+ update: { type: "object" },
128
+ upsert: {
129
+ type: "boolean",
130
+ default: false
131
+ }
132
+ },
133
+ required: [
134
+ "collection",
135
+ "filter",
136
+ "update"
137
+ ]
138
+ },
139
+ execute: async (_id, p) => {
140
+ const result = await db(p.database).collection(p.collection).updateMany(p.filter, p.update, { upsert: p.upsert || false });
141
+ audit({
142
+ database: p.database || DEFAULT_DB,
143
+ collection: p.collection,
144
+ operation: "update",
145
+ summary: `Modified ${String(result.modifiedCount)}`
146
+ });
147
+ return {
148
+ matchedCount: result.matchedCount,
149
+ modifiedCount: result.modifiedCount,
150
+ upsertedId: result.upsertedId ? String(result.upsertedId) : null
151
+ };
152
+ }
153
+ });
154
+ api.registerTool({
155
+ name: "db_delete",
156
+ description: "Delete documents matching a filter",
157
+ label: "db_delete",
158
+ parameters: {
159
+ type: "object",
160
+ properties: {
161
+ database: optStr(),
162
+ collection: str(),
163
+ filter: { type: "object" }
164
+ },
165
+ required: ["collection", "filter"]
166
+ },
167
+ execute: async (_id, p) => {
168
+ const result = await db(p.database).collection(p.collection).deleteMany(p.filter);
169
+ audit({
170
+ database: p.database || DEFAULT_DB,
171
+ collection: p.collection,
172
+ operation: "delete",
173
+ summary: `Deleted ${String(result.deletedCount)}`
174
+ });
175
+ return { deletedCount: result.deletedCount };
176
+ }
177
+ });
178
+ api.registerTool({
179
+ name: "db_count",
180
+ description: "Count documents matching a filter",
181
+ label: "db_count",
182
+ parameters: {
183
+ type: "object",
184
+ properties: {
185
+ database: optStr(),
186
+ collection: str(),
187
+ filter: {
188
+ type: "object",
189
+ default: {}
190
+ }
191
+ },
192
+ required: ["collection"]
193
+ },
194
+ execute: async (_id, p) => {
195
+ return { count: await db(p.database).collection(p.collection).countDocuments(p.filter) };
196
+ }
197
+ });
198
+ api.registerTool({
199
+ name: "db_aggregate",
200
+ description: "Run an aggregation pipeline",
201
+ label: "db_aggregate",
202
+ parameters: {
203
+ type: "object",
204
+ properties: {
205
+ database: optStr(),
206
+ collection: str(),
207
+ pipeline: {
208
+ type: "array",
209
+ items: { type: "object" }
210
+ }
211
+ },
212
+ required: ["collection", "pipeline"]
213
+ },
214
+ execute: async (_id, p) => {
215
+ const results = await db(p.database).collection(p.collection).aggregate(p.pipeline).toArray();
216
+ return {
217
+ results,
218
+ count: results.length
219
+ };
220
+ }
221
+ });
222
+ api.registerTool({
223
+ name: "db_list_databases",
224
+ description: "List databases the agent can access",
225
+ label: "db_list_databases",
226
+ parameters: {
227
+ type: "object",
228
+ properties: {}
229
+ },
230
+ execute: () => Promise.resolve({ databases })
231
+ });
232
+ api.registerTool({
233
+ name: "db_list_collections",
234
+ description: "List collections in a database",
235
+ label: "db_list_collections",
236
+ parameters: {
237
+ type: "object",
238
+ properties: { database: optStr() }
239
+ },
240
+ execute: async (_id, p) => {
241
+ return { collections: (await db(p.database).listCollections().toArray()).map((c) => ({
242
+ name: c.name,
243
+ type: c.type
244
+ })) };
245
+ }
246
+ });
247
+ api.registerTool({
248
+ name: "db_create_collection",
249
+ description: "Create a new collection",
250
+ label: "db_create_collection",
251
+ parameters: {
252
+ type: "object",
253
+ properties: {
254
+ database: optStr(),
255
+ collection: str()
256
+ },
257
+ required: ["collection"]
258
+ },
259
+ execute: async (_id, p) => {
260
+ await db(p.database).createCollection(p.collection);
261
+ audit({
262
+ database: p.database || DEFAULT_DB,
263
+ collection: p.collection,
264
+ operation: "create_collection"
265
+ });
266
+ return { success: true };
267
+ }
268
+ });
269
+ api.registerTool({
270
+ name: "db_drop_collection",
271
+ description: "Drop a collection",
272
+ label: "db_drop_collection",
273
+ parameters: {
274
+ type: "object",
275
+ properties: {
276
+ database: optStr(),
277
+ collection: str()
278
+ },
279
+ required: ["collection"]
280
+ },
281
+ execute: async (_id, p) => {
282
+ await db(p.database).dropCollection(p.collection);
283
+ audit({
284
+ database: p.database || DEFAULT_DB,
285
+ collection: p.collection,
286
+ operation: "drop_collection"
287
+ });
288
+ return { success: true };
289
+ }
290
+ });
291
+ api.registerTool({
292
+ name: "db_collection_schema",
293
+ description: "Inspect a collection's schema by sampling documents",
294
+ label: "db_collection_schema",
295
+ parameters: {
296
+ type: "object",
297
+ properties: {
298
+ database: optStr(),
299
+ collection: str()
300
+ },
301
+ required: ["collection"]
302
+ },
303
+ execute: async (_id, p) => {
304
+ const sample = await db(p.database).collection(p.collection).find().limit(100).toArray();
305
+ const fieldTypes = /* @__PURE__ */ new Map();
306
+ for (const doc of sample) for (const [key, value] of Object.entries(doc)) {
307
+ const types = fieldTypes.get(key) ?? /* @__PURE__ */ new Set();
308
+ types.add(value === null ? "null" : typeof value);
309
+ fieldTypes.set(key, types);
310
+ }
311
+ return {
312
+ sampleSize: sample.length,
313
+ fields: [...fieldTypes.entries()].map(([name, types]) => ({
314
+ name,
315
+ types: [...types]
316
+ }))
317
+ };
318
+ }
319
+ });
320
+ api.registerTool({
321
+ name: "db_collection_stats",
322
+ description: "Get collection doc count, size, and indexes",
323
+ label: "db_collection_stats",
324
+ parameters: {
325
+ type: "object",
326
+ properties: {
327
+ database: optStr(),
328
+ collection: str()
329
+ },
330
+ required: ["collection"]
331
+ },
332
+ execute: async (_id, p) => {
333
+ const coll = db(p.database).collection(p.collection);
334
+ const count = await coll.countDocuments();
335
+ const indexes = await coll.indexes();
336
+ return {
337
+ count,
338
+ indexCount: indexes.length,
339
+ indexes: indexes.map((i) => ({
340
+ name: i.name,
341
+ key: i.key
342
+ }))
343
+ };
344
+ }
345
+ });
346
+ api.registerTool({
347
+ name: "db_create_index",
348
+ description: "Create an index on a collection",
349
+ label: "db_create_index",
350
+ parameters: {
351
+ type: "object",
352
+ properties: {
353
+ database: optStr(),
354
+ collection: str(),
355
+ keys: { type: "object" },
356
+ options: {
357
+ type: "object",
358
+ default: {}
359
+ }
360
+ },
361
+ required: ["collection", "keys"]
362
+ },
363
+ execute: async (_id, p) => {
364
+ const indexName = await db(p.database).collection(p.collection).createIndex(p.keys, p.options);
365
+ audit({
366
+ database: p.database || DEFAULT_DB,
367
+ collection: p.collection,
368
+ operation: "create_index",
369
+ summary: indexName
370
+ });
371
+ return { indexName };
372
+ }
373
+ });
374
+ api.registerTool({
375
+ name: "db_list_indexes",
376
+ description: "List indexes on a collection",
377
+ label: "db_list_indexes",
378
+ parameters: {
379
+ type: "object",
380
+ properties: {
381
+ database: optStr(),
382
+ collection: str()
383
+ },
384
+ required: ["collection"]
385
+ },
386
+ execute: async (_id, p) => {
387
+ return { indexes: (await db(p.database).collection(p.collection).indexes()).map((i) => ({
388
+ name: i.name,
389
+ key: i.key,
390
+ unique: i.unique
391
+ })) };
392
+ }
393
+ });
394
+ api.registerTool({
395
+ name: "db_drop_index",
396
+ description: "Drop an index by name",
397
+ label: "db_drop_index",
398
+ parameters: {
399
+ type: "object",
400
+ properties: {
401
+ database: optStr(),
402
+ collection: str(),
403
+ indexName: str()
404
+ },
405
+ required: ["collection", "indexName"]
406
+ },
407
+ execute: async (_id, p) => {
408
+ await db(p.database).collection(p.collection).dropIndex(p.indexName);
409
+ audit({
410
+ database: p.database || DEFAULT_DB,
411
+ collection: p.collection,
412
+ operation: "drop_index",
413
+ summary: p.indexName
414
+ });
415
+ return { success: true };
416
+ }
417
+ });
418
+ }
419
+ //#endregion
420
+ //#region src/plugin.ts
421
+ const PLUGIN_ID = "@alfe.ai/openclaw-database";
422
+ let mongoClient = null;
423
+ async function fetchCredentials(apiUrl, apiKey) {
424
+ const res = await fetch(`${apiUrl}/agents/database/register`, {
425
+ method: "POST",
426
+ headers: {
427
+ "Content-Type": "application/json",
428
+ Authorization: `Bearer ${apiKey}`
429
+ }
430
+ });
431
+ if (!res.ok) throw new Error(`Failed to register database credentials: ${String(res.status)} ${res.statusText}`);
432
+ return (await res.json()).data;
433
+ }
434
+ function reportAudit(apiUrl, apiKey, entry) {
435
+ fetch(`${apiUrl}/agents/database/audit`, {
436
+ method: "POST",
437
+ headers: {
438
+ "Content-Type": "application/json",
439
+ Authorization: `Bearer ${apiKey}`
440
+ },
441
+ body: JSON.stringify(entry)
442
+ }).catch(() => {});
443
+ }
444
+ const plugin = {
445
+ id: PLUGIN_ID,
446
+ name: "Database",
447
+ version: "0.0.1",
448
+ async activate(api) {
449
+ const log = api.logger;
450
+ log.info("Database plugin activating...");
451
+ const pluginConfig = api.config?.plugins?.entries?.[PLUGIN_ID]?.config ?? {};
452
+ const apiUrl = pluginConfig.apiUrl || (process.env.ALFE_API_URL ?? "");
453
+ const apiKey = pluginConfig.apiKey || (process.env.ALFE_API_KEY ?? "");
454
+ if (!apiUrl || !apiKey) {
455
+ log.error("Database plugin: missing apiUrl or apiKey — skipping activation");
456
+ return;
457
+ }
458
+ const credentials = await fetchCredentials(apiUrl, apiKey);
459
+ if (!credentials.connectionString) {
460
+ log.error("Database plugin: no connection string returned — cluster may still be provisioning");
461
+ return;
462
+ }
463
+ const url = new URL(credentials.connectionString);
464
+ url.username = credentials.username;
465
+ url.password = credentials.password;
466
+ mongoClient = new MongoClient(url.toString(), {
467
+ maxPoolSize: 3,
468
+ minPoolSize: 1,
469
+ serverSelectionTimeoutMS: 5e3
470
+ });
471
+ await mongoClient.connect();
472
+ log.info(`Database plugin connected — ${String(credentials.databases.length)} databases available`);
473
+ registerTools(api, mongoClient, credentials.databases, (entry) => {
474
+ reportAudit(apiUrl, apiKey, entry);
475
+ });
476
+ },
477
+ async deactivate(api) {
478
+ api.logger.info("Database plugin deactivating...");
479
+ if (mongoClient) {
480
+ await mongoClient.close();
481
+ mongoClient = null;
482
+ }
483
+ }
484
+ };
485
+ //#endregion
486
+ export { plugin as t };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@alfe.ai/openclaw-database",
3
+ "version": "0.0.1",
4
+ "description": "OpenClaw database plugin — MongoDB access for agents via MCP tools",
5
+ "type": "module",
6
+ "main": "./dist/plugin.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./plugin": {
14
+ "import": "./dist/plugin.js",
15
+ "types": "./dist/plugin.d.ts"
16
+ }
17
+ },
18
+ "openclaw": {
19
+ "extensions": [
20
+ "./dist/plugin.js"
21
+ ]
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "dependencies": {
27
+ "mongodb": "^6.12.0"
28
+ },
29
+ "license": "UNLICENSED",
30
+ "scripts": {
31
+ "build": "tsdown",
32
+ "dev": "tsdown --watch",
33
+ "typecheck": "tsc --noEmit",
34
+ "lint": "eslint ."
35
+ }
36
+ }