@awarecorp/mcp-logger 0.0.2-dev.5 → 0.0.3-dev.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,421 +1,237 @@
1
1
  # @awarecorp/mcp-logger
2
2
 
3
- **Unified MCP Observability Solution** - One package for both SDK and CLI.
3
+ **Unified MCP Observability Solution** - Monitor your Model Context Protocol servers with zero configuration.
4
4
 
5
- [한국어 문서](./docs/README.ko.md)
5
+ [![npm version](https://img.shields.io/npm/v/@awarecorp/mcp-logger)](https://www.npmjs.com/package/@awarecorp/mcp-logger)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ [Aware Corp](https://awarecorp.io/)
6
9
 
7
10
  ---
8
11
 
9
- ## 🎯 Two Ways to Use
12
+ ## 🎯 Overview
13
+
14
+ `@awarecorp/mcp-logger` provides comprehensive observability for MCP servers in two ways:
10
15
 
11
- ### 1️⃣ **SDK**: Direct Code Integration
12
- If you're developing an MCP server, use the SDK.
16
+ - **SDK**: Direct integration for custom MCP servers
17
+ - **CLI**: Zero-code wrapper for existing MCP packages
13
18
 
14
- ### 2️⃣ **CLI**: Zero-Code Wrapper
15
- If you're using an existing MCP server (npm package), use the CLI.
19
+ All telemetry data is sent to OpenTelemetry-compatible endpoints with automatic request-response pairing.
16
20
 
17
21
  ---
18
22
 
19
23
  ## 📦 Installation
20
24
 
21
- \`\`\`bash
25
+ ```bash
22
26
  npm install @awarecorp/mcp-logger
23
- \`\`\`
27
+ ```
24
28
 
25
29
  ---
26
30
 
27
- ## 1️⃣ SDK Usage
31
+ ## 🚀 Quick Start
28
32
 
29
- ### TypeScript
33
+ ### SDK Mode
30
34
 
31
- \`\`\`typescript
32
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
33
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
34
- import { trace } from '@awarecorp/mcp-logger';
35
+ Instrument your MCP server with one line of code:
35
36
 
36
- const server = new Server(
37
- { name: 'my-server', version: '1.0.0' },
38
- { capabilities: { tools: {} } }
39
- );
37
+ ```typescript
38
+ import { trace } from '@awarecorp/mcp-logger';
40
39
 
41
- // 🔥 Enable auto-instrumentation (just add this!)
42
40
  trace(server, {
43
- apiKey: process.env.AWARE_API_KEY!,
44
- serviceName: 'my-awesome-mcp-server',
45
- debug: true, // Enable debug logs (optional)
46
- });
47
-
48
- // Register handlers as usual
49
- server.setRequestHandler('tools/list', async () => ({
50
- tools: [
51
- { name: 'read_file', description: 'Read a file' },
52
- { name: 'write_file', description: 'Write to a file' },
53
- ],
54
- }));
55
-
56
- server.setRequestHandler('tools/call', async (request) => {
57
- const { name, arguments: args } = request.params;
58
-
59
- // ✨ Add custom logs (optional)
60
- trace.addLog({ level: 'info', message: \`Processing \${name}\` });
61
-
62
- // Business logic
63
- const result = await processToolCall(name, args);
64
-
65
- trace.addLog({ level: 'info', message: 'Processing completed' });
66
-
67
- return { result };
41
+ apiKey: 'YOUR_API_KEY',
42
+ serviceName: 'my-mcp-server'
68
43
  });
44
+ ```
69
45
 
70
- // Start stdio transport
71
- const transport = new StdioServerTransport();
72
- await server.connect(transport);
73
- \`\`\`
46
+ ### CLI Mode
74
47
 
75
- ### JavaScript (CommonJS)
48
+ Wrap any existing MCP server without code changes:
76
49
 
77
- \`\`\`javascript
78
- const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
79
- const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
80
- const { trace } = require('@awarecorp/mcp-logger');
50
+ ```bash
51
+ npx @awarecorp/mcp-logger \
52
+ -k YOUR_API_KEY \
53
+ -s my-server \
54
+ npx your-mcp-server
55
+ ```
81
56
 
82
- const server = new Server(
83
- { name: 'my-server', version: '1.0.0' },
84
- { capabilities: { tools: {} } }
85
- );
86
-
87
- // 🔥 Enable auto-instrumentation
88
- trace(server, {
89
- apiKey: process.env.AWARE_API_KEY,
90
- serviceName: 'my-mcp-server',
91
- });
57
+ ---
92
58
 
93
- server.setRequestHandler('tools/list', async () => ({
94
- tools: [{ name: 'test', description: 'Test tool' }],
95
- }));
59
+ ## 📋 Features
96
60
 
97
- server.setRequestHandler('tools/call', async (request) => {
98
- // Add custom logs
99
- trace.addLog({ level: 'info', message: 'Tool called' });
100
-
101
- return { result: 'success' };
102
- });
61
+ ### Core Capabilities
103
62
 
104
- const transport = new StdioServerTransport();
105
- server.connect(transport);
106
- \`\`\`
63
+ - **Automatic Request-Response Pairing**: Single span per request-response cycle with intelligent message correlation
64
+ - **Notification Support**: Tracks both client→server and server→client notifications
65
+ - **Selective Method Tracking**: Configurable filtering to track only relevant methods (excludes noisy notifications like `notifications/initialized`)
66
+ - **Custom Event Logging**: Add context-aware logs within request handlers
67
+ - **Enhanced Error Handling**: Robust error serialization for complex error objects
68
+ - **API Key Validation**: Secure endpoint validation with SHA-256 hashing
69
+ - **Transport Support**: stdio and SSE (Server-Sent Events)
70
+ - **Zero Configuration**: Works out of the box with sensible defaults
107
71
 
108
- ### SDK API
72
+ ### Telemetry Data
109
73
 
110
- #### \`trace(server, options)\`
74
+ All spans include:
111
75
 
112
- Add auto-instrumentation to your MCP server.
76
+ - Request/response timestamps and payloads
77
+ - Method name and parameters
78
+ - Tool names and arguments (for `tools/call`)
79
+ - Custom events and logs
80
+ - Error details (if any)
81
+ - Duration metrics
82
+ - CLI server information (command, args, env)
113
83
 
114
- **Parameters:**
115
- - \`server\` (\`MCPServer\`): MCP Server instance
116
- - \`options\` (\`TraceOptions\`):
117
- - \`apiKey\` (string, required): Aware API key
118
- - \`serviceName\` (string, optional): Service name (default: auto-generated)
119
- - \`debug\` (boolean, optional): Enable debug logs (default: false)
120
- - \`endpoint\` (string, optional): Custom OTLP endpoint
84
+ ### Data Format
121
85
 
122
- **Returns:** \`void\`
86
+ Optimized for Elasticsearch with flat structure:
87
+ - `mcp.request.method` / `mcp.response.method`: Method names for request/response
88
+ - `mcp.source`: `sdk` or `cli`
89
+ - `mcp.transport`: `stdio` or `sse`
90
+ - `mcp.duration_ms`: Request duration
91
+ - `mcp.tool.name`: Tool name (tools/call only)
92
+ - `mcp.request.params` / `mcp.response.result`: Request/response payloads
93
+ - `mcp.error.message`: Enhanced error serialization (no more `[object Object]`)
94
+ - `mcp.events`: Custom log entries (JSON array)
95
+ - `mcp.cli.server`: CLI execution info (JSON object)
123
96
 
124
97
  ---
125
98
 
126
- #### \`trace.addLog(entry)\`
99
+ ## 🔧 SDK API
100
+
101
+ ### `trace(server, options)`
127
102
 
128
- Add custom logs to the currently executing tool handler.
103
+ Enable automatic instrumentation on an MCP server.
129
104
 
130
105
  **Parameters:**
131
- - \`entry\` (\`CustomLogEntry\`):
132
- - \`level\` ('info' | 'warn' | 'error', optional): Log level (default: 'info')
133
- - \`message\` (string, required): Log message
134
- - \`metadata\` (object, optional): Additional metadata
135
-
136
- **Returns:** \`void\`
137
-
138
- **Example:**
139
- \`\`\`typescript
140
- server.setRequestHandler('tools/call', async (request) => {
141
- trace.addLog({
142
- level: 'info',
143
- message: 'Processing started',
144
- metadata: { userId: '123', action: 'read' }
145
- });
146
-
147
- // Business logic...
148
-
149
- trace.addLog({ level: 'info', message: 'Processing completed' });
150
-
151
- return { result: 'success' };
152
- });
153
- \`\`\`
106
+ - `server`: MCP Server instance
107
+ - `options.apiKey`: API key for authentication (required)
108
+ - `options.serviceName`: Service identifier (optional, auto-generated if omitted)
109
+ - `options.endpoint`: Custom OTLP endpoint (optional)
110
+ - `options.debug`: Enable debug logging (optional, default: false)
154
111
 
155
- ---
112
+ **Returns:** `void`
156
113
 
157
- #### \`trace.shutdown()\`
114
+ ### `trace.addLog(entry)`
158
115
 
159
- Shutdown telemetry and flush remaining data.
116
+ Add custom log entries within request handlers.
160
117
 
161
- **Returns:** \`Promise<void>\`
118
+ **Parameters:**
119
+ - `entry.level`: Log level (`info` | `warn` | `error`)
120
+ - `entry.message`: Log message (required)
121
+ - `entry.metadata`: Additional data (optional)
122
+ - `entry.timestamp`: Unix timestamp in milliseconds (optional, auto-generated)
162
123
 
163
- **Example:**
164
- \`\`\`typescript
165
- process.on('SIGTERM', async () => {
166
- await trace.shutdown();
167
- process.exit(0);
168
- });
169
- \`\`\`
124
+ **Returns:** `void`
170
125
 
171
- ---
126
+ ### `trace.shutdown()`
172
127
 
173
- ## 2️⃣ CLI Usage
128
+ Gracefully shutdown telemetry and flush pending data.
174
129
 
175
- ### Quick Start
130
+ **Returns:** `Promise<void>`
176
131
 
177
- \`\`\`bash
178
- npx @awarecorp/mcp-logger \\
179
- --api-key YOUR_API_KEY \\
180
- -- \\
181
- npx @modelcontextprotocol/server-filesystem /path/to/dir
182
- \`\`\`
132
+ ---
183
133
 
184
- Just add your MCP server command after \`--\`.
134
+ ## 🖥️ CLI Usage
185
135
 
186
- ### Global Installation (Optional)
136
+ ### Basic Syntax
187
137
 
188
- \`\`\`bash
189
- npm install -g @awarecorp/mcp-logger
190
- \`\`\`
138
+ ```bash
139
+ mcp-logger [options] <command...>
140
+ ```
191
141
 
192
- Usage:
193
- \`\`\`bash
194
- mcp-logger --api-key YOUR_KEY -- npx server-filesystem /path
195
- \`\`\`
142
+ ### Options
196
143
 
197
- ### Claude Desktop Configuration
144
+ - `-k, --api-key <key>`: API key (required)
145
+ - `-s, --service-name <name>`: Service name (optional)
146
+ - `-e, --endpoint <url>`: Custom OTLP endpoint (optional)
198
147
 
199
- #### Before (No Observability)
200
- \`\`\`json
201
- {
202
- "mcpServers": {
203
- "filesystem": {
204
- "command": "npx",
205
- "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Documents"]
206
- }
207
- }
208
- }
209
- \`\`\`
148
+ ### Examples
210
149
 
211
- #### After (With Observability) ✨
212
- \`\`\`json
150
+ **Wrap an npx command:**
151
+ ```bash
152
+ mcp-logger -k API_KEY -s filesystem npx -y @modelcontextprotocol/server-filesystem /path
153
+ ```
154
+
155
+ **Use with Claude Desktop:**
156
+ ```json
213
157
  {
214
158
  "mcpServers": {
215
- "filesystem": {
159
+ "my-server": {
216
160
  "command": "npx",
217
161
  "args": [
218
162
  "-y",
219
163
  "@awarecorp/mcp-logger",
220
- "--api-key", "your-aware-api-key",
221
- "--service-name", "my-filesystem-server",
164
+ "-k",
165
+ "YOUR_API_KEY",
166
+ "-s",
167
+ "my-server",
222
168
  "--",
223
- "@modelcontextprotocol/server-filesystem",
224
- "/Users/username/Documents"
169
+ "npx",
170
+ "-y",
171
+ "your-mcp-server"
225
172
  ]
226
173
  }
227
174
  }
228
175
  }
229
- \`\`\`
230
-
231
- **That's it!** All requests/responses are now logged to the Aware platform without code changes.
232
-
233
- ### CLI Options
234
-
235
- #### Required Options
236
- - \`--api-key <key>\` or \`-k <key>\`: Aware API key (required)
237
-
238
- #### Optional Options
239
- - \`--service-name <name>\` or \`-s <name>\`: Service identifier name (default: auto-generated)
240
- - \`--debug\` or \`-d\`: Enable debug logs
241
- - \`--endpoint <url>\` or \`-e <url>\`: Custom OTLP endpoint
242
-
243
- ### CLI Usage Examples
244
-
245
- #### Filesystem Server
246
- \`\`\`bash
247
- npx @awarecorp/mcp-logger \\
248
- --api-key abc123 \\
249
- --service-name my-filesystem \\
250
- -- \\
251
- npx -y @modelcontextprotocol/server-filesystem /Users/username/Documents
252
- \`\`\`
253
-
254
- #### GitHub Server
255
- \`\`\`bash
256
- npx @awarecorp/mcp-logger \\
257
- -k abc123 \\
258
- -s github-integration \\
259
- -- \\
260
- npx -y @modelcontextprotocol/server-github
261
- \`\`\`
262
-
263
- #### Brave Search Server (Debug Mode)
264
- \`\`\`bash
265
- npx @awarecorp/mcp-logger \\
266
- --api-key abc123 \\
267
- --service-name brave-search \\
268
- --debug \\
269
- -- \\
270
- npx -y @modelcontextprotocol/server-brave-search
271
- \`\`\`
176
+ ```
272
177
 
273
178
  ---
274
179
 
275
- ## 🏗️ Architecture
276
-
277
- This package consists of three layers:
278
-
279
- \`\`\`
280
- @awarecorp/mcp-logger
281
- ├── SDK (Public API)
282
- │ ├── trace() - Instrument MCP server
283
- │ ├── trace.addLog() - Add custom logs
284
- │ └── trace.shutdown() - Shutdown telemetry
285
-
286
- ├── CLI (Binary-only access)
287
- │ ├── main.ts - CLI entry point
288
- │ ├── interceptor.ts - Stream interception
289
- │ └── pairing.ts - Request-Response matching
290
-
291
- └── Core (Internal implementation)
292
- ├── telemetry - OpenTelemetry initialization
293
- ├── span-builder - Unified Span creation
294
- ├── span-utils - Span utilities
295
- ├── config - Tracked method configuration
296
- └── types - Common type definitions
297
- \`\`\`
298
-
299
- ### Key Features
300
-
301
- #### ✅ Request-Response Pairing
302
- - Matches requests and responses by ID into a **single span**
303
- - Automatic cleanup of incomplete requests with 30s timeout
304
-
305
- #### ✅ Selective Tracking
306
- - Only tracks \`tools/call\` and \`tools/list\` (removes unnecessary overhead)
307
- - Configurable to extend tracking targets
308
-
309
- #### ✅ Custom Events
310
- - Add custom events inside handlers with \`trace.addLog()\`
311
- - Automatically linked to the currently executing span via context
312
-
313
- #### ✅ Elasticsearch Optimization
314
- - Extracts tool name and arguments as top-level fields
315
- - Fast searching with \`mcp.tool.name\`, \`mcp.tool.arguments\`, etc.
316
-
317
- ### Design Principles
318
-
319
- 1. **SDK-only public API**: Only SDK accessible via \`import\`
320
- 2. **CLI binary-only access**: Use via \`npx\` or \`mcp-logger\` command only
321
- 3. **Shared code reuse**: OpenTelemetry logic unified in core layer
322
- 4. **Type safety**: CLI type definitions not exposed
323
- 5. **dd-trace style API**: Intuitive usage with \`trace()\` namespace
180
+ ## 🏗️ How It Works
324
181
 
325
- ---
182
+ ### Request-Response Pairing
326
183
 
327
- ## 🔧 Development
184
+ Creates **one span per request-response pair** for complete transaction context:
328
185
 
329
- ### Build
186
+ 1. Request arrives → stored in pending map
187
+ 2. Response arrives → matched by ID, single span created
188
+ 3. Timeout (30s) → pending requests cleared
189
+ 4. Notifications → handled as single-direction spans (request-only or response-only)
330
190
 
331
- \`\`\`bash
332
- npm run build
333
- \`\`\`
191
+ ### Message Type Handling
334
192
 
335
- ### Tests
193
+ - **Paired Spans**: Request-response cycles with matching IDs (most common)
194
+ - **Request-Only Spans**: Client→server notifications (no response expected)
195
+ - **Response-Only Spans**: Server→client notifications (no matching request)
336
196
 
337
- \`\`\`bash
338
- npm test # Run all tests
339
- npm run test:watch # Watch mode
340
- \`\`\`
341
-
342
- ### Local Testing
343
-
344
- #### SDK Testing
345
- \`\`\`typescript
346
- // test.ts
347
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
348
- import { trace } from './src/index.js';
349
-
350
- const server = new Server({ name: 'test', version: '1.0.0' }, { capabilities: {} });
351
- trace(server, { apiKey: 'test', debug: true });
352
-
353
- server.setRequestHandler('tools/call', async (request) => {
354
- trace.addLog({ level: 'info', message: 'Test handler called' });
355
- return { result: 'success' };
356
- });
357
- \`\`\`
197
+ ### Tracked Methods
358
198
 
359
- #### CLI Testing
360
- \`\`\`bash
361
- npm run build
362
- node bin/mcp-logger.js --help
363
- \`\`\`
199
+ Configurable method filtering with exclusion list:
200
+ - Tracks: `tools/call`, `tools/list`, and other business-critical methods
201
+ - Excludes: `notifications/initialized` and other noisy protocol notifications
202
+ - Customizable via `EXCLUDED_METHODS` configuration
364
203
 
365
204
  ---
366
205
 
367
- ## 📝 License
206
+ ## 📊 Telemetry Data Structure
368
207
 
369
- MIT
208
+ Each span includes:
209
+ - Request/response timestamps and payloads (with optional fields for notifications)
210
+ - Method name and parameters (separate fields for request/response)
211
+ - Tool names and arguments (for `tools/call`)
212
+ - Custom events and logs
213
+ - Enhanced error details with proper serialization
214
+ - Duration metrics
215
+ - CLI server information (command, args, env)
370
216
 
371
- ---
372
-
373
- ## 🤝 Contributing
374
-
375
- Issues and PRs are always welcome!
217
+ ### Span Attributes
376
218
 
377
- See [DEVELOPMENT.md](./DEVELOPMENT.md) for detailed development guidelines.
219
+ All attributes use a flat structure optimized for Elasticsearch:
220
+ - Request attributes: `mcp.request.*` (id, method, params, timestamp)
221
+ - Response attributes: `mcp.response.*` (method, result, error, timestamp)
222
+ - Error handling: Proper serialization for Error objects, strings, and complex objects
223
+ - Metadata: source, transport, duration, tool information
378
224
 
379
225
  ---
380
226
 
381
- ## 📦 Publishing (Maintainers Only)
382
-
383
- **Recommended:** Automated deployment with GitHub Actions
384
-
385
- ### Automated Deployment (GitHub Actions) ⭐
386
- \`\`\`bash
387
- # Dev version (develop branch)
388
- git checkout develop
389
- npm run version:dev
390
- git push origin develop --tags
391
-
392
- # Production version (main branch)
393
- git checkout main
394
- npm run version:patch # or minor, major
395
- git push origin main --tags
396
- \`\`\`
227
+ ## 📄 License
397
228
 
398
- GitHub Actions will automatically build and publish to npm.
399
-
400
- ### Manual Deployment (Not Recommended)
401
- \`\`\`bash
402
- # Dev version
403
- npm run build
404
- npm publish --access public --tag dev
405
-
406
- # Production version
407
- npm run build
408
- npm publish --access public
409
- \`\`\`
229
+ MIT © [Aware Corp](https://awarecorp.io/)
410
230
 
411
231
  ---
412
232
 
413
- ## 🌐 Links
414
-
415
- - **Aware Platform**: https://aware.mcypher.com
416
- - **MCP Specification**: https://modelcontextprotocol.io
417
- - **GitHub Repository**: https://github.com/awarecorp/mcp-logger
418
-
419
- ---
233
+ ## 🔗 Links
420
234
 
421
- Made with ❤️ by [Aware](https://aware.mcypher.com)
235
+ - [Aware Corp](https://awarecorp.io/)
236
+ - [npm Package](https://www.npmjs.com/package/@awarecorp/mcp-logger)
237
+ - [Model Context Protocol](https://modelcontextprotocol.io/)
@@ -1 +1 @@
1
- import{Transform as e}from"stream";class s extends e{buffer="";direction;pairer;debug;constructor(e,s,r=!1){super(),this.direction=e,this.pairer=s,this.debug=r}_transform(e,s,r){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),r()}catch(e){r(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const s of e){const e=s.trim();if(e)try{const s=JSON.parse(e);this.handleMessage(s)}catch(s){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){"request"===this.direction&&e.method?this.pairer.onRequest(e):"response"!==this.direction||void 0===e.id||e.method||this.pairer.onResponse(e)}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.pairer.getStats();console.error(`[mcp-logger CLI] Stream closed. Pending requests: ${e.pending}`)}e()}catch(s){e(s)}}}export{s as MessageInterceptor};
1
+ import{Transform as e}from"stream";class s extends e{buffer="";direction;logger;debug;constructor(e,s,t=!1){super(),this.direction=e,this.logger=s,this.debug=t}_transform(e,s,t){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),t()}catch(e){t(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const s of e){const e=s.trim();if(e)try{const s=JSON.parse(e);this.handleMessage(s)}catch(s){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){console.error("[mcp-logger CLI] Handling message:",JSON.stringify(e).slice(0,100)),"request"===this.direction&&e.method?this.logger.onRequest(e):"response"===this.direction&&(void 0===e.id||e.method?e.method&&!e.id&&this.logger.onNotification(e):this.logger.onResponse(e))}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.logger.getStats();console.error("[mcp-logger CLI] Stream closed. Pending requests: "+e.pending)}e()}catch(s){e(s)}}}export{s as MessageInterceptor};
package/dist/cli/main.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as e}from"child_process";import{program as r}from"commander";import{NodeSDK as t}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as s}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as o}from"@opentelemetry/resources";import{trace as n,SpanStatusCode as i}from"@opentelemetry/api";import{Transform as c}from"stream";const a={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let p=null,m=null,u=!1,g=null;function l(){return m}function d(){return g}async function h(){if(p)try{await p.shutdown(),p=null,m=null,u=!1,g=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}class f extends c{buffer="";direction;pairer;debug;constructor(e,r,t=!1){super(),this.direction=e,this.pairer=r,this.debug=t}_transform(e,r,t){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),t()}catch(e){t(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const r=JSON.parse(e);this.handleMessage(r)}catch(r){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){"request"===this.direction&&e.method?this.pairer.onRequest(e):"response"!==this.direction||void 0===e.id||e.method||this.pairer.onResponse(e)}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.pairer.getStats();console.error(`[mcp-logger CLI] Stream closed. Pending requests: ${e.pending}`)}e()}catch(r){e(r)}}}function S(e,r=1e4){try{const t=JSON.stringify(e);return t.length>r?t.slice(0,r)+"... (truncated)":t}catch(e){return null}}function I(e){const r=l();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const t=`mcp.${e.method}`;r.startActiveSpan(t,r=>{try{const t={"mcp.method":e.method,"mcp.source":e.source,"mcp.transport":e.transport,"mcp.log_type":"request-response","mcp.request.id":String(e.request.id),"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":S(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const r=e.request.params.name,s=e.request.params.arguments;if(r&&(t["mcp.tool.name"]=r),s){const e=S(s);e&&(t["mcp.tool.arguments"]=e,t["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if(void 0!==e.response.result){const r=S(e.response.result);r&&(t["mcp.response.result"]=r,t["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const r=S(e.request.headers);r&&(t["mcp.headers"]=r,t["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=d();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(e.customEvents&&e.customEvents.length>0&&(t["mcp.events"]=JSON.stringify(e.customEvents),t["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{r.addEvent(`custom.${e.level||"info"}`,{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){t["mcp.error"]=!0;const s=e.response.error instanceof Error?e.response.error.message:String(e.response.error);t["mcp.error.message"]=s,e.response.error instanceof Error&&r.recordException(e.response.error),r.setStatus({code:i.ERROR,message:s})}else r.setStatus({code:i.OK});!function(e,r){for(const[t,s]of Object.entries(r))null!=s&&e.setAttribute(t,s)}(r,t)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),r.setStatus({code:i.ERROR,message:String(e)})}finally{r.end()}})}class y{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(e){var r;r=e.method,a.TRACKED_METHODS.includes(r)?e.id?(this.pendingRequests.set(e.id,{request:e,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${e.method} (ID: ${e.id})`),setTimeout(()=>this.cleanupStale(e.id),this.TIMEOUT)):this.debug&&console.error("[mcp-logger CLI] Request without ID, skipping:",e.method):this.debug&&console.error(`[mcp-logger CLI] Skipping method: ${e.method}`)}onResponse(e){if(!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const r=this.pendingRequests.get(e.id);if(!r)return void(this.debug&&console.error(`[mcp-logger CLI] No matching request for response ID: ${e.id}`));const t=Date.now(),s=t-r.timestamp;this.debug&&console.error(`[mcp-logger CLI] Pairing success: ${r.request.method} (${s}ms)`),this.createPairedSpan(r.request,e,r.timestamp,t,s),this.pendingRequests.delete(e.id)}createPairedSpan(e,r,t,s,o){I({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:t,params:e.params},response:{timestamp:s,result:r.result,error:r.error},duration:o})}cleanupStale(e){const r=this.pendingRequests.get(e);r&&Date.now()-r.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] Request timeout: ${r.request.method} (ID: ${e})`),this.pendingRequests.delete(e))}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}r.name("mcp-logger").description("Add observability to any MCP server without code changes").version("1.0.0").requiredOption("-k, --api-key <key>","Aware API key").option("-s, --service-name <name>","Service name for identification").option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(r,i)=>{try{const c=!0;!function(e){if(u)return console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),m;const r=e.serviceName||`${a.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${e.endpoint||a.ENDPOINT}`),console.error(`[MCP Logger] Service: ${r}`),p=new t({resource:new o({"service.name":r,"service.version":a.SDK_VERSION}),traceExporter:new s({url:e.endpoint||a.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),p.start(),m=n.getTracer("mcp-logger",a.SDK_VERSION),u=!0,console.error("[MCP Logger] Telemetry initialized successfully");const i=async()=>{console.error("[MCP Logger] Shutting down telemetry..."),await h()};process.once("SIGTERM",i),process.once("SIGINT",i)}({...i,debug:c}),console.error("[MCP Logger CLI] Starting MCP server:",r.join(" "));const[l,...d]=r;!function(e,r,t){g={command:e,args:r,env:t}}(l,d,{...process.env});const S=e(l,d,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),I=new y(c),C=new f("request",I,c);process.stdin.pipe(C).pipe(S.stdin);const E=new f("response",I,c);S.stdout.pipe(E).pipe(process.stdout),S.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),I.clear(),await h(),process.exit(1)}),S.on("exit",async(e,r)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${r}`),I.clear(),await h(),process.exit(e||0)});let q=!1;const v=async e=>{q||(q=!0,console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),S.kill(e),await Promise.race([new Promise(e=>S.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await h(),process.exit(0))};process.on("SIGTERM",()=>v("SIGTERM")),process.on("SIGINT",()=>v("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await h(),process.exit(1)}}),r.parse();
2
+ function e(e){return!y.includes(e)}function r(){return S}function s(){return v}async function t(){if(q)try{await q.shutdown(),q=null,S=null,C=!1,v=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function o(e,r){for(const[s,t]of Object.entries(r))null!=t&&e.setAttribute(s,t)}function n(e,r=1e4){try{const s=JSON.stringify(e);return s.length>r?s.slice(0,r)+"... (truncated)":s}catch(e){return null}}import{spawn as i}from"child_process";import{program as c}from"commander";import{NodeSDK as a}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as p}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as m}from"@opentelemetry/resources";import{trace as l,SpanStatusCode as d}from"@opentelemetry/api";import g from"crypto";import{Transform as u}from"stream";const h="https://aware.mcypher.com/v1/traces",f="1.0.0",y=["notifications/initialized"];let q=null,S=null,C=!1,v=null;class L extends u{buffer="";direction;logger;debug;constructor(e,r,s=!1){super(),this.direction=e,this.logger=r,this.debug=s}_transform(e,r,s){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),s()}catch(e){s(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const r=JSON.parse(e);this.handleMessage(r)}catch(r){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){console.error("[mcp-logger CLI] Handling message:",JSON.stringify(e).slice(0,100)),"request"===this.direction&&e.method?this.logger.onRequest(e):"response"===this.direction&&(void 0===e.id||e.method?e.method&&!e.id&&this.logger.onNotification(e):this.logger.onResponse(e))}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.logger.getStats();console.error("[mcp-logger CLI] Stream closed. Pending requests: "+e.pending)}e()}catch(r){e(r)}}}class R{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(r){if(this.debug&&console.error(`[mcp-logger CLI] 📥 REQUEST: method="${r.method}", id=${r.id}, params=${JSON.stringify(r.params)?.slice(0,100)}`),e(r.method)){if(!r.id)return this.debug&&console.error(`[mcp-logger CLI] 🔔 Notification detected: ${r.method} → Creating request-only span`),void this.createRequestOnlySpan(r,Date.now());this.pendingRequests.set(r.id,{request:r,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${r.method} (ID: ${r.id})`),setTimeout(()=>this.cleanupStale(r.id),this.TIMEOUT)}else this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+r.method)}onResponse(e){if(this.debug&&console.error(`[mcp-logger CLI] 📤 RESPONSE: id=${e.id}, hasResult=${!!e.result}, result=${JSON.stringify(e.result)?.slice(0,100)}, hasError=${!!e.error}`),!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const r=this.pendingRequests.get(e.id);if(!r)return this.debug&&console.error(`[mcp-logger CLI] ⏭️ No matching request for response ID: ${e.id} → Creating response-only span`),void this.createResponseOnlySpan(e,Date.now());const s=Date.now(),t=s-r.timestamp;this.debug&&console.error(`[mcp-logger CLI] ✅ Pairing success: ${r.request.method} (${t}ms)`),this.createPairedSpan(r.request,e,r.timestamp,s,t),this.pendingRequests.delete(e.id)}onNotification(r){this.debug&&console.error(`[mcp-logger CLI] 📢 NOTIFICATION (Server→Client): method="${r.method}", params=${JSON.stringify(r.params)?.slice(0,100)}`),e(r.method)?this.createResponseOnlySpan({jsonrpc:"2.0",id:`notification-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,method:r.method},Date.now()):this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+r.method)}createPairedSpan(e,t,i,c,a){!function(e){const t=r();if(!t)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const i="mcp."+e.method;t.startActiveSpan(i,r=>{try{const t={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.request.id":e.request.id+"","mcp.request.method":e.method,"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":n(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.id":e.request.id+"","mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const r=e.request.params.name,s=e.request.params.arguments;if(r&&(t["mcp.tool.name"]=r),s){const e=n(s);e&&(t["mcp.tool.arguments"]=e,t["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if(void 0!==e.response.result){const r=n(e.response.result);r&&(t["mcp.response.result"]=r,t["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const r=n(e.request.headers);r&&(t["mcp.headers"]=r,t["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=s();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(e.customEvents&&e.customEvents.length>0&&(t["mcp.events"]=JSON.stringify(e.customEvents),t["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{r.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){let s;t["mcp.error"]=!0,s=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:n(e.response.error)||"Unknown error",t["mcp.error.message"]=s,e.response.error instanceof Error&&r.recordException(e.response.error),r.setStatus({code:d.ERROR,message:s})}else r.setStatus({code:d.OK});o(r,t)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),r.setStatus({code:d.ERROR,message:e+""})}finally{r.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:i,params:e.params},response:{timestamp:c,result:t.result,error:t.error},duration:a})}cleanupStale(e){const r=this.pendingRequests.get(e);r&&Date.now()-r.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] ⏰ Request timeout: ${r.request.method} (ID: ${e}) → Creating request-only span`),this.createRequestOnlySpan(r.request,r.timestamp),this.pendingRequests.delete(e))}createRequestOnlySpan(e,t){!function(e){const t=r();if(!t)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const i="mcp."+e.method;t.startActiveSpan(i,r=>{try{const t={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.request.id":e.request.id+"","mcp.request.method":e.method,"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":n(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length};if("tools/call"===e.method&&e.request.params){const r=e.request.params.name,s=e.request.params.arguments;if(r&&(t["mcp.tool.name"]=r),s){const e=n(s);e&&(t["mcp.tool.arguments"]=e,t["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if("cli"===e.source){const e=s();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}r.setStatus({code:d.OK}),o(r,t)}catch(e){console.error("[MCP Logger] Error creating request-only span:",e),r.setStatus({code:d.ERROR,message:e+""})}finally{r.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:t,params:e.params}})}createResponseOnlySpan(e,t){!function(e){const t=r();if(!t)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const i="mcp."+e.method;t.startActiveSpan(i,r=>{try{const t={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.response.id":e.response.id+"","mcp.response.method":e.method,"mcp.response.timestamp":e.response.timestamp};if(void 0!==e.response.result){const r=n(e.response.result);r&&(t["mcp.response.result"]=r,t["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.response.error){let s;t["mcp.error"]=!0,s=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:n(e.response.error)||"Unknown error",t["mcp.error.message"]=s,e.response.error instanceof Error&&r.recordException(e.response.error),r.setStatus({code:d.ERROR,message:s})}else r.setStatus({code:d.OK});if("cli"===e.source){const e=s();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}o(r,t)}catch(e){console.error("[MCP Logger] Error creating response-only span:",e),r.setStatus({code:d.ERROR,message:e+""})}finally{r.end()}})}({method:e.method||"same_request_method",source:"cli",transport:"stdio",response:{id:e.id,timestamp:t,result:e.result,error:e.error}})}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}c.name("mcp-logger").description("Add observability to any MCP server without code changes").version("1.0.0").requiredOption("-k, --api-key <key>","Aware API key").option("-s, --service-name <name>","Service name for identification").option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(e,r)=>{try{const s=!0;(function(e){if(C)return console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),S;let r=e.endpoint||h;var s;s=e.apiKey,"34f17c05db5043bd9c066b2792924876de5ab515d7176c9996e47328aea49297"!==g.createHash("sha256").update(s).digest("hex")&&e.endpoint&&(console.error("[MCP Logger] Warning: Custom endpoint requires internal API key"),console.error("[MCP Logger] Using default endpoint:",h),r=h);const o=e.serviceName||"mcp-server-"+Math.random().toString(36).slice(2,8);console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+r),console.error("[MCP Logger] Service: "+o),q=new a({resource:new m({"service.name":o,"service.version":f}),traceExporter:new p({url:r,headers:{"x-api-key":e.apiKey}})}),q.start(),S=l.getTracer("mcp-logger",f),C=!0,console.error("[MCP Logger] Telemetry initialized successfully");const n=async()=>{console.error("[MCP Logger] Shutting down telemetry..."),await t()};process.once("SIGTERM",n),process.once("SIGINT",n)})({...r,debug:s}),console.error("[MCP Logger CLI] Starting MCP server:",e.join(" "));const[o,...n]=e;!function(e,r){v={command:e,args:r,env:{}}}(o,n);const c=i(o,n,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),d=new R(s),u=new L("request",d,s);process.stdin.pipe(u).pipe(c.stdin);const y=new L("response",d,s);c.stdout.pipe(y).pipe(process.stdout),c.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),d.clear(),await t(),process.exit(1)}),c.on("exit",async(e,r)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${r}`),d.clear(),await t(),process.exit(e||0)});let I=!1;const O=async e=>{I||(I=!0,console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),c.kill(e),await Promise.race([new Promise(e=>c.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await t(),process.exit(0))};process.on("SIGTERM",()=>O("SIGTERM")),process.on("SIGINT",()=>O("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await t(),process.exit(1)}}),c.parse();
@@ -0,0 +1 @@
1
+ function e(e){return!t.includes(e)}import"crypto";import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const t=["notifications/initialized"];class r{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(t){if(this.debug&&console.error(`[mcp-logger CLI] 📥 REQUEST: method="${t.method}", id=${t.id}, params=${JSON.stringify(t.params)?.slice(0,100)}`),e(t.method)){if(!t.id)return this.debug&&console.error(`[mcp-logger CLI] 🔔 Notification detected: ${t.method} → Creating request-only span`),void this.createRequestOnlySpan(t,Date.now());this.pendingRequests.set(t.id,{request:t,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${t.method} (ID: ${t.id})`),setTimeout(()=>this.cleanupStale(t.id),this.TIMEOUT)}else this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+t.method)}onResponse(e){if(this.debug&&console.error(`[mcp-logger CLI] 📤 RESPONSE: id=${e.id}, hasResult=${!!e.result}, result=${JSON.stringify(e.result)?.slice(0,100)}, hasError=${!!e.error}`),!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const t=this.pendingRequests.get(e.id);if(!t)return this.debug&&console.error(`[mcp-logger CLI] ⏭️ No matching request for response ID: ${e.id} → Creating response-only span`),void this.createResponseOnlySpan(e,Date.now());const r=Date.now(),o=r-t.timestamp;this.debug&&console.error(`[mcp-logger CLI] ✅ Pairing success: ${t.request.method} (${o}ms)`),this.createPairedSpan(t.request,e,t.timestamp,r,o),this.pendingRequests.delete(e.id)}onNotification(t){this.debug&&console.error(`[mcp-logger CLI] 📢 NOTIFICATION (Server→Client): method="${t.method}", params=${JSON.stringify(t.params)?.slice(0,100)}`),e(t.method)?this.createResponseOnlySpan({jsonrpc:"2.0",id:`notification-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,method:t.method},Date.now()):this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+t.method)}createPairedSpan(e,t,r,o,s){e.method,e.id,e.params,t.result,t.error,console.error("[MCP Logger] Tracer not initialized, cannot create span")}cleanupStale(e){const t=this.pendingRequests.get(e);t&&Date.now()-t.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] ⏰ Request timeout: ${t.request.method} (ID: ${e}) → Creating request-only span`),this.createRequestOnlySpan(t.request,t.timestamp),this.pendingRequests.delete(e))}createRequestOnlySpan(e,t){e.method,e.id,e.params,console.error("[MCP Logger] Tracer not initialized, cannot create span")}createResponseOnlySpan(e,t){e.method,e.id,e.result,e.error,console.error("[MCP Logger] Tracer not initialized, cannot create span")}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}export{r as MCPMessageLogger};
@@ -1 +1 @@
1
- const t={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};function E(E){return t.TRACKED_METHODS.includes(E)}export{t as CONFIG,E as shouldTrackMethod};
1
+ function e(e){return!a.includes(e)}function t(e){return c.createHash("sha256").update(e).digest("hex")===r.INTERNAL_KEY_HASH}import c from"crypto";const r={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",INTERNAL_KEY_HASH:"34f17c05db5043bd9c066b2792924876de5ab515d7176c9996e47328aea49297"},a=["notifications/initialized"];export{r as CONFIG,t as isInternalApiKey,e as shouldTrackMethod};
@@ -1 +1 @@
1
- import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";function e(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}export{e as createPairedSpan};
1
+ function e(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function r(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function o(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";import"crypto";export{e as createPairedSpan,r as createRequestOnlySpan,o as createResponseOnlySpan};
@@ -1 +1 @@
1
- import{SpanStatusCode as t,context as r,trace as e}from"@opentelemetry/api";function n(t,r){for(const[e,n]of Object.entries(r))null!=n&&t.setAttribute(e,n)}function o(t,r,e,n){try{e?t.addEvent(r,e):t.addEvent(r)}catch(t){n&&console.error("[MCP Logger] Failed to add span event:",t)}}function c(r,e,n){const o=e instanceof Error?e.message:"Unknown error",c=e instanceof Error?e.constructor.name:"Error";e instanceof Error&&r.recordException(e),r.setStatus({code:t.ERROR,message:o}),r.setAttribute("error",!0),r.setAttribute("error.type",c),r.setAttribute("error.message",o),n&&console.error("[MCP Logger] Error recorded in span:",e)}function s(r){r.setStatus({code:t.OK})}function a(t,r=1e4){try{const e=JSON.stringify(t);return e.length>r?e.slice(0,r)+"... (truncated)":e}catch(t){return null}}function i(){const t={},n=r.active(),o=e.getSpan(n);if(o){const r=o.spanContext();r&&(t.traceparent=`00-${r.traceId}-${r.spanId}-01`)}return t}export{o as addSpanEvent,i as getTraceHeaders,s as markSpanSuccess,c as recordSpanError,a as safeStringify,n as setSpanAttributes};
1
+ function t(t,r){for(const[e,n]of Object.entries(r))null!=n&&t.setAttribute(e,n)}function r(t,r,e,n){try{e?t.addEvent(r,e):t.addEvent(r)}catch(t){n&&console.error("[MCP Logger] Failed to add span event:",t)}}function e(t,r,e){const n=r instanceof Error?r.message:"Unknown error",o=r instanceof Error?r.constructor.name:"Error";r instanceof Error&&t.recordException(r),t.setStatus({code:s.ERROR,message:n}),t.setAttribute("error",!0),t.setAttribute("error.type",o),t.setAttribute("error.message",n),e&&console.error("[MCP Logger] Error recorded in span:",r)}function n(t){t.setStatus({code:s.OK})}function o(t,r=1e4){try{const e=JSON.stringify(t);return e.length>r?e.slice(0,r)+"... (truncated)":e}catch(t){return null}}function c(){const t={},r=a.active(),e=i.getSpan(r);if(e){const r=e.spanContext();r&&(t.traceparent=`00-${r.traceId}-${r.spanId}-01`)}return t}import{SpanStatusCode as s,context as a,trace as i}from"@opentelemetry/api";export{r as addSpanEvent,c as getTraceHeaders,n as markSpanSuccess,e as recordSpanError,o as safeStringify,t as setSpanAttributes};
@@ -1 +1 @@
1
- import{NodeSDK as e}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as r}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as o}from"@opentelemetry/resources";import{trace as t}from"@opentelemetry/api";const n="https://aware.mcypher.com/v1/traces",i="1.0.0";let c=null,l=null,s=!1,a=null;function u(a){if(s)return a.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),l;const u=a.serviceName||`mcp-server-${Math.random().toString(36).slice(2,8)}`;a.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${a.endpoint||n}`),console.error(`[MCP Logger] Service: ${u}`)),c=new e({resource:new o({"service.name":u,"service.version":i}),traceExporter:new r({url:a.endpoint||n,headers:{"x-api-key":a.apiKey}})}),c.start(),l=t.getTracer("mcp-logger",i),s=!0,a.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const g=async()=>{a.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await y()};return process.once("SIGTERM",g),process.once("SIGINT",g),l}function g(){return l}function m(){return s}function p(e,r,o){a={command:e,args:r,env:o}}function d(){return a}async function y(){if(c)try{await c.shutdown(),c=null,l=null,s=!1,a=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}export{d as getCLIServerInfo,g as getTracer,u as initializeTelemetry,m as isInitializedTelemetry,p as setCLIServerInfo,y as shutdownTelemetry};
1
+ function e(e){if(h)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),f;let r=e.endpoint||u;var o;o=e.apiKey,g.createHash("sha256").update(o).digest("hex")!==d&&e.endpoint&&(console.error("[MCP Logger] Warning: Custom endpoint requires internal API key"),console.error("[MCP Logger] Using default endpoint:",u),r=u);const t=e.serviceName||`${m}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+r),console.error("[MCP Logger] Service: "+t)),y=new c({resource:new l({"service.name":t,"service.version":p}),traceExporter:new s({url:r,headers:{"x-api-key":e.apiKey}})}),y.start(),f=a.getTracer("mcp-logger",p),h=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const n=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await i()};return process.once("SIGTERM",n),process.once("SIGINT",n),f}function r(){return f}function o(){return h}function t(e,r,o){M={command:e,args:r,env:o}}function n(){return M}async function i(){if(y)try{await y.shutdown(),y=null,f=null,h=!1,M=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}import{NodeSDK as c}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as s}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as l}from"@opentelemetry/resources";import{trace as a}from"@opentelemetry/api";import g from"crypto";const u="https://aware.mcypher.com/v1/traces",p="1.0.0",m="mcp-server",d="34f17c05db5043bd9c066b2792924876de5ab515d7176c9996e47328aea49297";let y=null,f=null,h=!1,M=null;export{n as getCLIServerInfo,r as getTracer,e as initializeTelemetry,o as isInitializedTelemetry,t as setCLIServerInfo,i as shutdownTelemetry};
@@ -1,34 +1,6 @@
1
- /**
2
- * 공통 설정 상수
3
- */
4
- declare const CONFIG: {
5
- /**
6
- * OTLP Endpoint (하드코딩)
7
- */
8
- readonly ENDPOINT: "https://aware.mcypher.com/v1/traces";
9
- /**
10
- * SDK 버전
11
- */
12
- readonly SDK_VERSION: "1.0.0";
13
- /**
14
- * 서비스 이름 접두사
15
- */
16
- readonly SERVICE_NAME_PREFIX: "mcp-server";
17
- /**
18
- * 추적 대상 메서드
19
- * CLI와 SDK 모두에서 공통으로 사용
20
- */
21
- readonly TRACKED_METHODS: readonly ["tools/call", "tools/list"];
22
- };
23
- /**
24
- * 추적 대상 메서드 타입
25
- */
26
- type TrackedMethod = typeof CONFIG.TRACKED_METHODS[number];
27
-
28
1
  /**
29
2
  * 공통 타입 정의
30
3
  */
31
-
32
4
  /**
33
5
  * 텔레메트리 초기화 옵션
34
6
  */
@@ -62,10 +34,6 @@ interface SpanAttributes {
62
34
  * MCP 메시지 방향
63
35
  */
64
36
  type MessageDirection = 'request' | 'response';
65
- /**
66
- * 로그 타입 분류
67
- */
68
- type MCPLogType = 'request-response' | 'custom-event' | 'internal-event';
69
37
  /**
70
38
  * JSON-RPC 메시지 타입
71
39
  */
@@ -102,20 +70,25 @@ interface JSONRPCResponse extends JSONRPCMessage {
102
70
  }
103
71
  /**
104
72
  * MCP Span 데이터 구조 (ES 최적화)
73
+ * Request/Response 필드가 optional이므로 log_type 불필요
74
+ * - request만 있음 = request-only
75
+ * - response만 있음 = response-only
76
+ * - 둘 다 있음 = paired (duration 포함)
105
77
  */
106
78
  interface MCPSpanData {
107
- 'mcp.method': TrackedMethod;
108
79
  'mcp.source': 'cli' | 'sdk';
109
80
  'mcp.transport': 'stdio' | 'sse';
110
- 'mcp.log_type': MCPLogType;
111
- 'mcp.request.id': string;
112
- 'mcp.request.timestamp': number;
113
- 'mcp.request.params': string;
114
- 'mcp.request.params.size': number;
115
- 'mcp.response.timestamp': number;
81
+ 'mcp.request.id'?: string;
82
+ 'mcp.request.method'?: string;
83
+ 'mcp.request.timestamp'?: number;
84
+ 'mcp.request.params'?: string;
85
+ 'mcp.request.params.size'?: number;
86
+ 'mcp.response.id'?: string;
87
+ 'mcp.response.method'?: string;
88
+ 'mcp.response.timestamp'?: number;
116
89
  'mcp.response.result'?: string;
117
90
  'mcp.response.result.size'?: number;
118
- 'mcp.duration_ms': number;
91
+ 'mcp.duration_ms'?: number;
119
92
  'mcp.tool.name'?: string;
120
93
  'mcp.tool.arguments'?: string;
121
94
  'mcp.tool.arguments.size'?: number;
@@ -153,7 +126,7 @@ interface CustomLogEntry {
153
126
  * Paired Span 입력 데이터
154
127
  */
155
128
  interface PairedSpanInput {
156
- method: TrackedMethod;
129
+ method: string;
157
130
  source: 'cli' | 'sdk';
158
131
  transport: 'stdio' | 'sse';
159
132
  request: {
@@ -170,5 +143,33 @@ interface PairedSpanInput {
170
143
  duration: number;
171
144
  customEvents?: CustomLogEntry[];
172
145
  }
146
+ /**
147
+ * Response-only Span 입력 데이터 (notification 등 response가 없는 경우)
148
+ */
149
+ interface ResponseOnlySpanInput {
150
+ method: string;
151
+ source: 'cli' | 'sdk';
152
+ transport: 'stdio' | 'sse';
153
+ response: {
154
+ id: string | number;
155
+ timestamp: number;
156
+ result?: any;
157
+ error?: any;
158
+ };
159
+ }
160
+ /**
161
+ * Request-only Span 입력 데이터 (timeout 등으로 response가 없는 경우)
162
+ */
163
+ interface RequestOnlySpanInput {
164
+ method: string;
165
+ source: 'cli' | 'sdk';
166
+ transport: 'stdio' | 'sse';
167
+ request: {
168
+ id: string | number;
169
+ timestamp: number;
170
+ params: any;
171
+ headers?: Record<string, string>;
172
+ };
173
+ }
173
174
 
174
- export type { CustomLogEntry, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, MCPLogType, MCPSpanData, MessageDirection, PairedSpanInput, SpanAttributes, TelemetryOptions };
175
+ export type { CustomLogEntry, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, MCPSpanData, MessageDirection, PairedSpanInput, RequestOnlySpanInput, ResponseOnlySpanInput, SpanAttributes, TelemetryOptions };
package/dist/index.d.ts CHANGED
@@ -3,7 +3,6 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  /**
4
4
  * 공통 타입 정의
5
5
  */
6
-
7
6
  /**
8
7
  * 텔레메트리 초기화 옵션
9
8
  */
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{trace as e,SpanStatusCode as t,context as r}from"@opentelemetry/api";import{NodeSDK as s}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as o}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as n}from"@opentelemetry/resources";const a={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let c=null,i=null,m=!1,l=null;function p(){return i}function u(){return l}async function g(){if(c)try{await c.shutdown(),c=null,i=null,m=!1,l=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function d(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function f(e){const r=p();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const s=`mcp.${e.method}`;r.startActiveSpan(s,r=>{try{const s={"mcp.method":e.method,"mcp.source":e.source,"mcp.transport":e.transport,"mcp.log_type":"request-response","mcp.request.id":String(e.request.id),"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":d(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const t=e.request.params.name,r=e.request.params.arguments;if(t&&(s["mcp.tool.name"]=t),r){const e=d(r);e&&(s["mcp.tool.arguments"]=e,s["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==e.response.result){const t=d(e.response.result);t&&(s["mcp.response.result"]=t,s["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const t=d(e.request.headers);t&&(s["mcp.headers"]=t,s["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=u();e&&(s["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(e.customEvents&&e.customEvents.length>0&&(s["mcp.events"]=JSON.stringify(e.customEvents),s["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{r.addEvent(`custom.${e.level||"info"}`,{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){s["mcp.error"]=!0;const o=e.response.error instanceof Error?e.response.error.message:String(e.response.error);s["mcp.error.message"]=o,e.response.error instanceof Error&&r.recordException(e.response.error),r.setStatus({code:t.ERROR,message:o})}else r.setStatus({code:t.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(r,s)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),r.setStatus({code:t.ERROR,message:String(e)})}finally{r.end()}})}const h=Symbol("mcp-logger.customEvents");const E=Object.assign(function(t,l){if(!l.apiKey)throw new Error("[mcp-logger] apiKey is required");if(!t)throw new Error("[mcp-logger] server instance is required");!function(t){if(m)return t.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),i;const r=t.serviceName||`${a.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;t.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${t.endpoint||a.ENDPOINT}`),console.error(`[MCP Logger] Service: ${r}`)),c=new s({resource:new n({"service.name":r,"service.version":a.SDK_VERSION}),traceExporter:new o({url:t.endpoint||a.ENDPOINT,headers:{"x-api-key":t.apiKey}})}),c.start(),i=e.getTracer("mcp-logger",a.SDK_VERSION),m=!0,t.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const l=async()=>{t.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await g()};process.once("SIGTERM",l),process.once("SIGINT",l)}(l),function(e,t){const s=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,o){const n=e.shape?.method,c=n?._def?.value||"unknown";return function(e){return a.TRACKED_METHODS.includes(e)}(c)?(t.debug&&console.log(`[mcp-logger] Instrumenting handler: ${c}`),s(e,async(e,s)=>{const n=Date.now(),a=`${c}-${n}-${Math.random().toString(36).slice(2,8)}`,i=[],m=r.active().setValue(h,i);try{const l=await r.with(m,async()=>await o(e,s)),p=Date.now();return f({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:n,params:e.params},response:{timestamp:p,result:l},duration:p-n,customEvents:i.length>0?i:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${p-n}ms):`,{method:c,customEvents:i.length}),l}catch(r){const s=Date.now();throw f({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:i.length>0?i:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})):(t.debug&&console.log(`[mcp-logger] Skipping instrumentation for method: ${c}`),s(e,o))},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(t,l),l.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(t){const s=r.active().getValue(h);if(!s)return void console.warn("[mcp-logger] addLog called outside of handler context");const o={level:t.level||"info",message:t.message,metadata:t.metadata,timestamp:t.timestamp||Date.now()};s.push(o);const n=e.getSpan(r.active());n&&n.addEvent(`custom.${o.level}`,{message:o.message,metadata:JSON.stringify(o.metadata||{}),timestamp:o.timestamp})},async shutdown(){await g()}});export{E as trace};
1
+ function e(){return f}function t(){return v}async function r(){if(d)try{await d.shutdown(),d=null,f=null,h=!1,v=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function o(r){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+r.method;o.startActiveSpan(n,e=>{try{const o={"mcp.source":r.source,"mcp.transport":r.transport,"mcp.request.id":r.request.id+"","mcp.request.method":r.method,"mcp.request.timestamp":r.request.timestamp,"mcp.request.params":s(r.request.params)||"{}","mcp.request.params.size":JSON.stringify(r.request.params||{}).length,"mcp.response.id":r.request.id+"","mcp.response.timestamp":r.response.timestamp,"mcp.duration_ms":r.duration};if("tools/call"===r.method&&r.request.params){const e=r.request.params.name,t=r.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),t){const e=s(t);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==r.response.result){const e=s(r.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(r.response.result).length)}if(r.request.headers){const e=s(r.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(r.request.headers).length)}if("cli"===r.source){const e=t();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(r.customEvents&&r.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(r.customEvents),o["mcp.events.count"]=r.customEvents.length,r.customEvents.forEach(t=>{e.addEvent("custom."+(t.level||"info"),{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp||Date.now()})})),r.response.error){let t;o["mcp.error"]=!0,t=r.response.error instanceof Error?r.response.error.message:"string"==typeof r.response.error?r.response.error:s(r.response.error)||"Unknown error",o["mcp.error.message"]=t,r.response.error instanceof Error&&e.recordException(r.response.error),e.setStatus({code:a.ERROR,message:t})}else e.setStatus({code:a.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(e,o)}catch(t){console.error("[MCP Logger] Error creating paired span:",t),e.setStatus({code:a.ERROR,message:t+""})}finally{e.end()}})}import{trace as n,SpanStatusCode as a,context as c}from"@opentelemetry/api";import{NodeSDK as i}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as m}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as p}from"@opentelemetry/resources";import l from"crypto";const u="https://aware.mcypher.com/v1/traces",g="1.0.0";let d=null,f=null,h=!1,v=null;const y=Symbol("mcp-logger.customEvents"),q=Object.assign(function(e,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");(function(e){if(h)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),f;let t=e.endpoint||u;var s;s=e.apiKey,"34f17c05db5043bd9c066b2792924876de5ab515d7176c9996e47328aea49297"!==l.createHash("sha256").update(s).digest("hex")&&e.endpoint&&(console.error("[MCP Logger] Warning: Custom endpoint requires internal API key"),console.error("[MCP Logger] Using default endpoint:",u),t=u);const o=e.serviceName||"mcp-server-"+Math.random().toString(36).slice(2,8);e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+t),console.error("[MCP Logger] Service: "+o)),d=new i({resource:new p({"service.name":o,"service.version":g}),traceExporter:new m({url:t,headers:{"x-api-key":e.apiKey}})}),d.start(),f=n.getTracer("mcp-logger",g),h=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const a=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",a),process.once("SIGINT",a)})(t),function(e,t){const r=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const n=e.shape?.method,a=n?._def?.value||"unknown";return t.debug&&console.log("[mcp-logger] Instrumenting handler: "+a),r(e,async(e,r)=>{const n=Date.now(),i=`${a}-${n}-${Math.random().toString(36).slice(2,8)}`,m=[],p=c.active().setValue(y,m);try{const l=await c.with(p,async()=>await s(e,r)),u=Date.now();return o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:u,result:l},duration:u-n,customEvents:m.length>0?m:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${u-n}ms):`,{method:a,customEvents:m.length}),l}catch(r){const s=Date.now();throw o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:m.length>0?m:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,t),t.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(e){const t=c.active().getValue(y);if(!t)return void console.warn("[mcp-logger] addLog called outside of handler context");const r={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(r);const s=n.getSpan(c.active());s&&s.addEvent("custom."+r.level,{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp})},async shutdown(){await r()}});export{q as trace};
package/dist/sdk/index.js CHANGED
@@ -1 +1 @@
1
- import{trace as e,SpanStatusCode as t,context as r}from"@opentelemetry/api";import{NodeSDK as s}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as o}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as n}from"@opentelemetry/resources";const a={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let c=null,i=null,m=!1,l=null;function p(){return i}function u(){return l}async function g(){if(c)try{await c.shutdown(),c=null,i=null,m=!1,l=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function d(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function f(e){const r=p();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const s=`mcp.${e.method}`;r.startActiveSpan(s,r=>{try{const s={"mcp.method":e.method,"mcp.source":e.source,"mcp.transport":e.transport,"mcp.log_type":"request-response","mcp.request.id":String(e.request.id),"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":d(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const t=e.request.params.name,r=e.request.params.arguments;if(t&&(s["mcp.tool.name"]=t),r){const e=d(r);e&&(s["mcp.tool.arguments"]=e,s["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==e.response.result){const t=d(e.response.result);t&&(s["mcp.response.result"]=t,s["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const t=d(e.request.headers);t&&(s["mcp.headers"]=t,s["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=u();e&&(s["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(e.customEvents&&e.customEvents.length>0&&(s["mcp.events"]=JSON.stringify(e.customEvents),s["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{r.addEvent(`custom.${e.level||"info"}`,{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){s["mcp.error"]=!0;const o=e.response.error instanceof Error?e.response.error.message:String(e.response.error);s["mcp.error.message"]=o,e.response.error instanceof Error&&r.recordException(e.response.error),r.setStatus({code:t.ERROR,message:o})}else r.setStatus({code:t.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(r,s)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),r.setStatus({code:t.ERROR,message:String(e)})}finally{r.end()}})}const h=Symbol("mcp-logger.customEvents");const E=Object.assign(function(t,l){if(!l.apiKey)throw new Error("[mcp-logger] apiKey is required");if(!t)throw new Error("[mcp-logger] server instance is required");!function(t){if(m)return t.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),i;const r=t.serviceName||`${a.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;t.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error(`[MCP Logger] Endpoint: ${t.endpoint||a.ENDPOINT}`),console.error(`[MCP Logger] Service: ${r}`)),c=new s({resource:new n({"service.name":r,"service.version":a.SDK_VERSION}),traceExporter:new o({url:t.endpoint||a.ENDPOINT,headers:{"x-api-key":t.apiKey}})}),c.start(),i=e.getTracer("mcp-logger",a.SDK_VERSION),m=!0,t.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const l=async()=>{t.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await g()};process.once("SIGTERM",l),process.once("SIGINT",l)}(l),function(e,t){const s=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,o){const n=e.shape?.method,c=n?._def?.value||"unknown";return function(e){return a.TRACKED_METHODS.includes(e)}(c)?(t.debug&&console.log(`[mcp-logger] Instrumenting handler: ${c}`),s(e,async(e,s)=>{const n=Date.now(),a=`${c}-${n}-${Math.random().toString(36).slice(2,8)}`,i=[],m=r.active().setValue(h,i);try{const l=await r.with(m,async()=>await o(e,s)),p=Date.now();return f({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:n,params:e.params},response:{timestamp:p,result:l},duration:p-n,customEvents:i.length>0?i:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${p-n}ms):`,{method:c,customEvents:i.length}),l}catch(r){const s=Date.now();throw f({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:i.length>0?i:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})):(t.debug&&console.log(`[mcp-logger] Skipping instrumentation for method: ${c}`),s(e,o))},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(t,l),l.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(t){const s=r.active().getValue(h);if(!s)return void console.warn("[mcp-logger] addLog called outside of handler context");const o={level:t.level||"info",message:t.message,metadata:t.metadata,timestamp:t.timestamp||Date.now()};s.push(o);const n=e.getSpan(r.active());n&&n.addEvent(`custom.${o.level}`,{message:o.message,metadata:JSON.stringify(o.metadata||{}),timestamp:o.timestamp})},async shutdown(){await g()}});export{E as trace};
1
+ function e(){return f}function t(){return v}async function r(){if(d)try{await d.shutdown(),d=null,f=null,h=!1,v=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function o(r){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+r.method;o.startActiveSpan(n,e=>{try{const o={"mcp.source":r.source,"mcp.transport":r.transport,"mcp.request.id":r.request.id+"","mcp.request.method":r.method,"mcp.request.timestamp":r.request.timestamp,"mcp.request.params":s(r.request.params)||"{}","mcp.request.params.size":JSON.stringify(r.request.params||{}).length,"mcp.response.id":r.request.id+"","mcp.response.timestamp":r.response.timestamp,"mcp.duration_ms":r.duration};if("tools/call"===r.method&&r.request.params){const e=r.request.params.name,t=r.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),t){const e=s(t);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==r.response.result){const e=s(r.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(r.response.result).length)}if(r.request.headers){const e=s(r.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(r.request.headers).length)}if("cli"===r.source){const e=t();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(r.customEvents&&r.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(r.customEvents),o["mcp.events.count"]=r.customEvents.length,r.customEvents.forEach(t=>{e.addEvent("custom."+(t.level||"info"),{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp||Date.now()})})),r.response.error){let t;o["mcp.error"]=!0,t=r.response.error instanceof Error?r.response.error.message:"string"==typeof r.response.error?r.response.error:s(r.response.error)||"Unknown error",o["mcp.error.message"]=t,r.response.error instanceof Error&&e.recordException(r.response.error),e.setStatus({code:a.ERROR,message:t})}else e.setStatus({code:a.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(e,o)}catch(t){console.error("[MCP Logger] Error creating paired span:",t),e.setStatus({code:a.ERROR,message:t+""})}finally{e.end()}})}import{trace as n,SpanStatusCode as a,context as c}from"@opentelemetry/api";import{NodeSDK as i}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as m}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as p}from"@opentelemetry/resources";import l from"crypto";const u="https://aware.mcypher.com/v1/traces",g="1.0.0";let d=null,f=null,h=!1,v=null;const y=Symbol("mcp-logger.customEvents"),q=Object.assign(function(e,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");(function(e){if(h)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),f;let t=e.endpoint||u;var s;s=e.apiKey,"34f17c05db5043bd9c066b2792924876de5ab515d7176c9996e47328aea49297"!==l.createHash("sha256").update(s).digest("hex")&&e.endpoint&&(console.error("[MCP Logger] Warning: Custom endpoint requires internal API key"),console.error("[MCP Logger] Using default endpoint:",u),t=u);const o=e.serviceName||"mcp-server-"+Math.random().toString(36).slice(2,8);e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+t),console.error("[MCP Logger] Service: "+o)),d=new i({resource:new p({"service.name":o,"service.version":g}),traceExporter:new m({url:t,headers:{"x-api-key":e.apiKey}})}),d.start(),f=n.getTracer("mcp-logger",g),h=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const a=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",a),process.once("SIGINT",a)})(t),function(e,t){const r=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const n=e.shape?.method,a=n?._def?.value||"unknown";return t.debug&&console.log("[mcp-logger] Instrumenting handler: "+a),r(e,async(e,r)=>{const n=Date.now(),i=`${a}-${n}-${Math.random().toString(36).slice(2,8)}`,m=[],p=c.active().setValue(y,m);try{const l=await c.with(p,async()=>await s(e,r)),u=Date.now();return o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:u,result:l},duration:u-n,customEvents:m.length>0?m:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${u-n}ms):`,{method:a,customEvents:m.length}),l}catch(r){const s=Date.now();throw o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:m.length>0?m:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,t),t.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(e){const t=c.active().getValue(y);if(!t)return void console.warn("[mcp-logger] addLog called outside of handler context");const r={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(r);const s=n.getSpan(c.active());s&&s.addEvent("custom."+r.level,{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp})},async shutdown(){await r()}});export{q as trace};
@@ -1 +1 @@
1
- import{context as e}from"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const t={TRACKED_METHODS:["tools/call","tools/list"]};function o(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}const n=Symbol("mcp-logger.customEvents");function r(r,c){const s=r.setRequestHandler.bind(r);r.setRequestHandler=function(r,l){const a=r.shape?.method,i=a?._def?.value||"unknown";return function(e){return t.TRACKED_METHODS.includes(e)}(i)?(c.debug&&console.log(`[mcp-logger] Instrumenting handler: ${i}`),s(r,async(t,r)=>{const s=Date.now(),a=(Math.random().toString(36).slice(2,8),[]),m=e.active().setValue(n,a);try{const n=await e.with(m,async()=>await l(t,r)),u=Date.now();return o(t.params),c.debug&&console.log(`[mcp-logger] Request completed (${u-s}ms):`,{method:i,customEvents:a.length}),n}catch(e){throw o(t.params),c.debug&&console.error("[mcp-logger] Request failed:",e),e}})):(c.debug&&console.log(`[mcp-logger] Skipping instrumentation for method: ${i}`),s(r,l))},c.debug&&console.log("[mcp-logger] Server instrumentation complete")}function c(){return e.active().getValue(n)}export{c as getCustomEventsFromContext,r as instrumentMCPServer};
1
+ function e(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function t(t,o){const c=t.setRequestHandler.bind(t);t.setRequestHandler=function(t,s){const a=t.shape?.method,l=a?._def?.value||"unknown";return o.debug&&console.log("[mcp-logger] Instrumenting handler: "+l),c(t,async(t,c)=>{const a=Date.now(),m=(Math.random().toString(36).slice(2,8),[]),i=n.active().setValue(r,m);try{const r=await n.with(i,async()=>await s(t,c)),p=Date.now();return e(t.params),o.debug&&console.log(`[mcp-logger] Request completed (${p-a}ms):`,{method:l,customEvents:m.length}),r}catch(n){throw e(t.params),o.debug&&console.error("[mcp-logger] Request failed:",n),n}})},o.debug&&console.log("[mcp-logger] Server instrumentation complete")}function o(){return n.active().getValue(r)}import{context as n}from"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";import"crypto";const r=Symbol("mcp-logger.customEvents");export{o as getCustomEventsFromContext,t as instrumentMCPServer};
@@ -5,7 +5,6 @@ import { Request, Result } from '@modelcontextprotocol/sdk/types.js';
5
5
  /**
6
6
  * 공통 타입 정의
7
7
  */
8
-
9
8
  /**
10
9
  * 텔레메트리 초기화 옵션
11
10
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awarecorp/mcp-logger",
3
- "version": "0.0.2-dev.5",
3
+ "version": "0.0.3-dev.0",
4
4
  "description": "Unified MCP observability solution - SDK for code integration and CLI for zero-config wrapping",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1 +0,0 @@
1
- import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const e={TRACKED_METHODS:["tools/call","tools/list"]};class t{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(t){var s;s=t.method,e.TRACKED_METHODS.includes(s)?t.id?(this.pendingRequests.set(t.id,{request:t,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${t.method} (ID: ${t.id})`),setTimeout(()=>this.cleanupStale(t.id),this.TIMEOUT)):this.debug&&console.error("[mcp-logger CLI] Request without ID, skipping:",t.method):this.debug&&console.error(`[mcp-logger CLI] Skipping method: ${t.method}`)}onResponse(e){if(!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const t=this.pendingRequests.get(e.id);if(!t)return void(this.debug&&console.error(`[mcp-logger CLI] No matching request for response ID: ${e.id}`));const s=Date.now(),o=s-t.timestamp;this.debug&&console.error(`[mcp-logger CLI] Pairing success: ${t.request.method} (${o}ms)`),this.createPairedSpan(t.request,e,t.timestamp,s,o),this.pendingRequests.delete(e.id)}createPairedSpan(e,t,s,o,r){e.method,e.id,e.params,t.result,t.error,console.error("[MCP Logger] Tracer not initialized, cannot create span")}cleanupStale(e){const t=this.pendingRequests.get(e);t&&Date.now()-t.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] Request timeout: ${t.request.method} (ID: ${e})`),this.pendingRequests.delete(e))}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}export{t as RequestResponsePairer};