@agent-inspect/mcp-server 6.3.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.mjs CHANGED
@@ -1801,6 +1801,9 @@ function toMetadataSafeStatus(status) {
1801
1801
  if (status === "SAFE WITH WARNINGS") return "SAFE_WITH_WARNINGS";
1802
1802
  return status;
1803
1803
  }
1804
+ function bundleFailsOnSafety(status, allowUnsafe) {
1805
+ return status === "UNSAFE" || status === "UNKNOWN";
1806
+ }
1804
1807
 
1805
1808
  // packages/core/src/bundle/manifest.ts
1806
1809
  var BUNDLE_NOTE = "Generated locally by AgentInspect. Bundles are derived copies for review \u2014 not compliance or security certification. Review before sharing.";
@@ -1833,6 +1836,44 @@ var STATUS_RANK = {
1833
1836
  warning: 1,
1834
1837
  pass: 2
1835
1838
  };
1839
+ var DEFAULT_SENSITIVE_KEYS = [
1840
+ "authorization",
1841
+ "cookie",
1842
+ "token",
1843
+ "apikey",
1844
+ "api_key",
1845
+ "password",
1846
+ "secret",
1847
+ "email"
1848
+ ];
1849
+ var DEFAULT_RAW_CONTENT_KEYS = [
1850
+ "body",
1851
+ "headers",
1852
+ "input",
1853
+ "messages",
1854
+ "output",
1855
+ "payload",
1856
+ "prompt",
1857
+ "requestbody",
1858
+ "request_body",
1859
+ "responsebody",
1860
+ "response_body",
1861
+ "rawprompt",
1862
+ "raw_prompt",
1863
+ "rawoutput",
1864
+ "raw_output",
1865
+ "toolinput",
1866
+ "tool_input",
1867
+ "tooloutput",
1868
+ "tool_output"
1869
+ ];
1870
+ var DEFAULT_SECRET_PATTERNS = [
1871
+ { id: "bearer-token", pattern: /Bearer\s+[A-Za-z0-9._~+/-]{12,}=*/ },
1872
+ { id: "openai-key", pattern: /sk-[A-Za-z0-9_-]{16,}/ },
1873
+ { id: "aws-access-key", pattern: /AKIA[0-9A-Z]{16}/ },
1874
+ { id: "github-token", pattern: /gh[opsu]_[A-Za-z0-9_]{20,}/ },
1875
+ { id: "key-value-secret", pattern: /(api[_-]?key|token|password|secret)=\S{8,}/i }
1876
+ ];
1836
1877
  function compareStrings(a, b) {
1837
1878
  return (a ?? "").localeCompare(b ?? "");
1838
1879
  }
@@ -2028,7 +2069,7 @@ function eventEvidence(event, path13) {
2028
2069
  kind: event.kind,
2029
2070
  name: event.name,
2030
2071
  status: event.status,
2031
- ...{}
2072
+ ...path13 ? { path: path13 } : {}
2032
2073
  };
2033
2074
  }
2034
2075
  function runEvidence(run) {
@@ -2045,6 +2086,84 @@ function failFinding(ruleId, message, evidence, expected, actual) {
2045
2086
  evidence: [...evidence]
2046
2087
  };
2047
2088
  }
2089
+ function isRecord6(value) {
2090
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2091
+ }
2092
+ function normalizedKey(value) {
2093
+ return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
2094
+ }
2095
+ function lastPathSegment(path13) {
2096
+ const parts = path13.split(".");
2097
+ return parts[parts.length - 1] ?? path13;
2098
+ }
2099
+ function valueType(value) {
2100
+ if (Array.isArray(value)) return "array";
2101
+ if (value === null) return "null";
2102
+ return typeof value;
2103
+ }
2104
+ function serializedByteLength(value) {
2105
+ try {
2106
+ return Buffer.byteLength(JSON.stringify(value), "utf-8");
2107
+ } catch {
2108
+ return void 0;
2109
+ }
2110
+ }
2111
+ function pushValueEntries(entries, event, value, path13, key, depth = 0) {
2112
+ entries.push({ event, path: path13, key, value });
2113
+ if (depth >= 8) return;
2114
+ if (Array.isArray(value)) {
2115
+ for (const [index, item] of value.entries()) {
2116
+ pushValueEntries(entries, event, item, `${path13}.${index}`, String(index), depth + 1);
2117
+ }
2118
+ return;
2119
+ }
2120
+ if (!isRecord6(value)) return;
2121
+ for (const nestedKey of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
2122
+ pushValueEntries(
2123
+ entries,
2124
+ event,
2125
+ value[nestedKey],
2126
+ `${path13}.${nestedKey}`,
2127
+ nestedKey,
2128
+ depth + 1
2129
+ );
2130
+ }
2131
+ }
2132
+ function eventValueEntries(event, options = {}) {
2133
+ const entries = [];
2134
+ if (event.attributes !== void 0) {
2135
+ pushValueEntries(entries, event, event.attributes, "attributes", "attributes");
2136
+ }
2137
+ if (options.includeSummaries) {
2138
+ if (event.inputSummary !== void 0) {
2139
+ pushValueEntries(entries, event, event.inputSummary, "inputSummary", "inputSummary");
2140
+ }
2141
+ if (event.outputSummary !== void 0) {
2142
+ pushValueEntries(entries, event, event.outputSummary, "outputSummary", "outputSummary");
2143
+ }
2144
+ }
2145
+ if (options.includeError && event.error !== void 0) {
2146
+ pushValueEntries(entries, event, event.error, "error", "error");
2147
+ }
2148
+ return entries;
2149
+ }
2150
+ function limitFindings(findings, maxFindings) {
2151
+ if (maxFindings === void 0 || findings.length <= maxFindings) return findings;
2152
+ return findings.slice(0, Math.max(0, maxFindings));
2153
+ }
2154
+ function hasRedactionMarker(value, markers) {
2155
+ return markers.some((marker) => value.includes(marker)) || /^\[HASH:[A-Za-z0-9_-]+\]$/.test(value);
2156
+ }
2157
+ function isSensitiveKey(key, sensitiveKeys) {
2158
+ if (!key) return false;
2159
+ const normalized = normalizedKey(key);
2160
+ return sensitiveKeys.some((sensitive) => normalized.includes(normalizedKey(sensitive)));
2161
+ }
2162
+ function isRawContentKey(key, forbiddenKeys) {
2163
+ if (!key) return false;
2164
+ const normalized = normalizedKey(key);
2165
+ return forbiddenKeys.some((forbidden) => normalized === normalizedKey(forbidden));
2166
+ }
2048
2167
  function createRunStatusRule(options = {}) {
2049
2168
  const expected = options.expected ?? "ok";
2050
2169
  const allowIncomplete = options.allowIncomplete === true;
@@ -2084,6 +2203,157 @@ function createRunStatusRule(options = {}) {
2084
2203
  }
2085
2204
  };
