autotel-mcp-instrumentation 29.0.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.
Files changed (54) hide show
  1. package/README.md +387 -0
  2. package/bin/intent.js +6 -0
  3. package/dist/client.cjs +482 -0
  4. package/dist/client.cjs.map +1 -0
  5. package/dist/client.d.cts +43 -0
  6. package/dist/client.d.ts +43 -0
  7. package/dist/client.js +480 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/context.cjs +43 -0
  10. package/dist/context.cjs.map +1 -0
  11. package/dist/context.d.cts +65 -0
  12. package/dist/context.d.ts +65 -0
  13. package/dist/context.js +39 -0
  14. package/dist/context.js.map +1 -0
  15. package/dist/index.cjs +767 -0
  16. package/dist/index.cjs.map +1 -0
  17. package/dist/index.d.cts +7 -0
  18. package/dist/index.d.ts +7 -0
  19. package/dist/index.js +754 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/metrics.cjs +69 -0
  22. package/dist/metrics.cjs.map +1 -0
  23. package/dist/metrics.d.cts +14 -0
  24. package/dist/metrics.d.ts +14 -0
  25. package/dist/metrics.js +66 -0
  26. package/dist/metrics.js.map +1 -0
  27. package/dist/semantic-conventions.cjs +65 -0
  28. package/dist/semantic-conventions.cjs.map +1 -0
  29. package/dist/semantic-conventions.d.cts +50 -0
  30. package/dist/semantic-conventions.d.ts +50 -0
  31. package/dist/semantic-conventions.js +60 -0
  32. package/dist/semantic-conventions.js.map +1 -0
  33. package/dist/server.cjs +345 -0
  34. package/dist/server.cjs.map +1 -0
  35. package/dist/server.d.cts +37 -0
  36. package/dist/server.d.ts +37 -0
  37. package/dist/server.js +343 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/types-9bzlI42J.d.cts +91 -0
  40. package/dist/types-9bzlI42J.d.ts +91 -0
  41. package/package.json +108 -0
  42. package/skills/autotel-mcp/SKILL.md +56 -0
  43. package/src/client.ts +513 -0
  44. package/src/context.test.ts +143 -0
  45. package/src/context.ts +108 -0
  46. package/src/index.ts +38 -0
  47. package/src/mcp.integration.test.ts +116 -0
  48. package/src/metrics.ts +57 -0
  49. package/src/semantic-conventions.ts +61 -0
  50. package/src/semconv-compliance.test.ts +283 -0
  51. package/src/server.ts +353 -0
  52. package/src/test-setup.ts +17 -0
  53. package/src/types.test.ts +63 -0
  54. package/src/types.ts +142 -0
