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.
- package/README.md +387 -0
- package/bin/intent.js +6 -0
- package/dist/client.cjs +482 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +43 -0
- package/dist/client.d.ts +43 -0
- package/dist/client.js +480 -0
- package/dist/client.js.map +1 -0
- package/dist/context.cjs +43 -0
- package/dist/context.cjs.map +1 -0
- package/dist/context.d.cts +65 -0
- package/dist/context.d.ts +65 -0
- package/dist/context.js +39 -0
- package/dist/context.js.map +1 -0
- package/dist/index.cjs +767 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +754 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics.cjs +69 -0
- package/dist/metrics.cjs.map +1 -0
- package/dist/metrics.d.cts +14 -0
- package/dist/metrics.d.ts +14 -0
- package/dist/metrics.js +66 -0
- package/dist/metrics.js.map +1 -0
- package/dist/semantic-conventions.cjs +65 -0
- package/dist/semantic-conventions.cjs.map +1 -0
- package/dist/semantic-conventions.d.cts +50 -0
- package/dist/semantic-conventions.d.ts +50 -0
- package/dist/semantic-conventions.js +60 -0
- package/dist/semantic-conventions.js.map +1 -0
- package/dist/server.cjs +345 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +37 -0
- package/dist/server.d.ts +37 -0
- package/dist/server.js +343 -0
- package/dist/server.js.map +1 -0
- package/dist/types-9bzlI42J.d.cts +91 -0
- package/dist/types-9bzlI42J.d.ts +91 -0
- package/package.json +108 -0
- package/skills/autotel-mcp/SKILL.md +56 -0
- package/src/client.ts +513 -0
- package/src/context.test.ts +143 -0
- package/src/context.ts +108 -0
- package/src/index.ts +38 -0
- package/src/mcp.integration.test.ts +116 -0
- package/src/metrics.ts +57 -0
- package/src/semantic-conventions.ts +61 -0
- package/src/semconv-compliance.test.ts +283 -0
- package/src/server.ts +353 -0
- package/src/test-setup.ts +17 -0
- package/src/types.test.ts +63 -0
- 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');
|