@reproapp/mcp 0.0.7 → 0.0.9
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 +1 -1
- package/src/index.js +221 -41
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -16,7 +16,8 @@ const SERVER_INSTRUCTIONS = [
|
|
|
16
16
|
'For Repro-scoped questions about sessions, requests, traces, privacy policies, apps, or configuration, use the Repro MCP tools only.',
|
|
17
17
|
'Do not create local scripts, read cached Repro auth files, or call Repro HTTP APIs directly for Repro data or policy workflows.',
|
|
18
18
|
'For privacy policy writes, inspect first and then use exact statementSid(s) or blockerSetId. Broad natural-language policy update queries are read-only.',
|
|
19
|
-
'
|
|
19
|
+
'For factual debugging questions, use exact evidence tools first and answer from their structured output. Use investigate only after exact evidence is gathered, or when the user explicitly asks for synthesis/root cause.',
|
|
20
|
+
'Never answer that captured client data is absent because code appears to omit it. Verify with request, DB, trace, function-call, and message payload tools, then say exactly what Repro captured or did not capture.',
|
|
20
21
|
].join(' ');
|
|
21
22
|
const DEFAULT_REPRO_AI_BASE_URL = 'https://repro-ai-u8lhw.ondigitalocean.app';
|
|
22
23
|
const DEFAULT_REPRO_API_BASE_URL = 'https://repro-api-d7288.ondigitalocean.app/api';
|
|
@@ -194,6 +195,31 @@ const LOCAL_TOOL_DEFINITIONS = [
|
|
|
194
195
|
},
|
|
195
196
|
];
|
|
196
197
|
|
|
198
|
+
const TOOL_DESCRIPTION_OVERRIDES = {
|
|
199
|
+
investigate:
|
|
200
|
+
'Synthesize a root-cause hypothesis from Repro evidence after exact telemetry tools have been checked. Do not use this as the first or only tool for factual inventory questions about requests, DB changes, traces, function args/returns, emails, Kafka/RabbitMQ/messages, or payload contents; call the exact evidence tool first and cite that output.',
|
|
201
|
+
list_requests:
|
|
202
|
+
'Exact evidence tool: list captured HTTP and backend transport requests in scope. Use this before investigate whenever the user asks what request happened, which endpoint ran, request timing/status, or which request RID anchors later DB/trace/message evidence.',
|
|
203
|
+
get_request:
|
|
204
|
+
'Exact evidence tool: fetch one captured request by RID, including request/response bodies and captured value refs when present. Use this before answering factual questions about a specific request payload, response, endpoint, or transport message wrapper.',
|
|
205
|
+
get_requests_relative_to_anchor:
|
|
206
|
+
'Exact evidence tool: list captured requests before or after an action/request/DB anchor. Use for ordering questions and localizing the request sequence around a UI action or backend event.',
|
|
207
|
+
get_db_changes:
|
|
208
|
+
'Exact evidence tool: fetch captured DB/Redis changes. Use this before answering what collections, documents, fields, queries, mutations, or result metadata changed. Set includeQueryDetails when the user needs query/update/body details.',
|
|
209
|
+
get_trace:
|
|
210
|
+
'Exact evidence tool: fetch captured trace batches for a request RID. Use this before answering which functions ran, trace timing, thrown errors, or trace event details for a request.',
|
|
211
|
+
get_action_path:
|
|
212
|
+
'Exact evidence tool: fetch the correlated action path with requests, DB changes, and emails/messages when available. Use this to connect a UI action to backend evidence before giving a narrative answer.',
|
|
213
|
+
get_function_candidates:
|
|
214
|
+
'Exact evidence tool: find likely captured functions for an action/request/function search. Use this before get_function_calls when the exact function anchor is unclear.',
|
|
215
|
+
get_function_calls:
|
|
216
|
+
'Exact evidence tool: fetch concrete captured function calls, arguments, return values, errors, and value refs. Use this before answering what function handled something or what args/returns were captured.',
|
|
217
|
+
get_kafka_messages:
|
|
218
|
+
'Exact evidence tool: fetch captured Kafka publish/consume payloads. Use this for messaging questions and payload contents; for exact single-message lookups, the MCP server expands captured large values automatically.',
|
|
219
|
+
get_captured_value:
|
|
220
|
+
'Exact evidence tool: fetch the full masked value behind a captured valueId. Use this whenever another tool reports a captured value reference or large_value_available before claiming the payload is unavailable.',
|
|
221
|
+
};
|
|
222
|
+
|
|
197
223
|
function loadConfig(env = process.env) {
|
|
198
224
|
const baseUrl = normalizeBaseUrl(env.REPRO_AI_BASE_URL || DEFAULT_REPRO_AI_BASE_URL);
|
|
199
225
|
const apiBaseUrl = normalizeOptionalBaseUrl(env.REPRO_API_BASE_URL, DEFAULT_REPRO_API_BASE_URL);
|
|
@@ -1142,11 +1168,20 @@ async function handleRpcMessage(message, config, client, getTools) {
|
|
|
1142
1168
|
function publicToolDefinition(tool) {
|
|
1143
1169
|
return {
|
|
1144
1170
|
name: tool.name,
|
|
1145
|
-
description: tool
|
|
1171
|
+
description: publicToolDescription(tool),
|
|
1146
1172
|
inputSchema: tool.inputSchema,
|
|
1147
1173
|
};
|
|
1148
1174
|
}
|
|
1149
1175
|
|
|
1176
|
+
function publicToolDescription(tool) {
|
|
1177
|
+
const base = typeof tool?.description === 'string' ? tool.description.trim() : '';
|
|
1178
|
+
const override = TOOL_DESCRIPTION_OVERRIDES[tool?.name];
|
|
1179
|
+
if (!override) {
|
|
1180
|
+
return base;
|
|
1181
|
+
}
|
|
1182
|
+
return base ? `${override} ${base}` : override;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1150
1185
|
function normalizeToolCatalog(catalog) {
|
|
1151
1186
|
if (!catalog || typeof catalog !== 'object' || !Array.isArray(catalog.tools)) {
|
|
1152
1187
|
throw new Error('repro-ai returned an invalid tool catalog');
|
|
@@ -1193,9 +1228,10 @@ async function executeTool(params, config, client, getTools) {
|
|
|
1193
1228
|
throw rpcError(-32602, `Unknown tool: ${name}`);
|
|
1194
1229
|
}
|
|
1195
1230
|
|
|
1196
|
-
const response = await client.executeTool(name, input);
|
|
1231
|
+
const response = await client.executeTool(name, inputWithExactCapturedPayloads(name, input));
|
|
1232
|
+
const expandedResponse = await expandLinkedCapturedPayloads(name, response, client, input);
|
|
1197
1233
|
|
|
1198
|
-
const augmented = augmentPaths(
|
|
1234
|
+
const augmented = augmentPaths(expandedResponse, config.pathMappings);
|
|
1199
1235
|
const isError =
|
|
1200
1236
|
augmented &&
|
|
1201
1237
|
typeof augmented === 'object' &&
|
|
@@ -1214,58 +1250,202 @@ async function executeTool(params, config, client, getTools) {
|
|
|
1214
1250
|
};
|
|
1215
1251
|
}
|
|
1216
1252
|
|
|
1217
|
-
function
|
|
1218
|
-
if (name
|
|
1219
|
-
|
|
1220
|
-
if (text) return text;
|
|
1253
|
+
function inputWithExactCapturedPayloads(name, input) {
|
|
1254
|
+
if (name !== 'get_kafka_messages') {
|
|
1255
|
+
return input;
|
|
1221
1256
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1257
|
+
return {
|
|
1258
|
+
...input,
|
|
1259
|
+
includeLargeValues: true,
|
|
1260
|
+
expandLargeValue: true,
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
async function expandLinkedCapturedPayloads(name, response, client, originalInput = {}) {
|
|
1265
|
+
if (name !== 'get_kafka_messages') {
|
|
1266
|
+
return response;
|
|
1225
1267
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1268
|
+
|
|
1269
|
+
const root = cloneJson(response) ?? response;
|
|
1270
|
+
const visited = new Set();
|
|
1271
|
+
|
|
1272
|
+
while (true) {
|
|
1273
|
+
const links = collectCapturedPayloadLinks(root)
|
|
1274
|
+
.filter((entry) => !visited.has(capturedPayloadLinkKey(entry.link)));
|
|
1275
|
+
if (!links.length) {
|
|
1276
|
+
return root;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
for (const { link } of links) {
|
|
1280
|
+
const key = capturedPayloadLinkKey(link);
|
|
1281
|
+
visited.add(key);
|
|
1282
|
+
|
|
1283
|
+
const toolName = typeof link.tool === 'string' ? link.tool : '';
|
|
1284
|
+
if (toolName !== 'get_kafka_messages') {
|
|
1285
|
+
throw new Error(`Cannot expand captured payload through unsupported MCP tool: ${toolName || 'unknown'}`);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const linkArgs = link.arguments && typeof link.arguments === 'object' && !Array.isArray(link.arguments)
|
|
1289
|
+
? link.arguments
|
|
1290
|
+
: {};
|
|
1291
|
+
const scopedLinkArgs = {
|
|
1292
|
+
...originalInput,
|
|
1293
|
+
...linkArgs,
|
|
1294
|
+
};
|
|
1295
|
+
const expanded = await client.executeTool(toolName, inputWithExactCapturedPayloads(toolName, scopedLinkArgs));
|
|
1296
|
+
const expandedLookup = lookupLinkedPayloadValue(expanded, link.path);
|
|
1297
|
+
if (!expandedLookup.found) {
|
|
1298
|
+
throw new Error(`Repro MCP could not expand captured payload at ${link.path || 'unknown path'}`);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
const applied = replaceLinkedPayloadValue(root, link.path, expandedLookup.value);
|
|
1302
|
+
if (!applied) {
|
|
1303
|
+
throw new Error(`Repro MCP could not replace captured payload at ${link.path || 'unknown path'}`);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1229
1306
|
}
|
|
1230
|
-
return JSON.stringify(value, null, 2);
|
|
1231
1307
|
}
|
|
1232
1308
|
|
|
1233
|
-
function
|
|
1309
|
+
function collectCapturedPayloadLinks(value, out = []) {
|
|
1234
1310
|
if (!value || typeof value !== 'object') {
|
|
1235
|
-
return
|
|
1311
|
+
return out;
|
|
1236
1312
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1313
|
+
if (Array.isArray(value)) {
|
|
1314
|
+
for (const item of value) {
|
|
1315
|
+
collectCapturedPayloadLinks(item, out);
|
|
1316
|
+
}
|
|
1317
|
+
return out;
|
|
1241
1318
|
}
|
|
1242
1319
|
|
|
1243
|
-
const
|
|
1244
|
-
|
|
1320
|
+
const link = value.valueOmitted === true ? value.valueLink : value.rawValueLink;
|
|
1321
|
+
if (
|
|
1322
|
+
(value.valueOmitted === true || value.rawValueOmitted === true) &&
|
|
1323
|
+
link &&
|
|
1324
|
+
typeof link === 'object' &&
|
|
1325
|
+
link.kind === 'mcp-tool-call' &&
|
|
1326
|
+
typeof link.tool === 'string' &&
|
|
1327
|
+
typeof link.path === 'string'
|
|
1328
|
+
) {
|
|
1329
|
+
out.push({ holder: value, link });
|
|
1330
|
+
}
|
|
1245
1331
|
|
|
1246
|
-
for (const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1332
|
+
for (const nested of Object.values(value)) {
|
|
1333
|
+
collectCapturedPayloadLinks(nested, out);
|
|
1334
|
+
}
|
|
1335
|
+
return out;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function capturedPayloadLinkKey(link) {
|
|
1339
|
+
return JSON.stringify({
|
|
1340
|
+
tool: link?.tool ?? null,
|
|
1341
|
+
path: link?.path ?? null,
|
|
1342
|
+
arguments: link?.arguments ?? null,
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function lookupLinkedPayloadValue(root, pathExpression) {
|
|
1347
|
+
const segments = parseLinkedPayloadPath(pathExpression);
|
|
1348
|
+
if (!segments.length) {
|
|
1349
|
+
return { found: false };
|
|
1350
|
+
}
|
|
1351
|
+
return lookupPathCandidates(root, segments);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function replaceLinkedPayloadValue(root, pathExpression, value) {
|
|
1355
|
+
const segments = parseLinkedPayloadPath(pathExpression);
|
|
1356
|
+
if (!segments.length) {
|
|
1357
|
+
return false;
|
|
1358
|
+
}
|
|
1359
|
+
const candidates = pathCandidateSegments(segments);
|
|
1360
|
+
for (const candidate of candidates) {
|
|
1361
|
+
const parentLookup = getAtPath(root, candidate.slice(0, -1));
|
|
1362
|
+
if (!parentLookup.found || !parentLookup.value || typeof parentLookup.value !== 'object') {
|
|
1363
|
+
continue;
|
|
1259
1364
|
}
|
|
1365
|
+
const leaf = candidate[candidate.length - 1];
|
|
1366
|
+
parentLookup.value[leaf] = value;
|
|
1367
|
+
if (leaf === 'value') {
|
|
1368
|
+
delete parentLookup.value.valueOmitted;
|
|
1369
|
+
delete parentLookup.value.valuePreview;
|
|
1370
|
+
delete parentLookup.value.valueLink;
|
|
1371
|
+
} else if (leaf === 'rawValue') {
|
|
1372
|
+
delete parentLookup.value.rawValueOmitted;
|
|
1373
|
+
delete parentLookup.value.rawValuePreview;
|
|
1374
|
+
delete parentLookup.value.rawValueLink;
|
|
1375
|
+
}
|
|
1376
|
+
return true;
|
|
1260
1377
|
}
|
|
1378
|
+
return false;
|
|
1379
|
+
}
|
|
1261
1380
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
)
|
|
1381
|
+
function lookupPathCandidates(root, segments) {
|
|
1382
|
+
for (const candidate of pathCandidateSegments(segments)) {
|
|
1383
|
+
const lookup = getAtPath(root, candidate);
|
|
1384
|
+
if (lookup.found) {
|
|
1385
|
+
return lookup;
|
|
1386
|
+
}
|
|
1266
1387
|
}
|
|
1388
|
+
return { found: false };
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
function pathCandidateSegments(segments) {
|
|
1392
|
+
return [
|
|
1393
|
+
['data', ...segments],
|
|
1394
|
+
segments,
|
|
1395
|
+
];
|
|
1396
|
+
}
|
|
1267
1397
|
|
|
1268
|
-
|
|
1398
|
+
function parseLinkedPayloadPath(pathExpression) {
|
|
1399
|
+
if (typeof pathExpression !== 'string' || !pathExpression.trim()) {
|
|
1400
|
+
return [];
|
|
1401
|
+
}
|
|
1402
|
+
const segments = [];
|
|
1403
|
+
const pattern = /([A-Za-z_$][\w$-]*)|\[(\d+)\]/g;
|
|
1404
|
+
let match;
|
|
1405
|
+
while ((match = pattern.exec(pathExpression)) !== null) {
|
|
1406
|
+
if (typeof match[1] === 'string') {
|
|
1407
|
+
segments.push(match[1]);
|
|
1408
|
+
} else if (typeof match[2] === 'string') {
|
|
1409
|
+
segments.push(Number.parseInt(match[2], 10));
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
return segments;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function getAtPath(root, segments) {
|
|
1416
|
+
let current = root;
|
|
1417
|
+
for (const segment of segments) {
|
|
1418
|
+
if (current == null) {
|
|
1419
|
+
return { found: false };
|
|
1420
|
+
}
|
|
1421
|
+
if (typeof segment === 'number') {
|
|
1422
|
+
if (!Array.isArray(current) || segment < 0 || segment >= current.length) {
|
|
1423
|
+
return { found: false };
|
|
1424
|
+
}
|
|
1425
|
+
current = current[segment];
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
if (typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, segment)) {
|
|
1429
|
+
return { found: false };
|
|
1430
|
+
}
|
|
1431
|
+
current = current[segment];
|
|
1432
|
+
}
|
|
1433
|
+
return { found: true, value: current };
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function formatLocalToolContentText(name, value) {
|
|
1437
|
+
if (name === 'inspect_privacy_policy' || name === 'list_privacy_policy_blockers') {
|
|
1438
|
+
const text = renderPolicyInspectionText(value);
|
|
1439
|
+
if (text) return text;
|
|
1440
|
+
}
|
|
1441
|
+
if (name === 'check_privacy_coverage') {
|
|
1442
|
+
const text = renderPrivacyCoverageText(value);
|
|
1443
|
+
if (text) return text;
|
|
1444
|
+
}
|
|
1445
|
+
if (name === 'get_kafka_messages') {
|
|
1446
|
+
return JSON.stringify(value, null, 2);
|
|
1447
|
+
}
|
|
1448
|
+
return JSON.stringify(value, null, 2);
|
|
1269
1449
|
}
|
|
1270
1450
|
|
|
1271
1451
|
function mergeLocalTools(remoteTools) {
|