2086
2205
  }
2206
+ function createSafetyRedactionRule(options = {}) {
2207
+ const sensitiveKeys = options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS;
2208
+ const markers = options.redactedMarkers ?? ["[REDACTED]", "[REDACTED:"];
2209
+ return {
2210
+ id: "safety.redaction",
2211
+ category: "safety",
2212
+ defaultSeverity: "error",
2213
+ evaluate(context) {
2214
+ const findings = [];
2215
+ for (const event of context.events) {
2216
+ for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
2217
+ if (!isSensitiveKey(entry.key ?? lastPathSegment(entry.path), sensitiveKeys)) continue;
2218
+ if (typeof entry.value === "string" && hasRedactionMarker(entry.value, markers)) continue;
2219
+ findings.push(
2220
+ failFinding(
2221
+ "safety.redaction",
2222
+ `Sensitive-looking field at ${entry.path} is not redacted.`,
2223
+ [eventEvidence(event, entry.path)],
2224
+ "redaction marker",
2225
+ { path: entry.path, valueType: valueType(entry.value) }
2226
+ )
2227
+ );
2228
+ }
2229
+ }
2230
+ return limitFindings(findings, options.maxFindings);
2231
+ }
2232
+ };
2233
+ }
2234
+ function createSafetyRawContentRule(options = {}) {
2235
+ const forbiddenKeys = options.forbiddenKeys ?? DEFAULT_RAW_CONTENT_KEYS;
2236
+ return {
2237
+ id: "safety.rawPrompt",
2238
+ category: "safety",
2239
+ defaultSeverity: "error",
2240
+ evaluate(context) {
2241
+ const findings = [];
2242
+ for (const event of context.events) {
2243
+ for (const entry of eventValueEntries(event, { includeSummaries: options.includeSummaries })) {
2244
+ const key = entry.key ?? lastPathSegment(entry.path);
2245
+ if (!isRawContentKey(key, forbiddenKeys)) continue;
2246
+ findings.push(
2247
+ failFinding(
2248
+ "safety.rawPrompt",
2249
+ `Raw content-like field ${entry.path} is present.`,
2250
+ [eventEvidence(event, entry.path)],
2251
+ "metadata-only trace fields",
2252
+ { path: entry.path, valueType: valueType(entry.value) }
2253
+ )
2254
+ );
2255
+ }
2256
+ }
2257
+ return limitFindings(findings, options.maxFindings);
2258
+ }
2259
+ };
2260
+ }
2261
+ function createSafetySecretPatternRule(options = {}) {
2262
+ const patterns = options.patterns ?? DEFAULT_SECRET_PATTERNS;
2263
+ const maxStringLength = options.maxStringLength ?? 4096;
2264
+ return {
2265
+ id: "safety.secretPattern",
2266
+ category: "safety",
2267
+ defaultSeverity: "error",
2268
+ evaluate(context) {
2269
+ const findings = [];
2270
+ for (const event of context.events) {
2271
+ for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
2272
+ if (typeof entry.value !== "string") continue;
2273
+ const sample = entry.value.slice(0, maxStringLength);
2274
+ for (const pattern of patterns) {
2275
+ pattern.pattern.lastIndex = 0;
2276
+ if (!pattern.pattern.test(sample)) continue;
2277
+ pattern.pattern.lastIndex = 0;
2278
+ findings.push(
2279
+ failFinding(
2280
+ "safety.secretPattern",
2281
+ `Secret-like pattern ${pattern.id} matched at ${entry.path}.`,
2282
+ [eventEvidence(event, entry.path)],
2283
+ "no secret-like strings",
2284
+ { pattern: pattern.id, path: entry.path }
2285
+ )
2286
+ );
2287
+ break;
2288
+ }
2289
+ }
2290
+ }
2291
+ return limitFindings(findings, options.maxFindings);
2292
+ }
2293
+ };
2294
+ }
2295
+ function createSafetyOversizedAttributeRule(options) {
2296
+ return {
2297
+ id: "safety.oversizedAttribute",
2298
+ category: "safety",
2299
+ defaultSeverity: "error",
2300
+ evaluate(context) {
2301
+ const findings = [];
2302
+ for (const event of context.events) {
2303
+ for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
2304
+ if (typeof entry.value === "string" && options.maxStringLength !== void 0 && entry.value.length > options.maxStringLength) {
2305
+ findings.push(
2306
+ failFinding(
2307
+ "safety.oversizedAttribute",
2308
+ `String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
2309
+ [eventEvidence(event, entry.path)],
2310
+ { maxStringLength: options.maxStringLength },
2311
+ { path: entry.path, length: entry.value.length }
2312
+ )
2313
+ );
2314
+ }
2315
+ if (Array.isArray(entry.value) && options.maxArrayLength !== void 0 && entry.value.length > options.maxArrayLength) {
2316
+ findings.push(
2317
+ failFinding(
2318
+ "safety.oversizedAttribute",
2319
+ `Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
2320
+ [eventEvidence(event, entry.path)],
2321
+ { maxArrayLength: options.maxArrayLength },
2322
+ { path: entry.path, length: entry.value.length }
2323
+ )
2324
+ );
2325
+ }
2326
+ if (isRecord6(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
2327
+ findings.push(
2328
+ failFinding(
2329
+ "safety.oversizedAttribute",
2330
+ `Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
2331
+ [eventEvidence(event, entry.path)],
2332
+ { maxObjectKeys: options.maxObjectKeys },
2333
+ { path: entry.path, keys: Object.keys(entry.value).length }
2334
+ )
2335
+ );
2336
+ }
2337
+ if (options.maxSerializedBytes !== void 0) {
2338
+ const bytes = serializedByteLength(entry.value);
2339
+ if (bytes !== void 0 && bytes > options.maxSerializedBytes) {
2340
+ findings.push(
2341
+ failFinding(
2342
+ "safety.oversizedAttribute",
2343
+ `Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
2344
+ [eventEvidence(event, entry.path)],
2345
+ { maxSerializedBytes: options.maxSerializedBytes },
2346
+ { path: entry.path, bytes }
2347
+ )
2348
+ );
2349
+ }
2350
+ }
2351
+ }
2352
+ }
2353
+ return limitFindings(findings, options.maxFindings);
2354
+ }
2355
+ };
2356
+ }
2087
2357
  function runTraceChecks(input, options = {}) {
2088
2358
  const selected = resolveSelectedRun(input, options.runId);
2089
2359
  if (selected.diagnostics.length > 0) {
@@ -2130,14 +2400,14 @@ function runTraceChecks(input, options = {}) {
2130
2400
  }
2131
2401
 
2132
2402
  // packages/core/src/persisted/token-usage.ts
2133
- function isRecord6(value) {
2403
+ function isRecord7(value) {
2134
2404
  return typeof value === "object" && value !== null && !Array.isArray(value);
2135
2405
  }
2136
2406
  function nonNegativeFinite(value) {
2137
2407
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
2138
2408
  }
2139
2409
  function normalizeTokenUsage(value) {
2140
- if (!isRecord6(value)) return void 0;
2410
+ if (!isRecord7(value)) return void 0;
2141
2411
  const input = nonNegativeFinite(value.input);
2142
2412
  const output = nonNegativeFinite(value.output);
2143
2413
  const suppliedTotal = nonNegativeFinite(value.total);
@@ -2895,7 +3165,7 @@ function persistedEventsForParsedTrace(parsed) {
2895
3165
  sourceName: "agent-inspect-jsonl-reader"
2896
3166
  });
2897
3167
  }
2898
- function isRecord7(value) {
3168
+ function isRecord8(value) {
2899
3169
  return typeof value === "object" && value !== null && !Array.isArray(value);
2900
3170
  }
2901
3171
  function isNonEmptyString3(value) {
@@ -2910,13 +3180,13 @@ function readStringField(record, keys) {
2910
3180
  }
2911
3181
  function readRecordField(record, key) {
2912
3182
  const value = record[key];
2913
- return isRecord7(value) ? value : void 0;
3183
+ return isRecord8(value) ? value : void 0;
2914
3184
  }
2915
3185
  function parseJsonDocument(content) {
2916
3186
  return JSON.parse(content);
2917
3187
  }
2918
3188
  function looksLikeOpenInferenceSpan(value) {
2919
- if (!isRecord7(value)) return false;
3189
+ if (!isRecord8(value)) return false;
2920
3190
  const attributes = readRecordField(value, "attributes");
2921
3191
  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);
2922
3192
  }
@@ -2941,7 +3211,7 @@ function extractOpenInferenceDocument(root) {
2941
3211
  unsupportedFields
2942
3212
  };
2943
3213
  }
2944
- if (!isRecord7(root)) return void 0;
3214
+ if (!isRecord8(root)) return void 0;
2945
3215
  const rootFormat = root.format;
2946
3216
  const rootCompatibility = root.compatibility;
2947
3217
  const version = typeof root.version === "string" && root.version.trim() !== "" ? root.version : void 0;
@@ -3081,7 +3351,7 @@ function summarizeAttributeValue(value) {
3081
3351
  if (Array.isArray(value)) {
3082
3352
  return { type: "array", length: value.length };
3083
3353
  }
3084
- if (isRecord7(value)) {
3354
+ if (isRecord8(value)) {
3085
3355
  return { type: "object", keyCount: Object.keys(value).length };
3086
3356
  }
3087
3357
  if (value === null) {
@@ -3168,7 +3438,7 @@ function mapOpenInferenceKind(span, attributes, pathPrefix) {
3168
3438
  }
3169
3439
  }
3170
3440
  function mapOpenInferenceStatus(status) {
3171
- if (!isRecord7(status)) return void 0;
3441
+ if (!isRecord8(status)) return void 0;
3172
3442
  const rawCode = status.code;
3173
3443
  if (typeof rawCode !== "string") return void 0;
3174
3444
  switch (rawCode.toUpperCase()) {
@@ -3268,7 +3538,7 @@ function mapOpenInferenceSpan(span, index, version) {
3268
3538
  warnings.push(...kindWarnings);
3269
3539
  const status = mapOpenInferenceStatus(span.status);
3270
3540
  const tokenUsage = readOpenInferenceTokenUsage(rawAttributes);
3271
- const errorMessage = isRecord7(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3541
+ const errorMessage = isRecord8(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3272
3542
  const event = {
3273
3543
  schemaVersion: "0.2",
3274
3544
  eventId: typeof rawAttributes["agent_inspect.event_id"] === "string" ? rawAttributes["agent_inspect.event_id"] : spanId,
@@ -3414,7 +3684,7 @@ var openInferenceJsonReader = {
3414
3684
  }
3415
3685
  };
3416
3686
  function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
3417
- if (!isRecord7(value)) {
3687
+ if (!isRecord8(value)) {
3418
3688
  unsupportedFields.push(field);
3419
3689
  warnings.push({
3420
3690
  code: "otlp_attribute_value_invalid",
@@ -3436,15 +3706,15 @@ function parseOtlpAnyValue(value, field, warnings, unsupportedFields) {
3436
3706
  if (typeof value.doubleValue === "number" && Number.isFinite(value.doubleValue)) {
3437
3707
  return value.doubleValue;
3438
3708
  }
3439
- if (isRecord7(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
3709
+ if (isRecord8(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
3440
3710
  return value.arrayValue.values.map(
3441
3711
  (item, index) => parseOtlpAnyValue(item, `${field}.arrayValue.values[${index}]`, warnings, unsupportedFields)
3442
3712
  );
3443
3713
  }
3444
- if (isRecord7(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
3714
+ if (isRecord8(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
3445
3715
  const out = {};
3446
3716
  for (const [index, item] of value.kvlistValue.values.entries()) {
3447
- if (!isRecord7(item) || typeof item.key !== "string") {
3717
+ if (!isRecord8(item) || typeof item.key !== "string") {
3448
3718
  unsupportedFields.push(`${field}.kvlistValue.values[${index}]`);
3449
3719
  continue;
3450
3720
  }
@@ -3495,7 +3765,7 @@ function parseOtlpAttributes(value, pathPrefix) {
3495
3765
  }
3496
3766
  for (const [index, item] of value.entries()) {
3497
3767
  const field = `${pathPrefix}[${index}]`;
3498
- if (!isRecord7(item) || typeof item.key !== "string") {
3768
+ if (!isRecord8(item) || typeof item.key !== "string") {
3499
3769
  unsupportedFields.push(field);
3500
3770
  warnings.push({
3501
3771
  code: "otlp_attribute_invalid",
@@ -3518,16 +3788,16 @@ function parseOtlpAttributes(value, pathPrefix) {
3518
3788
  return { attributes, warnings, unsupportedFields };
3519
3789
  }
3520
3790
  function looksLikeOtlpSpan(value) {
3521
- return isRecord7(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
3791
+ return isRecord8(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
3522
3792
  }
3523
3793
  function extractOtlpDocument(root) {
3524
- if (!isRecord7(root) || !Array.isArray(root.resourceSpans)) return void 0;
3794
+ if (!isRecord8(root) || !Array.isArray(root.resourceSpans)) return void 0;
3525
3795
  const spans = [];
3526
3796
  const warnings = [];
3527
3797
  const unsupportedFields = [];
3528
3798
  for (const [resourceIndex, resourceSpan] of root.resourceSpans.entries()) {
3529
3799
  const resourcePath = `resourceSpans[${resourceIndex}]`;
3530
- if (!isRecord7(resourceSpan)) {
3800
+ if (!isRecord8(resourceSpan)) {
3531
3801
  unsupportedFields.push(resourcePath);
3532
3802
  continue;
3533
3803
  }
@@ -3550,7 +3820,7 @@ function extractOtlpDocument(root) {
3550
3820
  }
3551
3821
  for (const [scopeIndex, scopeSpan] of resourceSpan.scopeSpans.entries()) {
3552
3822
  const scopePath = `${resourcePath}.scopeSpans[${scopeIndex}]`;
3553
- if (!isRecord7(scopeSpan)) {
3823
+ if (!isRecord8(scopeSpan)) {
3554
3824
  unsupportedFields.push(scopePath);
3555
3825
  continue;
3556
3826
  }
@@ -3617,7 +3887,7 @@ function extractOtlpDocument(root) {
3617
3887
  };
3618
3888
  }
3619
3889
  function mapOtlpStatus(status) {
3620
- if (!isRecord7(status)) return void 0;
3890
+ if (!isRecord8(status)) return void 0;
3621
3891
  const rawCode = status.code;
3622
3892
  if (typeof rawCode !== "string") return void 0;
3623
3893
  switch (rawCode.toUpperCase()) {
@@ -3717,7 +3987,7 @@ function mapOtlpEvents(value, pathPrefix) {
3717
3987
  const events = [];
3718
3988
  for (const [index, event] of value.entries()) {
3719
3989
  const eventPath = `${pathPrefix}[${index}]`;
3720
- if (!isRecord7(event)) {
3990
+ if (!isRecord8(event)) {
3721
3991
  unsupportedFields.push(eventPath);
3722
3992
  continue;
3723
3993
  }
@@ -3855,7 +4125,7 @@ function mapOtlpSpan(context) {
3855
4125
  warnings.push(...kindWarnings);
3856
4126
  const status = mapOtlpStatus(span.status);
3857
4127
  const tokenUsage = readOtlpTokenUsage(parsedSpanAttributes.attributes);
3858
- const errorMessage = isRecord7(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
4128
+ const errorMessage = isRecord8(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
3859
4129
  const event = {
3860
4130
  schemaVersion: "0.2",
3861
4131
  eventId: typeof parsedSpanAttributes.attributes["agent_inspect.event_id"] === "string" ? parsedSpanAttributes.attributes["agent_inspect.event_id"] : spanId,
@@ -4651,7 +4921,7 @@ function diffRuns(left, right, options) {
4651
4921
  var EXPORT_PAYLOAD_VERSION = "0.1.2";
4652
4922
 
4653
4923
  // packages/core/src/exporters/redact-export.ts
4654
- function isRecord8(value) {
4924
+ function isRecord9(value) {
4655
4925
  return typeof value === "object" && value !== null && !Array.isArray(value);
4656
4926
  }
4657
4927
  function deepClone(value) {
@@ -4725,7 +4995,7 @@ function redactEventAttributes(attrs, redactor, maxMetadataValueLength, maxPrevi
4725
4995
  0
4726
4996
  );
4727
4997
  const err = bounded.error;
4728
- if (isRecord8(err) && typeof err.message === "string") {
4998
+ if (isRecord9(err) && typeof err.message === "string") {
4729
4999
  bounded.error = {
4730
5000
  ...err,
4731
5001
  message: truncateStringForProfile(
@@ -5328,6 +5598,453 @@ function exportRunTree(tree, options) {
5328
5598
  }
5329
5599
  }
5330
5600
 
5601
+ // packages/mcp-server/src/assess-trace.ts
5602
+ var DEFAULT_MAX_STRING_LENGTH = 16384;
5603
+ var DEFAULT_MAX_ARRAY_LENGTH = 1e3;
5604
+ var DEFAULT_MAX_OBJECT_KEYS = 200;
5605
+ var DEFAULT_MAX_SERIALIZED_BYTES = 128 * 1024;
5606
+ function buildMcpSafetyRules() {
5607
+ return [
5608
+ createSafetyRawContentRule(),
5609
+ createSafetyRedactionRule(),
5610
+ createSafetySecretPatternRule(),
5611
+ createSafetyOversizedAttributeRule({
5612
+ maxStringLength: DEFAULT_MAX_STRING_LENGTH,
5613
+ maxArrayLength: DEFAULT_MAX_ARRAY_LENGTH,
5614
+ maxObjectKeys: DEFAULT_MAX_OBJECT_KEYS,
5615
+ maxSerializedBytes: DEFAULT_MAX_SERIALIZED_BYTES
5616
+ })
5617
+ ];
5618
+ }
5619
+ function statusFrom(findings, hasErrors) {
5620
+ if (hasErrors) return "UNKNOWN";
5621
+ if (findings.some((item) => item.severity === "error")) return "UNSAFE";
5622
+ if (findings.some((item) => item.severity === "warning")) return "SAFE WITH WARNINGS";
5623
+ return "SAFE";
5624
+ }
5625
+ function assessTraceForMcp(read, runId) {
5626
+ const rules = buildMcpSafetyRules();
5627
+ const checkResult = runTraceChecks({ read }, { rules, runId });
5628
+ const hasErrors = checkResult.diagnostics.some((item) => item.severity === "error");
5629
+ const status = statusFrom(checkResult.findings, hasErrors);
5630
+ return {
5631
+ status,
5632
+ errors: checkResult.diagnostics.filter((item) => item.severity === "error").length + checkResult.findings.filter((item) => item.severity === "error").length,
5633
+ warnings: checkResult.diagnostics.filter((item) => item.severity === "warning").length + checkResult.findings.filter((item) => item.severity === "warning").length,
5634
+ findings: checkResult.findings.length
5635
+ };
5636
+ }
5637
+ var DEFAULT_REDACT_KEYS2 = [
5638
+ "authorization",
5639
+ "cookie",
5640
+ "token",
5641
+ "apiKey",
5642
+ "password",
5643
+ "secret",
5644
+ "email"
5645
+ ];
5646
+ var SHARE_PROFILE_EXTRA_KEYS2 = [
5647
+ "userEmail",
5648
+ "customerEmail",
5649
+ "phone",
5650
+ "phoneNumber",
5651
+ "address",
5652
+ "ip",
5653
+ "ipAddress",
5654
+ "sessionId",
5655
+ "requestId",
5656
+ "correlationId",
5657
+ "decisionId",
5658
+ "groupId",
5659
+ "customerId",
5660
+ "userId",
5661
+ "accountId",
5662
+ "tenantId",
5663
+ "orgId",
5664
+ "organizationId",
5665
+ "traceId",
5666
+ "spanId",
5667
+ "parentSpanId"
5668
+ ];
5669
+ var STRICT_PROFILE_EXTRA_KEYS2 = [
5670
+ "prompt",
5671
+ "completion",
5672
+ "input",
5673
+ "output",
5674
+ "inputPreview",
5675
+ "outputPreview",
5676
+ "message",
5677
+ "messages",
5678
+ "transcript",
5679
+ "context",
5680
+ "document",
5681
+ "documents",
5682
+ "chunk",
5683
+ "chunks",
5684
+ "retrieval",
5685
+ "query"
5686
+ ];
5687
+ function isRecord10(value) {
5688
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5689
+ }
5690
+ function toKey2(key) {
5691
+ return key.toLowerCase();
5692
+ }
5693
+ function stableHash2(value) {
5694
+ const hash = crypto.createHash("sha256").update(value, "utf8").digest("hex");
5695
+ return hash.slice(0, 8);
5696
+ }
5697
+ function stringifyScalar(value) {
5698
+ if (typeof value === "string") return value;
5699
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
5700
+ return String(value);
5701
+ }
5702
+ return void 0;
5703
+ }
5704
+ function patternDetector(options) {
5705
+ return {
5706
+ id: options.id,
5707
+ severity: options.severity ?? "warning",
5708
+ matchKind: "value",
5709
+ detect(input) {
5710
+ if (typeof input.value !== "string") return [];
5711
+ options.pattern.lastIndex = 0;
5712
+ return options.pattern.test(input.value) ? [{ action: "replace", severity: options.severity ?? "warning", matchKind: "value" }] : [];
5713
+ }
5714
+ };
5715
+ }
5716
+ function digitsOnly(value) {
5717
+ return value.replace(/\D/g, "");
5718
+ }
5719
+ function passesLuhn(value) {
5720
+ const digits = digitsOnly(value);
5721
+ if (digits.length < 13 || digits.length > 19) return false;
5722
+ let sum = 0;
5723
+ let double = false;
5724
+ for (let i = digits.length - 1; i >= 0; i -= 1) {
5725
+ let digit = Number(digits[i]);
5726
+ if (double) {
5727
+ digit *= 2;
5728
+ if (digit > 9) digit -= 9;
5729
+ }
5730
+ sum += digit;
5731
+ double = !double;
5732
+ }
5733
+ return sum % 10 === 0;
5734
+ }
5735
+ var credentialDetectors = [
5736
+ patternDetector({
5737
+ id: "value.authorizationHeader",
5738
+ pattern: /^(?:basic|bearer|digest|apikey)\s+[a-z0-9._~+/=-]+$/i,
5739
+ severity: "error"
5740
+ }),
5741
+ patternDetector({
5742
+ id: "value.bearerToken",
5743
+ pattern: /\bbearer\s+[a-z0-9._~+/=-]{12,}\b/i,
5744
+ severity: "error"
5745
+ }),
5746
+ patternDetector({
5747
+ id: "value.cookie",
5748
+ pattern: /\b[a-z0-9_.-]+=[^;\s]+(?:;\s*[a-z0-9_.-]+=[^;\s]+)+/i,
5749
+ severity: "error"
5750
+ }),
5751
+ patternDetector({
5752
+ id: "value.jwt",
5753
+ pattern: /\beyJ[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\.[a-zA-Z0-9_-]{8,}\b/,
5754
+ severity: "error"
5755
+ }),
5756
+ patternDetector({
5757
+ id: "value.providerApiKey",
5758
+ pattern: /\b(?:sk-(?:proj-)?[a-zA-Z0-9_-]{16,}|sk-ant-[a-zA-Z0-9_-]{16,}|AIza[0-9A-Za-z_-]{20,})\b/,
5759
+ severity: "error"
5760
+ }),
5761
+ patternDetector({
5762
+ id: "value.githubToken",
5763
+ pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/,
5764
+ severity: "error"
5765
+ }),
5766
+ patternDetector({
5767
+ id: "value.awsAccessKey",
5768
+ pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/,
5769
+ severity: "error"
5770
+ }),
5771
+ patternDetector({
5772
+ id: "value.privateKey",
5773
+ pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*-----END [A-Z ]*PRIVATE KEY-----/,
5774
+ severity: "error"
5775
+ }),
5776
+ {
5777
+ id: "value.creditCard",
5778
+ severity: "error",
5779
+ matchKind: "value",
5780
+ detect(input) {
5781
+ if (typeof input.value !== "string") return [];
5782
+ const candidatePattern = /(?:\d[ -]?){13,19}/g;
5783
+ for (const match of input.value.matchAll(candidatePattern)) {
5784
+ const candidate = match[0] ?? "";
5785
+ if (passesLuhn(candidate)) {
5786
+ return [{ action: "replace", severity: "error", matchKind: "value" }];
5787
+ }
5788
+ }
5789
+ return [];
5790
+ }
5791
+ }
5792
+ ];
5793
+ var identifierDetectors = [
5794
+ patternDetector({
5795
+ id: "value.email",
5796
+ pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i
5797
+ }),
5798
+ patternDetector({
5799
+ id: "value.phone",
5800
+ pattern: /\b(?:\+\d{1,3}[\s.-]?)?(?:\(?\d{3}\)?[\s.-])\d{3}[\s.-]\d{4}\b/
5801
+ }),
5802
+ patternDetector({
5803
+ id: "value.ipv4",
5804
+ pattern: /\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)\b/
5805
+ }),
5806
+ patternDetector({
5807
+ id: "value.ipv6",
5808
+ pattern: /\b(?:[0-9a-f]{1,4}:){2,7}[0-9a-f]{1,4}\b/i
5809
+ })
5810
+ ];
5811
+ function builtInDetectorsForProfile(profile) {
5812
+ if (profile === "local") return credentialDetectors;
5813
+ return [...credentialDetectors, ...identifierDetectors];
5814
+ }
5815
+ function compileRules2(rules, extraKeys) {
5816
+ const out = /* @__PURE__ */ new Map();
5817
+ const set = (rule) => {
5818
+ const key = toKey2(rule.key);
5819
+ out.set(key, { ...rule, key });
5820
+ };
5821
+ for (const key of DEFAULT_REDACT_KEYS2) {
5822
+ set({ key, strategy: "full" });
5823
+ }
5824
+ for (const key of extraKeys ?? []) {
5825
+ if (typeof key === "string" && key.length > 0) {
5826
+ set({ key, strategy: "full" });
5827
+ }
5828
+ }
5829
+ for (const rule of rules ?? []) {
5830
+ if (typeof rule === "string") {
5831
+ set({ key: rule, strategy: "full" });
5832
+ continue;
5833
+ }
5834
+ if (rule.strategy === "full") set({ key: rule.key, strategy: "full" });
5835
+ if (rule.strategy === "hash") set({ key: rule.key, strategy: "hash" });
5836
+ if (rule.strategy === "prefix") {
5837
+ set({
5838
+ key: rule.key,
5839
+ strategy: "prefix",
5840
+ keep: typeof rule.keep === "number" ? rule.keep : 8
5841
+ });
5842
+ }
5843
+ }
5844
+ return [...out.values()];
5845
+ }
5846
+ function actionForRule(rule) {
5847
+ if (rule.strategy === "full") return "replace";
5848
+ return rule.strategy;
5849
+ }
5850
+ function applyRule(rule, value, replacement) {
5851
+ if (rule.strategy === "full") return replacement;
5852
+ const asString = stringifyScalar(value);
5853
+ if (rule.strategy === "prefix") {
5854
+ if (asString === void 0) return replacement;
5855
+ const keep = Math.max(0, Math.floor(rule.keep));
5856
+ return asString.length <= keep ? `${asString}\u2026` : `${asString.slice(0, keep)}\u2026`;
5857
+ }
5858
+ if (rule.strategy === "hash") {
5859
+ if (asString === void 0) return "[HASH:unknown]";
5860
+ return `[HASH:${stableHash2(asString)}]`;
5861
+ }
5862
+ return value;
5863
+ }
5864
+ function childPath(path13, key) {
5865
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
5866
+ return path13 ? `${path13}.${key}` : key;
5867
+ }
5868
+ return `${path13 || "$"}[${JSON.stringify(key)}]`;
5869
+ }
5870
+ function indexPath(path13, index) {
5871
+ return `${path13 || "$"}[${index}]`;
5872
+ }
5873
+ function makeFinding(path13, detector, action, matchKind, severity = "warning", preview) {
5874
+ return preview === void 0 ? { path: path13, detector, action, severity, matchKind } : { path: path13, detector, action, severity, matchKind, preview };
5875
+ }
5876
+ function createRedactionProfile(profile = "local") {
5877
+ switch (profile) {
5878
+ case "local":
5879
+ return { profile: "local", extraKeys: [] };
5880
+ case "share":
5881
+ return {
5882
+ profile: "share",
5883
+ extraKeys: SHARE_PROFILE_EXTRA_KEYS2,
5884
+ maxMetadataValueLengthCap: 500,
5885
+ maxPreviewLengthCap: 200
5886
+ };
5887
+ case "strict":
5888
+ return {
5889
+ profile: "strict",
5890
+ extraKeys: [...SHARE_PROFILE_EXTRA_KEYS2, ...STRICT_PROFILE_EXTRA_KEYS2],
5891
+ maxMetadataValueLengthCap: 200,
5892
+ maxPreviewLengthCap: 80
5893
+ };
5894
+ }
5895
+ }
5896
+ var Redactor2 = class {
5897
+ #rules;
5898
+ #detectors;
5899
+ #profile;
5900
+ #replacement;
5901
+ #maxDepth;
5902
+ #collectFindings;
5903
+ constructor(options) {
5904
+ const resolved = createRedactionProfile(options?.profile ?? "local");
5905
+ this.#profile = resolved.profile;
5906
+ this.#rules = compileRules2(options?.rules, [
5907
+ ...resolved.extraKeys,
5908
+ ...options?.extraKeys ?? []
5909
+ ]);
5910
+ this.#detectors = [
5911
+ ...builtInDetectorsForProfile(this.#profile),
5912
+ ...options?.detectors ?? []
5913
+ ];
5914
+ this.#replacement = options?.replacement ?? "[REDACTED]";
5915
+ this.#maxDepth = options?.maxDepth ?? 32;
5916
+ this.#collectFindings = options?.collectFindings ?? true;
5917
+ }
5918
+ redactValue(key, value) {
5919
+ return this.#redactValue(value, key, key, 0, {
5920
+ findings: [],
5921
+ seen: /* @__PURE__ */ new WeakMap()
5922
+ });
5923
+ }
5924
+ redactRecord(record) {
5925
+ return this.redact(record).value;
5926
+ }
5927
+ redact(value) {
5928
+ const state = {
5929
+ findings: [],
5930
+ seen: /* @__PURE__ */ new WeakMap()
5931
+ };
5932
+ const redacted = this.#redactValue(value, void 0, "$", 0, state);
5933
+ return {
5934
+ value: redacted,
5935
+ findings: state.findings,
5936
+ redacted: state.findings.some((finding) => finding.action !== "keep"),
5937
+ profile: this.#profile
5938
+ };
5939
+ }
5940
+ #recordFinding(state, finding) {
5941
+ if (this.#collectFindings) state.findings.push(finding);
5942
+ }
5943
+ #redactValue(value, key, path13, depth, state) {
5944
+ if (depth > this.#maxDepth) {
5945
+ this.#recordFinding(
5946
+ state,
5947
+ makeFinding(path13, "structure.maxDepth", "truncate", "value", "warning")
5948
+ );
5949
+ return "[Truncated]";
5950
+ }
5951
+ if (key !== void 0) {
5952
+ const rule = this.#rules.find((candidate) => candidate.key === toKey2(key));
5953
+ if (rule) {
5954
+ this.#recordFinding(
5955
+ state,
5956
+ makeFinding(path13, `key.${rule.key}`, actionForRule(rule), "key", "warning")
5957
+ );
5958
+ return applyRule(rule, value, this.#replacement);
5959
+ }
5960
+ }
5961
+ for (const detector of this.#detectors) {
5962
+ const detections = detector.detect({ path: path13, key, value });
5963
+ for (const detection of detections) {
5964
+ const action = detection.action ?? "replace";
5965
+ this.#recordFinding(
5966
+ state,
5967
+ makeFinding(
5968
+ path13,
5969
+ detector.id,
5970
+ action,
5971
+ detection.matchKind ?? detector.matchKind ?? "custom",
5972
+ detection.severity ?? detector.severity ?? "warning",
5973
+ detection.preview
5974
+ )
5975
+ );
5976
+ if (action !== "keep") {
5977
+ return detection.replacement ?? this.#replacement;
5978
+ }
5979
+ }
5980
+ }
5981
+ if (Array.isArray(value)) {
5982
+ if (state.seen.has(value)) return state.seen.get(value);
5983
+ const out = [];
5984
+ state.seen.set(value, out);
5985
+ value.forEach((item, index) => {
5986
+ out[index] = this.#redactValue(item, void 0, indexPath(path13, index), depth + 1, state);
5987
+ });
5988
+ return out;
5989
+ }
5990
+ if (isRecord10(value)) {
5991
+ if (state.seen.has(value)) return state.seen.get(value);
5992
+ const out = {};
5993
+ state.seen.set(value, out);
5994
+ for (const [entryKey, entryValue] of Object.entries(value)) {
5995
+ out[entryKey] = this.#redactValue(
5996
+ entryValue,
5997
+ entryKey,
5998
+ childPath(path13 === "$" ? "" : path13, entryKey),
5999
+ depth + 1,
6000
+ state
6001
+ );
6002
+ }
6003
+ return out;
6004
+ }
6005
+ return value;
6006
+ }
6007
+ };
6008
+ function createRedactor(options) {
6009
+ return new Redactor2(options);
6010
+ }
6011
+ function redact(value, options) {
6012
+ return createRedactor(options).redact(value);
6013
+ }
6014
+
6015
+ // packages/mcp-server/src/prepare-result.ts
6016
+ var DEFAULT_MCP_RESULT_MAX_BYTES = 512 * 1024;
6017
+ function stableStringify(value) {
6018
+ return JSON.stringify(value);
6019
+ }
6020
+ function prepareMcpToolResult(payload, options = {}) {
6021
+ const maxBytes = options.maxBytes ?? DEFAULT_MCP_RESULT_MAX_BYTES;
6022
+ const profile = options.redactionProfile ?? "share";
6023
+ const diagnostics = [];
6024
+ const redacted = redact(payload, { profile });
6025
+ let result = redacted.value;
6026
+ const redactionFindings = redacted.findings.length;
6027
+ if (redactionFindings > 0) {
6028
+ diagnostics.push(`Redacted ${redactionFindings} sensitive value(s) from MCP tool result.`);
6029
+ }
6030
+ let text = stableStringify(result);
6031
+ let truncated = false;
6032
+ if (text.length > maxBytes) {
6033
+ truncated = true;
6034
+ diagnostics.push(
6035
+ `MCP tool result truncated from ${text.length} to ${maxBytes} bytes.`
6036
+ );
6037
+ text = `${text.slice(0, maxBytes)}
6038
+ \u2026[truncated]`;
6039
+ try {
6040
+ result = JSON.parse(text.replace(/\n…\[truncated\]$/, ""));
6041
+ } catch {
6042
+ result = { truncated: true, preview: text.slice(0, maxBytes) };
6043
+ }
6044
+ }
6045
+ return { payload: result, diagnostics, truncated, redactionFindings };
6046
+ }
6047
+
5331
6048
  // packages/mcp-server/src/tools.ts
5332
6049
  var READ_ONLY_TOOLS = [
5333
6050
  {
@@ -5444,6 +6161,20 @@ function textResult(payload) {
5444
6161
  isError: false
5445
6162
  };
5446
6163
  }
6164
+ function deliverMcpPayload(payload, context) {
6165
+ const prepared = prepareMcpToolResult(payload, {
6166
+ redactionProfile: redactionProfileForExport(context)
6167
+ });
6168
+ const body = prepared.diagnostics.length > 0 || prepared.truncated ? {
6169
+ ...typeof prepared.payload === "object" && prepared.payload !== null && !Array.isArray(prepared.payload) ? prepared.payload : { value: prepared.payload },
6170
+ _mcp: {
6171
+ diagnostics: prepared.diagnostics,
6172
+ truncated: prepared.truncated,
6173
+ redactionFindings: prepared.redactionFindings
6174
+ }
6175
+ } : prepared.payload;
6176
+ return textResult(body);
6177
+ }
5447
6178
  function errorResult2(message) {
5448
6179
  return {
5449
6180
  content: [{ type: "text", text: message }],
@@ -5493,25 +6224,29 @@ async function callReadOnlyTool(context, name, args = {}) {
5493
6224
  files,
5494
6225
  (fileName) => td.getPath(fileName)
5495
6226
  );
5496
- return textResult(
6227
+ return deliverMcpPayload(
5497
6228
  metas.map((meta) => ({
5498
6229
  runId: meta.runId,
5499
6230
  name: meta.name,
5500
6231
  status: meta.status,
5501
6232
  file: path.basename(meta.filePath)
5502
- }))
6233
+ })),
6234
+ context
5503
6235
  );
5504
6236
  }
5505
6237
  case "read_trace": {
5506
6238
  const runId = String(args.runId ?? "");
5507
6239
  const { read } = await openRunTrace(context, runId);
5508
6240
  const events = read.events.length > context.maxEvents ? read.events.slice(0, context.maxEvents) : read.events;
5509
- return textResult({
5510
- runId,
5511
- format: read.format,
5512
- truncated: read.events.length > events.length,
5513
- events
5514
- });
6241
+ return deliverMcpPayload(
6242
+ {
6243
+ runId,
6244
+ format: read.format,
6245
+ truncated: read.events.length > events.length,
6246
+ events
6247
+ },
6248
+ context
6249
+ );
5515
6250
  }
5516
6251
  case "search_traces": {
5517
6252
  const query = String(args.query ?? "").trim();
@@ -5528,17 +6263,20 @@ async function callReadOnlyTool(context, name, args = {}) {
5528
6263
  name: query,
5529
6264
  limit: 25
5530
6265
  });
5531
- return textResult(results);
6266
+ return deliverMcpPayload(results, context);
5532
6267
  }
5533
6268
  case "find_first_error": {
5534
6269
  const runId = String(args.runId ?? "");
5535
6270
  const { read } = await openRunTrace(context, runId);
5536
6271
  const timeline = buildRunTimeline(legacyTraceEvents(read.events));
5537
6272
  const firstError = timeline.entries.find((entry) => entry.isError);
5538
- return textResult({
5539
- runId,
5540
- firstError: firstError ?? null
5541
- });
6273
+ return deliverMcpPayload(
6274
+ {
6275
+ runId,
6276
+ firstError: firstError ?? null
6277
+ },
6278
+ context
6279
+ );
5542
6280
  }
5543
6281
  case "find_slowest_path": {
5544
6282
  const runId = String(args.runId ?? "");
@@ -5548,11 +6286,14 @@ async function callReadOnlyTool(context, name, args = {}) {
5548
6286
  slowTopN: 5
5549
6287
  });
5550
6288
  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);
5551
- return textResult({
5552
- runId,
5553
- slowest: ranked[0] ?? null,
5554
- top: ranked
5555
- });
6289
+ return deliverMcpPayload(
6290
+ {
6291
+ runId,
6292
+ slowest: ranked[0] ?? null,
6293
+ top: ranked
6294
+ },
6295
+ context
6296
+ );
5556
6297
  }
5557
6298
  case "compare_runs": {
5558
6299
  const leftRunId = String(args.leftRunId ?? "");
@@ -5563,11 +6304,14 @@ async function callReadOnlyTool(context, name, args = {}) {
5563
6304
  manualTraceEventsToComparableRun(legacyTraceEvents(left.read.events)),
5564
6305
  manualTraceEventsToComparableRun(legacyTraceEvents(right.read.events))
5565
6306
  );
5566
- return textResult({
5567
- summary: diff.summary,
5568
- differences: diff.differences.slice(0, 50),
5569
- truncated: diff.differences.length > 50
5570
- });
6307
+ return deliverMcpPayload(
6308
+ {
6309
+ summary: diff.summary,
6310
+ differences: diff.differences.slice(0, 50),
6311
+ truncated: diff.differences.length > 50
6312
+ },
6313
+ context
6314
+ );
5571
6315
  }
5572
6316
  case "run_checks": {
5573
6317
  const runId = String(args.runId ?? "");
@@ -5576,7 +6320,7 @@ async function callReadOnlyTool(context, name, args = {}) {
5576
6320
  { read },
5577
6321
  { rules: [createRunStatusRule()], select: ["run.status"], runId }
5578
6322
  );
5579
- return textResult(result);
6323
+ return deliverMcpPayload(result, context);
5580
6324
  }
5581
6325
  case "create_share_safe_report": {
5582
6326
  const runId = String(args.runId ?? "");
@@ -5586,43 +6330,55 @@ async function callReadOnlyTool(context, name, args = {}) {
5586
6330
  const profile = redactionProfileForExport(context);
5587
6331
  const markdown = exportMarkdown(run, {
5588
6332
  redacted: true});
5589
- return textResult({ runId, profile, markdown: markdown.content });
6333
+ return deliverMcpPayload({ runId, profile, markdown: markdown.content }, context);
5590
6334
  }
5591
6335
  case "summarize_failed_run": {
5592
6336
  const runId = String(args.runId ?? "");
5593
6337
  const { read } = await openRunTrace(context, runId);
5594
6338
  const traceEvents = legacyTraceEvents(read.events);
5595
6339
  const summary = buildRunWhatSummary(traceEvents);
5596
- return textResult({
5597
- runId,
5598
- status: summary.status,
5599
- summary: renderRunWhat(summary),
5600
- failedStepNames: summary.failedStepNames,
5601
- correlation: summary.correlation ?? null
5602
- });
6340
+ return deliverMcpPayload(
6341
+ {
6342
+ runId,
6343
+ status: summary.status,
6344
+ summary: renderRunWhat(summary),
6345
+ failedStepNames: summary.failedStepNames,
6346
+ correlation: summary.correlation ?? null
6347
+ },
6348
+ context
6349
+ );
5603
6350
  }
5604
6351
  case "retrieve_decision_notes": {
5605
6352
  const runId = String(args.runId ?? "");
5606
6353
  const { read } = await openRunTrace(context, runId);
5607
6354
  const notes = decisionNotes(read.events);
5608
- return textResult({ runId, decisions: notes, count: notes.length });
6355
+ return deliverMcpPayload({ runId, decisions: notes, count: notes.length }, context);
5609
6356
  }
5610
6357
  case "find_failed_observation": {
5611
6358
  const runId = String(args.runId ?? "");
5612
6359
  const { read } = await openRunTrace(context, runId);
5613
6360
  const outcomes = extractOutcomesFromTraceEvents(legacyTraceEvents(read.events));
5614
6361
  const failed = outcomes.filter((outcome) => outcome.status === "failed");
5615
- return textResult({
5616
- runId,
5617
- failed,
5618
- count: failed.length
5619
- });
6362
+ return deliverMcpPayload(
6363
+ {
6364
+ runId,
6365
+ failed,
6366
+ count: failed.length
6367
+ },
6368
+ context
6369
+ );
5620
6370
  }
5621
6371
  case "create_share_safe_bundle": {
5622
6372
  const runId = String(args.runId ?? "");
5623
6373
  const { read } = await openRunTrace(context, runId);
5624
6374
  const run = read.runs.find((item) => item.runId === runId) ?? read.runs[0];
5625
6375
  if (!run) return errorResult2(`Run tree not found: ${runId}`);
6376
+ const safety = assessTraceForMcp(read, runId);
6377
+ if (bundleFailsOnSafety(safety.status)) {
6378
+ return errorResult2(
6379
+ `Share-safe bundle refused: safety status is ${safety.status}. Resolve findings before export.`
6380
+ );
6381
+ }
5626
6382
  const profile = redactionProfileForExport(context);
5627
6383
  const markdown = exportMarkdown(run, {
5628
6384
  redacted: true});
@@ -5636,20 +6392,31 @@ async function callReadOnlyTool(context, name, args = {}) {
5636
6392
  profile,
5637
6393
  resolve: { runIds: [runId] },
5638
6394
  checks: {
5639
- aggregateStatus: "SAFE",
5640
- runs: [{ runId, status: "SAFE", errors: 0, warnings: 0, findings: 0 }]
6395
+ aggregateStatus: safety.status,
6396
+ runs: [
6397
+ {
6398
+ runId,
6399
+ status: safety.status,
6400
+ errors: safety.errors,
6401
+ warnings: safety.warnings,
6402
+ findings: safety.findings
6403
+ }
6404
+ ]
5641
6405
  },
5642
6406
  files: ["report.md", "tree.json"]
5643
6407
  });
5644
- return textResult({
5645
- runId,
5646
- profile,
5647
- metadata,
5648
- files: {
5649
- "report.md": markdown.content,
5650
- "tree.json": tree.content
5651
- }
5652
- });
6408
+ return deliverMcpPayload(
6409
+ {
6410
+ runId,
6411
+ profile,
6412
+ metadata,
6413
+ files: {
6414
+ "report.md": markdown.content,
6415
+ "tree.json": tree.content
6416
+ }
6417
+ },
6418
+ context
6419
+ );
5653
6420
  }
5654
6421
  default:
5655
6422
  return errorResult2(`Unknown tool: ${name}`);