@frontmcp/observability 1.9.2 → 1.9.4

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/esm/index.mjs CHANGED
@@ -704,6 +704,13 @@ var RequestLogCollector = class {
704
704
  this.status = status;
705
705
  this.statusCode = statusCode;
706
706
  }
707
+ /**
708
+ * Set the HTTP status the request was answered with; one of 400 or more marks it failed.
709
+ */
710
+ setStatusCode(statusCode) {
711
+ this.statusCode = statusCode;
712
+ if (statusCode >= 400) this.status = "error";
713
+ }
707
714
  /**
708
715
  * Set error details.
709
716
  */
@@ -772,12 +779,17 @@ var RequestLogCollector = class {
772
779
  }
773
780
  };
774
781
 
782
+ // libs/observability/src/request-log/request-log.hooks.ts
783
+ import { isAnonymousSubject } from "@frontmcp/sdk";
784
+
775
785
  // libs/observability/src/plugin/observability.hooks.ts
776
786
  import {
777
787
  context as otelContext3,
778
- SpanKind as SpanKind11,
788
+ SpanKind as SpanKind12,
789
+ SpanStatusCode as SpanStatusCode2,
779
790
  trace as trace3
780
791
  } from "@opentelemetry/api";
792
+ import { FlowControl, InternalMcpError, toMcpError } from "@frontmcp/sdk";
781
793
  import { sha256Hex } from "@frontmcp/utils";
782
794
 
783
795
  // libs/observability/src/otel/otel.types.ts
@@ -977,8 +989,35 @@ function setFetchResponseStatus(span, statusCode) {
977
989
  span.setAttribute(HttpAttributes.STATUS_CODE, statusCode);
978
990
  }
979
991
 
980
- // libs/observability/src/otel/spans/http-server.span.ts
992
+ // libs/observability/src/otel/spans/hook.span.ts
981
993
  import { SpanKind as SpanKind4 } from "@opentelemetry/api";
