@frontmcp/observability 1.9.6 → 1.10.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/README.md CHANGED
@@ -1,21 +1,8 @@
1
1
  # @frontmcp/observability
2
2
 
3
- OpenTelemetry instrumentation, structured JSON logging, and Prometheus metrics
4
- for FrontMCP servers.
3
+ OpenTelemetry tracing, structured JSON logs, per-request log objects and counters for FrontMCP servers. Turn it on with `@FrontMcp({ observability })`: each request gets a span, and so does each tool call, resource read and prompt it serves, with no change to your tool code. It also holds the counters and the Prometheus serializer behind the `/metrics` endpoint that `@FrontMcp({ metrics })` serves.
5
4
 
6
- [![NPM](https://img.shields.io/npm/v/@frontmcp/observability.svg)](https://www.npmjs.com/package/@frontmcp/observability)
7
-
8
- ## What you get
9
-
10
- Install the plugin and every MCP request produces a trace span, a structured log
11
- line, and counters — without touching your tool code.
12
-
13
- - **Traces** — W3C trace context propagated across the request pipeline, with
14
- MCP-specific span attributes (tool name, session, transport, RPC method).
15
- - **Logs** — one structured JSON object per request; ready for Datadog, Loki,
16
- CloudWatch, or anything that reads JSON from stdout.
17
- - **Metrics** — counters and gauges exposed in Prometheus or JSON format.
18
- - **Process stats** — memory, event-loop lag, and uptime.
5
+ [![npm](https://img.shields.io/npm/v/@frontmcp/observability.svg)](https://www.npmjs.com/package/@frontmcp/observability) [![License](https://img.shields.io/npm/l/@frontmcp/observability.svg)](https://github.com/agentfront/frontmcp/blob/main/LICENSE)
19
6
 
20
7
  ## Install
21
8
 
@@ -23,84 +10,123 @@ line, and counters — without touching your tool code.
23
10
  npm install @frontmcp/observability
24
11
  ```
25
12
 
13
+ Requires Node.js 24 or later, and `@frontmcp/sdk` in the app (a peer dependency, as is `@frontmcp/utils`, which comes with the SDK). `@opentelemetry/api` is installed with the package.
14
+
15
+ The optional peers each enable one thing:
16
+
17
+ | Package | Enables |
18
+ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
19
+ | `@opentelemetry/sdk-trace-base` | Printing spans in development, `setupOTel({ exporter: 'console' })` and `createTestTracer()` |
20
+ | `@opentelemetry/sdk-node` | `setupOTel()`, which also needs `@opentelemetry/resources` and `@opentelemetry/semantic-conventions` |
21
+ | `@opentelemetry/exporter-trace-otlp-http` | Sending spans to an OpenTelemetry collector over OTLP (the OpenTelemetry protocol), such as `setupOTel({ exporter: 'otlp' })` |
22
+ | `pino`, `winston` | The `pino` and `winston` log sinks, which write to a logger you create |
23
+
26
24
  ## Usage
27
25
 
28
26
  ```ts
29
- import { ObservabilityPlugin } from '@frontmcp/observability';
30
- import { FrontMcp } from '@frontmcp/sdk';
27
+ import { createCounter, type RequestLog } from '@frontmcp/observability';
28
+ import { App, FrontMcp, Tool, ToolContext, z } from '@frontmcp/sdk';
29
+
30
+ const ticketsClosed = createCounter('helpdesk_tickets_closed_total', 'Tickets closed');
31
+
32
+ @Tool({ name: 'close_ticket', description: 'Close a support ticket', inputSchema: { id: z.string() } })
33
+ class CloseTicket extends ToolContext {
34
+ async execute({ id }: { id: string }) {
35
+ await this.telemetry.withSpan('tickets.close', async (span) => {
36
+ span.setAttribute('ticket.id', id);
37
+ });
38
+ ticketsClosed.inc();
39
+ return { id, status: 'closed' };
40
+ }
41
+ }
42
+
43
+ @App({ id: 'help-desk', name: 'Help Desk', tools: [CloseTicket] })
44
+ class HelpDeskApp {}
31
45
 
32
46
  @FrontMcp({
33
- info: { name: 'my-server', version: '1.0.0' },
34
- apps: [MyApp],
35
- plugins: [ObservabilityPlugin],
47
+ info: { name: 'help-desk', version: '1.0.0' },
48
+ apps: [HelpDeskApp],
49
+ observability: {
50
+ tracing: true,
51
+ logging: { sinks: [{ type: 'stdout' }], redactFields: ['password', 'token', 'authorization'] },
52
+ requestLogs: {
53
+ onRequestComplete: (entry) => {
54
+ const log = entry as RequestLog;
55
+ if (log.status === 'error') console.error(log.request_id, log.error);
56
+ },
57
+ },
58
+ },
59
+ metrics: { enabled: true, auth: 'token' }, // GET /metrics, with the bearer token in FRONTMCP_METRICS_TOKEN
36
60
  })
37
- class Server {}
61
+ export default class Server {}
38
62
  ```
39
63
 
40
- ### Configure
64
+ - **Spans** follow each request: `POST /` (or your entry path), then `tools/call`, then `tool close_ticket`, with your `tickets.close` span inside it and a client span for each outbound `this.fetch()`. They carry MCP attributes such as `mcp.method.name`, `frontmcp.tool.name` and a hash of the session id, never the id itself.
65
+ - **Logs**: each line of the server's log becomes one JSON object with the request's trace and request ids, sent to every sink in `logging.sinks`.
66
+ - **Request logs**: `onRequestComplete` gets one object per HTTP request, with its timing, what it called, its status and error, and the log lines written during it.
67
+ - **Metrics**: `/metrics` serves process gauges (memory, CPU, event-loop lag) and every counter, in the Prometheus text format, or JSON with `format: 'json'`.
41
68
 
42
- ```ts
43
- plugins: [
44
- ObservabilityPlugin.configure({
45
- logging: { level: 'info', includeRequestBody: false },
46
- otel: { serviceName: 'my-server', endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT },
47
- }),
48
- ];
49
- ```
69
+ `@FrontMcp({ observability: true })` turns on tracing alone. Without the package installed, a server with `observability` starts anyway and logs one warning; a server with `metrics.enabled` doesn't start.
50
70
 
51
- <!-- prettier-ignore -->
52
- > Do not log request bodies in production unless you have reviewed them for PII —
53
- > tool arguments frequently carry user data.
71
+ ### Sending spans somewhere
54
72
 
55
- ## Exposing metrics
73
+ The plugin records spans through OpenTelemetry's global tracer provider, and FrontMCP doesn't register one for you (in development it prints each span to the console). Register a provider before the server handles requests. `setupOTel()` does it with OpenTelemetry's Node SDK:
56
74
 
57
75
  ```ts
58
- import { PROMETHEUS_CONTENT_TYPE, renderPrometheusExposition } from '@frontmcp/observability';
59
-
60
- http: {
61
- routes: [
62
- {
63
- method: 'GET',
64
- path: '/metrics',
65
- handler: (_req, res) => {
66
- res.setHeader('Content-Type', PROMETHEUS_CONTENT_TYPE);
67
- res.status(200).send(renderPrometheusExposition());
68
- },
69
- },
70
- ];
71
- }
76
+ import { setupOTel } from '@frontmcp/observability';
77
+
78
+ const shutdown = setupOTel({ serviceName: 'help-desk', exporter: 'otlp', endpoint: 'http://localhost:4318' });
72
79
  ```
73
80
 
74
- `renderJsonExposition()` returns the same data as JSON when you would rather
75
- scrape structured output.
81
+ `exporter` is `'console'` (the default) or `'otlp'`, which sends to `endpoint` plus `/v1/traces`. `endpoint` defaults to `OTEL_EXPORTER_OTLP_ENDPOINT`, then `http://localhost:4318`, and `serviceName` to `OTEL_SERVICE_NAME`, then `'frontmcp-server'`. Call the function it returns to flush and stop the provider. Any other OpenTelemetry setup that registers a global tracer provider works too.
82
+
83
+ ### Options
84
+
85
+ | Option | Type | Default | Description |
86
+ | ------------- | ----------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------- |
87
+ | `tracing` | `boolean \| TracingOptions` | `true` | Spans for requests and calls, and `this.telemetry` in tools, resources and prompts |
88
+ | `logging` | `boolean \| { sinks, redactFields, includeStacks, staticFields }` | `false` | Structured log entries, sent to `sinks`. `logging: true` has no sinks and writes nothing |
89
+ | `requestLogs` | `boolean \| { maxEntries, includeSummaries, onRequestComplete }` | `false` | One object per HTTP request, passed to `onRequestComplete`. `includeSummaries` isn't read yet |
90
+
91
+ `TracingOptions` turns span groups on or off: `httpSpans`, `executionSpans`, `fetchSpans`, `transportSpans`, `authSpans`, `oauthSpans`, `elicitationSpans`, `flowStageEvents` and `startupReport` default to `true`, and `hookSpans` (a span for each hook, verbose) to `false`.
76
92
 
77
- <!-- prettier-ignore -->
78
- > `/metrics` is unauthenticated in the snippet above. Bind it to an internal
79
- > interface, or put it behind auth, before exposing the server publicly.
93
+ The log sinks are `{ type: 'stdout', pretty? }` (one JSON object per line), `{ type: 'console' }` (a readable line), `{ type: 'callback', fn }`, `{ type: 'otlp', endpoint?, headers?, batchSize?, flushIntervalMs?, serviceName? }`, `{ type: 'pino', logger }` and `{ type: 'winston', logger }`.
94
+
95
+ `@FrontMcp({ observability })` registers `ObservabilityPlugin` for you. You can register `ObservabilityPlugin.init(options)` in `plugins` yourself, but `observability` also connects the `logging` sinks to the server's logger, which the plugin alone doesn't.
80
96
 
81
97
  ## Key exports
82
98
 
83
- | Export | Purpose |
84
- | ---------------------------------------------------- | --------------------------------------------------- |
85
- | `ObservabilityPlugin` | The plugin — add it to `plugins: []` |
86
- | `setupOTel` | Wire an OTel SDK yourself instead of via the plugin |
87
- | `FrontMcpPropagator` | W3C trace-context propagator for FrontMCP contexts |
88
- | `McpAttributes`, `RpcAttributes`, `HttpAttributes` | Semantic-convention attribute keys |
89
- | `renderPrometheusExposition`, `renderJsonExposition` | Metrics rendering |
90
- | `ProcessStatsCollector` | Memory / event-loop / uptime sampling |
91
- | `reportStartup` | Emit a structured boot record |
99
+ | Export | Purpose |
100
+ | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
101
+ | `ObservabilityPlugin` | The plugin that `observability` loads |
102
+ | `setupOTel` | Register a tracer provider through OpenTelemetry's Node SDK |
103
+ | `createCounter`, `getMetricSnapshot`, `getCounterTotal`, `resetMetricSnapshot` | Counters for `/metrics` (and a global OpenTelemetry meter, if you register one), and reading them in tests |
104
+ | `renderPrometheusExposition(counters, gauges?)`, `renderJsonExposition`, `PROMETHEUS_CONTENT_TYPE` | Serialize a counter snapshot for an endpoint of your own |
105
+ | `ProcessStatsCollector` | Process gauges: CPU, memory, uptime, event-loop lag, active handles, open file descriptors |
106
+ | `StructuredLogTransport`, `createSinks`, `StdoutSink`, `ConsoleSink`, `CallbackSink`, `OtlpSink`, `PinoSink`, `WinstonSink` | Structured logging building blocks |
107
+ | `McpAttributes`, `FrontMcpAttributes`, `RpcAttributes`, `HttpAttributes`, `EnduserAttributes` | Span attribute names |
108
+ | `FrontMcpPropagator`, `withSpan`, `startSpan`, `startToolSpan`, ... | Lower-level span helpers |
109
+ | `createTestTracer`, `getFinishedSpans`, `assertSpanExists`, `assertSpanAttribute`, `findSpan` | Collect spans in memory and check them in a test |
92
110
 
93
111
  ## Trace context over MCP
94
112
 
95
- Protocol revision `2026-07-28` carries OpenTelemetry context in the request
96
- `_meta` (`traceparent`, `tracestate`, `baggage`) per SEP-414, and FrontMCP echoes
97
- it back on the result. A client can therefore stitch its span to the server's
98
- without an out-of-band correlation id — see the
99
- [protocol versions guide](https://frontmcp.dev/reference/server/protocol-versions).
113
+ On protocol revision `2026-07-28`, a client may send `traceparent`, `tracestate` and `baggage` in a request's `_meta`, and FrontMCP copies them to the result's `_meta`. To put the server's spans in the client's trace, send the W3C `traceparent` HTTP header. See [protocol versions](https://frontmcp.dev/reference/server/protocol-versions).
114
+
115
+ ## Docs
116
+
117
+ - [Observability and telemetry](https://frontmcp.dev/reference/server/observability): every option, the spans a call records, `this.telemetry`, structured logs, request logs and counters
118
+ - [Health checks and metrics](https://frontmcp.dev/reference/deployment/health-and-metrics): `/metrics`, `/healthz` and `/readyz`, and scraping with Prometheus
119
+ - [Logging](https://frontmcp.dev/reference/server/logging): the server's own log, which `logging` sinks receive
120
+
121
+ ## Related packages
122
+
123
+ - [`@frontmcp/sdk`](https://www.npmjs.com/package/@frontmcp/sdk): the framework; its `observability` and `metrics` options load this package
124
+ - [`@frontmcp/testing`](https://www.npmjs.com/package/@frontmcp/testing): end-to-end tests for a FrontMCP server
125
+
126
+ ## Contributing
100
127
 
101
- Full guide: [Observability](https://frontmcp.dev/reference/server/observability)
102
- &middot; [Metrics](https://frontmcp.dev/reference/deployment/health-and-metrics#metrics)
128
+ Source: [`libs/observability`](https://github.com/agentfront/frontmcp/tree/main/libs/observability). Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/agentfront/frontmcp/blob/main/CONTRIBUTING.md).
103
129
 
104
130
  ## License
105
131
 
106
- Apache-2.0
132
+ Apache-2.0. See [LICENSE](https://github.com/agentfront/frontmcp/blob/main/LICENSE).
package/esm/index.mjs CHANGED
@@ -3539,15 +3539,11 @@ var ProcessStatsCollector = class {
3539
3539
  };
3540
3540
 
3541
3541
  // libs/observability/src/testing/index.ts
3542
- import {
3543
- BasicTracerProvider,
3544
- InMemorySpanExporter,
3545
- SimpleSpanProcessor
3546
- } from "@opentelemetry/sdk-trace-base";
3547
3542
  function createTestTracer(name = "test") {
3548
- const exporter = new InMemorySpanExporter();
3549
- const provider = new BasicTracerProvider({
3550
- spanProcessors: [new SimpleSpanProcessor(exporter)]
3543
+ const sdkTraceBase = __require("@opentelemetry/sdk-trace-base");
3544
+ const exporter = new sdkTraceBase.InMemorySpanExporter();
3545
+ const provider = new sdkTraceBase.BasicTracerProvider({
3546
+ spanProcessors: [new sdkTraceBase.SimpleSpanProcessor(exporter)]
3551
3547
  });
3552
3548
  const tracer = provider.getTracer(name);
3553
3549
  return {
package/esm/package.json CHANGED
@@ -1,19 +1,21 @@
1
1
  {
2
2
  "name": "@frontmcp/observability",
3
- "version": "1.9.6",
4
- "description": "OpenTelemetry instrumentation, structured JSON logging, and request log objects for FrontMCP",
3
+ "version": "1.10.0",
4
+ "description": "OpenTelemetry tracing, structured JSON logging, request logs and Prometheus-format metrics for FrontMCP servers",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
7
7
  "keywords": [
8
+ "frontmcp",
9
+ "mcp",
10
+ "model-context-protocol",
8
11
  "opentelemetry",
9
12
  "otel",
10
13
  "observability",
11
14
  "tracing",
15
+ "metrics",
16
+ "prometheus",
12
17
  "structured-logging",
13
- "json-logging",
14
18
  "request-log",
15
- "mcp",
16
- "frontmcp",
17
19
  "typescript"
18
20
  ],
19
21
  "repository": {
@@ -51,8 +53,8 @@
51
53
  "@opentelemetry/api": "^1.9.0"
52
54
  },
53
55
  "peerDependencies": {
54
- "@frontmcp/sdk": "1.9.6",
55
- "@frontmcp/utils": "1.9.6",
56
+ "@frontmcp/sdk": "1.10.0",
57
+ "@frontmcp/utils": "1.10.0",
56
58
  "@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
57
59
  "@opentelemetry/sdk-node": "^0.219.0",
58
60
  "@opentelemetry/sdk-trace-base": "^2.8.0",
package/index.js CHANGED
@@ -3578,11 +3578,11 @@ var ProcessStatsCollector = class {
3578
3578
  };
3579
3579
 
3580
3580
  // libs/observability/src/testing/index.ts
3581
- var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
3582
3581
  function createTestTracer(name = "test") {
3583
- const exporter = new import_sdk_trace_base.InMemorySpanExporter();
3584
- const provider = new import_sdk_trace_base.BasicTracerProvider({
3585
- spanProcessors: [new import_sdk_trace_base.SimpleSpanProcessor(exporter)]
3582
+ const sdkTraceBase = require("@opentelemetry/sdk-trace-base");
3583
+ const exporter = new sdkTraceBase.InMemorySpanExporter();
3584
+ const provider = new sdkTraceBase.BasicTracerProvider({
3585
+ spanProcessors: [new sdkTraceBase.SimpleSpanProcessor(exporter)]
3586
3586
  });
3587
3587
  const tracer = provider.getTracer(name);
3588
3588
  return {
package/package.json CHANGED
@@ -1,19 +1,21 @@
1
1
  {
2
2
  "name": "@frontmcp/observability",
3
- "version": "1.9.6",
4
- "description": "OpenTelemetry instrumentation, structured JSON logging, and request log objects for FrontMCP",
3
+ "version": "1.10.0",
4
+ "description": "OpenTelemetry tracing, structured JSON logging, request logs and Prometheus-format metrics for FrontMCP servers",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "license": "Apache-2.0",
7
7
  "keywords": [
8
+ "frontmcp",
9
+ "mcp",
10
+ "model-context-protocol",
8
11
  "opentelemetry",
9
12
  "otel",
10
13
  "observability",
11
14
  "tracing",
15
+ "metrics",
16
+ "prometheus",
12
17
  "structured-logging",
13
- "json-logging",
14
18
  "request-log",
15
- "mcp",
16
- "frontmcp",
17
19
  "typescript"
18
20
  ],
19
21
  "repository": {
@@ -51,8 +53,8 @@
51
53
  "@opentelemetry/api": "^1.9.0"
52
54
  },
53
55
  "peerDependencies": {
54
- "@frontmcp/sdk": "1.9.6",
55
- "@frontmcp/utils": "1.9.6",
56
+ "@frontmcp/sdk": "1.10.0",
57
+ "@frontmcp/utils": "1.10.0",
56
58
  "@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
57
59
  "@opentelemetry/sdk-node": "^0.219.0",
58
60
  "@opentelemetry/sdk-trace-base": "^2.8.0",
@@ -28,7 +28,7 @@
28
28
  * ```
29
29
  */
30
30
  import { type Tracer } from '@opentelemetry/api';
31
- import { BasicTracerProvider, InMemorySpanExporter, type ReadableSpan } from '@opentelemetry/sdk-trace-base';
31
+ import type { BasicTracerProvider, InMemorySpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';
32
32
  /**
33
33
  * Result from createTestTracer().
34
34
  */