@awarecorp/mcp-logger 0.0.8 → 0.0.10

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
@@ -56,38 +56,58 @@ npx @awarecorp/mcp-logger \
56
56
  - **Automatic Request-Response Pairing**: Single span per request-response cycle with intelligent message correlation
57
57
  - **Notification Support**: Tracks both client→server and server→client notifications
58
58
  - **Selective Method Tracking**: Configurable filtering to track only relevant methods (excludes noisy notifications like `notifications/initialized`)
59
- - **Custom Event Logging**: Add context-aware logs within request handlers
59
+ - **Custom Event Logging**: Add context-aware logs within request handlers (SDK mode)
60
60
  - **Enhanced Error Handling**: Robust error serialization for complex error objects
61
61
  - **Custom OTLP Endpoint**: Send traces to any OTLP/HTTP compatible observability backend
62
62
  - **Transport Support**: stdio and SSE (Server-Sent Events)
63
63
  - **Zero Configuration**: Works out of the box with sensible defaults
64
64
 
65
- ### Telemetry Data
65
+ ### Data Format
66
66
 
67
- All spans include:
67
+ Flat key-value structure optimized for Elasticsearch/OpenSearch:
68
68
 
69
- - Request/response timestamps and payloads
70
- - Method name and parameters
71
- - Tool names and arguments (for `tools/call`)
72
- - Custom events and logs
73
- - Error details (if any)
74
- - Duration metrics
75
- - CLI server information (command, args, env)
69
+ **Discriminator & Type:**
76
70
 
77
- ### Data Format
71
+ - `ingest.type`: Always `"mcp"` — backend event type discriminator
72
+ - `mcp.type`: `"paired"` | `"request"` | `"response"` — span type
73
+ - `mcp.method`: JSON-RPC method name (e.g. `"tools/call"`)
74
+ - `mcp.transport`: `"stdio"` | `"sse"`
75
+
76
+ **Request/Response:**
77
+
78
+ - `mcp.request.id` / `mcp.response.id`: JSON-RPC message IDs
79
+ - `mcp.request.method` / `mcp.response.method`: Method names
80
+ - `mcp.request.params` / `mcp.response.result`: Payloads (JSON string)
81
+ - `mcp.request.timestamp` / `mcp.response.timestamp`: Unix timestamps
82
+ - `mcp.duration_ms`: Request duration (paired spans only)
83
+
84
+ **Tool (tools/call):**
85
+
86
+ - `mcp.tool.name`: Tool name
87
+ - `mcp.tool.arguments`: Tool arguments (JSON string)
88
+
89
+ **Error:**
78
90
 
79
- Optimized for Elasticsearch with flat structure:
91
+ - `mcp.error`: Boolean flag
92
+ - `mcp.error.code`: Error code
93
+ - `mcp.error.message`: Error message
80
94
 
81
- - `mcp.request.method` / `mcp.response.method`: Method names for request/response
82
- - `mcp.source`: `sdk` or `cli`
83
- - `mcp.transport`: `stdio` or `sse`
84
- - `mcp.duration_ms`: Request duration
85
- - `mcp.tool.name`: Tool name (tools/call only)
86
- - `mcp.request.params` / `mcp.response.result`: Request/response payloads
87
- - `mcp.error.message`: Enhanced error serialization (no more `[object Object]`)
88
- - `mcp.events`: Custom log entries (JSON array)
89
- - `mcp.sdk.name`: MCP SDK/framework name (e.g., `FastMCP`)
90
- - `mcp.sdk.version`: MCP SDK/framework version
95
+ **Context:**
96
+
97
+ - `mcp.session.id`: Session identifier
98
+ - `mcp.conversation.id`: Conversation identifier
99
+ - `mcp.user.id`: User identifier
100
+ - `mcp.client.name` / `mcp.client.version`: Client info (e.g. "Claude Desktop")
101
+ - `mcp.sdk.name` / `mcp.sdk.version`: MCP SDK/framework info
102
+
103
+ **Other:**
104
+
105
+ - `mcp.cli`: CLI server info (command, args, env) as JSON string
106
+ - `mcp.events`: Custom log entries (JSON array, SDK mode)
107
+ - `mcp.headers`: Transport headers (SSE only)
108
+ - `mcp.resource.type` / `mcp.resource.uri`: Resource tracking
109
+ - `mcp.permission.level`: Permission level
110
+ - `mcp.cost`: Estimated cost
91
111
 
92
112
  ## 🔧 SDK API
93
113
 
@@ -168,70 +188,27 @@ mcp-logger -k API_KEY -- npx -y @modelcontextprotocol/server-filesystem /path
168
188
 
169
189
  ## 🏗️ How It Works
170
190
 
171
- ### Request-Response Pairing
172
-
173
- Creates **one span per request-response pair** for complete transaction context:
191
+ ### Message Type Handling
174
192
 
175
- 1. Request arrives stored in pending map
176
- 2. Response arrives matched by ID, single span created
177
- 3. Timeout (30s) → pending requests cleared
178
- 4. Notifications → handled as single-direction spans (request-only or response-only)
193
+ - **Paired Spans** (`mcp.type: "paired"`): Request-response cycles with matching IDs. Duration automatically calculated.
194
+ - **Request Spans** (`mcp.type: "request"`): Timed-out requests (30s) with no matching response.
195
+ - **Response Spans** (`mcp.type: "response"`): Serverclient notifications or unmatched responses.
179
196
 
180
- ### Message Type Handling
197
+ ### Request-Response Pairing
181
198
 
182
- - **Paired Spans**: Request-response cycles with matching IDs (most common)
183
- - **Request-Only Spans**: Clientserver notifications (no response expected)
184
- - **Response-Only Spans**: Serverclient notifications (no matching request)
199
+ 1. Request arrives stored in pending map with timestamp
200
+ 2. Response arrivesmatched by JSON-RPC ID, paired span created with duration
201
+ 3. Timeout (30s)request span created for unmatched requests
202
+ 4. Notifications → handled as response spans (no ID to match)
185
203
 
186
204
  ### Tracked Methods
187
205
 
188
206
  Configurable method filtering with exclusion list:
189
207
 
190
- - Tracks: `tools/call`, `tools/list`, and other business-critical methods
191
- - Excludes: `notifications/initialized` and other noisy protocol notifications
208
+ - Tracks: `tools/call`, `tools/list`, `initialize`, and other protocol methods
209
+ - Excludes: `notifications/initialized` and other noisy notifications
192
210
  - Customizable via `EXCLUDED_METHODS` configuration
193
211
 
194
- ## 📊 Telemetry Data Structure
195
-
196
- Each span includes:
197
-
198
- - Request/response timestamps and payloads (with optional fields for notifications)
199
- - Method name and parameters (separate fields for request/response)
200
- - Tool names and arguments (for `tools/call`)
201
- - Custom events and logs
202
- - Enhanced error details with proper serialization
203
- - Duration metrics
204
- - CLI server information (command, args, env)
205
-
206
- ### Span Attributes
207
-
208
- All attributes use a flat structure optimized for Elasticsearch:
209
-
210
- **Core Attributes:**
211
-
212
- - Request attributes: `mcp.request.*` (id, method, params, timestamp)
213
- - Response attributes: `mcp.response.*` (method, result, error, timestamp)
214
- - Error handling: Proper serialization for Error objects, strings, and complex objects
215
- - Metadata: source, transport, duration, tool information
216
-
217
- **Context Tracking:**
218
-
219
- - `mcp.session.id`: Unique session identifier
220
- - `mcp.conversation.id`: Conversation/thread identifier
221
- - `mcp.user.id`: User identifier (from params, headers, or environment)
222
- - `mcp.client.name`: Client application name (e.g., 'Claude Desktop')
223
- - `mcp.client.version`: Client version
224
-
225
- **Permission & Security:**
226
-
227
- - `mcp.permission.level`: Permission level (read/write/admin/elevated)
228
-
229
- **Resource Tracking:**
230
-
231
- - `mcp.resource.type`: Resource type (tool/file/prompt)
232
- - `mcp.resource.uri`: Resource URI or identifier
233
- - `mcp.resource.access_count`: Number of times resource accessed
234
-
235
212
  ## 📄 License
