@motherbase/cli 0.2.6 → 0.2.8
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/LICENSE +21 -21
- package/README.md +248 -247
- package/bin/.motherbase/config.json +5 -0
- package/bin/motherbase.js +10 -10
- package/package.json +1 -1
- package/src/api.js +39 -16
- package/src/commands/_util.js +106 -106
- package/src/commands/backups.js +16 -16
- package/src/commands/branches.js +113 -113
- package/src/commands/completion.js +90 -90
- package/src/commands/config.js +47 -47
- package/src/commands/cron.js +65 -65
- package/src/commands/db.js +103 -103
- package/src/commands/domains.js +124 -124
- package/src/commands/gen.js +178 -172
- package/src/commands/import.js +55 -55
- package/src/commands/index.js +85 -85
- package/src/commands/inspect.js +178 -178
- package/src/commands/keys.js +72 -72
- package/src/commands/link.js +35 -35
- package/src/commands/local.js +100 -23
- package/src/commands/login.js +9 -3
- package/src/commands/logout.js +16 -16
- package/src/commands/mcp.js +61 -61
- package/src/commands/migrate.js +0 -5
- package/src/commands/migration.js +1 -1
- package/src/commands/network.js +83 -83
- package/src/commands/orgs.js +40 -40
- package/src/commands/postgres-config.js +113 -113
- package/src/commands/projects.js +131 -130
- package/src/commands/queues.js +133 -133
- package/src/commands/realtime.js +35 -35
- package/src/commands/rls.js +176 -176
- package/src/commands/secrets.js +66 -66
- package/src/commands/signing.js +130 -130
- package/src/commands/snippets.js +44 -44
- package/src/commands/status.js +42 -42
- package/src/commands/storage.js +261 -261
- package/src/commands/test.js +9 -9
- package/src/commands/unlink.js +15 -15
- package/src/commands/vault.js +82 -82
- package/src/commands/webhooks.js +62 -62
- package/src/commands/whoami.js +20 -20
- package/src/commands/wrappers.js +150 -150
- package/src/config.js +98 -98
- package/src/context.js +106 -106
- package/src/edge-templates.js +194 -194
- package/src/format.js +92 -92
- package/src/inspect/csv.js +22 -22
- package/src/inspect/queries.js +562 -562
- package/src/mcp/platform-tools.js +233 -233
- package/src/mcp/protocol.js +152 -152
- package/src/mcp/storage-tools.js +93 -93
- package/src/mcp/tools.js +478 -478
- package/src/migration/commands.js +2 -2
- package/src/migration/files.js +13 -0
- package/src/prompt.js +91 -91
- package/src/storage/remote.js +62 -62
- package/src/ui.js +111 -111
package/src/mcp/protocol.js
CHANGED
|
@@ -1,152 +1,152 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Minimal MCP server over stdio: newline-delimited JSON-RPC 2.0 on stdin/stdout.
|
|
4
|
-
//
|
|
5
|
-
// Why not the official SDK: a stdio tools server needs five methods, and
|
|
6
|
-
// `@modelcontextprotocol/sdk` pulls express, hono, cors, ajv, jose and a dozen
|
|
7
|
-
// more to provide them. This CLI is deliberately zero-dependency, and making
|
|
8
|
-
// `npx @motherbase/cli` install a web framework so that one subcommand can read
|
|
9
|
-
// stdin is not a trade worth making.
|
|
10
|
-
//
|
|
11
|
-
// ponytail: stdio transport and the tools capability only. No resources, no
|
|
12
|
-
// prompts, no sampling, no HTTP/SSE transport. Adopt the official SDK if any of
|
|
13
|
-
// those is ever needed rather than growing this file into a second one.
|
|
14
|
-
|
|
15
|
-
// Echoed back to the client. MCP clients negotiate by sending their own version;
|
|
16
|
-
// answering with a version we implement is the contract, and every client in the
|
|
17
|
-
// field accepts a server that names an older one.
|
|
18
|
-
const PROTOCOL_VERSION = '2024-11-05';
|
|
19
|
-
|
|
20
|
-
/** JSON-RPC error codes used here. */
|
|
21
|
-
const METHOD_NOT_FOUND = -32601;
|
|
22
|
-
const INTERNAL_ERROR = -32603;
|
|
23
|
-
const PARSE_ERROR = -32700;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Reads newline-delimited JSON from `input`, dispatches to `handlers`, writes
|
|
27
|
-
* responses to `output`.
|
|
28
|
-
*
|
|
29
|
-
* Split from the tool definitions so the transport can be driven by a test with
|
|
30
|
-
* two in-memory streams and no child process.
|
|
31
|
-
*/
|
|
32
|
-
function serve({ input, output, handlers, onError }) {
|
|
33
|
-
let buffer = '';
|
|
34
|
-
|
|
35
|
-
const write = message => {
|
|
36
|
-
output.write(JSON.stringify(message) + '\n');
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const respond = (id, result) => {
|
|
40
|
-
// A notification (no id) gets no response. Sending one is a protocol error
|
|
41
|
-
// that some clients treat as fatal.
|
|
42
|
-
if (id === undefined || id === null) return;
|
|
43
|
-
write({ jsonrpc: '2.0', id, result });
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const fail = (id, code, message) => {
|
|
47
|
-
if (id === undefined || id === null) return;
|
|
48
|
-
write({ jsonrpc: '2.0', id, error: { code, message } });
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const dispatch = async message => {
|
|
52
|
-
// Valid JSON is not necessarily a request: `null` or `42` would throw on
|
|
53
|
-
// destructuring and, un-awaited, take the process down.
|
|
54
|
-
if (message === null || typeof message !== 'object') return;
|
|
55
|
-
const { id, method, params } = message;
|
|
56
|
-
const handler = Object.hasOwn(handlers, method) ? handlers[method] : undefined;
|
|
57
|
-
if (!handler) {
|
|
58
|
-
// Unknown notifications are ignored rather than answered: the spec sends
|
|
59
|
-
// several the server is not required to know about.
|
|
60
|
-
if (id === undefined || id === null) return;
|
|
61
|
-
return fail(id, METHOD_NOT_FOUND, `Unknown method: ${method}`);
|
|
62
|
-
}
|
|
63
|
-
try {
|
|
64
|
-
respond(id, await handler(params || {}));
|
|
65
|
-
} catch (err) {
|
|
66
|
-
if (onError) onError(err);
|
|
67
|
-
fail(id, INTERNAL_ERROR, err && err.message ? err.message : String(err));
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
return new Promise(resolve => {
|
|
72
|
-
input.setEncoding('utf8');
|
|
73
|
-
input.on('data', chunk => {
|
|
74
|
-
buffer += chunk;
|
|
75
|
-
let index;
|
|
76
|
-
while ((index = buffer.indexOf('\n')) >= 0) {
|
|
77
|
-
const line = buffer.slice(0, index).trim();
|
|
78
|
-
buffer = buffer.slice(index + 1);
|
|
79
|
-
if (!line) continue;
|
|
80
|
-
let message;
|
|
81
|
-
try {
|
|
82
|
-
message = JSON.parse(line);
|
|
83
|
-
} catch {
|
|
84
|
-
write({ jsonrpc: '2.0', id: null, error: { code: PARSE_ERROR, message: 'Invalid JSON' } });
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
// Deliberately not awaited: a slow tool call must not stall the reader,
|
|
88
|
-
// and JSON-RPC responses carry their id so they may arrive out of order.
|
|
89
|
-
void dispatch(message);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
input.on('end', resolve);
|
|
93
|
-
input.on('close', resolve);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Build the five handlers a tools-only server needs.
|
|
99
|
-
*
|
|
100
|
-
* `tools` is a list of `{ name, description, inputSchema, run }`. `run` returns
|
|
101
|
-
* anything JSON-serialisable; it is rendered as a text content block, which is
|
|
102
|
-
* what every current client displays and what models read best.
|
|
103
|
-
*/
|
|
104
|
-
function toolServer({ name, version, tools }) {
|
|
105
|
-
const byName = new Map(tools.map(tool => [tool.name, tool]));
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
initialize: () => ({
|
|
109
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
110
|
-
capabilities: { tools: {} },
|
|
111
|
-
serverInfo: { name, version },
|
|
112
|
-
}),
|
|
113
|
-
'notifications/initialized': () => undefined,
|
|
114
|
-
ping: () => ({}),
|
|
115
|
-
'tools/list': () => ({
|
|
116
|
-
tools: tools.map(({ name: toolName, description, inputSchema }) => ({
|
|
117
|
-
name: toolName,
|
|
118
|
-
description,
|
|
119
|
-
inputSchema,
|
|
120
|
-
})),
|
|
121
|
-
}),
|
|
122
|
-
'tools/call': async params => {
|
|
123
|
-
const tool = byName.get(params.name);
|
|
124
|
-
if (!tool) {
|
|
125
|
-
// A missing tool is reported in-band, not as a JSON-RPC error: the model
|
|
126
|
-
// should see it and correct itself rather than the client showing a
|
|
127
|
-
// transport failure.
|
|
128
|
-
return {
|
|
129
|
-
isError: true,
|
|
130
|
-
content: [{ type: 'text', text: `Unknown tool: ${params.name}` }],
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
const result = await tool.run(params.arguments || {});
|
|
135
|
-
return { content: [{ type: 'text', text: render(result) }] };
|
|
136
|
-
} catch (err) {
|
|
137
|
-
return {
|
|
138
|
-
isError: true,
|
|
139
|
-
content: [{ type: 'text', text: err && err.message ? err.message : String(err) }],
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function render(value) {
|
|
147
|
-
if (typeof value === 'string') return value;
|
|
148
|
-
if (value === undefined) return 'ok';
|
|
149
|
-
return JSON.stringify(value, null, 2);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
module.exports = { serve, toolServer, PROTOCOL_VERSION };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Minimal MCP server over stdio: newline-delimited JSON-RPC 2.0 on stdin/stdout.
|
|
4
|
+
//
|
|
5
|
+
// Why not the official SDK: a stdio tools server needs five methods, and
|
|
6
|
+
// `@modelcontextprotocol/sdk` pulls express, hono, cors, ajv, jose and a dozen
|
|
7
|
+
// more to provide them. This CLI is deliberately zero-dependency, and making
|
|
8
|
+
// `npx @motherbase/cli` install a web framework so that one subcommand can read
|
|
9
|
+
// stdin is not a trade worth making.
|
|
10
|
+
//
|
|
11
|
+
// ponytail: stdio transport and the tools capability only. No resources, no
|
|
12
|
+
// prompts, no sampling, no HTTP/SSE transport. Adopt the official SDK if any of
|
|
13
|
+
// those is ever needed rather than growing this file into a second one.
|
|
14
|
+
|
|
15
|
+
// Echoed back to the client. MCP clients negotiate by sending their own version;
|
|
16
|
+
// answering with a version we implement is the contract, and every client in the
|
|
17
|
+
// field accepts a server that names an older one.
|
|
18
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
19
|
+
|
|
20
|
+
/** JSON-RPC error codes used here. */
|
|
21
|
+
const METHOD_NOT_FOUND = -32601;
|
|
22
|
+
const INTERNAL_ERROR = -32603;
|
|
23
|
+
const PARSE_ERROR = -32700;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Reads newline-delimited JSON from `input`, dispatches to `handlers`, writes
|
|
27
|
+
* responses to `output`.
|
|
28
|
+
*
|
|
29
|
+
* Split from the tool definitions so the transport can be driven by a test with
|
|
30
|
+
* two in-memory streams and no child process.
|
|
31
|
+
*/
|
|
32
|
+
function serve({ input, output, handlers, onError }) {
|
|
33
|
+
let buffer = '';
|
|
34
|
+
|
|
35
|
+
const write = message => {
|
|
36
|
+
output.write(JSON.stringify(message) + '\n');
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const respond = (id, result) => {
|
|
40
|
+
// A notification (no id) gets no response. Sending one is a protocol error
|
|
41
|
+
// that some clients treat as fatal.
|
|
42
|
+
if (id === undefined || id === null) return;
|
|
43
|
+
write({ jsonrpc: '2.0', id, result });
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const fail = (id, code, message) => {
|
|
47
|
+
if (id === undefined || id === null) return;
|
|
48
|
+
write({ jsonrpc: '2.0', id, error: { code, message } });
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const dispatch = async message => {
|
|
52
|
+
// Valid JSON is not necessarily a request: `null` or `42` would throw on
|
|
53
|
+
// destructuring and, un-awaited, take the process down.
|
|
54
|
+
if (message === null || typeof message !== 'object') return;
|
|
55
|
+
const { id, method, params } = message;
|
|
56
|
+
const handler = Object.hasOwn(handlers, method) ? handlers[method] : undefined;
|
|
57
|
+
if (!handler) {
|
|
58
|
+
// Unknown notifications are ignored rather than answered: the spec sends
|
|
59
|
+
// several the server is not required to know about.
|
|
60
|
+
if (id === undefined || id === null) return;
|
|
61
|
+
return fail(id, METHOD_NOT_FOUND, `Unknown method: ${method}`);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
respond(id, await handler(params || {}));
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (onError) onError(err);
|
|
67
|
+
fail(id, INTERNAL_ERROR, err && err.message ? err.message : String(err));
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return new Promise(resolve => {
|
|
72
|
+
input.setEncoding('utf8');
|
|
73
|
+
input.on('data', chunk => {
|
|
74
|
+
buffer += chunk;
|
|
75
|
+
let index;
|
|
76
|
+
while ((index = buffer.indexOf('\n')) >= 0) {
|
|
77
|
+
const line = buffer.slice(0, index).trim();
|
|
78
|
+
buffer = buffer.slice(index + 1);
|
|
79
|
+
if (!line) continue;
|
|
80
|
+
let message;
|
|
81
|
+
try {
|
|
82
|
+
message = JSON.parse(line);
|
|
83
|
+
} catch {
|
|
84
|
+
write({ jsonrpc: '2.0', id: null, error: { code: PARSE_ERROR, message: 'Invalid JSON' } });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
// Deliberately not awaited: a slow tool call must not stall the reader,
|
|
88
|
+
// and JSON-RPC responses carry their id so they may arrive out of order.
|
|
89
|
+
void dispatch(message);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
input.on('end', resolve);
|
|
93
|
+
input.on('close', resolve);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Build the five handlers a tools-only server needs.
|
|
99
|
+
*
|
|
100
|
+
* `tools` is a list of `{ name, description, inputSchema, run }`. `run` returns
|
|
101
|
+
* anything JSON-serialisable; it is rendered as a text content block, which is
|
|
102
|
+
* what every current client displays and what models read best.
|
|
103
|
+
*/
|
|
104
|
+
function toolServer({ name, version, tools }) {
|
|
105
|
+
const byName = new Map(tools.map(tool => [tool.name, tool]));
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
initialize: () => ({
|
|
109
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
110
|
+
capabilities: { tools: {} },
|
|
111
|
+
serverInfo: { name, version },
|
|
112
|
+
}),
|
|
113
|
+
'notifications/initialized': () => undefined,
|
|
114
|
+
ping: () => ({}),
|
|
115
|
+
'tools/list': () => ({
|
|
116
|
+
tools: tools.map(({ name: toolName, description, inputSchema }) => ({
|
|
117
|
+
name: toolName,
|
|
118
|
+
description,
|
|
119
|
+
inputSchema,
|
|
120
|
+
})),
|
|
121
|
+
}),
|
|
122
|
+
'tools/call': async params => {
|
|
123
|
+
const tool = byName.get(params.name);
|
|
124
|
+
if (!tool) {
|
|
125
|
+
// A missing tool is reported in-band, not as a JSON-RPC error: the model
|
|
126
|
+
// should see it and correct itself rather than the client showing a
|
|
127
|
+
// transport failure.
|
|
128
|
+
return {
|
|
129
|
+
isError: true,
|
|
130
|
+
content: [{ type: 'text', text: `Unknown tool: ${params.name}` }],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const result = await tool.run(params.arguments || {});
|
|
135
|
+
return { content: [{ type: 'text', text: render(result) }] };
|
|
136
|
+
} catch (err) {
|
|
137
|
+
return {
|
|
138
|
+
isError: true,
|
|
139
|
+
content: [{ type: 'text', text: err && err.message ? err.message : String(err) }],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function render(value) {
|
|
147
|
+
if (typeof value === 'string') return value;
|
|
148
|
+
if (value === undefined) return 'ok';
|
|
149
|
+
return JSON.stringify(value, null, 2);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = { serve, toolServer, PROTOCOL_VERSION };
|
package/src/mcp/storage-tools.js
CHANGED
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Storage configuration over MCP, the counterpart of Supabase's
|
|
4
|
-
// get_storage_config / update_storage_config. Motherbase keeps that
|
|
5
|
-
// configuration in three settings groups plus the S3 endpoint, so these two
|
|
6
|
-
// tools fold them back into the single view an agent expects.
|
|
7
|
-
|
|
8
|
-
const api = require('../api');
|
|
9
|
-
|
|
10
|
-
/** Setting group -> the tool argument that edits each of its fields. */
|
|
11
|
-
const WRITABLE = {
|
|
12
|
-
'storage.limits': { maxUploadBytes: 'maxUploadBytes', allowedMimeTypes: 'allowedMimeTypes' },
|
|
13
|
-
'storage.imageTransform': { enabled: 'imageTransformEnabled', maxSourceBytes: 'imageTransformMaxSourceBytes' },
|
|
14
|
-
'storage.policies': { enabled: 'policiesEnabled' },
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
/** Groups the arguments touch, each with the field values to change. */
|
|
18
|
-
function changesByGroup(args) {
|
|
19
|
-
const changes = {};
|
|
20
|
-
for (const [group, fields] of Object.entries(WRITABLE)) {
|
|
21
|
-
for (const [field, argName] of Object.entries(fields)) {
|
|
22
|
-
if (args[argName] === undefined) continue;
|
|
23
|
-
const value = Array.isArray(args[argName]) ? args[argName].join(', ') : args[argName];
|
|
24
|
-
(changes[group] ||= {})[field] = value;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return changes;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const valuesOf = group => (group && group.values) || {};
|
|
31
|
-
|
|
32
|
-
function readStorageTools(ctx) {
|
|
33
|
-
return [
|
|
34
|
-
{
|
|
35
|
-
name: 'get_storage_config',
|
|
36
|
-
description:
|
|
37
|
-
'Storage configuration: the global upload size limit and allowed MIME types, image transformations, whether PostgreSQL policies authorize storage access, and the S3 protocol endpoint. Per-bucket limits are on list_storage_buckets.',
|
|
38
|
-
inputSchema: { type: 'object', properties: {} },
|
|
39
|
-
run: async () => {
|
|
40
|
-
const [limits, imageTransform, policies, s3] = await Promise.all([
|
|
41
|
-
api.adminRequest(ctx, 'GET', '/settings/storage.limits'),
|
|
42
|
-
api.adminRequest(ctx, 'GET', '/settings/storage.imageTransform'),
|
|
43
|
-
api.adminRequest(ctx, 'GET', '/settings/storage.policies'),
|
|
44
|
-
api.adminRequest(ctx, 'GET', '/storage/s3'),
|
|
45
|
-
]);
|
|
46
|
-
return {
|
|
47
|
-
limits: valuesOf(limits),
|
|
48
|
-
imageTransform: valuesOf(imageTransform),
|
|
49
|
-
policies: valuesOf(policies),
|
|
50
|
-
s3,
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function writeStorageTools(ctx) {
|
|
58
|
-
return [
|
|
59
|
-
{
|
|
60
|
-
name: 'update_storage_config',
|
|
61
|
-
description:
|
|
62
|
-
'Change the global storage configuration. Only the fields you pass change. Per-bucket size limits and MIME types are edited on the bucket itself.',
|
|
63
|
-
inputSchema: {
|
|
64
|
-
type: 'object',
|
|
65
|
-
properties: {
|
|
66
|
-
maxUploadBytes: { type: 'integer', minimum: 0, description: 'Largest upload in bytes. 0 uses the server default.' },
|
|
67
|
-
allowedMimeTypes: {
|
|
68
|
-
type: 'array',
|
|
69
|
-
items: { type: 'string' },
|
|
70
|
-
description: 'Allowed MIME types, wildcards like image/* supported. Empty allows everything.',
|
|
71
|
-
},
|
|
72
|
-
imageTransformEnabled: { type: 'boolean', description: 'Serve resized images from /storage/v1/render/image.' },
|
|
73
|
-
imageTransformMaxSourceBytes: { type: 'integer', minimum: 1, description: 'Largest image, in bytes, that will be transformed.' },
|
|
74
|
-
policiesEnabled: { type: 'boolean', description: 'Authorize storage access with RLS policies on storage.files.' },
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
run: async args => {
|
|
78
|
-
const changes = changesByGroup(args);
|
|
79
|
-
if (!Object.keys(changes).length) throw new Error('Pass at least one field to change.');
|
|
80
|
-
// The settings API replaces a group wholesale, so an untouched field
|
|
81
|
-
// would fall back to its default unless it is sent back as it is.
|
|
82
|
-
const saved = {};
|
|
83
|
-
for (const [group, fields] of Object.entries(changes)) {
|
|
84
|
-
const current = valuesOf(await api.adminRequest(ctx, 'GET', `/settings/${group}`));
|
|
85
|
-
saved[group] = await api.adminRequest(ctx, 'PUT', `/settings/${group}`, { body: { ...current, ...fields } });
|
|
86
|
-
}
|
|
87
|
-
return saved;
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
module.exports = { readStorageTools, writeStorageTools, changesByGroup };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Storage configuration over MCP, the counterpart of Supabase's
|
|
4
|
+
// get_storage_config / update_storage_config. Motherbase keeps that
|
|
5
|
+
// configuration in three settings groups plus the S3 endpoint, so these two
|
|
6
|
+
// tools fold them back into the single view an agent expects.
|
|
7
|
+
|
|
8
|
+
const api = require('../api');
|
|
9
|
+
|
|
10
|
+
/** Setting group -> the tool argument that edits each of its fields. */
|
|
11
|
+
const WRITABLE = {
|
|
12
|
+
'storage.limits': { maxUploadBytes: 'maxUploadBytes', allowedMimeTypes: 'allowedMimeTypes' },
|
|
13
|
+
'storage.imageTransform': { enabled: 'imageTransformEnabled', maxSourceBytes: 'imageTransformMaxSourceBytes' },
|
|
14
|
+
'storage.policies': { enabled: 'policiesEnabled' },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Groups the arguments touch, each with the field values to change. */
|
|
18
|
+
function changesByGroup(args) {
|
|
19
|
+
const changes = {};
|
|
20
|
+
for (const [group, fields] of Object.entries(WRITABLE)) {
|
|
21
|
+
for (const [field, argName] of Object.entries(fields)) {
|
|
22
|
+
if (args[argName] === undefined) continue;
|
|
23
|
+
const value = Array.isArray(args[argName]) ? args[argName].join(', ') : args[argName];
|
|
24
|
+
(changes[group] ||= {})[field] = value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return changes;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const valuesOf = group => (group && group.values) || {};
|
|
31
|
+
|
|
32
|
+
function readStorageTools(ctx) {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
name: 'get_storage_config',
|
|
36
|
+
description:
|
|
37
|
+
'Storage configuration: the global upload size limit and allowed MIME types, image transformations, whether PostgreSQL policies authorize storage access, and the S3 protocol endpoint. Per-bucket limits are on list_storage_buckets.',
|
|
38
|
+
inputSchema: { type: 'object', properties: {} },
|
|
39
|
+
run: async () => {
|
|
40
|
+
const [limits, imageTransform, policies, s3] = await Promise.all([
|
|
41
|
+
api.adminRequest(ctx, 'GET', '/settings/storage.limits'),
|
|
42
|
+
api.adminRequest(ctx, 'GET', '/settings/storage.imageTransform'),
|
|
43
|
+
api.adminRequest(ctx, 'GET', '/settings/storage.policies'),
|
|
44
|
+
api.adminRequest(ctx, 'GET', '/storage/s3'),
|
|
45
|
+
]);
|
|
46
|
+
return {
|
|
47
|
+
limits: valuesOf(limits),
|
|
48
|
+
imageTransform: valuesOf(imageTransform),
|
|
49
|
+
policies: valuesOf(policies),
|
|
50
|
+
s3,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeStorageTools(ctx) {
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
name: 'update_storage_config',
|
|
61
|
+
description:
|
|
62
|
+
'Change the global storage configuration. Only the fields you pass change. Per-bucket size limits and MIME types are edited on the bucket itself.',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
maxUploadBytes: { type: 'integer', minimum: 0, description: 'Largest upload in bytes. 0 uses the server default.' },
|
|
67
|
+
allowedMimeTypes: {
|
|
68
|
+
type: 'array',
|
|
69
|
+
items: { type: 'string' },
|
|
70
|
+
description: 'Allowed MIME types, wildcards like image/* supported. Empty allows everything.',
|
|
71
|
+
},
|
|
72
|
+
imageTransformEnabled: { type: 'boolean', description: 'Serve resized images from /storage/v1/render/image.' },
|
|
73
|
+
imageTransformMaxSourceBytes: { type: 'integer', minimum: 1, description: 'Largest image, in bytes, that will be transformed.' },
|
|
74
|
+
policiesEnabled: { type: 'boolean', description: 'Authorize storage access with RLS policies on storage.files.' },
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
run: async args => {
|
|
78
|
+
const changes = changesByGroup(args);
|
|
79
|
+
if (!Object.keys(changes).length) throw new Error('Pass at least one field to change.');
|
|
80
|
+
// The settings API replaces a group wholesale, so an untouched field
|
|
81
|
+
// would fall back to its default unless it is sent back as it is.
|
|
82
|
+
const saved = {};
|
|
83
|
+
for (const [group, fields] of Object.entries(changes)) {
|
|
84
|
+
const current = valuesOf(await api.adminRequest(ctx, 'GET', `/settings/${group}`));
|
|
85
|
+
saved[group] = await api.adminRequest(ctx, 'PUT', `/settings/${group}`, { body: { ...current, ...fields } });
|
|
86
|
+
}
|
|
87
|
+
return saved;
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { readStorageTools, writeStorageTools, changesByGroup };
|