@spacelr/mcp 0.0.11 → 0.0.13
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/index.mjs +196 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -597,6 +597,53 @@ function registerAuthTools(server, api) {
|
|
|
597
597
|
}
|
|
598
598
|
}
|
|
599
599
|
);
|
|
600
|
+
server.registerTool(
|
|
601
|
+
"auth_verify_two_factor",
|
|
602
|
+
{
|
|
603
|
+
description: "Complete a two-factor authentication login. Call this after auth_login returns twoFactorRequired: true. Note: device trust is not persisted in MCP sessions \u2014 2FA will be required on every login. WARNING: token and code are visible in the conversation.",
|
|
604
|
+
inputSchema: {
|
|
605
|
+
token: z.string().min(1),
|
|
606
|
+
code: z.string().trim().regex(/^\d{6}$/, "Must be a 6-digit numeric code")
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
async ({ token, code }) => {
|
|
610
|
+
try {
|
|
611
|
+
const result = await api.post("/auth/verify-two-factor", { body: { token, code } });
|
|
612
|
+
return {
|
|
613
|
+
content: [{ type: "text", text: JSON.stringify(redactTokens(result), null, 2) }]
|
|
614
|
+
};
|
|
615
|
+
} catch (error) {
|
|
616
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
617
|
+
return {
|
|
618
|
+
content: [{ type: "text", text: `Two-factor verification failed: ${message}` }],
|
|
619
|
+
isError: true
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
server.registerTool(
|
|
625
|
+
"auth_resend_two_factor_code",
|
|
626
|
+
{
|
|
627
|
+
description: "Resend the two-factor authentication code. Rate-limited to 3 requests per minute. Requires the twoFactorToken from auth_login. WARNING: the token is sensitive and will be visible in the conversation.",
|
|
628
|
+
inputSchema: {
|
|
629
|
+
token: z.string().min(1)
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
async ({ token }) => {
|
|
633
|
+
try {
|
|
634
|
+
const result = await api.post("/auth/resend-two-factor-code", { body: { token } });
|
|
635
|
+
return {
|
|
636
|
+
content: [{ type: "text", text: JSON.stringify(redactTokens(result), null, 2) }]
|
|
637
|
+
};
|
|
638
|
+
} catch (error) {
|
|
639
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
640
|
+
return {
|
|
641
|
+
content: [{ type: "text", text: `Failed to resend two-factor code: ${message}` }],
|
|
642
|
+
isError: true
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
);
|
|
600
647
|
}
|
|
601
648
|
|
|
602
649
|
// libs/mcp-server/src/tools/projects.ts
|
|
@@ -1178,6 +1225,50 @@ function registerDatabaseTools(server, api) {
|
|
|
1178
1225
|
}
|
|
1179
1226
|
}
|
|
1180
1227
|
);
|
|
1228
|
+
server.registerTool(
|
|
1229
|
+
"database_collections_update_metadata",
|
|
1230
|
+
{
|
|
1231
|
+
description: "Update a collection's realtime delivery mode and optional stream retention. `realtimeMode: 'stream'` switches the collection to Redis Streams for durable event replay (needed for chat, notifications, activity feeds). `realtimeMode: 'pubsub'` (default) is fire-and-forget, used for dashboards and list views. Exactly one of `streamRetention.maxLen` (approx. entry count) or `streamRetention.maxAgeMs` (retention age in ms) may be set. Response may include `warnings[]` if the project exceeds the 50-collection soft cap.",
|
|
1232
|
+
inputSchema: {
|
|
1233
|
+
projectId: z4.string(),
|
|
1234
|
+
name: z4.string(),
|
|
1235
|
+
realtimeMode: z4.enum(["pubsub", "stream"]).optional(),
|
|
1236
|
+
streamRetention: z4.object({
|
|
1237
|
+
maxLen: z4.number().int().min(1).optional(),
|
|
1238
|
+
maxAgeMs: z4.number().int().min(1e3).optional()
|
|
1239
|
+
}).refine(
|
|
1240
|
+
(r) => !(r.maxLen !== void 0 && r.maxAgeMs !== void 0),
|
|
1241
|
+
{ message: "Set at most one of maxLen or maxAgeMs" }
|
|
1242
|
+
).optional()
|
|
1243
|
+
}
|
|
1244
|
+
},
|
|
1245
|
+
async ({ projectId, name, realtimeMode, streamRetention }) => {
|
|
1246
|
+
try {
|
|
1247
|
+
const body = {};
|
|
1248
|
+
if (realtimeMode !== void 0) body.realtimeMode = realtimeMode;
|
|
1249
|
+
if (streamRetention !== void 0) body.streamRetention = streamRetention;
|
|
1250
|
+
if (Object.keys(body).length === 0) {
|
|
1251
|
+
return {
|
|
1252
|
+
content: [{ type: "text", text: "At least one of realtimeMode or streamRetention must be provided" }],
|
|
1253
|
+
isError: true
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
const result = await api.patch(
|
|
1257
|
+
`/databases/${encodeURIComponent(projectId)}/collections/${encodeURIComponent(name)}`,
|
|
1258
|
+
{ body }
|
|
1259
|
+
);
|
|
1260
|
+
return {
|
|
1261
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1262
|
+
};
|
|
1263
|
+
} catch (error) {
|
|
1264
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1265
|
+
return {
|
|
1266
|
+
content: [{ type: "text", text: `Failed to update collection metadata: ${message}` }],
|
|
1267
|
+
isError: true
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
);
|
|
1181
1272
|
server.registerTool(
|
|
1182
1273
|
"database_collections_delete",
|
|
1183
1274
|
{
|
|
@@ -1592,6 +1683,9 @@ function registerDatabaseTools(server, api) {
|
|
|
1592
1683
|
}
|
|
1593
1684
|
|
|
1594
1685
|
// libs/mcp-server/src/tools/hosting.ts
|
|
1686
|
+
import { readdir, readFile } from "fs/promises";
|
|
1687
|
+
import { lstatSync } from "fs";
|
|
1688
|
+
import { join as join2, relative, resolve, sep } from "path";
|
|
1595
1689
|
import { z as z5 } from "zod";
|
|
1596
1690
|
|
|
1597
1691
|
// libs/mcp-server/src/zip.ts
|
|
@@ -1607,17 +1701,29 @@ var ZipBuilder = class {
|
|
|
1607
1701
|
* @param content - UTF-8 text content of the file
|
|
1608
1702
|
*/
|
|
1609
1703
|
addFile(name, content) {
|
|
1704
|
+
this.addFileBuffer(name, Buffer.from(content, "utf-8"));
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Add a binary file to the archive.
|
|
1708
|
+
*
|
|
1709
|
+
* @param name - File path inside the ZIP (e.g. "images/logo.png")
|
|
1710
|
+
* @param content - Raw file content as a Buffer
|
|
1711
|
+
*/
|
|
1712
|
+
addFileBuffer(name, content) {
|
|
1610
1713
|
const nameBuffer = Buffer.from(name, "utf-8");
|
|
1611
|
-
const
|
|
1612
|
-
const
|
|
1613
|
-
const crc32 = computeCrc32(contentBuffer);
|
|
1714
|
+
const compressed = deflateRawSync(content, { level: 9 });
|
|
1715
|
+
const crc32 = computeCrc32(content);
|
|
1614
1716
|
this.files.push({
|
|
1615
1717
|
name: nameBuffer,
|
|
1616
|
-
content
|
|
1718
|
+
content,
|
|
1617
1719
|
compressed,
|
|
1618
1720
|
crc32
|
|
1619
1721
|
});
|
|
1620
1722
|
}
|
|
1723
|
+
/** Number of files added to the archive. */
|
|
1724
|
+
get fileCount() {
|
|
1725
|
+
return this.files.length;
|
|
1726
|
+
}
|
|
1621
1727
|
/** Generate the complete ZIP archive as a Buffer. */
|
|
1622
1728
|
toBuffer() {
|
|
1623
1729
|
const localHeaders = [];
|
|
@@ -1691,6 +1797,17 @@ function computeCrc32(data) {
|
|
|
1691
1797
|
}
|
|
1692
1798
|
|
|
1693
1799
|
// libs/mcp-server/src/tools/hosting.ts
|
|
1800
|
+
async function walkDir(dir, base, zip) {
|
|
1801
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
1802
|
+
const fullPath = join2(dir, entry.name);
|
|
1803
|
+
if (entry.isSymbolicLink()) continue;
|
|
1804
|
+
if (entry.isDirectory()) {
|
|
1805
|
+
await walkDir(fullPath, base, zip);
|
|
1806
|
+
} else {
|
|
1807
|
+
zip.addFileBuffer(relative(base, fullPath).replace(/\\/g, "/"), await readFile(fullPath));
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1694
1811
|
function registerHostingTools(server, api) {
|
|
1695
1812
|
server.registerTool(
|
|
1696
1813
|
"hosting_deployments_list",
|
|
@@ -2106,6 +2223,73 @@ function registerHostingTools(server, api) {
|
|
|
2106
2223
|
}
|
|
2107
2224
|
}
|
|
2108
2225
|
);
|
|
2226
|
+
server.registerTool(
|
|
2227
|
+
"hosting_deployments_upload_directory",
|
|
2228
|
+
{
|
|
2229
|
+
description: "Upload a local directory as a ZIP archive to an existing deployment. Supports binary files (images, fonts, compiled assets). Use this instead of hosting_deployments_upload when deploying a built frontend from the local filesystem.\n\nTypical workflow:\n 1. hosting_deployments_create \u2192 get deploymentId\n 2. hosting_deployments_upload_directory \u2192 upload ./dist or any local folder\n 3. hosting_deployments_activate \u2192 make the deployment live",
|
|
2230
|
+
inputSchema: {
|
|
2231
|
+
projectId: z5.string().describe("Project ID"),
|
|
2232
|
+
deploymentId: z5.string().describe("Deployment ID (from hosting_deployments_create)"),
|
|
2233
|
+
directoryPath: z5.string().describe('Absolute or relative path to the local directory to upload (e.g. "./dist")')
|
|
2234
|
+
}
|
|
2235
|
+
},
|
|
2236
|
+
async ({ projectId, deploymentId, directoryPath }) => {
|
|
2237
|
+
try {
|
|
2238
|
+
const absDir = resolve(directoryPath);
|
|
2239
|
+
const cwd = process.cwd();
|
|
2240
|
+
if (absDir !== cwd && !absDir.startsWith(cwd + sep)) {
|
|
2241
|
+
return {
|
|
2242
|
+
content: [{ type: "text", text: `directoryPath must be within the current working directory (${cwd})` }],
|
|
2243
|
+
isError: true
|
|
2244
|
+
};
|
|
2245
|
+
}
|
|
2246
|
+
const stat = lstatSync(absDir, { throwIfNoEntry: false });
|
|
2247
|
+
if (!stat) {
|
|
2248
|
+
return {
|
|
2249
|
+
content: [{ type: "text", text: `Directory not found: ${absDir}` }],
|
|
2250
|
+
isError: true
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
if (!stat.isDirectory()) {
|
|
2254
|
+
return {
|
|
2255
|
+
content: [{ type: "text", text: `Path is not a directory: ${absDir}` }],
|
|
2256
|
+
isError: true
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
const zip = new ZipBuilder();
|
|
2260
|
+
await walkDir(absDir, absDir, zip);
|
|
2261
|
+
if (zip.fileCount === 0) {
|
|
2262
|
+
return {
|
|
2263
|
+
content: [{ type: "text", text: `Directory is empty: ${absDir}` }],
|
|
2264
|
+
isError: true
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
const zipBuffer = zip.toBuffer();
|
|
2268
|
+
const result = await api.uploadFile(
|
|
2269
|
+
`/hosting/projects/${encodeURIComponent(projectId)}/deployments/${encodeURIComponent(deploymentId)}/upload`,
|
|
2270
|
+
zipBuffer,
|
|
2271
|
+
"site.zip"
|
|
2272
|
+
);
|
|
2273
|
+
const response = {
|
|
2274
|
+
success: true,
|
|
2275
|
+
directoryPath: absDir,
|
|
2276
|
+
bundleSizeBytes: zipBuffer.length
|
|
2277
|
+
};
|
|
2278
|
+
if (result && typeof result === "object") {
|
|
2279
|
+
Object.assign(response, result);
|
|
2280
|
+
}
|
|
2281
|
+
return {
|
|
2282
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
|
|
2283
|
+
};
|
|
2284
|
+
} catch (error) {
|
|
2285
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2286
|
+
return {
|
|
2287
|
+
content: [{ type: "text", text: `Directory upload failed: ${message}` }],
|
|
2288
|
+
isError: true
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
);
|
|
2109
2293
|
}
|
|
2110
2294
|
|
|
2111
2295
|
// libs/mcp-server/src/tools/storage.ts
|
|
@@ -2428,7 +2612,10 @@ function registerFunctionTools(server, api) {
|
|
|
2428
2612
|
cronTimezone: z7.string().optional(),
|
|
2429
2613
|
timeout: z7.number().int().min(1e3).max(12e4).optional(),
|
|
2430
2614
|
memoryLimitMb: z7.number().int().min(16).max(512).optional(),
|
|
2431
|
-
enabled: z7.boolean().optional()
|
|
2615
|
+
enabled: z7.boolean().optional(),
|
|
2616
|
+
invokeMode: z7.enum(["webhook", "authenticated", "public", "hybrid"]).optional().describe(
|
|
2617
|
+
"Controls which credentials the public invoke endpoint accepts. webhook: X-Webhook-Secret only (default). authenticated: JWT only. public: no auth. hybrid: either; JWT wins on conflict."
|
|
2618
|
+
)
|
|
2432
2619
|
}
|
|
2433
2620
|
},
|
|
2434
2621
|
async ({ projectId, ...body }) => {
|
|
@@ -2463,7 +2650,10 @@ function registerFunctionTools(server, api) {
|
|
|
2463
2650
|
cronTimezone: z7.string().optional(),
|
|
2464
2651
|
timeout: z7.number().int().min(1e3).max(12e4).optional(),
|
|
2465
2652
|
memoryLimitMb: z7.number().int().min(16).max(512).optional(),
|
|
2466
|
-
enabled: z7.boolean().optional()
|
|
2653
|
+
enabled: z7.boolean().optional(),
|
|
2654
|
+
invokeMode: z7.enum(["webhook", "authenticated", "public", "hybrid"]).optional().describe(
|
|
2655
|
+
"Controls which credentials the public invoke endpoint accepts. webhook: X-Webhook-Secret only. authenticated: JWT only. public: no auth. hybrid: either; JWT wins on conflict."
|
|
2656
|
+
)
|
|
2467
2657
|
}
|
|
2468
2658
|
},
|
|
2469
2659
|
async ({ projectId, functionId, ...body }) => {
|