package/README.md ADDED
@@ -0,0 +1,387 @@
1
+ # autotel-mcp-instrumentation
2
+
3
+ OpenTelemetry instrumentation for [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) with automatic distributed tracing.
4
+
5
+ Automatically instrument MCP servers and clients with OpenTelemetry tracing. Uses W3C Trace Context propagation via the `_meta` field to enable distributed tracing across MCP boundaries.
6
+
7
+ ## Features
8
+
9
+ - **Automatic instrumentation** - One function call to instrument all tools, resources, and prompts
10
+ - **Distributed tracing** - W3C Trace Context propagation via `_meta` field
11
+ - **Transport-agnostic** - Works with stdio, HTTP, SSE, or any MCP transport
12
+ - **Node.js runtime** - Full support for Node.js applications with `autotel`
13
+ - **Tree-shakeable** - Import only what you need (~7KB total, 2-5KB per module)
14
+ - **Zero MCP modifications** - Uses Proxy pattern, no changes to MCP SDK required
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install autotel-mcp-instrumentation @modelcontextprotocol/sdk autotel
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ### Server-Side Instrumentation
25
+
26
+ ```typescript
27
+ import { Server } from '@modelcontextprotocol/sdk/server/index';
28
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio';
29
+ import { instrumentMcpServer } from 'autotel-mcp-instrumentation/server';
30
+ import { init } from 'autotel';
31
+
32
+ // Initialize OpenTelemetry
33
+ init({
34
+ service: 'mcp-weather-server',
35
+ endpoint: 'http://localhost:4318',
36
+ });
37
+
38
+ const server = new Server({
39
+ name: 'weather',
40
+ version: '1.0.0',
41
+ });
42
+
43
+ // Instrument the server (automatic tracing for all tools/resources/prompts)
44
+ const instrumented = instrumentMcpServer(server, {
45
+ captureArgs: true, // Log tool arguments
46
+ captureResults: false, // Don't log results (PII concerns)
47
+ });
48
+
49
+ // Register tools normally - they're automatically traced!
50
+ instrumented.registerTool({
51
+ name: 'get_weather',
52
+ description: 'Get current weather for a location',
53
+ inputSchema: {
54
+ type: 'object',
55
+ properties: {
56
+ location: { type: 'string' },
57
+ },
58
+ required: ['location'],
59
+ },
60
+ handler: async (args) => {
61
+ // This handler is automatically traced with parent context from _meta
62
+ const weather = await fetchWeather(args.location);
63
+ return {
64
+ content: [
65
+ {
66
+ type: 'text',
67
+ text: `Temperature in ${args.location}: ${weather.temp}°F`,
68
+ },
69
+ ],
70
+ };
71
+ },
72
+ });
73
+
74
+ await server.connect(new StdioServerTransport());
75
+ ```
76
+
77
+ ### Client-Side Instrumentation
78
+
79
+ ```typescript
80
+ import { Client } from '@modelcontextprotocol/sdk/client/index';
81
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio';
82
+ import { instrumentMcpClient } from 'autotel-mcp-instrumentation/client';
83
+ import { init } from 'autotel';
84
+
85
+ // Initialize OpenTelemetry
86
+ init({
87
+ service: 'mcp-weather-client',
88
+ endpoint: 'http://localhost:4318',
89
+ });
90
+
91
+ const client = new Client({
92
+ name: 'weather-client',
93
+ version: '1.0.0',
94
+ });
95
+
96
+ // Instrument the client (automatic trace context injection)
97
+ const instrumented = instrumentMcpClient(client, {
98
+ captureArgs: true,
99
+ captureResults: false,
100
+ });
101
+
102
+ await client.connect(new StdioClientTransport(/* ... */));
103
+
104
+ // Tool calls automatically create spans and inject _meta with trace context
105
+ const result = await instrumented.callTool('get_weather', {
106
+ location: 'New York',
107
+ // _meta field is automatically injected with traceparent/tracestate/baggage
108
+ });
109
+ ```
110
+
111
+ ## API Reference
112
+
113
+ ### Server Instrumentation
114
+
115
+ #### `instrumentMcpServer(server, config?)`
116
+
117
+ Wraps an MCP server to automatically trace all registered tools, resources, and prompts.
118
+
119
+ **Parameters:**
120
+
121
+ - `server` - MCP Server instance
122
+ - `config` - Optional instrumentation configuration
123
+
124
+ **Returns:** Instrumented server (Proxy)
125
+
126
+ **Configuration Options:**
127
+
128
+ ```typescript
129
+ interface McpInstrumentationConfig {
130
+ captureArgs?: boolean; // Capture tool/resource arguments (default: true)
131
+ captureResults?: boolean; // Capture results - may contain PII (default: false)
132
+ captureErrors?: boolean; // Capture errors and exceptions (default: true)
133
+ customAttributes?: (context) => Attributes; // Custom span attributes
134
+ }
135
+ ```
136
+
137
+ **Span Attributes Set:**
138
+
139
+ - `mcp.type` - Operation type ('tool', 'resource', 'prompt')
140
+ - `mcp.tool.name` / `mcp.resource.name` / `mcp.prompt.name` - Name
141
+ - `mcp.tool.args` - Arguments (if `captureArgs: true`)
142
+ - `mcp.tool.result` - Result (if `captureResults: true`)
143
+
144
+ ### Client Instrumentation
145
+
146
+ #### `instrumentMcpClient(client, config?)`
147
+
148
+ Wraps an MCP client to automatically create spans and inject trace context for all requests.
149
+
150
+ **Parameters:**
151
+
152
+ - `client` - MCP Client instance
153
+ - `config` - Optional instrumentation configuration
154
+
155
+ **Returns:** Instrumented client (Proxy)
156
+
157
+ **Span Attributes Set:**
158
+
159
+ - `mcp.client.operation` - Operation type ('callTool', 'getResource', 'getPrompt')
160
+ - `mcp.client.name` - Tool/resource/prompt name
161
+ - `mcp.client.args` - Arguments (if `captureArgs: true`)
162
+ - `mcp.client.result` - Result (if `captureResults: true`)
163
+
164
+ ### Context Utilities
165
+
166
+ #### `extractOtelContextFromMeta(meta?)`
167
+
168
+ Extract OpenTelemetry context from MCP `_meta` field.
169
+
170
+ ```typescript
171
+ import { extractOtelContextFromMeta } from 'autotel-mcp-instrumentation/context';
172
+ import { context } from '@opentelemetry/api';
173
+
174
+ const handler = async (args, _meta) => {
175
+ const parentContext = extractOtelContextFromMeta(_meta);
176
+ return context.with(parentContext, async () => {
177
+ // Your traced code with parent context
178
+ });
179
+ };
180
+ ```
181
+
182
+ #### `injectOtelContextToMeta(ctx?)`
183
+
184
+ Inject OpenTelemetry context into MCP `_meta` field.
185
+
186
+ ```typescript
187
+ import { injectOtelContextToMeta } from 'autotel-mcp-instrumentation/context';
188
+
189
+ const meta = injectOtelContextToMeta();
190
+ // Returns: { traceparent, tracestate, baggage }
191
+
192
+ await client.callTool('my_tool', { arg1: 'value', _meta: meta });
193
+ ```
194
+
195
+ #### `activateTraceContext(meta?)`
196
+
197
+ Extract and immediately activate trace context from `_meta` field.
198
+
199
+ ```typescript
200
+ import { activateTraceContext } from 'autotel-mcp-instrumentation/context';
201
+ import { context } from '@opentelemetry/api';
202
+
203
+ const ctx = activateTraceContext(_meta);
204
+ return context.with(ctx, () => {
205
+ // Traced code with parent context active
206
+ });
207
+ ```
208
+
209
+ ## How It Works
210
+
211
+ ### W3C Trace Context Propagation
212
+
213
+ MCP requests can include a `_meta` field for metadata. `autotel-mcp-instrumentation` uses this field to propagate W3C Trace Context headers across client-server boundaries:
214
+
215
+ ```
216
+ ┌─────────────┐ ┌─────────────┐
217
+ │ MCP Client │ │ MCP Server │
218
+ │ │ │ │
219
+ │ Span A │──── callTool ────▶│ Span B │
220
+ │ │ { args, │ │
221
+ │ │ _meta: { │ (parent: A) │
222
+ │ │ traceparent │ │
223
+ │ │ tracestate │ │
224
+ │ │ baggage }} │ │
225
+ └─────────────┘ └─────────────┘
226
+
227
+ Distributed Trace:
228
+ Span A (client) → Span B (server, child of A)
229
+ ```
230
+
231
+ **Client Side:**
232
+
233
+ 1. Creates span for tool call
234
+ 2. Injects W3C trace context into `_meta` field
235
+ 3. Sends request with `_meta`
236
+
237
+ **Server Side:**
238
+
239
+ 1. Receives request with `_meta` field
240
+ 2. Extracts parent trace context
241
+ 3. Creates child span with parent context
242
+ 4. Executes tool handler
243
+
244
+ ### Transport Agnostic
245
+
246
+ Because context is in the JSON payload itself (not HTTP headers), this works with **any** MCP transport:
247
+
248
+ - stdio (standard input/output)
249
+ - HTTP/SSE (server-sent events)
250
+ - WebSocket
251
+ - Custom transports
252
+
253
+ ## Runtime Support
254
+
255
+ ```typescript
256
+ import { instrumentMcpServer } from 'autotel-mcp-instrumentation/server';
257
+ import { init } from 'autotel';
258
+
259
+ init({ service: 'my-mcp-server', endpoint: 'http://localhost:4318' });
260
+ const instrumented = instrumentMcpServer(server);
261
+ ```
262
+
263
+ ## Bundle Size
264
+
265
+ - **Core context utilities**: ~2KB
266
+ - **Server instrumentation**: ~3KB
267
+ - **Client instrumentation**: ~2KB
268
+ - **Total (all modules)**: ~7KB
269
+
270
+ Tree-shakeable - import only what you need:
271
+
272
+ ```typescript
273
+ // Import just server instrumentation (~5KB)
274
+ import { instrumentMcpServer } from 'autotel-mcp-instrumentation/server';
275
+
276
+ // Import just client instrumentation (~4KB)
277
+ import { instrumentMcpClient } from 'autotel-mcp-instrumentation/client';
278
+
279
+ // Import just context utilities (~2KB)
280
+ import {
281
+ extractOtelContextFromMeta,
282
+ injectOtelContextToMeta,
283
+ } from 'autotel-mcp-instrumentation/context';
284
+ ```
285
+
286
+ ## Custom Attributes
287
+
288
+ Add custom span attributes based on your application logic:
289
+
290
+ ```typescript
291
+ const instrumented = instrumentMcpServer(server, {
292
+ customAttributes: ({ type, name, args, result }) => {
293
+ const attrs: Attributes = {};
294
+
295
+ // Add tenant ID from arguments
296
+ if (args?.tenantId) {
297
+ attrs['tenant.id'] = args.tenantId;
298
+ }
299
+
300
+ // Add result metadata
301
+ if (result?.metadata) {
302
+ attrs['result.metadata'] = JSON.stringify(result.metadata);
303
+ }
304
+
305
+ // Add operation-specific attributes
306
+ if (type === 'tool' && name === 'search') {
307
+ attrs['search.query'] = args?.query;
308
+ attrs['search.results.count'] = result?.items?.length ?? 0;
309
+ }
310
+
311
+ return attrs;
312
+ },
313
+ });
314
+ ```
315
+
316
+ ## Security Considerations
317
+
318
+ ### PII in Arguments/Results
319
+
320
+ By default, `captureResults` is disabled to prevent PII leakage:
321
+
322
+ ```typescript
323
+ const instrumented = instrumentMcpServer(server, {
324
+ captureArgs: true, // May contain PII
325
+ captureResults: false, // DISABLED by default - may contain sensitive data
326
+ });
327
+ ```
328
+
329
+ For production:
330
+
331
+ - Review what data is in tool arguments
332
+ - Disable `captureArgs` if arguments contain PII
333
+ - Never enable `captureResults` in production unless you control the data
334
+
335
+ ### Custom PII Redaction
336
+
337
+ Use `customAttributes` to redact PII:
338
+
339
+ ```typescript
340
+ const instrumented = instrumentMcpServer(server, {
341
+ captureArgs: false, // Disable default arg capture
342
+ customAttributes: ({ args }) => {
343
+ // Manually redact PII before logging
344
+ return {
345
+ 'tool.location': args?.location, // Safe to log
346
+ // Omit args.email, args.userId, etc.
347
+ };
348
+ },
349
+ });
350
+ ```
351
+
352
+ ## Examples
353
+
354
+ See the `apps/` directory for complete working examples:
355
+
356
+ - `apps/example-mcp-server` - Instrumented MCP server with stdio transport
357
+ - `apps/example-mcp-client` - Instrumented MCP client calling the server
358
+
359
+ ## Integration with Observability Backends
360
+
361
+ Works with any OTLP-compatible backend:
362
+
363
+ ```typescript
364
+ import { init } from 'autotel';
365
+
366
+ // Honeycomb
367
+ init({
368
+ service: 'mcp-server',
369
+ endpoint: 'https://api.honeycomb.io',
370
+ headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY },
371
+ });
372
+
373
+ // Datadog
374
+ init({
375
+ service: 'mcp-server',
376
+ endpoint: 'https://http-intake.logs.datadoghq.com',
377
+ headers: { 'DD-API-KEY': process.env.DD_API_KEY },
378
+ });
379
+ ```
380
+
381
+ ## License
382
+
383
+ MIT
384
+
385
+ ## Contributing
386
+
387
+ Issues and PRs welcome at [github.com/jagreehal/autotel](https://github.com/jagreehal/autotel)
package/bin/intent.js ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ // Auto-generated by @tanstack/intent setup
3
+ // Exposes the intent end-user CLI for consumers of this library.
4
+ // Commit this file, then add to your package.json:
5
+ // "bin": { "intent": "./bin/intent.js" }
6
+ await import('@tanstack/intent/intent-library');