994
+ function recordHookEvent(parentSpan, stage, owner) {
995
+ const attributes = {};
996
+ if (owner) {
997
+ attributes[FrontMcpAttributes.HOOK_OWNER] = owner;
998
+ }
999
+ parentSpan.addEvent(`hook.${stage}`, attributes);
1000
+ }
1001
+ function startHookSpan(tracer, options) {
1002
+ const attributes = {
1003
+ [FrontMcpAttributes.HOOK_STAGE]: options.stage
1004
+ };
1005
+ if (options.owner) {
1006
+ attributes[FrontMcpAttributes.HOOK_OWNER] = options.owner;
1007
+ }
1008
+ if (options.flowName) {
1009
+ attributes[FrontMcpAttributes.FLOW_NAME] = options.flowName;
1010
+ }
1011
+ return startSpan(tracer, {
1012
+ name: `hook ${options.stage}`,
1013
+ kind: SpanKind4.INTERNAL,
1014
+ attributes,
1015
+ parentContext: options.parentContext
1016
+ });
1017
+ }
1018
+
1019
+ // libs/observability/src/otel/spans/http-server.span.ts
1020
+ import { SpanKind as SpanKind5 } from "@opentelemetry/api";
982
1021
  function startHttpServerSpan(tracer, options) {
983
1022
  const attributes = {
984
1023
  [HttpAttributes.METHOD]: options.method.toUpperCase(),
@@ -1001,7 +1040,7 @@ function startHttpServerSpan(tracer, options) {
1001
1040
  }
1002
1041
  return startSpan(tracer, {
1003
1042
  name: `${options.method.toUpperCase()} ${options.path}`,
1004
- kind: SpanKind4.SERVER,
1043
+ kind: SpanKind5.SERVER,
1005
1044
  attributes,
1006
1045
  parentContext: options.parentContext
1007
1046
  });
@@ -1011,11 +1050,11 @@ function setHttpResponseStatus(span, statusCode) {
1011
1050
  }
1012
1051
 
1013
1052
  // libs/observability/src/otel/spans/prompt.span.ts
1014
- import { SpanKind as SpanKind5 } from "@opentelemetry/api";
1053
+ import { SpanKind as SpanKind6 } from "@opentelemetry/api";
1015
1054
  function startPromptSpan(tracer, options) {
1016
1055
  return startSpan(tracer, {
1017
1056
  name: `prompt ${options.name}`,
1018
- kind: SpanKind5.INTERNAL,
1057
+ kind: SpanKind6.INTERNAL,
1019
1058
  attributes: {
1020
1059
  [FrontMcpAttributes.PROMPT_NAME]: options.name,
1021
1060
  [McpAttributes.COMPONENT_TYPE]: "prompt",
@@ -1026,7 +1065,7 @@ function startPromptSpan(tracer, options) {
1026
1065
  }
1027
1066
 
1028
1067
  // libs/observability/src/otel/spans/resource.span.ts
1029
- import { SpanKind as SpanKind6 } from "@opentelemetry/api";
1068
+ import { SpanKind as SpanKind7 } from "@opentelemetry/api";
1030
1069
  function startResourceSpan(tracer, options) {
1031
1070
  const attributes = {
1032
1071
  [FrontMcpAttributes.RESOURCE_URI]: options.uri,
@@ -1039,14 +1078,14 @@ function startResourceSpan(tracer, options) {
1039
1078
  }
1040
1079
  return startSpan(tracer, {
1041
1080
  name: `resource ${options.uri}`,
1042
- kind: SpanKind6.INTERNAL,
1081
+ kind: SpanKind7.INTERNAL,
1043
1082
  attributes,
1044
1083
  parentContext: options.parentContext
1045
1084
  });
1046
1085
  }
1047
1086
 
1048
1087
  // libs/observability/src/otel/spans/rpc.span.ts
1049
- import { SpanKind as SpanKind7 } from "@opentelemetry/api";
1088
+ import { SpanKind as SpanKind8 } from "@opentelemetry/api";
1050
1089
  function startRpcSpan(tracer, options) {
1051
1090
  const attributes = {
1052
1091
  [RpcAttributes.SYSTEM]: "mcp",
@@ -1070,14 +1109,14 @@ function startRpcSpan(tracer, options) {
1070
1109
  }
1071
1110
  return startSpan(tracer, {
1072
1111
  name: options.method,
1073
- kind: SpanKind7.SERVER,
1112
+ kind: SpanKind8.SERVER,
1074
1113
  attributes,
1075
1114
  parentContext: options.parentContext
1076
1115
  });
1077
1116
  }
1078
1117
 
1079
1118
  // libs/observability/src/otel/spans/startup.span.ts
1080
- import { SpanKind as SpanKind8 } from "@opentelemetry/api";
1119
+ import { SpanKind as SpanKind9 } from "@opentelemetry/api";
1081
1120
  function emitStartupReport(tracer, data) {
1082
1121
  const attributes = {
1083
1122
  [FrontMcpAttributes.STARTUP_TOOLS_COUNT]: data.toolsCount,
@@ -1091,14 +1130,14 @@ function emitStartupReport(tracer, data) {
1091
1130
  }
1092
1131
  const { span } = startSpan(tracer, {
1093
1132
  name: "frontmcp.startup",
1094
- kind: SpanKind8.INTERNAL,
1133
+ kind: SpanKind9.INTERNAL,
1095
1134
  attributes
1096
1135
  });
1097
1136
  endSpanOk(span);
1098
1137
  }
1099
1138
 
1100
1139
  // libs/observability/src/otel/spans/tool.span.ts
1101
- import { SpanKind as SpanKind9 } from "@opentelemetry/api";
1140
+ import { SpanKind as SpanKind10 } from "@opentelemetry/api";
1102
1141
  function startToolSpan(tracer, options) {
1103
1142
  const attributes = {
1104
1143
  [FrontMcpAttributes.TOOL_NAME]: options.name,
@@ -1116,14 +1155,14 @@ function startToolSpan(tracer, options) {
1116
1155
  }
1117
1156
  return startSpan(tracer, {
1118
1157
  name: `tool ${options.name}`,
1119
- kind: SpanKind9.INTERNAL,
1158
+ kind: SpanKind10.INTERNAL,
1120
1159
  attributes,
1121
1160
  parentContext: options.parentContext
1122
1161
  });
1123
1162
  }
1124
1163
 
1125
1164
  // libs/observability/src/otel/spans/transport.span.ts
1126
- import { SpanKind as SpanKind10 } from "@opentelemetry/api";
1165
+ import { SpanKind as SpanKind11 } from "@opentelemetry/api";
1127
1166
  function startTransportSpan(tracer, options) {
1128
1167
  const attributes = {
1129
1168
  [FrontMcpAttributes.TRANSPORT_TYPE]: options.type
@@ -1136,7 +1175,7 @@ function startTransportSpan(tracer, options) {
1136
1175
  }
1137
1176
  return startSpan(tracer, {
1138
1177
  name: `transport ${options.type}`,
1139
- kind: SpanKind10.INTERNAL,
1178
+ kind: SpanKind11.INTERNAL,
1140
1179
  attributes,
1141
1180
  parentContext: options.parentContext
1142
1181
  });
@@ -1181,6 +1220,8 @@ var EXEC_SPAN_KEY = /* @__PURE__ */ Symbol.for("frontmcp:otel:exec-span");
1181
1220
  var ACTIVE_SPAN_KEY = /* @__PURE__ */ Symbol.for("frontmcp:otel:active-span");
1182
1221
  var ACTIVE_OTEL_CTX_KEY = /* @__PURE__ */ Symbol.for("frontmcp:otel:active-otel-ctx");
1183
1222
  var STAGE_TS_KEY = /* @__PURE__ */ Symbol.for("frontmcp:otel:stage-ts");
1223
+ var STAGE_EVENTS_KEY = /* @__PURE__ */ Symbol.for("frontmcp:otel:stage-events");
1224
+ var HTTP_SPAN_CTX_KEY = /* @__PURE__ */ Symbol.for("frontmcp:otel:http-span-ctx");
1184
1225
  function sessionTracingId(sessionId) {
1185
1226
  return sha256Hex(sessionId).slice(0, 16);
1186
1227
  }
@@ -1215,7 +1256,44 @@ function clearActiveSpan(flowCtx) {
1215
1256
  } catch {
1216
1257
  }
1217
1258
  }
1259
+ function beginFlowSpan(flowCtx, options, span, spanCtx) {
1260
+ flowCtx.state[SPAN_KEY] = span;
1261
+ flowCtx.state[SPAN_CTX_KEY] = spanCtx;
1262
+ flowCtx.state[STAGE_TS_KEY] = Date.now();
1263
+ flowCtx.state[STAGE_EVENTS_KEY] = options.flowStageEvents !== false;
1264
+ }
1265
+ function parentContextOf(ctx) {
1266
+ return ctx.get?.(HTTP_SPAN_CTX_KEY) ?? createOTelContextFromTrace(ctx.traceContext);
1267
+ }
1268
+ function flowFailureOf(flowError) {
1269
+ const failed = flowError?.originalError;
1270
+ const error = flowError instanceof FlowControl ? failed instanceof Error ? failed : new InternalMcpError(`Flow ended with: ${flowError.type}`) : flowError instanceof Error ? flowError : new Error(String(flowError));
1271
+ const reported = toMcpError(error);
1272
+ return {
1273
+ error,
1274
+ type: reported.name,
1275
+ message: reported.getPublicMessage(),
1276
+ code: reported.code,
1277
+ errorId: reported.errorId
1278
+ };
1279
+ }
1280
+ function endFailedSpan(span, flowError) {
1281
+ const failure = flowFailureOf(flowError);
1282
+ span.setStatus({ code: SpanStatusCode2.ERROR, message: failure.message });
1283
+ span.recordException({
1284
+ name: failure.type,
1285
+ message: failure.message,
1286
+ code: failure.code,
1287
+ stack: failure.error.stack
1288
+ });
1289
+ span.end();
1290
+ }
1291
+ function traceparentOf(span) {
1292
+ const { traceId, spanId, traceFlags } = span.spanContext();
1293
+ return `00-${traceId}-${spanId}-${traceFlags.toString(16).padStart(2, "0")}`;
1294
+ }
1218
1295
  function recordStageEvent(span, stageName, state) {
1296
+ if (state[STAGE_EVENTS_KEY] === false) return;
1219
1297
  const now = Date.now();
1220
1298
  const prev = state[STAGE_TS_KEY];
1221
1299
  const attributes = {};
@@ -1241,9 +1319,8 @@ function onHttpWillTrace(options, flowCtx) {
1241
1319
  });
1242
1320
  span.setAttribute(McpAttributes.SESSION_ID, sessionHash);
1243
1321
  span.setAttribute(FrontMcpAttributes.SESSION_ID_HASH, sessionHash);
1244
- flowCtx.state[SPAN_KEY] = span;
1245
- flowCtx.state[SPAN_CTX_KEY] = spanCtx;
1246
- flowCtx.state[STAGE_TS_KEY] = Date.now();
1322
+ ctx.set?.(HTTP_SPAN_CTX_KEY, spanCtx);
1323
+ beginFlowSpan(flowCtx, options, span, spanCtx);
1247
1324
  recordStageEvent(span, "traceRequest", flowCtx.state);
1248
1325
  }
1249
1326
  function onHttpWillAcquireQuota(flowCtx) {
@@ -1287,7 +1364,7 @@ function onToolWillParse(options, flowCtx) {
1287
1364
  const ctx = extractContext(flowCtx);
1288
1365
  if (!ctx) return;
1289
1366
  const tracer = getTracer();
1290
- const parentOTelCtx = createOTelContextFromTrace(ctx.traceContext);
1367
+ const parentOTelCtx = parentContextOf(ctx);
1291
1368
  const sessionHash = sessionTracingId(ctx.sessionId);
1292
1369
  const { span, context: spanCtx } = startRpcSpan(tracer, {
1293
1370
  method: "tools/call",
@@ -1295,9 +1372,7 @@ function onToolWillParse(options, flowCtx) {
1295
1372
  sessionIdHash: sessionHash,
1296
1373
  parentContext: parentOTelCtx
1297
1374
  });
1298
- flowCtx.state[SPAN_KEY] = span;
1299
- flowCtx.state[SPAN_CTX_KEY] = spanCtx;
1300
- flowCtx.state[STAGE_TS_KEY] = Date.now();
1375
+ beginFlowSpan(flowCtx, options, span, spanCtx);
1301
1376
  recordStageEvent(span, "parseInput", flowCtx.state);
1302
1377
  }
1303
1378
  function onToolWillFindTool(flowCtx) {
@@ -1367,8 +1442,8 @@ function onToolDidFinalize(flowCtx) {
1367
1442
  const toolSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1368
1443
  const error = flowCtx.state?.flowError;
1369
1444
  if (error) {
1370
- if (toolSpan) endSpanError(toolSpan, error instanceof Error ? error : String(error));
1371
- if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
1445
+ if (toolSpan) endFailedSpan(toolSpan, error);
1446
+ if (rpcSpan) endFailedSpan(rpcSpan, error);
1372
1447
  } else if (rpcSpan) {
1373
1448
  recordStageEvent(rpcSpan, "finalize", flowCtx.state);
1374
1449
  endSpanOk(rpcSpan);
@@ -1379,7 +1454,7 @@ function onResourceWillParse(options, flowCtx) {
1379
1454
  const ctx = extractContext(flowCtx);
1380
1455
  if (!ctx) return;
1381
1456
  const tracer = getTracer();
1382
- const parentOTelCtx = createOTelContextFromTrace(ctx.traceContext);
1457
+ const parentOTelCtx = parentContextOf(ctx);
1383
1458
  const sessionHash = sessionTracingId(ctx.sessionId);
1384
1459
  const { span, context: spanCtx } = startRpcSpan(tracer, {
1385
1460
  method: "resources/read",
@@ -1387,9 +1462,7 @@ function onResourceWillParse(options, flowCtx) {
1387
1462
  sessionIdHash: sessionHash,
1388
1463
  parentContext: parentOTelCtx
1389
1464
  });
1390
- flowCtx.state[SPAN_KEY] = span;
1391
- flowCtx.state[SPAN_CTX_KEY] = spanCtx;
1392
- flowCtx.state[STAGE_TS_KEY] = Date.now();
1465
+ beginFlowSpan(flowCtx, options, span, spanCtx);
1393
1466
  recordStageEvent(span, "parseInput", flowCtx.state);
1394
1467
  }
1395
1468
  function onResourceWillFind(flowCtx) {
@@ -1420,8 +1493,8 @@ function onResourceDidFinalize(flowCtx) {
1420
1493
  const resSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1421
1494
  const error = flowCtx.state?.flowError;
1422
1495
  if (error) {
1423
- if (resSpan) endSpanError(resSpan, error instanceof Error ? error : String(error));
1424
- if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
1496
+ if (resSpan) endFailedSpan(resSpan, error);
1497
+ if (rpcSpan) endFailedSpan(rpcSpan, error);
1425
1498
  } else if (rpcSpan) {
1426
1499
  recordStageEvent(rpcSpan, "finalize", flowCtx.state);
1427
1500
  endSpanOk(rpcSpan);
@@ -1432,7 +1505,7 @@ function onPromptWillParse(options, flowCtx) {
1432
1505
  const ctx = extractContext(flowCtx);
1433
1506
  if (!ctx) return;
1434
1507
  const tracer = getTracer();
1435
- const parentOTelCtx = createOTelContextFromTrace(ctx.traceContext);
1508
+ const parentOTelCtx = parentContextOf(ctx);
1436
1509
  const sessionHash = sessionTracingId(ctx.sessionId);
1437
1510
  const { span, context: spanCtx } = startRpcSpan(tracer, {
1438
1511
  method: "prompts/get",
@@ -1440,9 +1513,7 @@ function onPromptWillParse(options, flowCtx) {
1440
1513
  sessionIdHash: sessionHash,
1441
1514
  parentContext: parentOTelCtx
1442
1515
  });
1443
- flowCtx.state[SPAN_KEY] = span;
1444
- flowCtx.state[SPAN_CTX_KEY] = spanCtx;
1445
- flowCtx.state[STAGE_TS_KEY] = Date.now();
1516
+ beginFlowSpan(flowCtx, options, span, spanCtx);
1446
1517
  recordStageEvent(span, "parseInput", flowCtx.state);
1447
1518
  }
1448
1519
  function onPromptWillFind(flowCtx) {
@@ -1476,8 +1547,8 @@ function onPromptDidFinalize(flowCtx) {
1476
1547
  const promptSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1477
1548
  const error = flowCtx.state?.flowError;
1478
1549
  if (error) {
1479
- if (promptSpan) endSpanError(promptSpan, error instanceof Error ? error : String(error));
1480
- if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
1550
+ if (promptSpan) endFailedSpan(promptSpan, error);
1551
+ if (rpcSpan) endFailedSpan(rpcSpan, error);
1481
1552
  } else if (rpcSpan) {
1482
1553
  recordStageEvent(rpcSpan, "finalize", flowCtx.state);
1483
1554
  endSpanOk(rpcSpan);
@@ -1488,7 +1559,7 @@ function onAgentWillParse(options, flowCtx) {
1488
1559
  const ctx = extractContext(flowCtx);
1489
1560
  if (!ctx) return;
1490
1561
  const tracer = getTracer();
1491
- const parentOTelCtx = createOTelContextFromTrace(ctx.traceContext);
1562
+ const parentOTelCtx = parentContextOf(ctx);
1492
1563
  const sessionHash = sessionTracingId(ctx.sessionId);
1493
1564
  const { span, context: spanCtx } = startRpcSpan(tracer, {
1494
1565
  method: "agents/call",
@@ -1496,9 +1567,7 @@ function onAgentWillParse(options, flowCtx) {
1496
1567
  sessionIdHash: sessionHash,
1497
1568
  parentContext: parentOTelCtx
1498
1569
  });
1499
- flowCtx.state[SPAN_KEY] = span;
1500
- flowCtx.state[SPAN_CTX_KEY] = spanCtx;
1501
- flowCtx.state[STAGE_TS_KEY] = Date.now();
1570
+ beginFlowSpan(flowCtx, options, span, spanCtx);
1502
1571
  recordStageEvent(span, "parseInput", flowCtx.state);
1503
1572
  }
1504
1573
  function onAgentWillFind(flowCtx) {
@@ -1512,7 +1581,7 @@ function onAgentWillExecute(options, flowCtx) {
1512
1581
  const agentName = flowCtx.state?.input?.name ?? "unknown";
1513
1582
  const { span: agentSpan } = startSpan(tracer, {
1514
1583
  name: `agent ${agentName}`,
1515
- kind: SpanKind11.INTERNAL,
1584
+ kind: SpanKind12.INTERNAL,
1516
1585
  attributes: {
1517
1586
  [FrontMcpAttributes.FLOW_NAME]: "agents:call-agent",
1518
1587
  [McpAttributes.COMPONENT_TYPE]: "agent",
@@ -1539,8 +1608,8 @@ function onAgentDidFinalize(flowCtx) {
1539
1608
  const agentSpan = flowCtx.state?.[EXEC_SPAN_KEY];
1540
1609
  const error = flowCtx.state?.flowError;
1541
1610
  if (error) {
1542
- if (agentSpan) endSpanError(agentSpan, error instanceof Error ? error : String(error));
1543
- if (rpcSpan) endSpanError(rpcSpan, error instanceof Error ? error : String(error));
1611
+ if (agentSpan) endFailedSpan(agentSpan, error);
1612
+ if (rpcSpan) endFailedSpan(rpcSpan, error);
1544
1613
  } else if (rpcSpan) {
1545
1614
  recordStageEvent(rpcSpan, "finalize", flowCtx.state);
1546
1615
  endSpanOk(rpcSpan);
@@ -1551,11 +1620,11 @@ function onGenericFlowWillStart(flowName, options, flowCtx) {
1551
1620
  const ctx = extractContext(flowCtx);
1552
1621
  if (!ctx) return;
1553
1622
  const tracer = getTracer();
1554
- const parentOTelCtx = createOTelContextFromTrace(ctx.traceContext);
1623
+ const parentOTelCtx = parentContextOf(ctx);
1555
1624
  const sessionHash = sessionTracingId(ctx.sessionId);
1556
1625
  const { span, context: spanCtx } = startSpan(tracer, {
1557
1626
  name: flowName,
1558
- kind: SpanKind11.INTERNAL,
1627
+ kind: SpanKind12.INTERNAL,
1559
1628
  attributes: {
1560
1629
  [FrontMcpAttributes.FLOW_NAME]: flowName,
1561
1630
  [McpAttributes.SESSION_ID]: sessionHash,
@@ -1565,9 +1634,7 @@ function onGenericFlowWillStart(flowName, options, flowCtx) {
1565
1634
  },
1566
1635
  parentContext: parentOTelCtx
1567
1636
  });
1568
- flowCtx.state[SPAN_KEY] = span;
1569
- flowCtx.state[SPAN_CTX_KEY] = spanCtx;
1570
- flowCtx.state[STAGE_TS_KEY] = Date.now();
1637
+ beginFlowSpan(flowCtx, options, span, spanCtx);
1571
1638
  }
1572
1639
  function onGenericFlowStage(stageName, flowCtx) {
1573
1640
  const span = flowCtx.state?.[SPAN_KEY];
@@ -1578,7 +1645,7 @@ function onGenericFlowDidFinalize(flowCtx) {
1578
1645
  if (!span) return;
1579
1646
  const error = flowCtx.state?.flowError;
1580
1647
  if (error) {
1581
- endSpanError(span, error instanceof Error ? error : String(error));
1648
+ endFailedSpan(span, error);
1582
1649
  } else {
1583
1650
  endSpanOk(span);
1584
1651
  }
@@ -1588,16 +1655,14 @@ function onTransportWillStart(transportType, options, flowCtx) {
1588
1655
  const ctx = extractContext(flowCtx);
1589
1656
  if (!ctx) return;
1590
1657
  const tracer = getTracer();
1591
- const parentOTelCtx = createOTelContextFromTrace(ctx.traceContext);
1658
+ const parentOTelCtx = parentContextOf(ctx);
1592
1659
  const sessionHash = sessionTracingId(ctx.sessionId);
1593
1660
  const { span, context: spanCtx } = startTransportSpan(tracer, {
1594
1661
  type: transportType,
1595
1662
  sessionIdHash: sessionHash,
1596
1663
  parentContext: parentOTelCtx
1597
1664
  });
1598
- flowCtx.state[SPAN_KEY] = span;
1599
- flowCtx.state[SPAN_CTX_KEY] = spanCtx;
1600
- flowCtx.state[STAGE_TS_KEY] = Date.now();
1665
+ beginFlowSpan(flowCtx, options, span, spanCtx);
1601
1666
  recordStageEvent(span, "parseInput", flowCtx.state);
1602
1667
  }
1603
1668
  function onTransportDidRoute(flowCtx) {
@@ -1617,7 +1682,7 @@ function onTransportDidFinalize(flowCtx) {
1617
1682
  recordStageEvent(span, "cleanup", flowCtx.state);
1618
1683
  const error = flowCtx.state?.flowError;
1619
1684
  if (error) {
1620
- endSpanError(span, error instanceof Error ? error : String(error));
1685
+ endFailedSpan(span, error);
1621
1686
  } else {
1622
1687
  endSpanOk(span);
1623
1688
  }
@@ -1627,14 +1692,12 @@ function onAuthWillStart(flowName, options, flowCtx) {
1627
1692
  const ctx = extractContext(flowCtx);
1628
1693
  if (!ctx) return;
1629
1694
  const tracer = getTracer();
1630
- const parentOTelCtx = createOTelContextFromTrace(ctx.traceContext);
1695
+ const parentOTelCtx = parentContextOf(ctx);
1631
1696
  const { span, context: spanCtx } = startAuthSpan(tracer, {
1632
1697
  flowName,
1633
1698
  parentContext: parentOTelCtx
1634
1699
  });
1635
- flowCtx.state[SPAN_KEY] = span;
1636
- flowCtx.state[SPAN_CTX_KEY] = spanCtx;
1637
- flowCtx.state[STAGE_TS_KEY] = Date.now();
1700
+ beginFlowSpan(flowCtx, options, span, spanCtx);
1638
1701
  recordStageEvent(span, "parseInput", flowCtx.state);
1639
1702
  }
1640
1703
  function onAuthDidDetermineMode(flowCtx) {
@@ -1654,30 +1717,35 @@ function onAuthDidFinalize(flowCtx) {
1654
1717
  const error = flowCtx.state?.flowError;
1655
1718
  if (error) {
1656
1719
  setAuthResult(span, "unauthorized");
1657
- endSpanError(span, error instanceof Error ? error : String(error));
1720
+ endFailedSpan(span, error);
1658
1721
  } else {
1659
1722
  setAuthResult(span, "authorized");
1660
1723
  endSpanOk(span);
1661
1724
  }
1662
1725
  }
1663
1726
  function wrapContextFetch(options, flowCtx) {
1664
- if (options.fetchSpans === false) return;
1665
1727
  const toolCtx = flowCtx.state?.toolContext ?? flowCtx.state?.toolCallContext;
1666
1728
  if (!toolCtx || typeof toolCtx.fetch !== "function") return;
1667
1729
  const originalFetch = toolCtx.fetch.bind(toolCtx);
1668
1730
  const parentOTelCtx = flowCtx.state?.[SPAN_CTX_KEY];
1669
1731
  toolCtx.fetch = async (input, init) => {
1670
- const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
1671
- const method = init?.method ?? "GET";
1672
- const tracer = getTracer();
1673
- const { span } = startFetchSpan(tracer, { method, url, parentContext: parentOTelCtx });
1732
+ const fetchSpan = options.fetchSpans === false ? void 0 : startFetchSpan(getTracer(), {
1733
+ method: init?.method ?? (input instanceof Request ? input.method : "GET"),
1734
+ url: typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url,
1735
+ parentContext: parentOTelCtx
1736
+ }).span;
1737
+ const callingSpan = fetchSpan ?? flowCtx.state?.[EXEC_SPAN_KEY] ?? flowCtx.state?.[SPAN_KEY];
1738
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
1739
+ if (callingSpan && !headers.has("traceparent")) headers.set("traceparent", traceparentOf(callingSpan));
1674
1740
  try {
1675
- const response = await originalFetch(input, init);
1676
- setFetchResponseStatus(span, response.status);
1677
- endSpanOk(span);
1741
+ const response = await originalFetch(input, { ...init, headers });
1742
+ if (fetchSpan) {
1743
+ setFetchResponseStatus(fetchSpan, response.status);
1744
+ endSpanOk(fetchSpan);
1745
+ }
1678
1746
  return response;
1679
1747
  } catch (err) {
1680
- endSpanError(span, err instanceof Error ? err : String(err));
1748
+ if (fetchSpan) endSpanError(fetchSpan, err instanceof Error ? err : String(err));
1681
1749
  throw err;
1682
1750
  }
1683
1751
  };
@@ -1695,6 +1763,26 @@ function onAgentDidExecuteEnrich(flowCtx) {
1695
1763
  }
1696
1764
  }
1697
1765
  }
1766
+ var HOOK_OBSERVER_KEY = /* @__PURE__ */ Symbol.for("frontmcp:hook-observer");
1767
+ function installHookObserver(flowCtx, observer) {
1768
+ const ctx = extractContext(flowCtx);
1769
+ if (ctx && !ctx.get?.(HOOK_OBSERVER_KEY)) ctx.set?.(HOOK_OBSERVER_KEY, observer);
1770
+ }
1771
+ async function runInHookSpan(flowCtx, hook, run) {
1772
+ const ctx = extractContext(flowCtx);
1773
+ const { span } = startHookSpan(getTracer(), {
1774
+ ...hook,
1775
+ parentContext: flowCtx.state?.[SPAN_CTX_KEY] ?? (ctx && parentContextOf(ctx))
1776
+ });
1777
+ try {
1778
+ await run();
1779
+ endSpanOk(span);
1780
+ } catch (error) {
1781
+ if (error instanceof FlowControl && error.type !== "fail" && error.type !== "abort") endSpanOk(span);
1782
+ else endFailedSpan(span, error);
1783
+ throw error;
1784
+ }
1785
+ }
1698
1786
  function reportStartup(data) {
1699
1787
  const tracer = getTracer();
1700
1788
  emitStartupReport(tracer, data);
@@ -1734,27 +1822,34 @@ function startRequestLog(flowCtx, options) {
1734
1822
  if (typeof rpcMethod === "string") collector.setRpcMethod(rpcMethod);
1735
1823
  }
1736
1824
  function recordRequestLogFailure(flowCtx) {
1737
- const failure = flowCtx.state?.["flowError"];
1738
- if (!failure) return;
1739
- const error = failure instanceof Error ? failure : new Error(String(failure));
1740
- currentRequestLog(flowCtx)?.setError({
1741
- type: error.name,
1742
- message: error.message,
1743
- code: error.code
1744
- });
1825
+ const flowError = flowCtx.state?.["flowError"];
1826
+ if (!flowError) return;
1827
+ const { type, message, code, errorId } = flowFailureOf(flowError);
1828
+ currentRequestLog(flowCtx)?.setError({ type, message, code, error_id: errorId });
1829
+ }
1830
+ function recordRequestLogAuth(flowCtx) {
1831
+ const verifyResult = flowCtx.state?.["verifyResult"];
1832
+ const authorization = flowCtx.rawInput?.request?.headers?.["authorization"];
1833
+ const scheme = typeof authorization === "string" ? authorization.trim().split(/\s+/)[0].toLowerCase() : "";
1834
+ const sub = verifyResult?.authorization?.user?.sub;
1835
+ const anonymous = typeof sub !== "string" || isAnonymousSubject(sub);
1836
+ currentRequestLog(flowCtx)?.setAuthInfo(
1837
+ anonymous || !scheme ? "anonymous" : scheme,
1838
+ verifyResult?.kind === "authorized" && !anonymous
1839
+ );
1745
1840
  }
1746
1841
  async function completeRequestLog(flowCtx) {
1747
1842
  const collector = currentRequestLog(flowCtx);
1748
1843
  if (!collector || collector.isFinalized()) return;
1749
1844
  const statusCode = flowCtx.state?.["statusCode"] ?? flowCtx.rawInput?.response?.statusCode;
1750
- if (typeof statusCode === "number" && statusCode >= 400) collector.setStatus("error", statusCode);
1845
+ if (typeof statusCode === "number") collector.setStatusCode(statusCode);
1751
1846
  await collector.finalize();
1752
1847
  }
1753
1848
 
1754
1849
  // libs/observability/src/telemetry/telemetry.accessor.ts
1755
1850
  import {
1756
- SpanKind as SpanKind12,
1757
- SpanStatusCode as SpanStatusCode2,
1851
+ SpanKind as SpanKind13,
1852
+ SpanStatusCode as SpanStatusCode3,
1758
1853
  trace as trace4
1759
1854
  } from "@opentelemetry/api";
1760
1855
 
@@ -1873,14 +1968,14 @@ var TelemetrySpan = class {
1873
1968
  /** Record an error on this span */
1874
1969
  recordError(error) {
1875
1970
  this.span.recordException(error);
1876
- this.span.setStatus({ code: SpanStatusCode2.ERROR, message: error.message });
1971
+ this.span.setStatus({ code: SpanStatusCode3.ERROR, message: error.message });
1877
1972
  this.hasError = true;
1878
1973
  return this;
1879
1974
  }
1880
1975
  /** End the span (preserves ERROR status if already set) */
1881
1976
  end() {
1882
1977
  if (!this.hasError) {
1883
- this.span.setStatus({ code: SpanStatusCode2.OK });
1978
+ this.span.setStatus({ code: SpanStatusCode3.OK });
1884
1979
  }
1885
1980
  this.span.end();
1886
1981
  }
@@ -1888,7 +1983,7 @@ var TelemetrySpan = class {
1888
1983
  endWithError(error) {
1889
1984
  const msg = typeof error === "string" ? error : error.message;
1890
1985
  if (error instanceof Error) this.span.recordException(error);
1891
- this.span.setStatus({ code: SpanStatusCode2.ERROR, message: msg });
1986
+ this.span.setStatus({ code: SpanStatusCode3.ERROR, message: msg });
1892
1987
  this.span.end();
1893
1988
  }
1894
1989
  /** Get the underlying OTel span (escape hatch) */
@@ -1935,7 +2030,7 @@ var TelemetryAccessor = class {
1935
2030
  const span = this.tracer.startSpan(
1936
2031
  name,
1937
2032
  {
1938
- kind: SpanKind12.INTERNAL,
2033
+ kind: SpanKind13.INTERNAL,
1939
2034
  attributes: { ...this.baseAttributes, ...attributes }
1940
2035
  },
1941
2036
  this.getActiveContext()
@@ -1984,11 +2079,11 @@ var TelemetryAccessor = class {
1984
2079
  } else {
1985
2080
  const span = this.tracer.startSpan(
1986
2081
  name,
1987
- { kind: SpanKind12.INTERNAL, attributes: this.baseAttributes },
2082
+ { kind: SpanKind13.INTERNAL, attributes: this.baseAttributes },
1988
2083
  this.fallbackContext
1989
2084
  );
1990
2085
  if (attributes) span.addEvent(name, attributes);
1991
- span.setStatus({ code: SpanStatusCode2.OK });
2086
+ span.setStatus({ code: SpanStatusCode3.OK });
1992
2087
  span.end();
1993
2088
  }
1994
2089
  }
@@ -2036,7 +2131,7 @@ var TelemetryAccessor = class {
2036
2131
  };
2037
2132
 
2038
2133
  // libs/observability/src/telemetry/telemetry.factory.ts
2039
- import { SpanStatusCode as SpanStatusCode3, trace as trace5 } from "@opentelemetry/api";
2134
+ import { SpanStatusCode as SpanStatusCode4, trace as trace5 } from "@opentelemetry/api";
2040
2135
  var TelemetryFactory = class {
2041
2136
  constructor(tracerName = "@frontmcp/observability") {
2042
2137
  this.tracer = trace5.getTracer(tracerName);
@@ -2127,6 +2222,14 @@ var DEFAULT_TRACING = {
2127
2222
  var ObservabilityPlugin = class extends DynamicPlugin {
2128
2223
  constructor(options = {}) {
2129
2224
  super();
2225
+ /** Wraps each other hook the request runs: names it on the request's log, and gives it a span with `hookSpans`. */
2226
+ this.observeHook = async ({ flowName, stage, target, flowContext }, run) => {
2227
+ if (target === this) return run();
2228
+ if (this.requestLogsEnabled) currentRequestLog(flowContext)?.addHook(`${flowName}:${stage}`);
2229
+ if (!this.traces("hookSpans")) return run();
2230
+ const owner = target?.constructor?.name;
2231
+ return runInHookSpan(flowContext, { flowName, stage, owner }, run);
2232
+ };
2130
2233
  this.options = resolveOptions(options);
2131
2234
  this.tracingOpts = this.options.tracing !== false ? this.options.tracing : DEFAULT_TRACING;
2132
2235
  }
@@ -2136,6 +2239,15 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2136
2239
  get requestLogsEnabled() {
2137
2240
  return this.options.requestLogs !== false;
2138
2241
  }
2242
+ /** Whether tracing is on and its `kind` option isn't off. */
2243
+ traces(kind) {
2244
+ return this.tracingEnabled && this.tracingOpts[kind] !== false;
2245
+ }
2246
+ /** Watches the hooks of the request `ctx` serves, from the first flow of it that this plugin sees. */
2247
+ watchHooks(ctx) {
2248
+ if (this.requestLogsEnabled || this.traces("hookSpans"))
2249
+ installHookObserver(ctx, this.observeHook);
2250
+ }
2139
2251
  /** Names the entry a request serves on its request log, and records the entry flow's failure. */
2140
2252
  annotateRequestLog(ctx, set) {
2141
2253
  if (!this.requestLogsEnabled) return;
@@ -2146,6 +2258,7 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2146
2258
  _httpWillTrace(ctx) {
2147
2259
  if (this.tracingEnabled) onHttpWillTrace(this.tracingOpts, ctx);
2148
2260
  if (this.options.requestLogs !== false) startRequestLog(ctx, this.options.requestLogs);
2261
+ this.watchHooks(ctx);
2149
2262
  }
2150
2263
  _httpWillQuota(ctx) {
2151
2264
  if (this.tracingEnabled) onHttpWillAcquireQuota(ctx);
@@ -2158,6 +2271,7 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2158
2271
  }
2159
2272
  _httpDidAuth(ctx) {
2160
2273
  if (this.tracingEnabled) onHttpDidCheckAuth(ctx);
2274
+ if (this.requestLogsEnabled) recordRequestLogAuth(ctx);
2161
2275
  }
2162
2276
  _httpWillRoute(ctx) {
2163
2277
  if (this.tracingEnabled) onHttpWillRoute(ctx);
@@ -2170,6 +2284,7 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2170
2284
  if (this.requestLogsEnabled) void completeRequestLog(ctx);
2171
2285
  }
2172
2286
  _toolWillParse(ctx) {
2287
+ this.watchHooks(ctx);
2173
2288
  if (this.tracingEnabled) onToolWillParse(this.tracingOpts, ctx);
2174
2289
  }
2175
2290
  _toolWillFind(ctx) {
@@ -2208,6 +2323,7 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2208
2323
  if (this.requestLogsEnabled) recordRequestLogFailure(ctx);
2209
2324
  }
2210
2325
  _resourceWillParse(ctx) {
2326
+ this.watchHooks(ctx);
2211
2327
  if (this.tracingEnabled) onResourceWillParse(this.tracingOpts, ctx);
2212
2328
  }
2213
2329
  _resourceWillFind(ctx) {
@@ -2231,6 +2347,7 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2231
2347
  if (this.requestLogsEnabled) recordRequestLogFailure(ctx);
2232
2348
  }
2233
2349
  _promptWillParse(ctx) {
2350
+ this.watchHooks(ctx);
2234
2351
  if (this.tracingEnabled) onPromptWillParse(this.tracingOpts, ctx);
2235
2352
  }
2236
2353
  _promptWillFind(ctx) {
@@ -2254,6 +2371,7 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2254
2371
  if (this.requestLogsEnabled) recordRequestLogFailure(ctx);
2255
2372
  }
2256
2373
  _agentWillParse(ctx) {
2374
+ this.watchHooks(ctx);
2257
2375
  if (this.tracingEnabled) onAgentWillParse(this.tracingOpts, ctx);
2258
2376
  }
2259
2377
  _agentWillFind(ctx) {
@@ -2407,46 +2525,46 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2407
2525
  if (this.tracingEnabled) onAuthDidFinalize(ctx);
2408
2526
  }
2409
2527
  _oauthTokenWill(ctx) {
2410
- if (this.tracingEnabled) onGenericFlowWillStart("oauth/token", this.tracingOpts, ctx);
2528
+ if (this.traces("oauthSpans")) onGenericFlowWillStart("oauth/token", this.tracingOpts, ctx);
2411
2529
  }
2412
2530
  _oauthTokenDone(ctx) {
2413
- if (this.tracingEnabled) onGenericFlowDidFinalize(ctx);
2531
+ if (this.traces("oauthSpans")) onGenericFlowDidFinalize(ctx);
2414
2532
  }
2415
2533
  _oauthAuthzWill(ctx) {
2416
- if (this.tracingEnabled) onGenericFlowWillStart("oauth/authorize", this.tracingOpts, ctx);
2534
+ if (this.traces("oauthSpans")) onGenericFlowWillStart("oauth/authorize", this.tracingOpts, ctx);
2417
2535
  }
2418
2536
  _oauthAuthzDone(ctx) {
2419
- if (this.tracingEnabled) onGenericFlowDidFinalize(ctx);
2537
+ if (this.traces("oauthSpans")) onGenericFlowDidFinalize(ctx);
2420
2538
  }
2421
2539
  _oauthCallbackWill(ctx) {
2422
- if (this.tracingEnabled) onGenericFlowWillStart("oauth/callback", this.tracingOpts, ctx);
2540
+ if (this.traces("oauthSpans")) onGenericFlowWillStart("oauth/callback", this.tracingOpts, ctx);
2423
2541
  }
2424
2542
  _oauthCallbackDone(ctx) {
2425
- if (this.tracingEnabled) onGenericFlowDidFinalize(ctx);
2543
+ if (this.traces("oauthSpans")) onGenericFlowDidFinalize(ctx);
2426
2544
  }
2427
2545
  _oauthProviderWill(ctx) {
2428
- if (this.tracingEnabled) onGenericFlowWillStart("oauth/provider-callback", this.tracingOpts, ctx);
2546
+ if (this.traces("oauthSpans")) onGenericFlowWillStart("oauth/provider-callback", this.tracingOpts, ctx);
2429
2547
  }
2430
2548
  _oauthProviderDone(ctx) {
2431
- if (this.tracingEnabled) onGenericFlowDidFinalize(ctx);
2549
+ if (this.traces("oauthSpans")) onGenericFlowDidFinalize(ctx);
2432
2550
  }
2433
2551
  _oauthRegisterWill(ctx) {
2434
- if (this.tracingEnabled) onGenericFlowWillStart("oauth/register", this.tracingOpts, ctx);
2552
+ if (this.traces("oauthSpans")) onGenericFlowWillStart("oauth/register", this.tracingOpts, ctx);
2435
2553
  }
2436
2554
  _oauthRegisterDone(ctx) {
2437
- if (this.tracingEnabled) onGenericFlowDidFinalize(ctx);
2555
+ if (this.traces("oauthSpans")) onGenericFlowDidFinalize(ctx);
2438
2556
  }
2439
2557
  _elicitReqWill(ctx) {
2440
- if (this.tracingEnabled) onGenericFlowWillStart("elicitation/request", this.tracingOpts, ctx);
2558
+ if (this.traces("elicitationSpans")) onGenericFlowWillStart("elicitation/request", this.tracingOpts, ctx);
2441
2559
  }
2442
2560
  _elicitReqDone(ctx) {
2443
- if (this.tracingEnabled) onGenericFlowDidFinalize(ctx);
2561
+ if (this.traces("elicitationSpans")) onGenericFlowDidFinalize(ctx);
2444
2562
  }
2445
2563
  _elicitResWill(ctx) {
2446
- if (this.tracingEnabled) onGenericFlowWillStart("elicitation/result", this.tracingOpts, ctx);
2564
+ if (this.traces("elicitationSpans")) onGenericFlowWillStart("elicitation/result", this.tracingOpts, ctx);
2447
2565
  }
2448
2566
  _elicitResDone(ctx) {
2449
- if (this.tracingEnabled) onGenericFlowDidFinalize(ctx);
2567
+ if (this.traces("elicitationSpans")) onGenericFlowDidFinalize(ctx);
2450
2568
  }
2451
2569
  _subscribeWill(ctx) {
2452
2570
  if (this.tracingEnabled) onGenericFlowWillStart("resources/subscribe", this.tracingOpts, ctx);
@@ -2512,12 +2630,10 @@ var ObservabilityPlugin = class extends DynamicPlugin {
2512
2630
  // Startup Report (public method)
2513
2631
  // ═══════════════════════════════════════════════════════════════════════════
2514
2632
  /**
2515
- * Emit startup telemetry report.
2516
- * Call after scope.initialize() completes with component counts.
2633
+ * Emit the `frontmcp.startup` span (`tracing.startupReport`). The scope calls it once it is ready.
2517
2634
  */
2518
2635
  reportStartupTelemetry(data) {
2519
- if (this.options.tracing === false) return;
2520
- reportStartup(data);
2636
+ if (this.traces("startupReport")) reportStartup(data);
2521
2637
  }
2522
2638
  };
2523
2639
  ObservabilityPlugin.defaultOptions = {
@@ -3084,35 +3200,8 @@ function setupOTel(options) {
3084
3200
  }
3085
3201
  }
3086
3202
 
3087
- // libs/observability/src/otel/spans/hook.span.ts
3088
- import { SpanKind as SpanKind13 } from "@opentelemetry/api";
3089
- function recordHookEvent(parentSpan, stage, owner) {
3090
- const attributes = {};
3091
- if (owner) {
3092
- attributes[FrontMcpAttributes.HOOK_OWNER] = owner;
3093
- }
3094
- parentSpan.addEvent(`hook.${stage}`, attributes);
3095
- }
3096
- function startHookSpan(tracer, options) {
3097
- const attributes = {
3098
- [FrontMcpAttributes.HOOK_STAGE]: options.stage
3099
- };
3100
- if (options.owner) {
3101
- attributes[FrontMcpAttributes.HOOK_OWNER] = options.owner;
3102
- }
3103
- if (options.flowName) {
3104
- attributes[FrontMcpAttributes.FLOW_NAME] = options.flowName;
3105
- }
3106
- return startSpan(tracer, {
3107
- name: `hook ${options.stage}`,
3108
- kind: SpanKind13.INTERNAL,
3109
- attributes,
3110
- parentContext: options.parentContext
3111
- });
3112
- }
3113
-
3114
3203
  // libs/observability/src/otel/spans/pretty-span-exporter.ts
3115
- import { SpanKind as SpanKind14, SpanStatusCode as SpanStatusCode4 } from "@opentelemetry/api";
3204
+ import { SpanKind as SpanKind14, SpanStatusCode as SpanStatusCode5 } from "@opentelemetry/api";
3116
3205
  var RESET = "\x1B[0m";
3117
3206
  var DIM = "\x1B[2m";
3118
3207
  var BOLD = "\x1B[1m";
@@ -3164,7 +3253,7 @@ var PrettySpanExporter = class {
3164
3253
  const traceShort = span.spanContext().traceId.slice(0, 8);
3165
3254
  const durationMs = (span.duration[0] * 1e3 + span.duration[1] / 1e6).toFixed(1);
3166
3255
  const kind = KIND_LABELS[span.kind] ?? "UNKNOWN";
3167
- const statusOk = span.status.code !== SpanStatusCode4.ERROR;
3256
+ const statusOk = span.status.code !== SpanStatusCode5.ERROR;
3168
3257
  const attrs = Object.entries(span.attributes).filter(([k]) => SHOW_ATTRS.has(k)).map(([k, v]) => {
3169
3258
  const short = k.replace("frontmcp.", "").replace("mcp.", "");
3170
3259
  return `${short}=${v}`;
@@ -3353,7 +3442,7 @@ var ProcessStatsCollector = class {
3353
3442
  });
3354
3443
  }
3355
3444
  const mem = probeSafely(() => this.memoryUsage());
3356
- if (mem) {
3445
+ if (mem && mem.rss > 0) {
3357
3446
  entries.push({
3358
3447
  name: "frontmcp_process_resident_memory_bytes",
3359
3448
  value: mem.rss,
@@ -3376,7 +3465,7 @@ var ProcessStatsCollector = class {
3376
3465
  });
3377
3466
  }
3378
3467
  const uptime = probeSafely(() => this.uptime());
3379
- if (uptime !== void 0) {
3468
+ if (uptime !== void 0 && uptime > 0) {
3380
3469
  entries.push({
3381
3470
  name: "frontmcp_process_uptime_seconds",
3382
3471
  value: uptime,