@agent-inspect/mcp-server 6.4.0 → 6.5.0

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 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 isRecord6(value) {
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 (!isRecord6(value)) return void 0;
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 isRecord7(value) {
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 isRecord7(value) ? value : void 0;
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 (!isRecord7(value)) return false;
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 (!isRecord7(root)) return void 0;
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 (isRecord7(value)) {
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 (!isRecord7(status)) return void 0;
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 = isRecord7(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
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 (!isRecord7(value)) {
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 (isRecord7(value.arrayValue) && Array.isArray(value.arrayValue.values)) {
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 (isRecord7(value.kvlistValue) && Array.isArray(value.kvlistValue.values)) {
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 (!isRecord7(item) || typeof item.key !== "string") {
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 (!isRecord7(item) || typeof item.key !== "string") {
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 isRecord7(value) && readStringField(value, ["traceId"]) !== void 0 && readStringField(value, ["spanId"]) !== void 0 && readStringField(value, ["name"]) !== void 0;
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 (!isRecord7(root) || !Array.isArray(root.resourceSpans)) return void 0;
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 (!isRecord7(resourceSpan)) {
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 (!isRecord7(scopeSpan)) {
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 (!isRecord7(status)) return void 0;
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 (!isRecord7(event)) {
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 = isRecord7(span.status) && typeof span.status.message === "string" ? span.status.message : void 0;
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 isRecord8(value) {
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 (isRecord8(err) && typeof err.message === "string") {
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 textResult(
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 textResult({
5519
- runId,
5520
- format: read.format,
5521
- truncated: read.events.length > events.length,
5522
- events
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 textResult(results);
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 textResult({
5548
- runId,
5549
- firstError: firstError ?? null
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 textResult({
5561
- runId,
5562
- slowest: ranked[0] ?? null,
5563
- top: ranked
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 textResult({
5576
- summary: diff.summary,
5577
- differences: diff.differences.slice(0, 50),
5578
- truncated: diff.differences.length > 50
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 textResult(result);
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 textResult({ runId, profile, markdown: markdown.content });
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 textResult({
5606
- runId,
5607
- status: summary.status,
5608
- summary: renderRunWhat(summary),
5609
- failedStepNames: summary.failedStepNames,
5610
- correlation: summary.correlation ?? null
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 textResult({ runId, decisions: notes, count: notes.length });
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 textResult({
5625
- runId,
5626
- failed,
5627
- count: failed.length
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: "SAFE",
5649
- runs: [{ runId, status: "SAFE", errors: 0, warnings: 0, findings: 0 }]
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 textResult({
5654
- runId,
5655
- profile,
5656
- metadata,
5657
- files: {
5658
- "report.md": markdown.content,
5659
- "tree.json": tree.content
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}`);