@awarecorp/mcp-logger 0.0.2 → 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
@@ -60,9 +60,12 @@ npx @awarecorp/mcp-logger \
60
60
 
61
61
  ### Core Capabilities
62
62
 
63
- - **Automatic Request-Response Pairing**: Single span per request-response cycle
64
- - **Selective Method Tracking**: Only tracks `tools/call` and `tools/list` for efficiency
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`)
65
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
66
69
  - **Transport Support**: stdio and SSE (Server-Sent Events)
67
70
  - **Zero Configuration**: Works out of the box with sensible defaults
68
71
 
@@ -81,11 +84,13 @@ All spans include:
81
84
  ### Data Format
82
85
 
83
86
  Optimized for Elasticsearch with flat structure:
84
- - `mcp.method`: Method name
87
+ - `mcp.request.method` / `mcp.response.method`: Method names for request/response
85
88
  - `mcp.source`: `sdk` or `cli`
86
89
  - `mcp.transport`: `stdio` or `sse`
87
90
  - `mcp.duration_ms`: Request duration
88
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]`)
89
94
  - `mcp.events`: Custom log entries (JSON array)
90
95
  - `mcp.cli.server`: CLI execution info (JSON object)
91
96
 
@@ -181,32 +186,41 @@ Creates **one span per request-response pair** for complete transaction context:
181
186
  1. Request arrives → stored in pending map
182
187
  2. Response arrives → matched by ID, single span created
183
188
  3. Timeout (30s) → pending requests cleared
189
+ 4. Notifications → handled as single-direction spans (request-only or response-only)
184
190
 
185
- ### Tracked Methods
191
+ ### Message Type Handling
192
+
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)
186
196
 
187
- Only tracks business-critical methods:
188
- - `tools/call` - Tool executions
189
- - `tools/list` - Tool discovery
197
+ ### Tracked Methods
190
198
 
191
- Protocol methods (`initialize`, `ping`, etc.) are ignored.
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
192
203
 
193
204
  ---
194
205
 
195
206
  ## 📊 Telemetry Data Structure
196
207
 
197
208
  Each span includes:
198
- - Request/response timestamps and payloads
199
- - Method name and parameters
209
+ - Request/response timestamps and payloads (with optional fields for notifications)
210
+ - Method name and parameters (separate fields for request/response)
200
211
  - Tool names and arguments (for `tools/call`)
201
212
  - Custom events and logs
202
- - Error details and duration metrics
213
+ - Enhanced error details with proper serialization
214
+ - Duration metrics
203
215
  - CLI server information (command, args, env)
204
216
 
205
- ---
206
-
207
- ## 🤝 Contributing
217
+ ### Span Attributes
208
218
 
