@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 +53 -76
- package/dist/cli/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -2
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
|
-
###
|
|
65
|
+
### Data Format
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
Flat key-value structure optimized for Elasticsearch/OpenSearch:
|
|
68
68
|
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
+
- `mcp.error`: Boolean flag
|
|
92
|
+
- `mcp.error.code`: Error code
|
|
93
|
+
- `mcp.error.message`: Error message
|
|
80
94
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
- `mcp.
|
|
84
|
-
- `mcp.
|
|
85
|
-
- `mcp.
|
|
86
|
-
- `mcp.
|
|
87
|
-
- `mcp.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
###
|
|
172
|
-
|
|
173
|
-
Creates **one span per request-response pair** for complete transaction context:
|
|
191
|
+
### Message Type Handling
|
|
174
192
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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"`): Server→client notifications or unmatched responses.
|
|
179
196
|
|
|
180
|
-
###
|
|
197
|
+
### Request-Response Pairing
|
|
181
198
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
199
|
+
1. Request arrives → stored in pending map with timestamp
|
|
200
|
+
2. Response arrives → matched 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
|
|
191
|
-
- Excludes: `notifications/initialized` and other noisy
|
|
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
|
|
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.
|
|
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"
|