@ductape/mcp 0.1.60 → 0.1.61
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.js +39 -5
- package/package.json +1 -1
- package/scripts/check-frontend-analytics-guidance.mjs +11 -0
- package/src/index.ts +44 -6
package/dist/index.js
CHANGED
|
@@ -946,7 +946,7 @@ const payloadGenerateInputSchema = z.object({
|
|
|
946
946
|
execution_context: z.enum(['user', 'delegated', 'system']).optional().default('user').describe('Actor intent for this runtime operation. "user" means an active request initiated by the authenticated user; ' +
|
|
947
947
|
'"delegated" means work acting on behalf of a user with a short-lived delegated identity or application-owned ' +
|
|
948
948
|
'immutable actor context; "system" means intentionally unattributed background work. This drives session warnings.'),
|
|
949
|
-
include_cache: z.boolean().optional().
|
|
949
|
+
include_cache: z.boolean().optional().describe('Include a cache tag inside the generated payload. Defaults to false; set true only when the runtime call should use cache explicitly.'),
|
|
950
950
|
schema_mode: z.enum(['strict', 'best_effort']).optional().default('best_effort').describe('"strict" — fail if any required field cannot be resolved. ' +
|
|
951
951
|
'"best_effort" — fill what is known, leave unknowns as null/placeholder. Use best_effort when exploring.'),
|
|
952
952
|
input_hint: z.record(z.any()).optional().describe('Optional. Partial input values you already know. These are merged into the generated payload template ' +
|
|
@@ -1142,7 +1142,12 @@ const ADMIN_SUBCOMMANDS = [
|
|
|
1142
1142
|
];
|
|
1143
1143
|
function checkCli() {
|
|
1144
1144
|
try {
|
|
1145
|
-
const out = execSync('ductape --version', {
|
|
1145
|
+
const out = execSync('ductape --version', {
|
|
1146
|
+
encoding: 'utf8',
|
|
1147
|
+
timeout: 5000,
|
|
1148
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1149
|
+
env: cliEnvironment(),
|
|
1150
|
+
}).trim();
|
|
1146
1151
|
return { available: true, version: out || 'unknown' };
|
|
1147
1152
|
}
|
|
1148
1153
|
catch {
|
|
@@ -1158,6 +1163,7 @@ function checkLoginState() {
|
|
|
1158
1163
|
encoding: 'utf8',
|
|
1159
1164
|
timeout: 10000,
|
|
1160
1165
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1166
|
+
env: cliEnvironment(),
|
|
1161
1167
|
});
|
|
1162
1168
|
authState = 'ok';
|
|
1163
1169
|
return 'ok';
|
|
@@ -1173,7 +1179,12 @@ function syncWorkspace() {
|
|
|
1173
1179
|
if (!target)
|
|
1174
1180
|
return;
|
|
1175
1181
|
try {
|
|
1176
|
-
execSync(`ductape workspaces use "${target}"`, {
|
|
1182
|
+
execSync(`ductape workspaces use "${target}"`, {
|
|
1183
|
+
encoding: 'utf8',
|
|
1184
|
+
timeout: 10000,
|
|
1185
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1186
|
+
env: cliEnvironment(),
|
|
1187
|
+
});
|
|
1177
1188
|
}
|
|
1178
1189
|
catch {
|
|
1179
1190
|
// best-effort; if it fails the user will see workspace-mismatch errors on subsequent commands
|
|
@@ -1200,6 +1211,7 @@ function runCli(command) {
|
|
|
1200
1211
|
// instead of this wrapper killing the CLI first and reducing it to "(no data)".
|
|
1201
1212
|
timeout: 90000,
|
|
1202
1213
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1214
|
+
env: cliEnvironment(),
|
|
1203
1215
|
});
|
|
1204
1216
|
return { success: true, output: output.trim() };
|
|
1205
1217
|
}
|
|
@@ -1243,6 +1255,16 @@ function runCli(command) {
|
|
|
1243
1255
|
return { success: false, output: msg };
|
|
1244
1256
|
}
|
|
1245
1257
|
}
|
|
1258
|
+
/**
|
|
1259
|
+
* MCP is publishable-key-only. The standalone CLI may manage its own access-key
|
|
1260
|
+
* credentials, but an access key present in the MCP host environment is never
|
|
1261
|
+
* forwarded into CLI subprocesses.
|
|
1262
|
+
*/
|
|
1263
|
+
function cliEnvironment() {
|
|
1264
|
+
const environment = { ...process.env };
|
|
1265
|
+
delete environment.DUCTAPE_ACCESS_KEY;
|
|
1266
|
+
return environment;
|
|
1267
|
+
}
|
|
1246
1268
|
const docsInputSchema = z.object({
|
|
1247
1269
|
topic: z.string().describe('Feature topic to look up. Supported: ' +
|
|
1248
1270
|
'transactions, presave, triggers, aggregations, migrations, indexes, performance, actions, ' +
|
|
@@ -3834,7 +3856,8 @@ async function main() {
|
|
|
3834
3856
|
}
|
|
3835
3857
|
const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
|
|
3836
3858
|
if (!key) {
|
|
3837
|
-
throw new Error('
|
|
3859
|
+
throw new Error('Runtime authentication is missing. Set DUCTAPE_PUBLISHABLE_KEY in the MCP server environment ' +
|
|
3860
|
+
'or pass publishable_key to ductape_execute. The MCP server never accepts access keys.');
|
|
3838
3861
|
}
|
|
3839
3862
|
// The TS SDK uses ductape.events.* for broker operations; the backend proxy uses messageBrokers.
|
|
3840
3863
|
const proxyModule = args.module === 'events' ? 'messageBrokers' : args.module;
|
|
@@ -3857,7 +3880,18 @@ async function main() {
|
|
|
3857
3880
|
return p;
|
|
3858
3881
|
});
|
|
3859
3882
|
}
|
|
3860
|
-
|
|
3883
|
+
let result;
|
|
3884
|
+
try {
|
|
3885
|
+
result = await executeViaProxy(key, proxyModule, args.method, params);
|
|
3886
|
+
}
|
|
3887
|
+
catch (error) {
|
|
3888
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3889
|
+
if (/authentication failed|unauthorized|invalid.*key/i.test(message)) {
|
|
3890
|
+
throw new Error('The DUCTAPE_PUBLISHABLE_KEY was rejected by the runtime proxy. ' +
|
|
3891
|
+
'Use a publishable key for the same workspace/product. The MCP server never accepts or forwards access keys.');
|
|
3892
|
+
}
|
|
3893
|
+
throw error;
|
|
3894
|
+
}
|
|
3861
3895
|
return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
|
|
3862
3896
|
}
|
|
3863
3897
|
catch (err) {
|
package/package.json
CHANGED
|
@@ -6,6 +6,17 @@ import { dirname, resolve } from 'node:path';
|
|
|
6
6
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
const source = readFileSync(resolve(here, '../src/index.ts'), 'utf8');
|
|
8
8
|
|
|
9
|
+
assert.doesNotMatch(
|
|
10
|
+
source,
|
|
11
|
+
/(?:const|let|var)\s+\w+\s*=\s*process\.env\.DUCTAPE_ACCESS_KEY/,
|
|
12
|
+
'MCP server must never read DUCTAPE_ACCESS_KEY',
|
|
13
|
+
);
|
|
14
|
+
assert.match(
|
|
15
|
+
source,
|
|
16
|
+
/delete environment\.DUCTAPE_ACCESS_KEY/,
|
|
17
|
+
'MCP CLI subprocess environment must strip DUCTAPE_ACCESS_KEY',
|
|
18
|
+
);
|
|
19
|
+
|
|
9
20
|
const checks = [
|
|
10
21
|
['dedicated docs topic', /'frontend-analytics': `/],
|
|
11
22
|
['anonymous analytics without an invented session', /Before login:[\s\S]*do not invent a session/i],
|
package/src/index.ts
CHANGED
|
@@ -973,8 +973,8 @@ const payloadGenerateInputSchema = z.object({
|
|
|
973
973
|
'"delegated" means work acting on behalf of a user with a short-lived delegated identity or application-owned ' +
|
|
974
974
|
'immutable actor context; "system" means intentionally unattributed background work. This drives session warnings.'
|
|
975
975
|
),
|
|
976
|
-
include_cache: z.boolean().optional().
|
|
977
|
-
'Include
|
|
976
|
+
include_cache: z.boolean().optional().describe(
|
|
977
|
+
'Include a cache tag inside the generated payload. Defaults to false; set true only when the runtime call should use cache explicitly.'
|
|
978
978
|
),
|
|
979
979
|
schema_mode: z.enum(['strict', 'best_effort']).optional().default('best_effort').describe(
|
|
980
980
|
'"strict" — fail if any required field cannot be resolved. ' +
|
|
@@ -1204,7 +1204,12 @@ const ADMIN_SUBCOMMANDS = [
|
|
|
1204
1204
|
|
|
1205
1205
|
function checkCli(): { available: boolean; version?: string } {
|
|
1206
1206
|
try {
|
|
1207
|
-
const out = execSync('ductape --version', {
|
|
1207
|
+
const out = execSync('ductape --version', {
|
|
1208
|
+
encoding: 'utf8',
|
|
1209
|
+
timeout: 5000,
|
|
1210
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1211
|
+
env: cliEnvironment(),
|
|
1212
|
+
}).trim();
|
|
1208
1213
|
return { available: true, version: out || 'unknown' };
|
|
1209
1214
|
} catch {
|
|
1210
1215
|
return { available: false };
|
|
@@ -1220,6 +1225,7 @@ function checkLoginState(): 'ok' | 'none' {
|
|
|
1220
1225
|
encoding: 'utf8',
|
|
1221
1226
|
timeout: 10000,
|
|
1222
1227
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1228
|
+
env: cliEnvironment(),
|
|
1223
1229
|
});
|
|
1224
1230
|
authState = 'ok';
|
|
1225
1231
|
return 'ok';
|
|
@@ -1234,7 +1240,12 @@ function syncWorkspace(): void {
|
|
|
1234
1240
|
workspaceSynced = true; // mark done regardless so we don't retry on every call
|
|
1235
1241
|
if (!target) return;
|
|
1236
1242
|
try {
|
|
1237
|
-
execSync(`ductape workspaces use "${target}"`, {
|
|
1243
|
+
execSync(`ductape workspaces use "${target}"`, {
|
|
1244
|
+
encoding: 'utf8',
|
|
1245
|
+
timeout: 10000,
|
|
1246
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1247
|
+
env: cliEnvironment(),
|
|
1248
|
+
});
|
|
1238
1249
|
} catch {
|
|
1239
1250
|
// best-effort; if it fails the user will see workspace-mismatch errors on subsequent commands
|
|
1240
1251
|
}
|
|
@@ -1261,6 +1272,7 @@ function runCli(command: string): { success: boolean; output: string } {
|
|
|
1261
1272
|
// instead of this wrapper killing the CLI first and reducing it to "(no data)".
|
|
1262
1273
|
timeout: 90000,
|
|
1263
1274
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1275
|
+
env: cliEnvironment(),
|
|
1264
1276
|
});
|
|
1265
1277
|
return { success: true, output: output.trim() };
|
|
1266
1278
|
} catch (err: any) {
|
|
@@ -1304,6 +1316,17 @@ function runCli(command: string): { success: boolean; output: string } {
|
|
|
1304
1316
|
}
|
|
1305
1317
|
}
|
|
1306
1318
|
|
|
1319
|
+
/**
|
|
1320
|
+
* MCP is publishable-key-only. The standalone CLI may manage its own access-key
|
|
1321
|
+
* credentials, but an access key present in the MCP host environment is never
|
|
1322
|
+
* forwarded into CLI subprocesses.
|
|
1323
|
+
*/
|
|
1324
|
+
function cliEnvironment(): NodeJS.ProcessEnv {
|
|
1325
|
+
const environment = { ...process.env };
|
|
1326
|
+
delete environment.DUCTAPE_ACCESS_KEY;
|
|
1327
|
+
return environment;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1307
1330
|
const docsInputSchema = z.object({
|
|
1308
1331
|
topic: z.string().describe(
|
|
1309
1332
|
'Feature topic to look up. Supported: ' +
|
|
@@ -3949,7 +3972,10 @@ async function main() {
|
|
|
3949
3972
|
}
|
|
3950
3973
|
const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
|
|
3951
3974
|
if (!key) {
|
|
3952
|
-
throw new Error(
|
|
3975
|
+
throw new Error(
|
|
3976
|
+
'Runtime authentication is missing. Set DUCTAPE_PUBLISHABLE_KEY in the MCP server environment ' +
|
|
3977
|
+
'or pass publishable_key to ductape_execute. The MCP server never accepts access keys.',
|
|
3978
|
+
);
|
|
3953
3979
|
}
|
|
3954
3980
|
// The TS SDK uses ductape.events.* for broker operations; the backend proxy uses messageBrokers.
|
|
3955
3981
|
const proxyModule: SDKModule = args.module === 'events' ? 'messageBrokers' : args.module;
|
|
@@ -3974,7 +4000,19 @@ async function main() {
|
|
|
3974
4000
|
});
|
|
3975
4001
|
}
|
|
3976
4002
|
|
|
3977
|
-
|
|
4003
|
+
let result: unknown;
|
|
4004
|
+
try {
|
|
4005
|
+
result = await executeViaProxy(key, proxyModule, args.method, params);
|
|
4006
|
+
} catch (error) {
|
|
4007
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4008
|
+
if (/authentication failed|unauthorized|invalid.*key/i.test(message)) {
|
|
4009
|
+
throw new Error(
|
|
4010
|
+
'The DUCTAPE_PUBLISHABLE_KEY was rejected by the runtime proxy. ' +
|
|
4011
|
+
'Use a publishable key for the same workspace/product. The MCP server never accepts or forwards access keys.',
|
|
4012
|
+
);
|
|
4013
|
+
}
|
|
4014
|
+
throw error;
|
|
4015
|
+
}
|
|
3978
4016
|
return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
|
|
3979
4017
|
} catch (err) {
|
|
3980
4018
|
let message = err instanceof Error ? err.message : String(err);
|