209
- For development setup and guidelines, visit [GitHub Repository](https://github.com/awarecorp/mcp-logger).
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
210
224
 
211
225
  ---
212
226
 
@@ -220,5 +234,4 @@ MIT © [Aware Corp](https://awarecorp.io/)
220
234
 
221
235
  - [Aware Corp](https://awarecorp.io/)
222
236
  - [npm Package](https://www.npmjs.com/package/@awarecorp/mcp-logger)
223
- - [GitHub Repository](https://github.com/awarecorp/mcp-logger)
224
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
- function e(){return h}function r(){return I}async function t(){if(d)try{await d.shutdown(),d=null,h=null,f=!1,I=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}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 o(t){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+t.method;o.startActiveSpan(n,e=>{try{const o={"mcp.method":t.method,"mcp.source":t.source,"mcp.transport":t.transport,"mcp.log_type":"request-response","mcp.request.id":t.request.id+"","mcp.request.timestamp":t.request.timestamp,"mcp.request.params":s(t.request.params)||"{}","mcp.request.params.size":JSON.stringify(t.request.params||{}).length,"mcp.response.timestamp":t.response.timestamp,"mcp.duration_ms":t.duration};if("tools/call"===t.method&&t.request.params){const e=t.request.params.name,r=t.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),r){const e=s(r);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==t.response.result){const e=s(t.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(t.response.result).length)}if(t.request.headers){const e=s(t.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(t.request.headers).length)}if("cli"===t.source){const e=r();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(t.customEvents&&t.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(t.customEvents),o["mcp.events.count"]=t.customEvents.length,t.customEvents.forEach(r=>{e.addEvent("custom."+(r.level||"info"),{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp||Date.now()})})),t.response.error){o["mcp.error"]=!0;const r=t.response.error instanceof Error?t.response.error.message:t.response.error+"";o["mcp.error.message"]=r,t.response.error instanceof Error&&e.recordException(t.response.error),e.setStatus({code:u.ERROR,message:r})}else e.setStatus({code:u.OK});!function(e,r){for(const[t,s]of Object.entries(r))null!=s&&e.setAttribute(t,s)}(e,o)}catch(r){console.error("[MCP Logger] Error creating paired span:",r),e.setStatus({code:u.ERROR,message:r+""})}finally{e.end()}})}import{spawn as n}from"child_process";import{program as i}from"commander";import{NodeSDK as c}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as a}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as p}from"@opentelemetry/resources";import{trace as m,SpanStatusCode as u}from"@opentelemetry/api";import{Transform as l}from"stream";const g={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let d=null,h=null,f=!1,I=null;class y extends l{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)}}}class S{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(e){var r;r=e.method,g.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,n){o({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:n})}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()}}i.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(f)return console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),h;const r=e.serviceName||`${g.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||g.ENDPOINT)),console.error("[MCP Logger] Service: "+r),d=new c({resource:new p({"service.name":r,"service.version":g.SDK_VERSION}),traceExporter:new a({url:e.endpoint||g.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),d.start(),h=m.getTracer("mcp-logger",g.SDK_VERSION),f=!0,console.error("[MCP Logger] Telemetry initialized successfully");const s=async()=>{console.error("[MCP Logger] Shutting down telemetry..."),await t()};process.once("SIGTERM",s),process.once("SIGINT",s)}({...r,debug:s}),console.error("[MCP Logger CLI] Starting MCP server:",e.join(" "));const[o,...i]=e;!function(e,r,t){I={command:e,args:r,env:t}}(o,i,{...process.env});const u=n(o,i,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),l=new S(s),C=new y("request",l,s);process.stdin.pipe(C).pipe(u.stdin);const E=new y("response",l,s);u.stdout.pipe(E).pipe(process.stdout),u.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),l.clear(),await t(),process.exit(1)}),u.on("exit",async(e,r)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${r}`),l.clear(),await t(),process.exit(e||0)});let q=!1;const v=async e=>{q||(q=!0,console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),u.kill(e),await Promise.race([new Promise(e=>u.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await t(),process.exit(0))};process.on("SIGTERM",()=>v("SIGTERM")),process.on("SIGINT",()=>v("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await t(),process.exit(1)}}),i.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
- function t(t){return E.TRACKED_METHODS.includes(t)}const E={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};export{E as CONFIG,t 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
- function e(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";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
- function e(e){if(y)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),d;const r=e.serviceName||`${m}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||u)),console.error("[MCP Logger] Service: "+r)),p=new c({resource:new s({"service.name":r,"service.version":g}),traceExporter:new l({url:e.endpoint||u,headers:{"x-api-key":e.apiKey}})}),p.start(),d=a.getTracer("mcp-logger",g),y=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const o=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await i()};return process.once("SIGTERM",o),process.once("SIGINT",o),d}function r(){return d}function o(){return y}function t(e,r,o){f={command:e,args:r,env:o}}function n(){return f}async function i(){if(p)try{await p.shutdown(),p=null,d=null,y=!1,f=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}import{NodeSDK as c}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as l}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as s}from"@opentelemetry/resources";import{trace as a}from"@opentelemetry/api";const u="https://aware.mcypher.com/v1/traces",g="1.0.0",m="mcp-server";let p=null,d=null,y=!1,f=null;export{n as getCLIServerInfo,r as getTracer,e as initializeTelemetry,o as isInitializedTelemetry,t as setCLIServerInfo,i 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
- function e(){return g}function t(){return f}async function r(){if(u)try{await u.shutdown(),u=null,g=null,d=!1,f=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.method":r.method,"mcp.source":r.source,"mcp.transport":r.transport,"mcp.log_type":"request-response","mcp.request.id":r.request.id+"","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.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){o["mcp.error"]=!0;const t=r.response.error instanceof Error?r.response.error.message:r.response.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 l}from"@opentelemetry/resources";const p={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let u=null,g=null,d=!1,f=null;const h=Symbol("mcp-logger.customEvents"),E=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(d)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),g;const t=e.serviceName||`${p.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||p.ENDPOINT)),console.error("[MCP Logger] Service: "+t)),u=new i({resource:new l({"service.name":t,"service.version":p.SDK_VERSION}),traceExporter:new m({url:e.endpoint||p.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),u.start(),g=n.getTracer("mcp-logger",p.SDK_VERSION),d=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const s=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",s),process.once("SIGINT",s)}(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 function(e){return p.TRACKED_METHODS.includes(e)}(a)?(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=[],l=c.active().setValue(h,m);try{const p=await c.with(l,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:p},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}),p}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] Skipping instrumentation for method: "+a),r(e,s))},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(h);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{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
- function e(){return g}function t(){return f}async function r(){if(u)try{await u.shutdown(),u=null,g=null,d=!1,f=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.method":r.method,"mcp.source":r.source,"mcp.transport":r.transport,"mcp.log_type":"request-response","mcp.request.id":r.request.id+"","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.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){o["mcp.error"]=!0;const t=r.response.error instanceof Error?r.response.error.message:r.response.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 l}from"@opentelemetry/resources";const p={ENDPOINT:"https://aware.mcypher.com/v1/traces",SDK_VERSION:"1.0.0",SERVICE_NAME_PREFIX:"mcp-server",TRACKED_METHODS:["tools/call","tools/list"]};let u=null,g=null,d=!1,f=null;const h=Symbol("mcp-logger.customEvents"),E=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(d)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),g;const t=e.serviceName||`${p.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+(e.endpoint||p.ENDPOINT)),console.error("[MCP Logger] Service: "+t)),u=new i({resource:new l({"service.name":t,"service.version":p.SDK_VERSION}),traceExporter:new m({url:e.endpoint||p.ENDPOINT,headers:{"x-api-key":e.apiKey}})}),u.start(),g=n.getTracer("mcp-logger",p.SDK_VERSION),d=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const s=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",s),process.once("SIGINT",s)}(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 function(e){return p.TRACKED_METHODS.includes(e)}(a)?(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=[],l=c.active().setValue(h,m);try{const p=await c.with(l,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:p},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}),p}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] Skipping instrumentation for method: "+a),r(e,s))},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(h);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{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
- function e(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function t(t,o){const l=t.setRequestHandler.bind(t);t.setRequestHandler=function(t,s){const a=t.shape?.method,i=a?._def?.value||"unknown";return function(e){return r.TRACKED_METHODS.includes(e)}(i)?(o.debug&&console.log("[mcp-logger] Instrumenting handler: "+i),l(t,async(t,r)=>{const l=Date.now(),a=(Math.random().toString(36).slice(2,8),[]),m=n.active().setValue(c,a);try{const c=await n.with(m,async()=>await s(t,r)),u=Date.now();return e(t.params),o.debug&&console.log(`[mcp-logger] Request completed (${u-l}ms):`,{method:i,customEvents:a.length}),c}catch(n){throw e(t.params),o.debug&&console.error("[mcp-logger] Request failed:",n),n}})):(o.debug&&console.log("[mcp-logger] Skipping instrumentation for method: "+i),l(t,s))},o.debug&&console.log("[mcp-logger] Server instrumentation complete")}function o(){return n.active().getValue(c)}import{context as n}from"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const r={TRACKED_METHODS:["tools/call","tools/list"]},c=Symbol("mcp-logger.customEvents");export{o as getCustomEventsFromContext,t 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",
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};