236
213
 
237
214
  MIT © [Aware Corp](https://awarecorp.io/)
package/dist/cli/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- function e(...e){console.error("[mcp-logger]",...e)}function s(...e){N&&console.error("[mcp-logger]",...e)}function t(...e){x&&console.error("[mcp-logger]",...e)}function r(e){return!M.includes(e)}function n(){return A.getTracer()}function o(){return A.getCLIServerInfo()}async function i(){await A.shutdown()}function c(e,s){for(const[t,r]of Object.entries(s))null!=r&&e.setAttribute(t,r)}function a(e,s=2e5){try{const t=JSON.stringify(e);return t.length>s?t.slice(0,s)+"... (truncated)":t}catch{return null}}function p(e,s){return e.startsWith("resources/")?{type:e.split("/")[1],uri:s?.uri||s?.path||s?.resource}:e.startsWith("prompts/")?{type:"prompt",uri:s?.name||s?.promptId}:"tools/call"===e?{type:"tool",uri:s?.name}:s?.resourceType&&s?.resourceUri?{type:s.resourceType,uri:s.resourceUri}:{type:void 0,uri:void 0}}function m(e,s,t,r,n){const o=function(e,s){const t=e?.getClientInfo();if(t?.name||t?.version)return{name:t.name,version:t.version};const r=e?.getHeaders()||s;if(r){const e=r["user-agent"]||r["User-Agent"];if(e){const s=e.match(/^([^\/]+)\/([^\s]+)/);if(s)return{name:s[1],version:s[2]}}}return{name:void 0,version:void 0}}(r,n),i=`${o.name||"unknown"}-${o.version||"0"}`,c=T.getOrCreateSessionId(i),a=T.getOrCreateConversationId(c,e),m=function(e,s){return e?.userId||e?.user_id?e.userId||e.user_id:s?s["x-user-id"]||s["X-User-Id"]:void 0}(s,n),u=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(s),d=p(e,s);d.uri&&T.trackResourceAccess(d.uri);const l=function(e,s){return void 0!==e?.cost?e.cost:void 0!==s?.cost?s.cost:void 0}(s,t),h={};return c&&(h.sessionId=c),a&&(h.conversationId=a),m&&(h.userId=m),o.name&&(h.clientName=o.name),o.version&&(h.clientVersion=o.version),u&&(h.permissionLevel=u),void 0!==l&&(h.cost=l),h}function u(e,s){const t=p(e,s);if(!t.uri)return{};const r=T.getResourceAccessCount(t.uri);return{resourceType:t.type,resourceUri:t.uri,resourceAccessCount:r}}import{execSync as d,spawn as l}from"child_process";import{program as h}from"commander";import{NodeSDK as f}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as v}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as g}from"@opentelemetry/resources";import{trace as I,SpanStatusCode as S}from"@opentelemetry/api";import{readFileSync as y}from"fs";import{fileURLToPath as q}from"url";import{dirname as w,join as C}from"path";import{Transform as R}from"stream";import{ulid as E}from"ulid";const N="true"===process.env.AWARE_DEBUG||"1"===process.env.AWARE_DEBUG,x="true"===process.env.AWARE_RAW_DEBUG||"1"===process.env.AWARE_RAW_DEBUG,O={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=q(import.meta.url),s=w(e),t=C(s,"..","..","package.json");return JSON.parse(y(t,"utf-8")).version||"1.0.0"}catch{return"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"},M=["notifications/initialized"],A=new class{sdk=null;tracer=null;initialized=!1;cliServerInfo=null;initialize(e){if(this.initialized)return s("Telemetry already initialized, returning existing tracer"),this.tracer;const t=e.endpoint||O.ENDPOINT,r=e.serviceName||`${O.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`,n=e.serviceVersion||"unknown";s("Initializing telemetry..."),s("Endpoint: "+t),s("Service: "+r),s("Version: "+n),this.sdk=new f({resource:new g({"service.name":r,"service.version":n}),traceExporter:new v({url:t,headers:{"x-api-key":e.apiKey}})}),this.sdk.start(),this.tracer=I.getTracer("mcp-logger",O.SDK_VERSION),this.initialized=!0,s("Telemetry initialized successfully");const o=async()=>{s("Shutting down telemetry..."),await this.shutdown()};return process.once("SIGTERM",o),process.once("SIGINT",o),this.tracer}getTracer(){return this.tracer}isInitialized(){return this.initialized}setCLIServerInfo(e,s,t){this.cliServerInfo={command:e,args:s,env:t}}getCLIServerInfo(){return this.cliServerInfo}async shutdown(){if(this.sdk)try{await this.sdk.shutdown(),this.sdk=null,this.tracer=null,this.initialized=!1,this.cliServerInfo=null}catch(e){console.error("[mcp-logger] Error shutting down telemetry:",e)}}};class k extends R{buffer="";direction;logger;sessionContextStore;constructor(e,s,t){super(),this.direction=e,this.logger=s,this.sessionContextStore=t}_transform(e,t,r){try{if("request"===this.direction)return this.push(e),this.buffer+=e.toString(),this.tryParseMessages(),void r();this.buffer+=e.toString(),this.buffer.length>1e6&&(s("Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseAndForwardMessages(),r()}catch(e){r(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 s=JSON.parse(e);t(`[${this.direction}] Raw message:`,e),this.handleMessage(s)}catch{s("Failed to parse:",e.slice(0,100))}}}tryParseAndForwardMessages(){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);"2.0"===r.jsonrpc?(this.push(e+"\n"),t(`[${this.direction}] Raw message:`,e),this.handleMessage(r)):s("Non-JSON-RPC JSON ignored:",e.slice(0,100))}catch{s("Non-JSON output filtered:",e.slice(0,100))}}}isValidInfo(e){return!(!e?.name||!e?.version)}handleMessage(e){if("request"===this.direction&&"initialize"===e.method&&e.params?.clientInfo&&!this.isValidInfo(this.sessionContextStore.getClientInfo())){const s=e.params;this.sessionContextStore.setClientInfo({name:s.clientInfo?.name,version:s.clientInfo?.version})}if("response"===this.direction&&void 0!==e.id&&!e.method){const s=e;if(s.result?.serverInfo&&!this.isValidInfo(this.sessionContextStore.getServerInfo())){const e=s.result.serverInfo;this.sessionContextStore.setServerInfo({name:e.name,version:e.version})}}"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);"response"===this.direction&&"2.0"===e.jsonrpc&&this.push(this.buffer.trim()+"\n"),this.handleMessage(e)}catch{"request"===this.direction&&this.push(this.buffer),s("Failed to parse final buffer")}N&&s("Stream closed. Pending requests: "+this.logger.getStats().pending),e()}catch(s){e(s)}}}const T=new class{sessionMap=new Map;sessionCounter=0;conversationMap=new Map;conversationCounter=0;resourceAccessCount=new Map;getOrCreateSessionId(e){if(!this.sessionMap.has(e)){const s=E();this.sessionMap.set(e,s),this.sessionCounter++}return this.sessionMap.get(e)}getOrCreateConversationId(e,s){const t=`${e}-${s}`;return this.conversationMap.has(t)||this.conversationMap.set(t,"conv-"+ ++this.conversationCounter),this.conversationMap.get(t)}trackResourceAccess(e){const s=(this.resourceAccessCount.get(e)||0)+1;return this.resourceAccessCount.set(e,s),s}getResourceAccessCount(e){return this.resourceAccessCount.get(e)||0}clear(){this.sessionMap.clear(),this.conversationMap.clear(),this.resourceAccessCount.clear(),this.sessionCounter=0,this.conversationCounter=0}};class ${pendingRequests=new Map;TIMEOUT=3e4;sessionContextStore;constructor(e){this.sessionContextStore=e}onRequest(e){r(e.method)&&(e.id?(this.pendingRequests.set(e.id,{request:e,timestamp:Date.now()}),"initialize"===e.method&&e.params?.clientInfo&&this.sessionContextStore.setClientInfo({name:e.params.clientInfo.name,version:e.params.clientInfo.version}),setTimeout(()=>this.cleanupStale(e.id),this.TIMEOUT)):this.createRequestOnlySpan(e,Date.now()))}onResponse(e){if(!e.id)return;const s=this.pendingRequests.get(e.id);if(!s)return void this.createResponseOnlySpan(e,Date.now());const t=Date.now(),r=t-s.timestamp;this.createPairedSpan(s.request,e,s.timestamp,t,r),this.pendingRequests.delete(e.id)}onNotification(e){r(e.method)&&this.createResponseOnlySpan({jsonrpc:"2.0",id:`notification-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,method:e.method},Date.now())}createPairedSpan(e,s,r,i,p){const d=m(e.method,e.params,s.result,this.sessionContextStore,void 0);let l=this.sessionContextStore.getServerInfo();"initialize"===e.method&&s.result?.serverInfo&&(l={name:s.result.serverInfo.name,version:s.result.serverInfo.version}),function(e){const s=n();if(!s)return;const r="mcp."+e.method;s.startActiveSpan(r,s=>{try{const r={"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":a(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 s=e.request.params.name,t=e.request.params.arguments;if(s&&(r["mcp.tool.name"]=s),t){const e=a(t);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==e.response.result){const s=a(e.response.result);s&&(r["mcp.response.result"]=s,r["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const s=a(e.request.headers);s&&(r["mcp.headers"]=s,r["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=o();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}e.serverInfo&&(e.serverInfo.name&&(r["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(r["mcp.sdk.version"]=e.serverInfo.version));const n=e.contextMetadata||m(e.method,e.request.params,e.response.result,void 0,e.request.headers);n.sessionId&&(r["mcp.session.id"]=n.sessionId),n.conversationId&&(r["mcp.conversation.id"]=n.conversationId),n.sessionId&&n.conversationId&&(r["mcp.trace.id"]=`${n.sessionId}:${n.conversationId}`),n.userId&&(r["mcp.user.id"]=n.userId),n.clientName&&(r["mcp.client.name"]=n.clientName),n.clientVersion&&(r["mcp.client.version"]=n.clientVersion),n.permissionLevel&&(r["mcp.permission.level"]=n.permissionLevel),void 0!==n.cost&&(r["mcp.cost"]=n.cost);const i=u(e.method,e.request.params);if(i.resourceType&&(r["mcp.resource.type"]=i.resourceType),i.resourceUri&&(r["mcp.resource.uri"]=i.resourceUri),i.resourceAccessCount&&(r["mcp.resource.access_count"]=i.resourceAccessCount),e.customEvents&&e.customEvents.length>0&&(r["mcp.events"]=JSON.stringify(e.customEvents),r["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{s.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){let t;r["mcp.error"]=!0,t=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:a(e.response.error)||"Unknown error",r["mcp.error.message"]=t,e.response.error instanceof Error&&s.recordException(e.response.error),s.setStatus({code:S.ERROR,message:t})}else s.setStatus({code:S.OK});c(s,r),t("[OTLP] Span data:",JSON.stringify(r,null,2))}catch{s.setStatus({code:S.ERROR,message:"Error creating span"})}finally{s.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:r,params:e.params},response:{timestamp:i,result:s.result,error:s.error},duration:p,contextMetadata:d,serverInfo:l})}cleanupStale(e){const s=this.pendingRequests.get(e);s&&Date.now()-s.timestamp>this.TIMEOUT&&(this.createRequestOnlySpan(s.request,s.timestamp),this.pendingRequests.delete(e))}createRequestOnlySpan(e,s){const r=m(e.method,e.params,void 0,this.sessionContextStore,void 0);!function(e){const s=n();if(!s)return;const r="mcp."+e.method;s.startActiveSpan(r,s=>{try{const r={"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":a(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length};if("tools/call"===e.method&&e.request.params){const s=e.request.params.name,t=e.request.params.arguments;if(s&&(r["mcp.tool.name"]=s),t){const e=a(t);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if("cli"===e.source){const e=o();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}e.serverInfo&&(e.serverInfo.name&&(r["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(r["mcp.sdk.version"]=e.serverInfo.version));const n=e.contextMetadata||m(e.method,e.request.params,void 0,void 0,e.request.headers);n.sessionId&&(r["mcp.session.id"]=n.sessionId),n.conversationId&&(r["mcp.conversation.id"]=n.conversationId),n.sessionId&&n.conversationId&&(r["mcp.trace.id"]=`${n.sessionId}:${n.conversationId}`),n.userId&&(r["mcp.user.id"]=n.userId),n.clientName&&(r["mcp.client.name"]=n.clientName),n.clientVersion&&(r["mcp.client.version"]=n.clientVersion),n.permissionLevel&&(r["mcp.permission.level"]=n.permissionLevel),void 0!==n.cost&&(r["mcp.cost"]=n.cost);const i=u(e.method,e.request.params);i.resourceType&&(r["mcp.resource.type"]=i.resourceType),i.resourceUri&&(r["mcp.resource.uri"]=i.resourceUri),i.resourceAccessCount&&(r["mcp.resource.access_count"]=i.resourceAccessCount),s.setStatus({code:S.OK}),c(s,r),t("[OTLP] Span data:",JSON.stringify(r,null,2))}catch{s.setStatus({code:S.ERROR,message:"Error creating span"})}finally{s.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:s,params:e.params},contextMetadata:r})}createResponseOnlySpan(e,s){const r=m(e.method||"unknown",void 0,e.result,this.sessionContextStore,void 0),i=this.sessionContextStore.getServerInfo();!function(e){const s=n();if(!s)return;const r="mcp."+e.method;s.startActiveSpan(r,s=>{try{const r={"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 s=a(e.response.result);s&&(r["mcp.response.result"]=s,r["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.response.error){let t;r["mcp.error"]=!0,t=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:a(e.response.error)||"Unknown error",r["mcp.error.message"]=t,e.response.error instanceof Error&&s.recordException(e.response.error),s.setStatus({code:S.ERROR,message:t})}else s.setStatus({code:S.OK});if("cli"===e.source){const e=o();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}e.serverInfo&&(e.serverInfo.name&&(r["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(r["mcp.sdk.version"]=e.serverInfo.version));const n=e.contextMetadata||m(e.method,void 0,e.response.result,void 0,void 0);n.sessionId&&(r["mcp.session.id"]=n.sessionId),n.conversationId&&(r["mcp.conversation.id"]=n.conversationId),n.sessionId&&n.conversationId&&(r["mcp.trace.id"]=`${n.sessionId}:${n.conversationId}`),n.userId&&(r["mcp.user.id"]=n.userId),n.clientName&&(r["mcp.client.name"]=n.clientName),n.clientVersion&&(r["mcp.client.version"]=n.clientVersion),n.permissionLevel&&(r["mcp.permission.level"]=n.permissionLevel),void 0!==n.cost&&(r["mcp.cost"]=n.cost),c(s,r),t("[OTLP] Span data:",JSON.stringify(r,null,2))}catch{s.setStatus({code:S.ERROR,message:"Error creating span"})}finally{s.end()}})}({method:e.method||"same_request_method",source:"cli",transport:"stdio",response:{id:e.id,timestamp:s,result:e.result,error:e.error},contextMetadata:r,serverInfo:i})}getStats(){return{pending:this.pendingRequests.size}}clear(){this.pendingRequests.clear()}}class P{context={};getClientInfo(){return this.context.clientInfo}setClientInfo(e){this.context.clientInfo=e}getServerInfo(){return this.context.serverInfo}setServerInfo(e){this.context.serverInfo=e}getHeaders(){return this.context.headers}setHeaders(e){this.context.headers=e}snapshot(){return{clientInfo:this.context.clientInfo?{...this.context.clientInfo}:void 0,serverInfo:this.context.serverInfo?{...this.context.serverInfo}:void 0,headers:this.context.headers?{...this.context.headers}:void 0}}}const _=new Set(["npx","node","uvx","python","python3","pip","pipx","deno","bun"]),z=new Set(["uvx","python","python3","pip","pipx"]);h.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("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(t,r)=>{try{const n=function(e){const s={};for(const t of e){const e=process.env[t];void 0!==e&&(s[t]=e)}return s}(function(){try{return d("aware mcpenv sync",{encoding:"utf-8",timeout:1e4,stdio:["pipe","pipe","pipe"]}),d("aware mcpenv keys",{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim().split("\n").filter(e=>e.length>0)}catch{return[]}}()),[o,...c]=t,a=function(e,s){const t=function(e){for(const s of e){if(_.has(s))continue;const e=s.match(/^(@[^@]+\/[^@]+)@(.+)$/);if(e)return{packageName:e[1],version:e[2]};const t=s.match(/^([^@]+)@(.+)$/);if(t)return{packageName:t[1],version:t[2]};const r=s.match(/^(@[^@]+\/[^@]+)$/);if(r)return{packageName:r[1],version:"latest"};if(s.match(/^[a-z0-9-]+$/)&&!s.includes("/"))return{packageName:s,version:"latest"}}return null}(s.filter(e=>!e.startsWith("-")));let r,n;if(r=t?.packageName?t.packageName:e.split("/").pop()||e||O.SERVICE_NAME_PREFIX+"-unknown",t&&(n=t.version,"latest"===t.version)){const s=z.has(e)?"pip":"npm",r=function(e,s){try{if("pip"===s){let s=null;for(const t of["pip3","pip"])try{s=d(`${t} index versions ${e}`,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]});break}catch{continue}if(!s)return null;const t=s.match(/\(([^)]+)\)/);if(t)return t[1];const r=s.match(/Available versions:\s*([^\s,]+)/);return r?r[1]:null}{const s=d(`npm view ${e} version --json`,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim().replace(/^["']|["']$/g,""),t=s.match(/^(\d+\.\d+\.\d+(?:-[\w.]+)?)/);return t?t[1]:s}}catch{return null}}(t.packageName,s);r&&(n=r)}return{name:r,version:n}}(o,c);!function(e){A.initialize(e)}({...r,serviceName:a.name,serviceVersion:a.version}),e("Starting MCP server: "+t.join(" ")),e(`Service: ${a.name} = ${a.version||"unknown"}`),function(e,s,t){A.setCLIServerInfo(e,s,t)}(o,c,n);const p=l(o,c,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),m=new P,u=new $(m),h=new k("request",u,m);process.stdin.pipe(h).pipe(p.stdin);const f=new k("response",u,m);p.stdout.pipe(f).pipe(process.stdout),p.on("error",async s=>{e("Failed to start MCP server:",s),u.clear(),await i(),process.exit(1)}),p.on("exit",async(e,t)=>{s(`MCP server exited with code ${e}, signal ${t}`),u.clear(),await i(),process.exit(e||0)});let v=!1;const g=async e=>{v||(v=!0,s(`Received ${e}, shutting down...`),p.kill(e),await Promise.race([new Promise(e=>p.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await i(),process.exit(0))};process.on("SIGTERM",()=>g("SIGTERM")),process.on("SIGINT",()=>g("SIGINT"))}catch(s){e("Fatal error:",s),await i(),process.exit(1)}}),h.parse();
2
+ function e(...e){console.error("[mcp-logger]",...e)}function t(...e){x&&console.error("[mcp-logger]",...e)}function s(...e){E&&console.error("[mcp-logger]",...e)}function r(e){return!N.includes(e)}async function n(){await R.shutdown()}function o(e,t=2e5){try{const s=JSON.stringify(e);return s.length>t?s.slice(0,t)+"... (truncated)":s}catch{return null}}function i(e,t){return e.startsWith("resources/")?{type:e.split("/")[1],uri:t?.uri||t?.path||t?.resource}:e.startsWith("prompts/")?{type:"prompt",uri:t?.name||t?.promptId}:"tools/call"===e?{type:"tool",uri:t?.name}:t?.resourceType&&t?.resourceUri?{type:t.resourceType,uri:t.resourceUri}:{type:void 0,uri:void 0}}function c(e,t,s,r,n){const o=function(e,t){const s=e?.getClientInfo();if(s?.name||s?.version)return{name:s.name,version:s.version};const r=e?.getHeaders()||t;if(r){const e=r["user-agent"]||r["User-Agent"];if(e){const t=e.match(/^([^\/]+)\/([^\s]+)/);if(t)return{name:t[1],version:t[2]}}}return{name:void 0,version:void 0}}(r,n),c=`${o.name||"unknown"}-${o.version||"0"}`,a=T.getOrCreateSessionId(c),p=T.getOrCreateConversationId(a,e),u=function(e,t){return e?.userId||e?.user_id?e.userId||e.user_id:t?t["x-user-id"]||t["X-User-Id"]:void 0}(t,n),m=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(t),d=i(e,t);d.uri&&T.trackResourceAccess(d.uri);const l=function(e,t){return void 0!==e?.cost?e.cost:void 0!==t?.cost?t.cost:void 0}(t,s),h={};return a&&(h.sessionId=a),p&&(h.conversationId=p),u&&(h.userId=u),o.name&&(h.clientName=o.name),o.version&&(h.clientVersion=o.version),m&&(h.permissionLevel=m),void 0!==l&&(h.cost=l),h}function a(e){const t=R.getTracer();if(!t)return;const r="mcp."+e.method;t.startActiveSpan(r,t=>{try{const r={"ingest.type":"mcp","mcp.type":e.type,"mcp.transport":e.transport,"mcp.method":e.method,"mcp.source":e.source},n=e.contextMetadata||c(e.method,e.params,e.result,void 0,e.headers);if(n.sessionId&&n.conversationId&&(r["mcp.trace.id"]=`${n.sessionId}:${n.conversationId}`),"paired"===e.type||"request"===e.type){if(void 0!==e.requestId&&(r["mcp.request.id"]=e.requestId+""),r["mcp.request.method"]=e.method,void 0!==e.requestTimestamp&&(r["mcp.request.timestamp"]=e.requestTimestamp),void 0!==e.params&&(r["mcp.request.params"]=o(e.params)||"{}",r["mcp.request.params.size"]=JSON.stringify(e.params||{}).length),"tools/call"===e.method&&e.params){const t=e.params.name,s=e.params.arguments;if(t&&(r["mcp.tool.name"]=t),s){const e=o(s);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if(e.headers){const t=o(e.headers);t&&(r["mcp.headers"]=t,r["mcp.headers.size"]=JSON.stringify(e.headers).length)}}if(("paired"===e.type||"response"===e.type)&&(void 0!==e.responseId&&(r["mcp.response.id"]=e.responseId+""),r["mcp.response.method"]=e.method,void 0!==e.responseTimestamp&&(r["mcp.response.timestamp"]=e.responseTimestamp),void 0!==e.result)){const t=o(e.result);t&&(r["mcp.response.result"]=t,r["mcp.response.result.size"]=JSON.stringify(e.result).length)}void 0!==e.durationMs&&(r["mcp.duration_ms"]=e.durationMs);const a=R.getCLIServerInfo();a&&(r["mcp.cli"]=JSON.stringify({command:a.command,args:a.args,env:a.env})),e.serverInfo&&(e.serverInfo.name&&(r["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(r["mcp.sdk.version"]=e.serverInfo.version)),n.sessionId&&(r["mcp.session.id"]=n.sessionId),n.conversationId&&(r["mcp.conversation.id"]=n.conversationId),n.userId&&(r["mcp.user.id"]=n.userId),n.clientName&&(r["mcp.client.name"]=n.clientName),n.clientVersion&&(r["mcp.client.version"]=n.clientVersion),n.permissionLevel&&(r["mcp.permission.level"]=n.permissionLevel),void 0!==n.cost&&(r["mcp.cost"]=n.cost);const p=function(e,t){const s=i(e,t);if(!s.uri)return{};const r=T.getResourceAccessCount(s.uri);return{resourceType:s.type,resourceUri:s.uri,resourceAccessCount:r}}(e.method,e.params);if(p.resourceType&&(r["mcp.resource.type"]=p.resourceType),p.resourceUri&&(r["mcp.resource.uri"]=p.resourceUri),p.resourceAccessCount&&(r["mcp.resource.access_count"]=p.resourceAccessCount),e.customEvents&&e.customEvents.length>0&&(r["mcp.events"]=JSON.stringify(e.customEvents),r["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{t.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.error){let s;r["mcp.error"]=!0,e.error instanceof Error?(s=e.error.message,t.recordException(e.error)):s="string"==typeof e.error?e.error:o(e.error)||"Unknown error",r["mcp.error.message"]=s,t.setStatus({code:v.ERROR,message:s})}else t.setStatus({code:v.OK});!function(e,t){for(const[s,r]of Object.entries(t))null!=r&&e.setAttribute(s,r)}(t,r),s("[OTLP] Span data:",JSON.stringify(r,null,2))}catch{t.setStatus({code:v.ERROR,message:"Error creating span"})}finally{t.end()}})}import{execSync as p,spawn as u}from"child_process";import{program as m}from"commander";import{NodeSDK as d}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as l}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as h}from"@opentelemetry/resources";import{trace as f,SpanStatusCode as v}from"@opentelemetry/api";import{readFileSync as g}from"fs";import{fileURLToPath as I}from"url";import{dirname as y,join as S}from"path";import{Transform as C}from"stream";import{ulid as w}from"ulid";const x="true"===process.env.AWARE_DEBUG||"1"===process.env.AWARE_DEBUG,E="true"===process.env.AWARE_RAW_DEBUG||"1"===process.env.AWARE_RAW_DEBUG,M={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=I(import.meta.url),t=y(e),s=S(t,"..","..","package.json");return JSON.parse(g(s,"utf-8")).version||"1.0.0"}catch{return"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"},N=["notifications/initialized"],R=new class{sdk=null;tracer=null;initialized=!1;cliServerInfo=null;initialize(e){if(this.initialized)return t("Telemetry already initialized, returning existing tracer"),this.tracer;const s=e.endpoint||M.ENDPOINT,r=e.serviceName||`${M.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`,n=e.serviceVersion||"unknown";t("Initializing telemetry..."),t("Endpoint: "+s),t("Service: "+r),t("Version: "+n),this.sdk=new d({resource:new h({"service.name":r,"service.version":n}),traceExporter:new l({url:s,headers:{"x-api-key":e.apiKey}})}),this.sdk.start(),this.tracer=f.getTracer("mcp-logger",M.SDK_VERSION),this.initialized=!0,t("Telemetry initialized successfully");const o=async()=>{t("Shutting down telemetry..."),await this.shutdown()};return process.once("SIGTERM",o),process.once("SIGINT",o),this.tracer}getTracer(){return this.tracer}isInitialized(){return this.initialized}setCLIServerInfo(e,t,s){this.cliServerInfo={command:e,args:t,env:s}}getCLIServerInfo(){return this.cliServerInfo}async shutdown(){if(this.sdk)try{await this.sdk.shutdown(),this.sdk=null,this.tracer=null,this.initialized=!1,this.cliServerInfo=null}catch(e){console.error("[mcp-logger] Error shutting down telemetry:",e)}}};class q extends C{buffer="";direction;logger;sessionContextStore;constructor(e,t,s){super(),this.direction=e,this.logger=t,this.sessionContextStore=s}_transform(e,s,r){try{if("request"===this.direction)return this.push(e),this.buffer+=e.toString(),this.tryParseMessages(),void r();this.buffer+=e.toString(),this.buffer.length>1e6&&(t("Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseAndForwardMessages(),r()}catch(e){r(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 t=JSON.parse(e);s(`[${this.direction}] Raw message:`,e),this.handleMessage(t)}catch{t("Failed to parse:",e.slice(0,100))}}}tryParseAndForwardMessages(){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);"2.0"===r.jsonrpc?(this.push(e+"\n"),s(`[${this.direction}] Raw message:`,e),this.handleMessage(r)):t("Non-JSON-RPC JSON ignored:",e.slice(0,100))}catch{t("Non-JSON output filtered:",e.slice(0,100))}}}isValidInfo(e){return!(!e?.name||!e?.version)}handleMessage(e){if("request"===this.direction&&"initialize"===e.method&&e.params?.clientInfo&&!this.isValidInfo(this.sessionContextStore.getClientInfo())){const t=e.params;this.sessionContextStore.setClientInfo({name:t.clientInfo?.name,version:t.clientInfo?.version})}if("response"===this.direction&&void 0!==e.id&&!e.method){const t=e;if(t.result?.serverInfo&&!this.isValidInfo(this.sessionContextStore.getServerInfo())){const e=t.result.serverInfo;this.sessionContextStore.setServerInfo({name:e.name,version:e.version})}}"request"===this.direction&&e.method?this.logger.onRequest(e):"response"===this.direction&&(void 0===e.id||e.method?e.method&&null==e.id&&this.logger.onNotification(e):this.logger.onResponse(e))}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);"response"===this.direction&&"2.0"===e.jsonrpc&&this.push(this.buffer.trim()+"\n"),this.handleMessage(e)}catch{"request"===this.direction&&this.push(this.buffer),t("Failed to parse final buffer")}x&&t("Stream closed. Pending requests: "+this.logger.getStats().pending),e()}catch(t){e(t)}}}const T=new class{sessionMap=new Map;sessionCounter=0;conversationMap=new Map;conversationCounter=0;resourceAccessCount=new Map;getOrCreateSessionId(e){if(!this.sessionMap.has(e)){const t=w();this.sessionMap.set(e,t),this.sessionCounter++}return this.sessionMap.get(e)}getOrCreateConversationId(e,t){const s=`${e}-${t}`;return this.conversationMap.has(s)||this.conversationMap.set(s,"conv-"+ ++this.conversationCounter),this.conversationMap.get(s)}trackResourceAccess(e){const t=(this.resourceAccessCount.get(e)||0)+1;return this.resourceAccessCount.set(e,t),t}getResourceAccessCount(e){return this.resourceAccessCount.get(e)||0}clear(){this.sessionMap.clear(),this.conversationMap.clear(),this.resourceAccessCount.clear(),this.sessionCounter=0,this.conversationCounter=0}};class A{pendingRequests=new Map;TIMEOUT=3e4;sessionContextStore;constructor(e){this.sessionContextStore=e}onRequest(e){if(r(e.method)){if(null==e.id){const t=c(e.method,e.params,void 0,this.sessionContextStore,void 0);return void a({type:"response",method:e.method,transport:"stdio",source:"cli",params:e.params,contextMetadata:t})}"initialize"===e.method&&e.params?.clientInfo&&this.sessionContextStore.setClientInfo({name:e.params.clientInfo.name,version:e.params.clientInfo.version}),this.pendingRequests.set(e.id,{request:e,timestamp:Date.now()}),setTimeout(()=>this.handleTimeout(e.id),this.TIMEOUT)}}onResponse(e){if(null==e.id)return;const t=this.pendingRequests.get(e.id),s=Date.now(),r=t?.request.method||e.method||"unknown";let n=this.sessionContextStore.getServerInfo();"initialize"===t?.request.method&&e.result?.serverInfo&&(n={name:e.result.serverInfo.name,version:e.result.serverInfo.version});const o=c(r,t?.request.params,e.result,this.sessionContextStore,void 0);t?(a({type:"paired",method:r,transport:"stdio",source:"cli",requestId:t.request.id,requestTimestamp:t.timestamp,params:t.request.params,responseId:e.id,responseTimestamp:s,result:e.result,error:e.error,durationMs:s-t.timestamp,contextMetadata:o,serverInfo:n}),this.pendingRequests.delete(e.id)):a({type:"response",method:r,transport:"stdio",source:"cli",responseId:e.id,responseTimestamp:s,result:e.result,error:e.error,contextMetadata:o,serverInfo:n})}onNotification(e){if(!r(e.method))return;const t=c(e.method,e.params,void 0,this.sessionContextStore,void 0);a({type:"response",method:e.method,transport:"stdio",source:"cli",params:e.params,contextMetadata:t})}handleTimeout(e){const t=this.pendingRequests.get(e);if(t&&Date.now()-t.timestamp>this.TIMEOUT){const s=c(t.request.method,t.request.params,void 0,this.sessionContextStore,void 0);a({type:"request",method:t.request.method,transport:"stdio",source:"cli",requestId:t.request.id,requestTimestamp:t.timestamp,params:t.request.params,contextMetadata:s}),this.pendingRequests.delete(e)}}getStats(){return{pending:this.pendingRequests.size}}clear(){this.pendingRequests.clear()}}class O{context={};getClientInfo(){return this.context.clientInfo}setClientInfo(e){this.context.clientInfo=e}getServerInfo(){return this.context.serverInfo}setServerInfo(e){this.context.serverInfo=e}getHeaders(){return this.context.headers}setHeaders(e){this.context.headers=e}snapshot(){return{clientInfo:this.context.clientInfo?{...this.context.clientInfo}:void 0,serverInfo:this.context.serverInfo?{...this.context.serverInfo}:void 0,headers:this.context.headers?{...this.context.headers}:void 0}}}const k=new Set(["npx","node","uvx","python","python3","pip","pipx","deno","bun"]),P=new Set(["uvx","python","python3","pip","pipx"]);m.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("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(s,r)=>{try{const o=function(e){const t={};for(const s of e){const e=process.env[s];void 0!==e&&(t[s]=e)}return t}(function(){try{return p("aware mcpenv sync",{encoding:"utf-8",timeout:1e4,stdio:["pipe","pipe","pipe"]}),p("aware mcpenv keys",{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim().split("\n").filter(e=>e.length>0)}catch{return[]}}()),[i,...c]=s,a=function(e,t){const s=function(e){for(const t of e){if(k.has(t))continue;const e=t.match(/^(@[^@]+\/[^@]+)@(.+)$/);if(e)return{packageName:e[1],version:e[2]};const s=t.match(/^([^@]+)@(.+)$/);if(s)return{packageName:s[1],version:s[2]};const r=t.match(/^(@[^@]+\/[^@]+)$/);if(r)return{packageName:r[1],version:"latest"};if(t.match(/^[a-z0-9-]+$/)&&!t.includes("/"))return{packageName:t,version:"latest"}}return null}(t.filter(e=>!e.startsWith("-")));let r,n;if(r=s?.packageName?s.packageName:e.split("/").pop()||e||M.SERVICE_NAME_PREFIX+"-unknown",s&&(n=s.version,"latest"===s.version)){const t=P.has(e)?"pip":"npm",r=function(e,t){try{if("pip"===t){let t=null;for(const s of["pip3","pip"])try{t=p(`${s} index versions ${e}`,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]});break}catch{continue}if(!t)return null;const s=t.match(/\(([^)]+)\)/);if(s)return s[1];const r=t.match(/Available versions:\s*([^\s,]+)/);return r?r[1]:null}{const t=p(`npm view ${e} version --json`,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim().replace(/^["']|["']$/g,""),s=t.match(/^(\d+\.\d+\.\d+(?:-[\w.]+)?)/);return s?s[1]:t}}catch{return null}}(s.packageName,t);r&&(n=r)}return{name:r,version:n}}(i,c);!function(e){R.initialize(e)}({...r,serviceName:a.name,serviceVersion:a.version}),e("Starting MCP server: "+s.join(" ")),e(`Service: ${a.name} = ${a.version||"unknown"}`),function(e,t,s){R.setCLIServerInfo(e,t,s)}(i,c,o);const m=u(i,c,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),d=new O,l=new A(d),h=new q("request",l,d);process.stdin.pipe(h).pipe(m.stdin);const f=new q("response",l,d);m.stdout.pipe(f).pipe(process.stdout),m.on("error",async t=>{e("Failed to start MCP server:",t),l.clear(),await n(),process.exit(1)}),m.on("exit",async(e,s)=>{t(`MCP server exited with code ${e}, signal ${s}`),l.clear(),await n(),process.exit(e||0)});let v=!1;const g=async e=>{v||(v=!0,t(`Received ${e}, shutting down...`),m.kill(e),await Promise.race([new Promise(e=>m.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await n(),process.exit(0))};process.on("SIGTERM",()=>g("SIGTERM")),process.on("SIGINT",()=>g("SIGINT"))}catch(t){e("Fatal error:",t),await n(),process.exit(1)}}),m.parse();
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- function e(...e){g&&console.error("[mcp-logger]",...e)}function s(e,s=2e5){try{const t=JSON.stringify(e);return t.length>s?t.slice(0,s)+"... (truncated)":t}catch{return null}}function t(e,s){return e.startsWith("resources/")?{type:e.split("/")[1],uri:s?.uri||s?.path||s?.resource}:e.startsWith("prompts/")?{type:"prompt",uri:s?.name||s?.promptId}:"tools/call"===e?{type:"tool",uri:s?.name}:s?.resourceType&&s?.resourceUri?{type:s.resourceType,uri:s.resourceUri}:{type:void 0,uri:void 0}}function r(e,s,r,n,o){const i=function(e,s){const t=s;if(t){const e=t["user-agent"]||t["User-Agent"];if(e){const s=e.match(/^([^\/]+)\/([^\s]+)/);if(s)return{name:s[1],version:s[2]}}}return{name:void 0,version:void 0}}(0,o),c=`${i.name||"unknown"}-${i.version||"0"}`,a=E.getOrCreateSessionId(c),u=E.getOrCreateConversationId(a,e),m=function(e,s){return e?.userId||e?.user_id?e.userId||e.user_id:s?s["x-user-id"]||s["X-User-Id"]:void 0}(s,o),p=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(s),d=t(e,s);d.uri&&E.trackResourceAccess(d.uri);const l=function(e,s){return void 0!==e?.cost?e.cost:void 0!==s?.cost?s.cost:void 0}(s,r),v={};return a&&(v.sessionId=a),u&&(v.conversationId=u),m&&(v.userId=m),i.name&&(v.clientName=i.name),i.version&&(v.clientVersion=i.version),p&&(v.permissionLevel=p),void 0!==l&&(v.cost=l),v}function n(e){const n=I.getTracer();if(!n)return;const o="mcp."+e.method;n.startActiveSpan(o,n=>{try{const o={"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":s(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 t=e.request.params.name,r=e.request.params.arguments;if(t&&(o["mcp.tool.name"]=t),r){const e=s(r);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==e.response.result){const t=s(e.response.result);t&&(o["mcp.response.result"]=t,o["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const t=s(e.request.headers);t&&(o["mcp.headers"]=t,o["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=I.getCLIServerInfo();e&&(o["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}e.serverInfo&&(e.serverInfo.name&&(o["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(o["mcp.sdk.version"]=e.serverInfo.version));const c=e.contextMetadata||r(e.method,e.request.params,e.response.result,0,e.request.headers);c.sessionId&&(o["mcp.session.id"]=c.sessionId),c.conversationId&&(o["mcp.conversation.id"]=c.conversationId),c.sessionId&&c.conversationId&&(o["mcp.trace.id"]=`${c.sessionId}:${c.conversationId}`),c.userId&&(o["mcp.user.id"]=c.userId),c.clientName&&(o["mcp.client.name"]=c.clientName),c.clientVersion&&(o["mcp.client.version"]=c.clientVersion),c.permissionLevel&&(o["mcp.permission.level"]=c.permissionLevel),void 0!==c.cost&&(o["mcp.cost"]=c.cost);const a=function(e,s){const r=t(e,s);if(!r.uri)return{};const n=E.getResourceAccessCount(r.uri);return{resourceType:r.type,resourceUri:r.uri,resourceAccessCount:n}}(e.method,e.request.params);if(a.resourceType&&(o["mcp.resource.type"]=a.resourceType),a.resourceUri&&(o["mcp.resource.uri"]=a.resourceUri),a.resourceAccessCount&&(o["mcp.resource.access_count"]=a.resourceAccessCount),e.customEvents&&e.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(e.customEvents),o["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{n.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){let t;o["mcp.error"]=!0,t=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:s(e.response.error)||"Unknown error",o["mcp.error.message"]=t,e.response.error instanceof Error&&n.recordException(e.response.error),n.setStatus({code:i.ERROR,message:t})}else n.setStatus({code:i.OK});!function(e,s){for(const[t,r]of Object.entries(s))null!=r&&e.setAttribute(t,r)}(n,o),function(...e){f&&console.error("[mcp-logger]",...e)}("[OTLP] Span data:",JSON.stringify(o,null,2))}catch{n.setStatus({code:i.ERROR,message:"Error creating span"})}finally{n.end()}})}import{trace as o,SpanStatusCode as i,context as c}from"@opentelemetry/api";import{NodeSDK as a}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as u}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as m}from"@opentelemetry/resources";import{readFileSync as p}from"fs";import{fileURLToPath as d}from"url";import{dirname as l,join as v}from"path";import{ulid as h}from"ulid";const g="true"===process.env.AWARE_DEBUG||"1"===process.env.AWARE_DEBUG,f="true"===process.env.AWARE_RAW_DEBUG||"1"===process.env.AWARE_RAW_DEBUG,y={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=d(import.meta.url),s=l(e),t=v(s,"..","..","package.json");return JSON.parse(p(t,"utf-8")).version||"1.0.0"}catch{return"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"},I=new class{sdk=null;tracer=null;initialized=!1;cliServerInfo=null;initialize(s){if(this.initialized)return e("Telemetry already initialized, returning existing tracer"),this.tracer;const t=s.endpoint||y.ENDPOINT,r=s.serviceName||`${y.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`,n=s.serviceVersion||"unknown";e("Initializing telemetry..."),e("Endpoint: "+t),e("Service: "+r),e("Version: "+n),this.sdk=new a({resource:new m({"service.name":r,"service.version":n}),traceExporter:new u({url:t,headers:{"x-api-key":s.apiKey}})}),this.sdk.start(),this.tracer=o.getTracer("mcp-logger",y.SDK_VERSION),this.initialized=!0,e("Telemetry initialized successfully");const i=async()=>{e("Shutting down telemetry..."),await this.shutdown()};return process.once("SIGTERM",i),process.once("SIGINT",i),this.tracer}getTracer(){return this.tracer}isInitialized(){return this.initialized}setCLIServerInfo(e,s,t){this.cliServerInfo={command:e,args:s,env:t}}getCLIServerInfo(){return this.cliServerInfo}async shutdown(){if(this.sdk)try{await this.sdk.shutdown(),this.sdk=null,this.tracer=null,this.initialized=!1,this.cliServerInfo=null}catch(e){console.error("[mcp-logger] Error shutting down telemetry:",e)}}},E=new class{sessionMap=new Map;sessionCounter=0;conversationMap=new Map;conversationCounter=0;resourceAccessCount=new Map;getOrCreateSessionId(e){if(!this.sessionMap.has(e)){const s=h();this.sessionMap.set(e,s),this.sessionCounter++}return this.sessionMap.get(e)}getOrCreateConversationId(e,s){const t=`${e}-${s}`;return this.conversationMap.has(t)||this.conversationMap.set(t,"conv-"+ ++this.conversationCounter),this.conversationMap.get(t)}trackResourceAccess(e){const s=(this.resourceAccessCount.get(e)||0)+1;return this.resourceAccessCount.set(e,s),s}getResourceAccessCount(e){return this.resourceAccessCount.get(e)||0}clear(){this.sessionMap.clear(),this.conversationMap.clear(),this.resourceAccessCount.clear(),this.sessionCounter=0,this.conversationCounter=0}},S=Symbol("mcp-logger.customEvents"),w=Object.assign(function(s,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!s)throw Error("[mcp-logger] server instance is required");!function(e){I.initialize(e)}(t),function(s){const t=s.setRequestHandler.bind(s);s.setRequestHandler=function(s,o){const i=s.shape?.method,a=i?._def?.value||"unknown";return e("Instrumenting handler: "+a),t(s,async(s,t)=>{const i=Date.now(),u=`${a}-${i}-${Math.random().toString(36).slice(2,8)}`,m=[],p=c.active().setValue(S,m);try{const d=await c.with(p,async()=>await o(s,t)),l=Date.now(),v=r(a,s.params,d,0,void 0);return n({method:a,source:"sdk",transport:"stdio",request:{id:u,timestamp:i,params:s.params},response:{timestamp:l,result:d},duration:l-i,customEvents:m.length>0?m:void 0,contextMetadata:v}),e(`Request completed (${l-i}ms):`,{method:a,customEvents:m.length}),d}catch(t){const o=Date.now(),c=r(a,s.params,void 0,0,void 0);throw n({method:a,source:"sdk",transport:"stdio",request:{id:u,timestamp:i,params:s.params},response:{timestamp:o,error:t},duration:o-i,customEvents:m.length>0?m:void 0,contextMetadata:c}),e("Request failed:",t),t}})},e("Server instrumentation complete")}(s)},{addLog(e){const s=c.active().getValue(S);if(!s)return;const t={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};s.push(t);const r=o.getSpan(c.active());r&&r.addEvent("custom."+t.level,{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp})},async shutdown(){await async function(){await I.shutdown()}()}});export{w as trace};
1
+ function e(...e){g&&console.error("[mcp-logger]",...e)}function t(e,t=2e5){try{const s=JSON.stringify(e);return s.length>t?s.slice(0,t)+"... (truncated)":s}catch{return null}}function s(e,t){return e.startsWith("resources/")?{type:e.split("/")[1],uri:t?.uri||t?.path||t?.resource}:e.startsWith("prompts/")?{type:"prompt",uri:t?.name||t?.promptId}:"tools/call"===e?{type:"tool",uri:t?.name}:t?.resourceType&&t?.resourceUri?{type:t.resourceType,uri:t.resourceUri}:{type:void 0,uri:void 0}}function r(e,t,r,n,o){const i=function(e,t){const s=t;if(s){const e=s["user-agent"]||s["User-Agent"];if(e){const t=e.match(/^([^\/]+)\/([^\s]+)/);if(t)return{name:t[1],version:t[2]}}}return{name:void 0,version:void 0}}(0,o),c=`${i.name||"unknown"}-${i.version||"0"}`,a=E.getOrCreateSessionId(c),m=E.getOrCreateConversationId(a,e),u=function(e,t){return e?.userId||e?.user_id?e.userId||e.user_id:t?t["x-user-id"]||t["X-User-Id"]:void 0}(t,o),p=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(t),d=s(e,t);d.uri&&E.trackResourceAccess(d.uri);const l=function(e,t){return void 0!==e?.cost?e.cost:void 0!==t?.cost?t.cost:void 0}(t,r),v={};return a&&(v.sessionId=a),m&&(v.conversationId=m),u&&(v.userId=u),i.name&&(v.clientName=i.name),i.version&&(v.clientVersion=i.version),p&&(v.permissionLevel=p),void 0!==l&&(v.cost=l),v}function n(e){const n=I.getTracer();if(!n)return;const o="mcp."+e.method;n.startActiveSpan(o,n=>{try{const o={"ingest.type":"mcp","mcp.type":e.type,"mcp.transport":e.transport,"mcp.method":e.method,"mcp.source":e.source},c=e.contextMetadata||r(e.method,e.params,e.result,0,e.headers);if(c.sessionId&&c.conversationId&&(o["mcp.trace.id"]=`${c.sessionId}:${c.conversationId}`),"paired"===e.type||"request"===e.type){if(void 0!==e.requestId&&(o["mcp.request.id"]=e.requestId+""),o["mcp.request.method"]=e.method,void 0!==e.requestTimestamp&&(o["mcp.request.timestamp"]=e.requestTimestamp),void 0!==e.params&&(o["mcp.request.params"]=t(e.params)||"{}",o["mcp.request.params.size"]=JSON.stringify(e.params||{}).length),"tools/call"===e.method&&e.params){const s=e.params.name,r=e.params.arguments;if(s&&(o["mcp.tool.name"]=s),r){const e=t(r);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(e.headers){const s=t(e.headers);s&&(o["mcp.headers"]=s,o["mcp.headers.size"]=JSON.stringify(e.headers).length)}}if(("paired"===e.type||"response"===e.type)&&(void 0!==e.responseId&&(o["mcp.response.id"]=e.responseId+""),o["mcp.response.method"]=e.method,void 0!==e.responseTimestamp&&(o["mcp.response.timestamp"]=e.responseTimestamp),void 0!==e.result)){const s=t(e.result);s&&(o["mcp.response.result"]=s,o["mcp.response.result.size"]=JSON.stringify(e.result).length)}void 0!==e.durationMs&&(o["mcp.duration_ms"]=e.durationMs);const a=I.getCLIServerInfo();a&&(o["mcp.cli"]=JSON.stringify({command:a.command,args:a.args,env:a.env})),e.serverInfo&&(e.serverInfo.name&&(o["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(o["mcp.sdk.version"]=e.serverInfo.version)),c.sessionId&&(o["mcp.session.id"]=c.sessionId),c.conversationId&&(o["mcp.conversation.id"]=c.conversationId),c.userId&&(o["mcp.user.id"]=c.userId),c.clientName&&(o["mcp.client.name"]=c.clientName),c.clientVersion&&(o["mcp.client.version"]=c.clientVersion),c.permissionLevel&&(o["mcp.permission.level"]=c.permissionLevel),void 0!==c.cost&&(o["mcp.cost"]=c.cost);const m=function(e,t){const r=s(e,t);if(!r.uri)return{};const n=E.getResourceAccessCount(r.uri);return{resourceType:r.type,resourceUri:r.uri,resourceAccessCount:n}}(e.method,e.params);if(m.resourceType&&(o["mcp.resource.type"]=m.resourceType),m.resourceUri&&(o["mcp.resource.uri"]=m.resourceUri),m.resourceAccessCount&&(o["mcp.resource.access_count"]=m.resourceAccessCount),e.customEvents&&e.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(e.customEvents),o["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{n.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.error){let s;o["mcp.error"]=!0,e.error instanceof Error?(s=e.error.message,n.recordException(e.error)):s="string"==typeof e.error?e.error:t(e.error)||"Unknown error",o["mcp.error.message"]=s,n.setStatus({code:i.ERROR,message:s})}else n.setStatus({code:i.OK});!function(e,t){for(const[s,r]of Object.entries(t))null!=r&&e.setAttribute(s,r)}(n,o),function(...e){f&&console.error("[mcp-logger]",...e)}("[OTLP] Span data:",JSON.stringify(o,null,2))}catch{n.setStatus({code:i.ERROR,message:"Error creating span"})}finally{n.end()}})}import{trace as o,SpanStatusCode as i,context as c}from"@opentelemetry/api";import{NodeSDK as a}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as m}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as u}from"@opentelemetry/resources";import{readFileSync as p}from"fs";import{fileURLToPath as d}from"url";import{dirname as l,join as v}from"path";import{ulid as h}from"ulid";const g="true"===process.env.AWARE_DEBUG||"1"===process.env.AWARE_DEBUG,f="true"===process.env.AWARE_RAW_DEBUG||"1"===process.env.AWARE_RAW_DEBUG,y={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=d(import.meta.url),t=l(e),s=v(t,"..","..","package.json");return JSON.parse(p(s,"utf-8")).version||"1.0.0"}catch{return"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"},I=new class{sdk=null;tracer=null;initialized=!1;cliServerInfo=null;initialize(t){if(this.initialized)return e("Telemetry already initialized, returning existing tracer"),this.tracer;const s=t.endpoint||y.ENDPOINT,r=t.serviceName||`${y.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`,n=t.serviceVersion||"unknown";e("Initializing telemetry..."),e("Endpoint: "+s),e("Service: "+r),e("Version: "+n),this.sdk=new a({resource:new u({"service.name":r,"service.version":n}),traceExporter:new m({url:s,headers:{"x-api-key":t.apiKey}})}),this.sdk.start(),this.tracer=o.getTracer("mcp-logger",y.SDK_VERSION),this.initialized=!0,e("Telemetry initialized successfully");const i=async()=>{e("Shutting down telemetry..."),await this.shutdown()};return process.once("SIGTERM",i),process.once("SIGINT",i),this.tracer}getTracer(){return this.tracer}isInitialized(){return this.initialized}setCLIServerInfo(e,t,s){this.cliServerInfo={command:e,args:t,env:s}}getCLIServerInfo(){return this.cliServerInfo}async shutdown(){if(this.sdk)try{await this.sdk.shutdown(),this.sdk=null,this.tracer=null,this.initialized=!1,this.cliServerInfo=null}catch(e){console.error("[mcp-logger] Error shutting down telemetry:",e)}}},E=new class{sessionMap=new Map;sessionCounter=0;conversationMap=new Map;conversationCounter=0;resourceAccessCount=new Map;getOrCreateSessionId(e){if(!this.sessionMap.has(e)){const t=h();this.sessionMap.set(e,t),this.sessionCounter++}return this.sessionMap.get(e)}getOrCreateConversationId(e,t){const s=`${e}-${t}`;return this.conversationMap.has(s)||this.conversationMap.set(s,"conv-"+ ++this.conversationCounter),this.conversationMap.get(s)}trackResourceAccess(e){const t=(this.resourceAccessCount.get(e)||0)+1;return this.resourceAccessCount.set(e,t),t}getResourceAccessCount(e){return this.resourceAccessCount.get(e)||0}clear(){this.sessionMap.clear(),this.conversationMap.clear(),this.resourceAccessCount.clear(),this.sessionCounter=0,this.conversationCounter=0}},S=Symbol("mcp-logger.customEvents"),w=Object.assign(function(t,s){if(!s.apiKey)throw Error("[mcp-logger] apiKey is required");if(!t)throw Error("[mcp-logger] server instance is required");!function(e){I.initialize(e)}(s),function(t){const s=t.setRequestHandler.bind(t);t.setRequestHandler=function(t,o){const i=t.shape?.method,a=i?._def?.value||"unknown";return e("Instrumenting handler: "+a),s(t,async(t,s)=>{const i=Date.now(),m=`${a}-${i}-${Math.random().toString(36).slice(2,8)}`,u=[],p=c.active().setValue(S,u);try{const d=await c.with(p,async()=>await o(t,s)),l=Date.now(),v=r(a,t.params,d,0,void 0);return n({type:"paired",method:a,transport:"stdio",source:"sdk",requestId:m,requestTimestamp:i,params:t.params,responseId:m,responseTimestamp:l,result:d,durationMs:l-i,customEvents:u.length>0?u:void 0,contextMetadata:v}),e(`Request completed (${l-i}ms):`,{method:a,customEvents:u.length}),d}catch(s){const o=Date.now(),c=r(a,t.params,void 0,0,void 0);throw n({type:"paired",method:a,transport:"stdio",source:"sdk",requestId:m,requestTimestamp:i,params:t.params,responseId:m,responseTimestamp:o,error:s,durationMs:o-i,customEvents:u.length>0?u:void 0,contextMetadata:c}),e("Request failed:",s),s}})},e("Server instrumentation complete")}(t)},{addLog(e){const t=c.active().getValue(S);if(!t)return;const s={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(s);const r=o.getSpan(c.active());r&&r.addEvent("custom."+s.level,{message:s.message,metadata:JSON.stringify(s.metadata||{}),timestamp:s.timestamp})},async shutdown(){await async function(){await I.shutdown()}()}});export{w as trace};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awarecorp/mcp-logger",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
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",
@@ -65,6 +65,7 @@
65
65
  "@opentelemetry/resources": "^1.25.0",
66
66
  "@opentelemetry/sdk-node": "^0.52.0",
67
67
  "@opentelemetry/semantic-conventions": "^1.25.0",
68
+ "@pinta-ai/types": "^0.0.2",
68
69
  "commander": "^11.1.0",
69
70
  "ulid": "^3.0.1"
70
71
  },
@@ -73,11 +74,13 @@
73
74
  "@rollup/plugin-terser": "^0.4.4",
74
75
  "@rollup/plugin-typescript": "^12.1.4",
75
76
  "@types/node": "^20.0.0",
77
+ "@types/ws": "^8.18.1",
76
78
  "rollup": "^4.52.5",
77
79
  "rollup-plugin-dts": "^6.2.3",
78
80
  "tslib": "^2.8.1",
79
81
  "tsx": "^4.20.6",
80
- "typescript": "^5.3.0"
82
+ "typescript": "^5.3.0",
83
+ "ws": "^8.19.0"
81
84
  },
82
85
  "engines": {
83
86
  "node": ">=18"