@agent-inspect/mcp-server 6.4.0 → 6.4.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.
- package/dist/index.cjs +840 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +840 -73
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -1810,6 +1810,9 @@ function toMetadataSafeStatus(status) {
|
|
|
1810
1810
|
if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
|
|
1811
1811
|
return status;
|
|
1812
1812
|
}
|
|
1813
|
+
function bundleFailsOnSafety(status, allowUnsafe) {
|
|
1814
|
+
return status === "UNSAFE" || status === "UNKNOWN";
|
|
1815
|
+
}
|
|
1813
1816
|
|
|
1814
1817
|
// packages/core/src/bundle/manifest.ts
|
|
1815
1818
|
var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
|
|
@@ -1842,6 +1845,44 @@ var STATUS_RANK = {
|
|
|
1842
1845
|
warning: 1,
|
|
1843
1846
|
pass: 2
|
|
1844
1847
|
};
|
|
1848
|
+
var DEFAULT_SENSITIVE_KEYS = [
|
|
1849
|
+
"authorization",
|
|
1850
|
+
"cookie",
|
|
1851
|
+
"token",
|
|
1852
|
+
"apikey",
|
|
1853
|
+
"api_key",
|
|
1854
|
+
"password",
|
|
1855
|
+
"secret",
|
|
1856
|
+
"email"
|
|
1857
|
+
];
|
|
1858
|
+
var DEFAULT_RAW_CONTENT_KEYS = [
|
|
1859
|
+
"body",
|
|
1860
|
+
"headers",
|
|
1861
|
+
"input",
|
|
1862
|
+
"messages",
|
|
1863
|
+
"output",
|
|
1864
|
+
"payload",
|
|
1865
|
+
"prompt",
|
|
1866
|
+
"requestbody",
|
|
1867
|
+
"request_body",
|
|
1868
|
+
"responsebody",
|
|
1869
|
+
"response_body",
|
|
1870
|
+
"rawprompt",
|
|
1871
|
+
"raw_prompt",
|
|
1872
|
+
"rawoutput",
|
|
1873
|
+
"raw_output",
|
|
1874
|
+
"toolinput",
|
|
1875
|
+
"tool_input",
|
|
1876
|
+
"tooloutput",
|
|
1877
|
+
"tool_output"
|
|
1878
|
+
];
|
|
1879
|
+
var DEFAULT_SECRET_PATTERNS = [
|
|
1880
|
+
{ id: "bearer-token", pattern: /Bearer\s+[A-Za-z0-9._~+/-]{12,}=*/ },
|
|
1881
|
+
{ id: "openai-key", pattern: /sk-[A-Za-z0-9_-]{16,}/ },
|
|
1882
|
+
{ id: "aws-access-key", pattern: /AKIA[0-9A-Z]{16}/ },
|
|
1883
|
+
{ id: "github-token", pattern: /gh[opsu]_[A-Za-z0-9_]{20,}/ },
|
|
1884
|
+
{ id: "key-value-secret", pattern: /(api[_-]?key|token|password|secret)=\S{8,}/i }
|
|
1885
|
+
];
|
|
1845
1886
|
function compareStrings(a, b) {
|
|
1846
1887
|
return (a ?? "").localeCompare(b ?? "");
|
|
1847
1888
|
}
|
|
@@ -2037,7 +2078,7 @@ function eventEvidence(event, path13) {
|
|
|
2037
2078
|
kind: event.kind,
|
|
2038
2079
|
name: event.name,
|
|
2039
2080
|
status: event.status,
|
|
2040
|
-
...{}
|
|
2081
|
+
...path13 ? { path: path13 } : {}
|
|
2041
2082
|
};
|
|
2042
2083
|
}
|
|
2043
2084
|
function runEvidence(run) {
|
|
@@ -2054,6 +2095,84 @@ function failFinding(ruleId, message, evidence, expected, actual) {
|
|
|
2054
2095
|
evidence: [...evidence]
|
|
2055
2096
|
};
|
|
2056
2097
|
}
|
|
2098
|
+
function isRecord6(value) {
|
|
2099
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2100
|
+
}
|
|
2101
|
+
function normalizedKey(value) {
|
|
2102
|
+
return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
|
|
2103
|
+
}
|
|
2104
|
+
function lastPathSegment(path13) {
|
|
2105
|
+
const parts = path13.split(".");
|
|
2106
|
+
return parts[parts.length - 1] ?? path13;
|
|
2107
|
+
}
|
|
2108
|
+
function valueType(value) {
|
|
2109
|
+
if (Array.isArray(value)) return "array";
|
|
2110
|
+
if (value === null) return "null";
|
|
2111
|
+
return typeof value;
|
|
2112
|
+
}
|
|
2113
|
+
function serializedByteLength(value) {
|
|
2114
|
+
try {
|
|
2115
|
+
return Buffer.byteLength(JSON.stringify(value), "utf-8");
|
|
2116
|
+
} catch {
|
|
2117
|
+
return void 0;
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
function pushValueEntries(entries, event, value, path13, key, depth = 0) {
|
|
2121
|
+
entries.push({ event, path: path13, key, value });
|
|
2122
|
+
if (depth >= 8) return;
|
|
2123
|
+
if (Array.isArray(value)) {
|
|
2124
|
+
for (const [index, item] of value.entries()) {
|
|
2125
|
+
pushValueEntries(entries, event, item, `${path13}.${index}`, String(index), depth + 1);
|
|
2126
|
+
}
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
if (!isRecord6(value)) return;
|
|
2130
|
+
for (const nestedKey of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
|
|
2131
|
+
pushValueEntries(
|
|
2132
|
+
entries,
|
|
2133
|
+
event,
|
|
2134
|
+
value[nestedKey],
|
|
2135
|
+
`${path13}.${nestedKey}`,
|
|
2136
|
+
nestedKey,
|
|
2137
|
+
depth + 1
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
function eventValueEntries(event, options = {}) {
|
|
2142
|
+
const entries = [];
|
|
2143
|
+
if (event.attributes !== void 0) {
|
|
2144
|
+
pushValueEntries(entries, event, event.attributes, "attributes", "attributes");
|
|
2145
|
+
}
|
|
2146
|
+
if (options.includeSummaries) {
|
|
2147
|
+
if (event.inputSummary !== void 0) {
|
|
2148
|
+
pushValueEntries(entries, event, event.inputSummary, "inputSummary", "inputSummary");
|
|
2149
|
+
}
|
|
2150
|
+
if (event.outputSummary !== void 0) {
|
|
2151
|
+
pushValueEntries(entries, event, event.outputSummary, "outputSummary", "outputSummary");
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
if (options.includeError && event.error !== void 0) {
|
|
2155
|
+
pushValueEntries(entries, event, event.error, "error", "error");
|
|
2156
|
+
}
|
|
2157
|
+
return entries;
|
|
2158
|
+
}
|
|
2159
|
+
function limitFindings(findings, maxFindings) {
|
|
2160
|
+
if (maxFindings === void 0 || findings.length <= maxFindings) return findings;
|
|
2161
|
+
return findings.slice(0, Math.max(0, maxFindings));
|
|
2162
|
+
}
|
|
2163
|
+
function hasRedactionMarker(value, markers) {
|
|
2164
|
+
return markers.some((marker) => value.includes(marker)) || /^\[HASH:[A-Za-z0-9_-]+\]$/.test(value);
|
|
2165
|
+
}
|
|
2166
|
+
function isSensitiveKey(key, sensitiveKeys) {
|
|
2167
|
+
if (!key) return false;
|
|
2168
|
+
const normalized = normalizedKey(key);
|
|
2169
|
+
return sensitiveKeys.some((sensitive) => normalized.includes(normalizedKey(sensitive)));
|
|
2170
|
+
}
|
|
2171
|
+
function isRawContentKey(key, forbiddenKeys) {
|
|
2172
|
+
if (!key) return false;
|
|
2173
|
+
const normalized = normalizedKey(key);
|
|
2174
|
+
return forbiddenKeys.some((forbidden) => normalized === normalizedKey(forbidden));
|
|
2175
|
+
}
|
|
2057
2176
|
function createRunStatusRule(options = {}) {
|
|
2058
2177
|
const expected = options.expected ?? "ok";
|
|
2059
2178
|
const allowIncomplete = options.allowIncomplete === true;
|
|
@@ -2093,6 +2212,157 @@ function createRunStatusRule(options = {}) {
|
|
|
2093
2212
|
}
|
|
2094
2213
|
};
|
|
2095
2214
|
}
|
|
2215
|
+
function createSafetyRedactionRule(options = {}) {
|
|
2216
|
+
const sensitiveKeys = options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS;
|
|
2217
|
+
const markers = options.redactedMarkers ?? ["[REDACTED]", "[REDACTED:"];
|
|
2218
|
+
return {
|
|
2219
|
+
id: "safety.redaction",
|
|
2220
|
+
category: "safety",
|
|
2221
|
+
defaultSeverity: "error",
|
|
2222
|
+
evaluate(context) {
|
|
2223
|
+
const findings = [];
|
|
2224
|
+
for (const event of context.events) {
|
|
2225
|
+
for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
|
|
2226
|
+
if (!isSensitiveKey(entry.key ?? lastPathSegment(entry.path), sensitiveKeys)) continue;
|
|
2227
|
+
if (typeof entry.value === "string" && hasRedactionMarker(entry.value, markers)) continue;
|
|
2228
|
+
findings.push(
|
|
2229
|
+
failFinding(
|
|
2230
|
+
"safety.redaction",
|
|
2231
|
+
`Sensitive-looking field at ${entry.path} is not redacted.`,
|
|
2232
|
+
[eventEvidence(event, entry.path)],
|
|
2233
|
+
"redaction marker",
|
|
2234
|
+
{ path: entry.path, valueType: valueType(entry.value) }
|
|
2235
|
+
)
|
|
2236
|
+
);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
return limitFindings(findings, options.maxFindings);
|
|
2240
|
+
}
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
2243
|
+
function createSafetyRawContentRule(options = {}) {
|
|
2244
|
+
const forbiddenKeys = options.forbiddenKeys ?? DEFAULT_RAW_CONTENT_KEYS;
|
|
2245
|
+
return {
|
|
2246
|
+
id: "safety.rawPrompt",
|
|
2247
|
+
category: "safety",
|
|
2248
|
+
defaultSeverity: "error",
|
|
2249
|
+
evaluate(context) {
|
|
2250
|
+
const findings = [];
|
|
2251
|
+
for (const event of context.events) {
|
|
2252
|
+
for (const entry of eventValueEntries(event, { includeSummaries: options.includeSummaries })) {
|
|
2253
|
+
const key = entry.key ?? lastPathSegment(entry.path);
|
|
2254
|
+
if (!isRawContentKey(key, forbiddenKeys)) continue;
|
|
2255
|
+
findings.push(
|
|
2256
|
+
failFinding(
|
|
2257
|
+
"safety.rawPrompt",
|
|
2258
|
+
`Raw content-like field ${entry.path} is present.`,
|
|
2259
|
+
[eventEvidence(event, entry.path)],
|
|
2260
|
+
"metadata-only trace fields",
|
|
2261
|
+
{ path: entry.path, valueType: valueType(entry.value) }
|
|
2262
|
+
)
|
|
2263
|
+
);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
return limitFindings(findings, options.maxFindings);
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
function createSafetySecretPatternRule(options = {}) {
|
|
2271
|
+
const patterns = options.patterns ?? DEFAULT_SECRET_PATTERNS;
|
|
2272
|
+
const maxStringLength = options.maxStringLength ?? 4096;
|
|
2273
|
+
return {
|
|
2274
|
+
id: "safety.secretPattern",
|
|
2275
|
+
category: "safety",
|
|
2276
|
+
defaultSeverity: "error",
|
|
2277
|
+
evaluate(context) {
|
|
2278
|
+
const findings = [];
|
|
2279
|
+
for (const event of context.events) {
|
|
2280
|
+
for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
|
|
2281
|
+
if (typeof entry.value !== "string") continue;
|
|
2282
|
+
const sample = entry.value.slice(0, maxStringLength);
|
|
2283
|
+
for (const pattern of patterns) {
|
|
2284
|
+
pattern.pattern.lastIndex = 0;
|
|
2285
|
+
if (!pattern.pattern.test(sample)) continue;
|
|
2286
|
+
pattern.pattern.lastIndex = 0;
|
|
2287
|
+
findings.push(
|
|
2288
|
+
failFinding(
|
|
2289
|
+
"safety.secretPattern",
|
|
2290
|
+
`Secret-like pattern ${pattern.id} matched at ${entry.path}.`,
|
|
2291
|
+
[eventEvidence(event, entry.path)],
|
|
2292
|
+
"no secret-like strings",
|
|
2293
|
+
{ pattern: pattern.id, path: entry.path }
|
|
2294
|
+
)
|
|
2295
|
+
);
|
|
2296
|
+
break;
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
return limitFindings(findings, options.maxFindings);
|
|
2301
|
+
}
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
function createSafetyOversizedAttributeRule(options) {
|
|
2305
|
+
return {
|
|
2306
|
+
id: "safety.oversizedAttribute",
|
|
2307
|
+
category: "safety",
|
|
2308
|
+
defaultSeverity: "error",
|
|
2309
|
+
evaluate(context) {
|
|
2310
|
+
const findings = [];
|
|
2311
|
+
for (const event of context.events) {
|
|
2312
|
+
for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
|
|
2313
|
+
if (typeof entry.value === "string" && options.maxStringLength !== void 0 && entry.value.length > options.maxStringLength) {
|
|
2314
|
+
findings.push(
|
|
2315
|
+
failFinding(
|
|
2316
|
+
"safety.oversizedAttribute",
|
|
2317
|
+
`String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
|
|
2318
|
+
[eventEvidence(event, entry.path)],
|
|
2319
|
+
{ maxStringLength: options.maxStringLength },
|
|
2320
|
+
{ path: entry.path, length: entry.value.length }
|
|
2321
|
+
)
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
if (Array.isArray(entry.value) && options.maxArrayLength !== void 0 && entry.value.length > options.maxArrayLength) {
|
|
2325
|
+
findings.push(
|
|
2326
|
+
failFinding(
|
|
2327
|
+
"safety.oversizedAttribute",
|
|
2328
|
+
`Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
|
|
2329
|
+
[eventEvidence(event, entry.path)],
|
|
2330
|
+
{ maxArrayLength: options.maxArrayLength },
|
|
2331
|
+
{ path: entry.path, length: entry.value.length }
|
|
2332
|
+
)
|
|
2333
|
+
);
|
|
2334
|
+
}
|
|
2335
|
+
if (isRecord6(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
|
|
2336
|
+
findings.push(
|
|
2337
|
+
failFinding(
|
|
2338
|
+
"safety.oversizedAttribute",
|
|
2339
|
+
`Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
|
|
2340
|
+
[eventEvidence(event, entry.path)],
|
|
2341
|
+
{ maxObjectKeys: options.maxObjectKeys },
|
|
2342
|
+
{ path: entry.path, keys: Object.keys(entry.value).length }
|
|
2343
|
+
)
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
if (options.maxSerializedBytes !== void 0) {
|
|
2347
|
+
const bytes = serializedByteLength(entry.value);
|
|
2348
|
+
if (bytes !== void 0 && bytes > options.maxSerializedBytes) {
|
|
2349
|
+
findings.push(
|
|
2350
|
+
failFinding(
|
|
2351
|
+
"safety.oversizedAttribute",
|
|
2352
|
+
`Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
|
|
2353
|
+
[eventEvidence(event, entry.path)],
|
|
2354
|
+
{ maxSerializedBytes: options.maxSerializedBytes },
|
|
2355
|
+
{ path: entry.path, bytes }
|
|
2356
|
+
)
|
|
2357
|
+
);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return limitFindings(findings, options.maxFindings);
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2096
2366
|
function runTraceChecks(input, options = {}) {
|
|
2097
2367
|
const selected = resolveSelectedRun(input, options.runId);
|
|
2098
2368
|
if (selected.diagnostics.length > 0) {
|
|
@@ -2139,14 +2409,14 @@ function runTraceChecks(input, options = {}) {
|
|
|
2139
2409
|
}
|
|
2140
2410
|
|
|
2141
2411
|
// packages/core/src/persisted/token-usage.ts
|
|
2142
|
-
function
|
|
2412
|
+
function isRecord7(value) {
|
|
2143
2413
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2144
2414
|
}
|
|
2145
2415
|
function nonNegativeFinite(value) {
|
|
2146
2416
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
2147
2417
|
}
|
|
2148
2418
|
function normalizeTokenUsage(value) {
|
|
2149
|
-
if (!
|
|
2419
|
+
if (!isRecord7(value)) return void 0;
|
|
2150
2420
|
const input = nonNegativeFinite(value.input);
|
|
2151
2421
|
const output = nonNegativeFinite(value.output);
|
|
2152
2422
|
const suppliedTotal = nonNegativeFinite(value.total);
|
|
@@ -2904,7 +3174,7 @@ function persistedEventsForParsedTrace(parsed) {
|
|
|
2904
3174
|
sourceName: "agent-inspect-jsonl-reader"
|
|
2905
3175
|
});
|
|
2906
3176
|
}
|
|
2907
|
-
function
|
|
3177
|
+
function isRecord8(value) {
|
|
2908
3178
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2909
3179
|
}
|
|
2910
3180
|
function isNonEmptyString3(value) {
|
|
@@ -2919,13 +3189,13 @@ function readStringField(record, keys) {
|
|
|
2919
3189
|
}
|
|
2920
3190
|
function readRecordField(record, key) {
|
|
2921
3191
|
const value = record[key];
|
|
2922
|
-
return
|
|
3192
|
+
return isRecord8(value) ? value : void 0;
|
|
2923
3193
|
}
|
|
2924
3194
|
function parseJsonDocument(content) {
|
|
2925
3195
|
return JSON.parse(content);
|
|
2926
3196
|
}
|
|
2927
3197
|
function looksLikeOpenInferenceSpan(value) {
|
|
2928
|
-
if (!
|
|
3198
|
+
if (!isRecord8(value)) return false;
|
|
2929
3199
|
const attributes = readRecordField(value, "attributes");
|
|
2930
3200
|
return readStringField(value, ["trace_id", "traceId"]) !== void 0 && readStringField(value, ["span_id", "spanId"]) !== void 0 && (readStringField(value, ["name"]) !== void 0 || attributes?.["openinference.span.kind"] !== void 0);
|
|
2931
3201
|
}
|
|
@@ -2950,7 +3220,7 @@ function extractOpenInferenceDocument(root) {
|
|
|
2950
3220
|
unsupportedFields
|
|
2951
3221
|
};
|
|
2952
3222
|
}
|
|
2953
|
-
if (!
|
|
3223
|
+
if (!isRecord8(root)) return void 0;
|
|
2954
3224
|
const rootFormat = root.format;
|
|
2955
3225
|
const rootCompatibility = root.compatibility;
|
|
2956
3226
|
const version = typeof root.version === "string" && root.version.trim() !== "" ? root.version : void 0;
|
|
@@ -3090,7 +3360,7 @@ function summarizeAttributeValue(value) {
|
|
|
3090
3360
|
if (Array.isArray(value)) {
|
|
3091
3361
|
return { type: "array", length: value.length };
|
|
3092
3362
|
}
|
|
3093
|
-
if (
|
|
3363
|
+
if (isRecord8(value)) {
|
|
3094
3364
|
return { type: "object", keyCount: Object.keys(value).length };
|
|
3095
3365
|
}
|
|
3096
3366
|
if (value === null) {
|
|
@@ -3177,7 +3447,7 @@ function mapOpenInferenceKind(span, attributes, pathPrefix) {
|
|
|
3177
3447
|
}
|
|
3178
3448
|
}
|
|
3179
3449
|
function mapOpenInferenceStatus(status) {
|
|
3180
|
-
if (!
|
|
3450
|
+
if (!isRecord8(status)) return void 0;
|
|
3181
3451
|
const rawCode = status.code;
|
|
3182
3452
|
if (typeof rawCode !== "string") return void 0;
|
|
3183
3453
|
switch (rawCode.toUpperCase()) {
|
|
@@ -3277,7 +3547,7 @@ function mapOpenInferenceSpan(span, index, version) {
|
|
|
3277
3547
|
warnings.push(...kindWarnings);
|
|
3278
3548
|
const status = mapOpenInferenceStatus(span.status);
|
|
3279
3549
|
const tokenUsage = readOpenInferenceTokenUsage(rawAttributes);
|
|
3280
|
-
const errorMessage =
|
|
3550
|
+
const errorMessage = isRecord8(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
|
|
3281
3551
|
const event = {
|
|
3282
3552
|
schemaVersion: "0.2",
|
|
3283
3553
|
eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
|
|
@@ -3423,7 +3693,7 @@ var openInferenceJsonReader = {
|
|
|
3423
3693
|
}
|
|
3424
3694
|
};
|
|
3425
3695
|
function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
|
|
3426
|
-
if (!
|
|
3696
|
+
if (!isRecord8(value)) {
|
|
3427
3697
|
unsupportedFields.push(field);
|
|
3428
3698
|
warnings.push({
|
|
3429
3699
|
code: "otlp_attribute_value_invalid",
|
|
@@ -3445,15 +3715,15 @@ function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
|
|
|
3445
3715
|
if (typeof value.doubleValue === "number" && Number.isFinite(value.doubleValue)) {
|
|
3446
3716
|
return value.doubleValue;
|
|
3447
3717
|
}
|
|
3448
|
-
if (
|
|
3718
|
+
if (isRecord8(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
|
|
3449
3719
|
return value.arrayValue.values.map(
|
|
3450
3720
|
(item, index) => parseOtlpAnyValue(item, `${field}.arrayValue.values[${index}]`, warnings, unsupportedFields)
|
|
3451
3721
|
);
|
|
3452
3722
|
}
|
|
3453
|
-
if (
|
|
3723
|
+
if (isRecord8(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
|
|
3454
3724
|
const out = {};
|
|
3455
3725
|
for (const [index, item] of value.kvlistValue.values.entries()) {
|
|
3456
|
-
if (!
|
|
3726
|
+
if (!isRecord8(item) || typeof item.key !== "string") {
|
|
3457
3727
|
unsupportedFields.push(`${field}.kvlistValue.values[${index}]`);
|
|
3458
3728
|
continue;
|
|
3459
3729
|
}
|
|
@@ -3504,7 +3774,7 @@ function parseOtlpAttributes(value, pathPrefix) {
|
|
|
3504
3774
|
}
|
|
3505
3775
|
for (const [index, item] of value.entries()) {
|
|
3506
3776
|
const field = `${pathPrefix}[${index}]`;
|
|
3507
|
-
if (!
|
|
3777
|
+
if (!isRecord8(item) || typeof item.key !== "string") {
|
|
3508
3778
|
unsupportedFields.push(field);
|
|
3509
3779
|
warnings.push({
|
|
3510
3780
|
code: "otlp_attribute_invalid",
|
|
@@ -3527,16 +3797,16 @@ function parseOtlpAttributes(value, pathPrefix) {
|
|
|
3527
3797
|
return { attributes, warnings, unsupportedFields };
|
|
3528
3798
|
}
|
|
3529
3799
|
function looksLikeOtlpSpan(value) {
|
|
3530
|
-
return
|
|
3800
|
+
return isRecord8(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
|
|
3531
3801
|
}
|
|
3532
3802
|
function extractOtlpDocument(root) {
|
|
3533
|
-
if (!
|
|
3803
|
+
if (!isRecord8(root) || !Array.isArray(root.resourceSpans)) return void 0;
|
|
3534
3804
|
const spans = [];
|
|
3535
3805
|
const warnings = [];
|
|
3536
3806
|
const unsupportedFields = [];
|
|
3537
3807
|
for (const [resourceIndex, resourceSpan] of root.resourceSpans.entries()) {
|
|
3538
3808
|
const resourcePath = `resourceSpans[${resourceIndex}]`;
|
|
3539
|
-
if (!
|
|
3809
|
+
if (!isRecord8(resourceSpan)) {
|
|
3540
3810
|
unsupportedFields.push(resourcePath);
|
|
3541
3811
|
continue;
|
|
3542
3812
|
}
|
|
@@ -3559,7 +3829,7 @@ function extractOtlpDocument(root) {
|
|
|
3559
3829
|
}
|
|
3560
3830
|
for (const [scopeIndex, scopeSpan] of resourceSpan.scopeSpans.entries()) {
|
|
3561
3831
|
const scopePath = `${resourcePath}.scopeSpans[${scopeIndex}]`;
|
|
3562
|
-
if (!
|
|
3832
|
+
if (!isRecord8(scopeSpan)) {
|
|
3563
3833
|
unsupportedFields.push(scopePath);
|
|
3564
3834
|
continue;
|
|
3565
3835
|
}
|
|
@@ -3626,7 +3896,7 @@ function extractOtlpDocument(root) {
|
|
|
3626
3896
|
};
|
|
3627
3897
|
}
|
|
3628
3898
|
function mapOtlpStatus(status) {
|
|
3629
|
-
if (!
|
|
3899
|
+
if (!isRecord8(status)) return void 0;
|
|
3630
3900
|
const rawCode = status.code;
|
|
3631
3901
|
if (typeof rawCode !== "string") return void 0;
|
|
3632
3902
|
switch (rawCode.toUpperCase()) {
|
|
@@ -3726,7 +3996,7 @@ function mapOtlpEvents(value, pathPrefix) {
|
|
|
3726
3996
|
const events = [];
|
|
3727
3997
|
for (const [index, event] of value.entries()) {
|
|
3728
3998
|
const eventPath = `${pathPrefix}[${index}]`;
|
|
3729
|
-
if (!
|
|
3999
|
+
if (!isRecord8(event)) {
|
|
3730
4000
|
unsupportedFields.push(eventPath);
|
|
3731
4001
|
continue;
|
|
3732
4002
|
}
|
|
@@ -3864,7 +4134,7 @@ function mapOtlpSpan(context) {
|
|
|
3864
4134
|
warnings.push(...kindWarnings);
|
|
3865
4135
|
const status = mapOtlpStatus(span.status);
|
|
3866
4136
|
const tokenUsage = readOtlpTokenUsage(parsedSpanAttributes.attributes);
|
|
3867
|
-
const errorMessage =
|
|
4137
|
+
const errorMessage = isRecord8(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
|
|
3868
4138
|
const event = {
|
|
3869
4139
|
schemaVersion: "0.2",
|
|
3870
4140
|
eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
|
|
@@ -4660,7 +4930,7 @@ function diffRuns(left, right, options) {
|
|
|
4660
4930
|
var EXPORT_PAYLOAD_VERSION = "0.1.2";
|
|
4661
4931
|
|
|
4662
4932
|
// packages/core/src/exporters/redact-export.ts
|
|
4663
|
-
function
|
|
4933
|
+
function isRecord9(value) {
|
|
4664
4934
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4665
4935
|
}
|
|
4666
4936
|
function deepClone(value) {
|
|
@@ -4734,7 +5004,7 @@ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPrevi
|
|
|
4734
5004
|
0
|
|
4735
5005
|
);
|
|
4736
5006
|
const err = bounded.error;
|
|
4737
|
-
if (
|
|
5007
|
+
if (isRecord9(err) && typeof err.message === "string") {
|
|
4738
5008
|
bounded.error = {
|
|
4739
5009
|
...err,
|
|
4740
5010
|
message: truncateStringForProfile(
|
|
@@ -5337,6 +5607,453 @@ function exportRunTree(tree, options) {
|
|
|
5337
5607
|
}
|
|
5338
5608
|
}
|
|
5339
5609
|
|
|
5610
|
+
// packages/mcp-server/src/assess-trace.ts
|
|
5611
|
+
var DEFAULT_MAX_STRING_LENGTH = 16384;
|
|
5612
|
+
var DEFAULT_MAX_ARRAY_LENGTH = 1e3;
|
|
5613
|
+
var DEFAULT_MAX_OBJECT_KEYS = 200;
|
|
5614
|
+
var DEFAULT_MAX_SERIALIZED_BYTES = 128 * 1024;
|
|
5615
|
+
function buildMcpSafetyRules() {
|
|
5616
|
+
return [
|
|
5617
|
+
createSafetyRawContentRule(),
|
|
5618
|
+
createSafetyRedactionRule(),
|
|
5619
|
+
createSafetySecretPatternRule(),
|
|
5620
|
+
createSafetyOversizedAttributeRule({
|
|
5621
|
+
maxStringLength: DEFAULT_MAX_STRING_LENGTH,
|
|
5622
|
+
maxArrayLength: DEFAULT_MAX_ARRAY_LENGTH,
|
|
5623
|
+
maxObjectKeys: DEFAULT_MAX_OBJECT_KEYS,
|
|
5624
|
+
maxSerializedBytes: DEFAULT_MAX_SERIALIZED_BYTES
|
|
5625
|
+
})
|
|
5626
|
+
];
|
|
5627
|
+
}
|
|
5628
|
+
function statusFrom(findings, hasErrors) {
|
|
5629
|
+
if (hasErrors) return "UNKNOWN";
|
|
5630
|
+
if (findings.some((item) => item.severity === "error")) return "UNSAFE";
|
|
5631
|
+
if (findings.some((item) => item.severity === "warning")) return "SAFE WITH WARNINGS";
|
|
5632
|
+
return "SAFE";
|
|
5633
|
+
}
|
|
5634
|
+
function assessTraceForMcp(read, runId) {
|
|
5635
|
+
const rules = buildMcpSafetyRules();
|
|
5636
|
+
const checkResult = runTraceChecks({ read }, { rules, runId });
|
|
5637
|
+
const hasErrors = checkResult.diagnostics.some((item) => item.severity === "error");
|
|
5638
|
+
const status = statusFrom(checkResult.findings, hasErrors);
|
|
5639
|
+
return {
|
|
5640
|
+
status,
|
|
5641
|
+
errors: checkResult.diagnostics.filter((item) => item.severity === "error").length + checkResult.findings.filter((item) => item.severity === "error").length,
|
|
5642
|
+
warnings: checkResult.diagnostics.filter((item) => item.severity === "warning").length + checkResult.findings.filter((item) => item.severity === "warning").length,
|
|
5643
|
+
findings: checkResult.findings.length
|
|
5644
|
+
};
|
|
5645
|
+
}
|
|
5646
|
+
var DEFAULT_REDACT_KEYS2 = [
|
|
5647
|
+
"authorization",
|
|
5648
|
+
"cookie",
|
|
5649
|
+
"token",
|
|
5650
|
+
"apiKey",
|
|
5651
|
+
"password",
|
|
5652
|
+
"secret",
|
|
5653
|
+
"email"
|
|
5654
|
+
];
|
|
5655
|
+
var SHARE_PROFILE_EXTRA_KEYS2 = [
|
|
5656
|
+
"userEmail",
|
|
5657
|
+
"customerEmail",
|
|
5658
|
+
"phone",
|
|
5659
|
+
"phoneNumber",
|
|
5660
|
+
"address",
|
|
5661
|
+
"ip",
|
|
5662
|
+
"ipAddress",
|
|
5663
|
+
"sessionId",
|
|
5664
|
+
"requestId",
|
|
5665
|
+
"correlationId",
|
|
5666
|
+
"decisionId",
|
|
5667
|
+
"groupId",
|
|
5668
|
+
"customerId",
|
|
5669
|
+
"userId",
|
|
5670
|
+
"accountId",
|
|
5671
|
+
"tenantId",
|
|
5672
|
+
"orgId",
|
|
5673
|
+
"organizationId",
|
|
5674
|
+
"traceId",
|
|
5675
|
+
"spanId",
|
|
5676
|
+
"parentSpanId"
|
|
5677
|
+
];
|
|
5678
|
+
var STRICT_PROFILE_EXTRA_KEYS2 = [
|
|
5679
|
+
"prompt",
|
|
5680
|
+
"completion",
|
|
5681
|
+
"input",
|
|
5682
|
+
"output",
|
|
5683
|
+
"inputPreview",
|
|
5684
|
+
"outputPreview",
|
|
5685
|
+
"message",
|
|
5686
|
+
"messages",
|
|
5687
|
+
"transcript",
|
|
5688
|
+
"context",
|
|
5689
|
+
"document",
|
|
5690
|
+
"documents",
|
|
5691
|
+
"chunk",
|
|
5692
|
+
"chunks",
|
|
5693
|
+
"retrieval",
|
|
5694
|
+
"query"
|
|
5695
|
+
];
|
|
5696
|
+
function isRecord10(value) {
|
|
5697
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5698
|
+
}
|
|
5699
|
+
function toKey2(key) {
|
|
5700
|
+
return key.toLowerCase();
|
|
5701
|
+
}
|
|
5702
|
+
function stableHash2(value) {
|
|
5703
|
+
const hash = crypto__default.default.createHash("sha256").update(value, "utf8").digest("hex");
|
|
5704
|
+
return hash.slice(0, 8);
|
|
5705
|
+
}
|
|
5706
|
+
function stringifyScalar(value) {
|
|
5707
|
+
if (typeof value === "string") return value;
|
|
5708
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
5709
|
+
return String(value);
|
|
5710
|
+
}
|
|
5711
|
+
return void 0;
|
|
5712
|
+
}
|
|
5713
|
+
function patternDetector(options) {
|
|
5714
|
+
return {
|
|
5715
|
+
id: options.id,
|
|
5716
|
+
severity: options.severity ?? "warning",
|
|
5717
|
+
matchKind: "value",
|
|
5718
|
+
detect(input) {
|
|
5719
|
+
if (typeof input.value !== "string") return [];
|
|
5720
|
+
options.pattern.lastIndex = 0;
|
|
5721
|
+
return options.pattern.test(input.value) ? [{ action: "replace", severity: options.severity ?? "warning", matchKind: "value" }] : [];
|
|
5722
|
+
}
|
|
5723
|
+
};
|
|
5724
|
+
}
|
|
5725
|
+
function digitsOnly(value) {
|
|
5726
|
+
return value.replace(/\D/g, "");
|
|
5727
|
+
}
|
|
5728
|
+
function passesLuhn(value) {
|
|
5729
|
+
const digits = digitsOnly(value);
|
|
5730
|
+
if (digits.length < 13 || digits.length > 19) return false;
|
|
5731
|
+
let sum = 0;
|
|
5732
|
+
let double = false;
|
|
5733
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
5734
|
+
let digit = Number(digits[i]);
|
|
5735
|
+
if (double) {
|
|
5736
|
+
digit *= 2;
|
|
5737
|
+
if (digit > 9) digit -= 9;
|
|
5738
|
+
}
|
|
5739
|
+
sum += digit;
|
|
5740
|
+
double = !double;
|
|
5741
|
+
}
|
|
5742
|
+
return sum % 10 === 0;
|
|
5743
|
+
}
|
|
5744
|
+
var credentialDetectors = [
|
|
5745
|
+
patternDetector({
|
|
5746
|
+
id: "value.authorizationHeader",
|
|
5747
|
+
pattern: /^(?:basic|bearer|digest|apikey)\s+[a-z0-9._~+/=-]+$/i,
|
|
5748
|
+
severity: "error"
|
|
5749
|
+
}),
|
|
5750
|
+
patternDetector({
|
|
5751
|
+
id: "value.bearerToken",
|
|
5752
|
+
pattern: /\bbearer\s+[a-z0-9._~+/=-]{12,}\b/i,
|
|
5753
|
+
severity: "error"
|
|
5754
|
+
}),
|
|
5755
|
+
patternDetector({
|
|
5756
|
+
id: "value.cookie",
|
|
5757
|
+
pattern: /\b[a-z0-9_.-]+=[^;\s]+(?:;\s*[a-z0-9_.-]+=[^;\s]+)+/i,
|
|
5758
|
+
severity: "error"
|
|
5759
|
+
}),
|
|
5760
|
+
patternDetector({
|
|
5761
|
+
id: "value.jwt",
|
|
5762
|
+
pattern: /\beyJ[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\b/,
|
|
5763
|
+
severity: "error"
|
|
5764
|
+
}),
|
|
5765
|
+
patternDetector({
|
|
5766
|
+
id: "value.providerApiKey",
|
|
5767
|
+
pattern: /\b(?:sk-(?:proj-)?[a-zA-Z0-9_-]{16,}|sk-ant-[a-zA-Z0-9_-]{16,}|AIza[0-9A-Za-z_-]{20,})\b/,
|
|
5768
|
+
severity: "error"
|
|
5769
|
+
}),
|
|
5770
|
+
patternDetector({
|
|
5771
|
+
id: "value.githubToken",
|
|
5772
|
+
pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/,
|
|
5773
|
+
severity: "error"
|
|
5774
|
+
}),
|
|
5775
|
+
patternDetector({
|
|
5776
|
+
id: "value.awsAccessKey",
|
|
5777
|
+
pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
|
|
5778
|
+
severity: "error"
|
|
5779
|
+
}),
|
|
5780
|
+
patternDetector({
|
|
5781
|
+
id: "value.privateKey",
|
|
5782
|
+
pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*-----END [A-Z ]*PRIVATE KEY-----/,
|
|
5783
|
+
severity: "error"
|
|
5784
|
+
}),
|
|
5785
|
+
{
|
|
5786
|
+
id: "value.creditCard",
|
|
5787
|
+
severity: "error",
|
|
5788
|
+
matchKind: "value",
|
|
5789
|
+
detect(input) {
|
|
5790
|
+
if (typeof input.value !== "string") return [];
|
|
5791
|
+
const candidatePattern = /(?:\d[ -]?){13,19}/g;
|
|
5792
|
+
for (const match of input.value.matchAll(candidatePattern)) {
|
|
5793
|
+
const candidate = match[0] ?? "";
|
|
5794
|
+
if (passesLuhn(candidate)) {
|
|
5795
|
+
return [{ action: "replace", severity: "error", matchKind: "value" }];
|
|
5796
|
+
}
|
|
5797
|
+
}
|
|
5798
|
+
return [];
|
|
5799
|
+
}
|
|
5800
|
+
}
|
|
5801
|
+
];
|
|
5802
|
+
var identifierDetectors = [
|
|
5803
|
+
patternDetector({
|
|
5804
|
+
id: "value.email",
|
|
5805
|
+
pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i
|
|
5806
|
+
}),
|
|
5807
|
+
patternDetector({
|
|
5808
|
+
id: "value.phone",
|
|
5809
|
+
pattern: /\b(?:\+\d{1,3}[\s.-]?)?(?:\(?\d{3}\)?[\s.-])\d{3}[\s.-]\d{4}\b/
|
|
5810
|
+
}),
|
|
5811
|
+
patternDetector({
|
|
5812
|
+
id: "value.ipv4",
|
|
5813
|
+
pattern: /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/
|
|
5814
|
+
}),
|
|
5815
|
+
patternDetector({
|
|
5816
|
+
id: "value.ipv6",
|
|
5817
|
+
pattern: /\b(?:[0-9a-f]{1,4}:){2,7}[0-9a-f]{1,4}\b/i
|
|
5818
|
+
})
|
|
5819
|
+
];
|
|
5820
|
+
function builtInDetectorsForProfile(profile) {
|
|
5821
|
+
if (profile === "local") return credentialDetectors;
|
|
5822
|
+
return [...credentialDetectors, ...identifierDetectors];
|
|
5823
|
+
}
|
|
5824
|
+
function compileRules2(rules, extraKeys) {
|
|
5825
|
+
const out = /* @__PURE__ */ new Map();
|
|
5826
|
+
const set = (rule) => {
|
|
5827
|
+
const key = toKey2(rule.key);
|
|
5828
|
+
out.set(key, { ...rule, key });
|
|
5829
|
+
};
|
|
5830
|
+
for (const key of DEFAULT_REDACT_KEYS2) {
|
|
5831
|
+
set({ key, strategy: "full" });
|
|
5832
|
+
}
|
|
5833
|
+
for (const key of extraKeys ?? []) {
|
|
5834
|
+
if (typeof key === "string" && key.length > 0) {
|
|
5835
|
+
set({ key, strategy: "full" });
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5838
|
+
for (const rule of rules ?? []) {
|
|
5839
|
+
if (typeof rule === "string") {
|
|
5840
|
+
set({ key: rule, strategy: "full" });
|
|
5841
|
+
continue;
|
|
5842
|
+
}
|
|
5843
|
+
if (rule.strategy === "full") set({ key: rule.key, strategy: "full" });
|
|
5844
|
+
if (rule.strategy === "hash") set({ key: rule.key, strategy: "hash" });
|
|
5845
|
+
if (rule.strategy === "prefix") {
|
|
5846
|
+
set({
|
|
5847
|
+
key: rule.key,
|
|
5848
|
+
strategy: "prefix",
|
|
5849
|
+
keep: typeof rule.keep === "number" ? rule.keep : 8
|
|
5850
|
+
});
|
|
5851
|
+
}
|
|
5852
|
+
}
|
|
5853
|
+
return [...out.values()];
|
|
5854
|
+
}
|
|
5855
|
+
function actionForRule(rule) {
|
|
5856
|
+
if (rule.strategy === "full") return "replace";
|
|
5857
|
+
return rule.strategy;
|
|
5858
|
+
}
|
|
5859
|
+
function applyRule(rule, value, replacement) {
|
|
5860
|
+
if (rule.strategy === "full") return replacement;
|
|
5861
|
+
const asString = stringifyScalar(value);
|
|
5862
|
+
if (rule.strategy === "prefix") {
|
|
5863
|
+
if (asString === void 0) return replacement;
|
|
5864
|
+
const keep = Math.max(0, Math.floor(rule.keep));
|
|
5865
|
+
return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
|
|
5866
|
+
}
|
|
5867
|
+
if (rule.strategy === "hash") {
|
|
5868
|
+
if (asString === void 0) return "[HASH:unknown]";
|
|
5869
|
+
return `[HASH:${stableHash2(asString)}]`;
|
|
5870
|
+
}
|
|
5871
|
+
return value;
|
|
5872
|
+
}
|
|
5873
|
+
function childPath(path13, key) {
|
|
5874
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
|
|
5875
|
+
return path13 ? `${path13}.${key}` : key;
|
|
5876
|
+
}
|
|
5877
|
+
return `${path13 || "$"}[${JSON.stringify(key)}]`;
|
|
5878
|
+
}
|
|
5879
|
+
function indexPath(path13, index) {
|
|
5880
|
+
return `${path13 || "$"}[${index}]`;
|
|
5881
|
+
}
|
|
5882
|
+
function makeFinding(path13, detector, action, matchKind, severity = "warning", preview) {
|
|
5883
|
+
return preview === void 0 ? { path: path13, detector, action, severity, matchKind } : { path: path13, detector, action, severity, matchKind, preview };
|
|
5884
|
+
}
|
|
5885
|
+
function createRedactionProfile(profile = "local") {
|
|
5886
|
+
switch (profile) {
|
|
5887
|
+
case "local":
|
|
5888
|
+
return { profile: "local", extraKeys: [] };
|
|
5889
|
+
case "share":
|
|
5890
|
+
return {
|
|
5891
|
+
profile: "share",
|
|
5892
|
+
extraKeys: SHARE_PROFILE_EXTRA_KEYS2,
|
|
5893
|
+
maxMetadataValueLengthCap: 500,
|
|
5894
|
+
maxPreviewLengthCap: 200
|
|
5895
|
+
};
|
|
5896
|
+
case "strict":
|
|
5897
|
+
return {
|
|
5898
|
+
profile: "strict",
|
|
5899
|
+
extraKeys: [...SHARE_PROFILE_EXTRA_KEYS2, ...STRICT_PROFILE_EXTRA_KEYS2],
|
|
5900
|
+
maxMetadataValueLengthCap: 200,
|
|
5901
|
+
maxPreviewLengthCap: 80
|
|
5902
|
+
};
|
|
5903
|
+
}
|
|
5904
|
+
}
|
|
5905
|
+
var Redactor2 = class {
|
|
5906
|
+
#rules;
|
|
5907
|
+
#detectors;
|
|
5908
|
+
#profile;
|
|
5909
|
+
#replacement;
|
|
5910
|
+
#maxDepth;
|
|
5911
|
+
#collectFindings;
|
|
5912
|
+
constructor(options) {
|
|
5913
|
+
const resolved = createRedactionProfile(options?.profile ?? "local");
|
|
5914
|
+
this.#profile = resolved.profile;
|
|
5915
|
+
this.#rules = compileRules2(options?.rules, [
|
|
5916
|
+
...resolved.extraKeys,
|
|
5917
|
+
...options?.extraKeys ?? []
|
|
5918
|
+
]);
|
|
5919
|
+
this.#detectors = [
|
|
5920
|
+
...builtInDetectorsForProfile(this.#profile),
|
|
5921
|
+
...options?.detectors ?? []
|
|
5922
|
+
];
|
|
5923
|
+
this.#replacement = options?.replacement ?? "[REDACTED]";
|
|
5924
|
+
this.#maxDepth = options?.maxDepth ?? 32;
|
|
5925
|
+
this.#collectFindings = options?.collectFindings ?? true;
|
|
5926
|
+
}
|
|
5927
|
+
redactValue(key, value) {
|
|
5928
|
+
return this.#redactValue(value, key, key, 0, {
|
|
5929
|
+
findings: [],
|
|
5930
|
+
seen: /* @__PURE__ */ new WeakMap()
|
|
5931
|
+
});
|
|
5932
|
+
}
|
|
5933
|
+
redactRecord(record) {
|
|
5934
|
+
return this.redact(record).value;
|
|
5935
|
+
}
|
|
5936
|
+
redact(value) {
|
|
5937
|
+
const state = {
|
|
5938
|
+
findings: [],
|
|
5939
|
+
seen: /* @__PURE__ */ new WeakMap()
|
|
5940
|
+
};
|
|
5941
|
+
const redacted = this.#redactValue(value, void 0, "$", 0, state);
|
|
5942
|
+
return {
|
|
5943
|
+
value: redacted,
|
|
5944
|
+
findings: state.findings,
|
|
5945
|
+
redacted: state.findings.some((finding) => finding.action !== "keep"),
|
|
5946
|
+
profile: this.#profile
|
|
5947
|
+
};
|
|
5948
|
+
}
|
|
5949
|
+
#recordFinding(state, finding) {
|
|
5950
|
+
if (this.#collectFindings) state.findings.push(finding);
|
|
5951
|
+
}
|
|
5952
|
+
#redactValue(value, key, path13, depth, state) {
|
|
5953
|
+
if (depth > this.#maxDepth) {
|
|
5954
|
+
this.#recordFinding(
|
|
5955
|
+
state,
|
|
5956
|
+
makeFinding(path13, "structure.maxDepth", "truncate", "value", "warning")
|
|
5957
|
+
);
|
|
5958
|
+
return "[Truncated]";
|
|
5959
|
+
}
|
|
5960
|
+
if (key !== void 0) {
|
|
5961
|
+
const rule = this.#rules.find((candidate) => candidate.key === toKey2(key));
|
|
5962
|
+
if (rule) {
|
|
5963
|
+
this.#recordFinding(
|
|
5964
|
+
state,
|
|
5965
|
+
makeFinding(path13, `key.${rule.key}`, actionForRule(rule), "key", "warning")
|
|
5966
|
+
);
|
|
5967
|
+
return applyRule(rule, value, this.#replacement);
|
|
5968
|
+
}
|
|
5969
|
+
}
|
|
5970
|
+
for (const detector of this.#detectors) {
|
|
5971
|
+
const detections = detector.detect({ path: path13, key, value });
|
|
5972
|
+
for (const detection of detections) {
|
|
5973
|
+
const action = detection.action ?? "replace";
|
|
5974
|
+
this.#recordFinding(
|
|
5975
|
+
state,
|
|
5976
|
+
makeFinding(
|
|
5977
|
+
path13,
|
|
5978
|
+
detector.id,
|
|
5979
|
+
action,
|
|
5980
|
+
detection.matchKind ?? detector.matchKind ?? "custom",
|
|
5981
|
+
detection.severity ?? detector.severity ?? "warning",
|
|
5982
|
+
detection.preview
|
|
5983
|
+
)
|
|
5984
|
+
);
|
|
5985
|
+
if (action !== "keep") {
|
|
5986
|
+
return detection.replacement ?? this.#replacement;
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5989
|
+
}
|
|
5990
|
+
if (Array.isArray(value)) {
|
|
5991
|
+
if (state.seen.has(value)) return state.seen.get(value);
|
|
5992
|
+
const out = [];
|
|
5993
|
+
state.seen.set(value, out);
|
|
5994
|
+
value.forEach((item, index) => {
|
|
5995
|
+
out[index] = this.#redactValue(item, void 0, indexPath(path13, index), depth + 1, state);
|
|
5996
|
+
});
|
|
5997
|
+
return out;
|
|
5998
|
+
}
|
|
5999
|
+
if (isRecord10(value)) {
|
|
6000
|
+
if (state.seen.has(value)) return state.seen.get(value);
|
|
6001
|
+
const out = {};
|
|
6002
|
+
state.seen.set(value, out);
|
|
6003
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
6004
|
+
out[entryKey] = this.#redactValue(
|
|
6005
|
+
entryValue,
|
|
6006
|
+
entryKey,
|
|
6007
|
+
childPath(path13 === "$" ? "" : path13, entryKey),
|
|
6008
|
+
depth + 1,
|
|
6009
|
+
state
|
|
6010
|
+
);
|
|
6011
|
+
}
|
|
6012
|
+
return out;
|
|
6013
|
+
}
|
|
6014
|
+
return value;
|
|
6015
|
+
}
|
|
6016
|
+
};
|
|
6017
|
+
function createRedactor(options) {
|
|
6018
|
+
return new Redactor2(options);
|
|
6019
|
+
}
|
|
6020
|
+
function redact(value, options) {
|
|
6021
|
+
return createRedactor(options).redact(value);
|
|
6022
|
+
}
|
|
6023
|
+
|
|
6024
|
+
// packages/mcp-server/src/prepare-result.ts
|
|
6025
|
+
var DEFAULT_MCP_RESULT_MAX_BYTES = 512 * 1024;
|
|
6026
|
+
function stableStringify(value) {
|
|
6027
|
+
return JSON.stringify(value);
|
|
6028
|
+
}
|
|
6029
|
+
function prepareMcpToolResult(payload, options = {}) {
|
|
6030
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MCP_RESULT_MAX_BYTES;
|
|
6031
|
+
const profile = options.redactionProfile ?? "share";
|
|
6032
|
+
const diagnostics = [];
|
|
6033
|
+
const redacted = redact(payload, { profile });
|
|
6034
|
+
let result = redacted.value;
|
|
6035
|
+
const redactionFindings = redacted.findings.length;
|
|
6036
|
+
if (redactionFindings > 0) {
|
|
6037
|
+
diagnostics.push(`Redacted ${redactionFindings} sensitive value(s) from MCP tool result.`);
|
|
6038
|
+
}
|
|
6039
|
+
let text = stableStringify(result);
|
|
6040
|
+
let truncated = false;
|
|
6041
|
+
if (text.length > maxBytes) {
|
|
6042
|
+
truncated = true;
|
|
6043
|
+
diagnostics.push(
|
|
6044
|
+
`MCP tool result truncated from ${text.length} to ${maxBytes} bytes.`
|
|
6045
|
+
);
|
|
6046
|
+
text = `${text.slice(0, maxBytes)}
|
|
6047
|
+
\u2026[truncated]`;
|
|
6048
|
+
try {
|
|
6049
|
+
result = JSON.parse(text.replace(/\n…\[truncated\]$/, ""));
|
|
6050
|
+
} catch {
|
|
6051
|
+
result = { truncated: true, preview: text.slice(0, maxBytes) };
|
|
6052
|
+
}
|
|
6053
|
+
}
|
|
6054
|
+
return { payload: result, diagnostics, truncated, redactionFindings };
|
|
6055
|
+
}
|
|
6056
|
+
|
|
5340
6057
|
// packages/mcp-server/src/tools.ts
|
|
5341
6058
|
var READ_ONLY_TOOLS = [
|
|
5342
6059
|
{
|
|
@@ -5453,6 +6170,20 @@ function textResult(payload) {
|
|
|
5453
6170
|
isError: false
|
|
5454
6171
|
};
|
|
5455
6172
|
}
|
|
6173
|
+
function deliverMcpPayload(payload, context) {
|
|
6174
|
+
const prepared = prepareMcpToolResult(payload, {
|
|
6175
|
+
redactionProfile: redactionProfileForExport(context)
|
|
6176
|
+
});
|
|
6177
|
+
const body = prepared.diagnostics.length > 0 || prepared.truncated ? {
|
|
6178
|
+
...typeof prepared.payload === "object" && prepared.payload !== null && !Array.isArray(prepared.payload) ? prepared.payload : { value: prepared.payload },
|
|
6179
|
+
_mcp: {
|
|
6180
|
+
diagnostics: prepared.diagnostics,
|
|
6181
|
+
truncated: prepared.truncated,
|
|
6182
|
+
redactionFindings: prepared.redactionFindings
|
|
6183
|
+
}
|
|
6184
|
+
} : prepared.payload;
|
|
6185
|
+
return textResult(body);
|
|
6186
|
+
}
|
|
5456
6187
|
function errorResult2(message) {
|
|
5457
6188
|
return {
|
|
5458
6189
|
content: [{ type: "text", text: message }],
|
|
@@ -5502,25 +6233,29 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5502
6233
|
files,
|
|
5503
6234
|
(fileName) => td.getPath(fileName)
|
|
5504
6235
|
);
|
|
5505
|
-
return
|
|
6236
|
+
return deliverMcpPayload(
|
|
5506
6237
|
metas.map((meta) => ({
|
|
5507
6238
|
runId: meta.runId,
|
|
5508
6239
|
name: meta.name,
|
|
5509
6240
|
status: meta.status,
|
|
5510
6241
|
file: path__default.default.basename(meta.filePath)
|
|
5511
|
-
}))
|
|
6242
|
+
})),
|
|
6243
|
+
context
|
|
5512
6244
|
);
|
|
5513
6245
|
}
|
|
5514
6246
|
case "read_trace": {
|
|
5515
6247
|
const runId = String(args.runId ?? "");
|
|
5516
6248
|
const { read } = await openRunTrace(context, runId);
|
|
5517
6249
|
const events = read.events.length > context.maxEvents ? read.events.slice(0, context.maxEvents) : read.events;
|
|
5518
|
-
return
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
6250
|
+
return deliverMcpPayload(
|
|
6251
|
+
{
|
|
6252
|
+
runId,
|
|
6253
|
+
format: read.format,
|
|
6254
|
+
truncated: read.events.length > events.length,
|
|
6255
|
+
events
|
|
6256
|
+
},
|
|
6257
|
+
context
|
|
6258
|
+
);
|
|
5524
6259
|
}
|
|
5525
6260
|
case "search_traces": {
|
|
5526
6261
|
const query = String(args.query ?? "").trim();
|
|
@@ -5537,17 +6272,20 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5537
6272
|
name: query,
|
|
5538
6273
|
limit: 25
|
|
5539
6274
|
});
|
|
5540
|
-
return
|
|
6275
|
+
return deliverMcpPayload(results, context);
|
|
5541
6276
|
}
|
|
5542
6277
|
case "find_first_error": {
|
|
5543
6278
|
const runId = String(args.runId ?? "");
|
|
5544
6279
|
const { read } = await openRunTrace(context, runId);
|
|
5545
6280
|
const timeline = buildRunTimeline(legacyTraceEvents(read.events));
|
|
5546
6281
|
const firstError = timeline.entries.find((entry) => entry.isError);
|
|
5547
|
-
return
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
6282
|
+
return deliverMcpPayload(
|
|
6283
|
+
{
|
|
6284
|
+
runId,
|
|
6285
|
+
firstError: firstError ?? null
|
|
6286
|
+
},
|
|
6287
|
+
context
|
|
6288
|
+
);
|
|
5551
6289
|
}
|
|
5552
6290
|
case "find_slowest_path": {
|
|
5553
6291
|
const runId = String(args.runId ?? "");
|
|
@@ -5557,11 +6295,14 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5557
6295
|
slowTopN: 5
|
|
5558
6296
|
});
|
|
5559
6297
|
const ranked = [...timeline.entries].filter((entry) => entry.durationMs !== void 0 && Number.isFinite(entry.durationMs)).sort((a, b) => (b.durationMs ?? 0) - (a.durationMs ?? 0)).slice(0, 5);
|
|
5560
|
-
return
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
6298
|
+
return deliverMcpPayload(
|
|
6299
|
+
{
|
|
6300
|
+
runId,
|
|
6301
|
+
slowest: ranked[0] ?? null,
|
|
6302
|
+
top: ranked
|
|
6303
|
+
},
|
|
6304
|
+
context
|
|
6305
|
+
);
|
|
5565
6306
|
}
|
|
5566
6307
|
case "compare_runs": {
|
|
5567
6308
|
const leftRunId = String(args.leftRunId ?? "");
|
|
@@ -5572,11 +6313,14 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5572
6313
|
manualTraceEventsToComparableRun(legacyTraceEvents(left.read.events)),
|
|
5573
6314
|
manualTraceEventsToComparableRun(legacyTraceEvents(right.read.events))
|
|
5574
6315
|
);
|
|
5575
|
-
return
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
6316
|
+
return deliverMcpPayload(
|
|
6317
|
+
{
|
|
6318
|
+
summary: diff.summary,
|
|
6319
|
+
differences: diff.differences.slice(0, 50),
|
|
6320
|
+
truncated: diff.differences.length > 50
|
|
6321
|
+
},
|
|
6322
|
+
context
|
|
6323
|
+
);
|
|
5580
6324
|
}
|
|
5581
6325
|
case "run_checks": {
|
|
5582
6326
|
const runId = String(args.runId ?? "");
|
|
@@ -5585,7 +6329,7 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5585
6329
|
{ read },
|
|
5586
6330
|
{ rules: [createRunStatusRule()], select: ["run.status"], runId }
|
|
5587
6331
|
);
|
|
5588
|
-
return
|
|
6332
|
+
return deliverMcpPayload(result, context);
|
|
5589
6333
|
}
|
|
5590
6334
|
case "create_share_safe_report": {
|
|
5591
6335
|
const runId = String(args.runId ?? "");
|
|
@@ -5595,43 +6339,55 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5595
6339
|
const profile = redactionProfileForExport(context);
|
|
5596
6340
|
const markdown = exportMarkdown(run, {
|
|
5597
6341
|
redacted: true});
|
|
5598
|
-
return
|
|
6342
|
+
return deliverMcpPayload({ runId, profile, markdown: markdown.content }, context);
|
|
5599
6343
|
}
|
|
5600
6344
|
case "summarize_failed_run": {
|
|
5601
6345
|
const runId = String(args.runId ?? "");
|
|
5602
6346
|
const { read } = await openRunTrace(context, runId);
|
|
5603
6347
|
const traceEvents = legacyTraceEvents(read.events);
|
|
5604
6348
|
const summary = buildRunWhatSummary(traceEvents);
|
|
5605
|
-
return
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
6349
|
+
return deliverMcpPayload(
|
|
6350
|
+
{
|
|
6351
|
+
runId,
|
|
6352
|
+
status: summary.status,
|
|
6353
|
+
summary: renderRunWhat(summary),
|
|
6354
|
+
failedStepNames: summary.failedStepNames,
|
|
6355
|
+
correlation: summary.correlation ?? null
|
|
6356
|
+
},
|
|
6357
|
+
context
|
|
6358
|
+
);
|
|
5612
6359
|
}
|
|
5613
6360
|
case "retrieve_decision_notes": {
|
|
5614
6361
|
const runId = String(args.runId ?? "");
|
|
5615
6362
|
const { read } = await openRunTrace(context, runId);
|
|
5616
6363
|
const notes = decisionNotes(read.events);
|
|
5617
|
-
return
|
|
6364
|
+
return deliverMcpPayload({ runId, decisions: notes, count: notes.length }, context);
|
|
5618
6365
|
}
|
|
5619
6366
|
case "find_failed_observation": {
|
|
5620
6367
|
const runId = String(args.runId ?? "");
|
|
5621
6368
|
const { read } = await openRunTrace(context, runId);
|
|
5622
6369
|
const outcomes = extractOutcomesFromTraceEvents(legacyTraceEvents(read.events));
|
|
5623
6370
|
const failed = outcomes.filter((outcome) => outcome.status === "failed");
|
|
5624
|
-
return
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
6371
|
+
return deliverMcpPayload(
|
|
6372
|
+
{
|
|
6373
|
+
runId,
|
|
6374
|
+
failed,
|
|
6375
|
+
count: failed.length
|
|
6376
|
+
},
|
|
6377
|
+
context
|
|
6378
|
+
);
|
|
5629
6379
|
}
|
|
5630
6380
|
case "create_share_safe_bundle": {
|
|
5631
6381
|
const runId = String(args.runId ?? "");
|
|
5632
6382
|
const { read } = await openRunTrace(context, runId);
|
|
5633
6383
|
const run = read.runs.find((item) => item.runId === runId) ?? read.runs[0];
|
|
5634
6384
|
if (!run) return errorResult2(`Run tree not found: ${runId}`);
|
|
6385
|
+
const safety = assessTraceForMcp(read, runId);
|
|
6386
|
+
if (bundleFailsOnSafety(safety.status)) {
|
|
6387
|
+
return errorResult2(
|
|
6388
|
+
`Share-safe bundle refused: safety status is ${safety.status}. Resolve findings before export.`
|
|
6389
|
+
);
|
|
6390
|
+
}
|
|
5635
6391
|
const profile = redactionProfileForExport(context);
|
|
5636
6392
|
const markdown = exportMarkdown(run, {
|
|
5637
6393
|
redacted: true});
|
|
@@ -5645,20 +6401,31 @@ async function callReadOnlyTool(context, name, args = {}) {
|
|
|
5645
6401
|
profile,
|
|
5646
6402
|
resolve: { runIds: [runId] },
|
|
5647
6403
|
checks: {
|
|
5648
|
-
aggregateStatus:
|
|
5649
|
-
runs: [
|
|
6404
|
+
aggregateStatus: safety.status,
|
|
6405
|
+
runs: [
|
|
6406
|
+
{
|
|
6407
|
+
runId,
|
|
6408
|
+
status: safety.status,
|
|
6409
|
+
errors: safety.errors,
|
|
6410
|
+
warnings: safety.warnings,
|
|
6411
|
+
findings: safety.findings
|
|
6412
|
+
}
|
|
6413
|
+
]
|
|
5650
6414
|
},
|
|
5651
6415
|
files: ["report.md", "tree.json"]
|
|
5652
6416
|
});
|
|
5653
|
-
return
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
6417
|
+
return deliverMcpPayload(
|
|
6418
|
+
{
|
|
6419
|
+
runId,
|
|
6420
|
+
profile,
|
|
6421
|
+
metadata,
|
|
6422
|
+
files: {
|
|
6423
|
+
"report.md": markdown.content,
|
|
6424
|
+
"tree.json": tree.content
|
|
6425
|
+
}
|
|
6426
|
+
},
|
|
6427
|
+
context
|
|
6428
|
+
);
|
|
5662
6429
|
}
|
|
5663
6430
|
default:
|
|
5664
6431
|
return errorResult2(`Unknown tool: ${name}`);
|