@awarecorp/mcp-logger 0.0.8-dev.0 โ†’ 0.0.9

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
@@ -46,8 +46,7 @@ Wrap any existing MCP server without code changes:
46
46
  ```bash
47
47
  npx @awarecorp/mcp-logger \
48
48
  -k YOUR_API_KEY \
49
- -s my-server \
50
- npx your-mcp-server
49
+ -- npx your-mcp-server
51
50
  ```
52
51
 
53
52
  ## ๐Ÿ“‹ Features
@@ -57,37 +56,58 @@ npx @awarecorp/mcp-logger \
57
56
  - **Automatic Request-Response Pairing**: Single span per request-response cycle with intelligent message correlation
58
57
  - **Notification Support**: Tracks both clientโ†’server and serverโ†’client notifications
59
58
  - **Selective Method Tracking**: Configurable filtering to track only relevant methods (excludes noisy notifications like `notifications/initialized`)
60
- - **Custom Event Logging**: Add context-aware logs within request handlers
59
+ - **Custom Event Logging**: Add context-aware logs within request handlers (SDK mode)
61
60
  - **Enhanced Error Handling**: Robust error serialization for complex error objects
62
61
  - **Custom OTLP Endpoint**: Send traces to any OTLP/HTTP compatible observability backend
63
62
  - **Transport Support**: stdio and SSE (Server-Sent Events)
64
63
  - **Zero Configuration**: Works out of the box with sensible defaults
65
64
 
66
- ### Telemetry Data
65
+ ### Data Format
67
66
 
68
- All spans include:
67
+ Flat key-value structure optimized for Elasticsearch/OpenSearch:
69
68
 
70
- - Request/response timestamps and payloads
71
- - Method name and parameters
72
- - Tool names and arguments (for `tools/call`)
73
- - Custom events and logs
74
- - Error details (if any)
75
- - Duration metrics
76
- - CLI server information (command, args, env)
69
+ **Discriminator & Type:**
77
70
 
78
- ### 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:**
79
90
 
80
- Optimized for Elasticsearch with flat structure:
91
+ - `mcp.error`: Boolean flag
92
+ - `mcp.error.code`: Error code
93
+ - `mcp.error.message`: Error message
81
94
 
82
- - `mcp.request.method` / `mcp.response.method`: Method names for request/response
83
- - `mcp.source`: `sdk` or `cli`
84
- - `mcp.transport`: `stdio` or `sse`
85
- - `mcp.duration_ms`: Request duration
86
- - `mcp.tool.name`: Tool name (tools/call only)
87
- - `mcp.request.params` / `mcp.response.result`: Request/response payloads
88
- - `mcp.error.message`: Enhanced error serialization (no more `[object Object]`)
89
- - `mcp.events`: Custom log entries (JSON array)
90
- - `mcp.cli.server`: CLI execution info (JSON object)
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
 
@@ -99,13 +119,8 @@ Enable automatic instrumentation on an MCP server.
99
119
 
100
120
  - `server`: MCP Server instance
101
121
  - `options.apiKey`: API key for authentication (required)
102
- - Currently accepts any string value for testing purposes
103
- - For production use, sign up at [Aware](https://awarecorp.io/) to get your API key
104
122
  - `options.serviceName`: Service identifier (optional, auto-generated if omitted)
105
- - `options.endpoint`: Custom OTLP endpoint (optional, default: `https://aware.mcypher.com/v1/traces`)
106
- - Accepts any OTLP/HTTP compatible endpoint URL
107
- - Use this to send traces to your own observability backend (e.g., Jaeger, Grafana, etc.)
108
- - `options.debug`: Enable debug logging (optional, default: false)
123
+ - `options.endpoint`: Custom OTLP endpoint (optional)
109
124
 
110
125
  **Returns:** `void`
111
126
 
@@ -139,21 +154,14 @@ mcp-logger [options] <command...>
139
154
  ### Options
140
155
 
141
156
  - `-k, --api-key <key>`: API key (required)
142
- - Currently accepts any string value for testing
143
- - Sign up at [Aware](https://awarecorp.io/) to get your API key for production use
144
- - `-s, --service-name <name>`: Service name (optional)
145
- - `-e, --endpoint <url>`: Custom OTLP endpoint (optional, default: `https://aware.mcypher.com/v1/traces`)
146
- - Send traces to any OTLP/HTTP compatible backend
147
- - Examples: Jaeger, Grafana Tempo, New Relic, Honeycomb, etc.
148
- - `-d, --debug`: Enable debug logging (optional)
149
- - Shows detailed information about message processing and span creation
157
+ - `-e, --endpoint <url>`: Custom OTLP endpoint (optional)
150
158
 
151
159
  ### Examples
152
160
 
153
161
  **Wrap an npx command:**
154
162
 
155
163
  ```bash
156
- mcp-logger -k API_KEY -s filesystem npx -y @modelcontextprotocol/server-filesystem /path
164
+ mcp-logger -k API_KEY -- npx -y @modelcontextprotocol/server-filesystem /path
157
165
  ```
158
166
 
159
167
  **Use with Claude Desktop:**
@@ -168,8 +176,6 @@ mcp-logger -k API_KEY -s filesystem npx -y @modelcontextprotocol/server-filesyst
168
176
  "@awarecorp/mcp-logger",
169
177
  "-k",
170
178
  "YOUR_API_KEY",
171
- "-s",
172
- "my-server",
173
179
  "--",
174
180
  "npx",
175
181
  "-y",
@@ -182,70 +188,27 @@ mcp-logger -k API_KEY -s filesystem npx -y @modelcontextprotocol/server-filesyst
182
188
 
183
189
  ## ๐Ÿ—๏ธ How It Works
184
190
 
185
- ### Request-Response Pairing
186
-
187
- Creates **one span per request-response pair** for complete transaction context:
191
+ ### Message Type Handling
188
192
 
189
- 1. Request arrives โ†’ stored in pending map
190
- 2. Response arrives โ†’ matched by ID, single span created
191
- 3. Timeout (30s) โ†’ pending requests cleared
192
- 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.
193
196
 
194
- ### Message Type Handling
197
+ ### Request-Response Pairing
195
198
 
196
- - **Paired Spans**: Request-response cycles with matching IDs (most common)
197
- - **Request-Only Spans**: Clientโ†’server notifications (no response expected)
198
- - **Response-Only Spans**: Serverโ†’client notifications (no matching request)
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)
199
203
 
200
204
  ### Tracked Methods
201
205
 
202
206
  Configurable method filtering with exclusion list:
203
207
 
204
- - Tracks: `tools/call`, `tools/list`, and other business-critical methods
205
- - 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
206
210
  - Customizable via `EXCLUDED_METHODS` configuration
207
211
 
208
- ## ๐Ÿ“Š Telemetry Data Structure
209
-
210
- Each span includes:
211
-
212
- - Request/response timestamps and payloads (with optional fields for notifications)
213
- - Method name and parameters (separate fields for request/response)
214
- - Tool names and arguments (for `tools/call`)
215
- - Custom events and logs
216
- - Enhanced error details with proper serialization
217
- - Duration metrics
218
- - CLI server information (command, args, env)
219
-
220
- ### Span Attributes
221
-
222
- All attributes use a flat structure optimized for Elasticsearch:
223
-
224
- **Core Attributes:**
225
-
226
- - Request attributes: `mcp.request.*` (id, method, params, timestamp)
227
- - Response attributes: `mcp.response.*` (method, result, error, timestamp)
228
- - Error handling: Proper serialization for Error objects, strings, and complex objects
229
- - Metadata: source, transport, duration, tool information
230
-
231
- **Context Tracking:**
232
-
233
- - `mcp.session.id`: Unique session identifier
234
- - `mcp.conversation.id`: Conversation/thread identifier
235
- - `mcp.user.id`: User identifier (from params, headers, or environment)
236
- - `mcp.client.name`: Client application name (e.g., 'Claude Desktop')
237
- - `mcp.client.version`: Client version
238
-
239
- **Permission & Security:**
240
-
241
- - `mcp.permission.level`: Permission level (read/write/admin/elevated)
242
-
243
- **Resource Tracking:**
244
-
245
- - `mcp.resource.type`: Resource type (tool/file/prompt)
246
- - `mcp.resource.uri`: Resource URI or identifier
247
- - `mcp.resource.access_count`: Number of times resource accessed
248
-
249
212
  ## ๐Ÿ“„ License
250
213
 
251
214
  MIT ยฉ [Aware Corp](https://awarecorp.io/)
package/bin/mcp-logger.js CHANGED
@@ -6,4 +6,4 @@
6
6
  * ์ปดํŒŒ์ผ๋œ CLI๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
7
7
  */
8
8
 
9
- import '../dist/cli/main.js';
9
+ import '../dist/cli/index.js';
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
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&&!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(!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(!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.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
 
3
3
  /**
4
- * ๊ณตํ†ต ํƒ€์ž… ์ •์˜
4
+ * ์„ธ์…˜ ๋ฐ ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ๊ด€๋ จ ํƒ€์ž… ์ •์˜
5
5
  */
6
6
  /**
7
7
  * ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ์ดˆ๊ธฐํ™” ์˜ต์…˜
@@ -16,20 +16,21 @@ interface TelemetryOptions {
16
16
  * ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ž๋™์œผ๋กœ 'mcp-server-{random}' ํ˜•์‹์œผ๋กœ ์ƒ์„ฑ
17
17
  */
18
18
  serviceName?: string;
19
+ /**
20
+ * ์„œ๋น„์Šค ๋ฒ„์ „ (์˜ต์…˜)
21
+ * MCP ์„œ๋ฒ„์˜ ๋ฒ„์ „. ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด 'unknown'
22
+ */
23
+ serviceVersion?: string;
19
24
  /**
20
25
  * ์ปค์Šคํ…€ OTLP ์—”๋“œํฌ์ธํŠธ (์˜ต์…˜)
21
26
  * ๊ธฐ๋ณธ๊ฐ’: CONFIG.ENDPOINT
22
27
  */
23
28
  endpoint?: string;
24
- /**
25
- * ๋””๋ฒ„๊ทธ ๋กœ๊ทธ ํ™œ์„ฑํ™” (์˜ต์…˜)
26
- */
27
- debug?: boolean;
28
29
  }
29
30
  /**
30
31
  * ์ปค์Šคํ…€ ๋กœ๊ทธ ์—”ํŠธ๋ฆฌ
31
32
  */
32
- interface CustomLogEntry$1 {
33
+ interface CustomLogEntry {
33
34
  /**
34
35
  * ๋กœ๊ทธ ๋ ˆ๋ฒจ
35
36
  */
@@ -57,10 +58,7 @@ interface CustomLogEntry$1 {
57
58
  */
58
59
  interface TraceOptions extends TelemetryOptions {
59
60
  }
60
- /**
61
- * Custom Log Entry (re-export from core)
62
- */
63
- type CustomLogEntry = CustomLogEntry$1;
61
+
64
62
  /**
65
63
  * MCP Server ํƒ€์ž… (์žฌexport)
66
64
  */
@@ -74,60 +72,16 @@ type MCPServer = Server;
74
72
 
75
73
  /**
76
74
  * trace ๋„ค์ž„์ŠคํŽ˜์ด์Šค
77
- *
78
- * @example
79
- * ```typescript
80
- * import { trace } from '@awarecorp/mcp-logger';
81
- *
82
- * // ์„œ๋ฒ„ ๊ณ„์ธก
83
- * trace(server, {
84
- * apiKey: 'your-api-key',
85
- * serviceName: 'my-service',
86
- * });
87
- *
88
- * // Handler ๋‚ด๋ถ€์—์„œ ์ปค์Šคํ…€ ๋กœ๊ทธ ์ถ”๊ฐ€
89
- * server.setRequestHandler('tools/call', async (request) => {
90
- * trace.addLog({
91
- * level: 'info',
92
- * message: 'Processing started',
93
- * metadata: { id: request.params.arguments.id },
94
- * });
95
- *
96
- * const result = await process();
97
- *
98
- * trace.addLog({
99
- * level: 'info',
100
- * message: 'Processing completed',
101
- * metadata: { duration: 100 },
102
- * });
103
- *
104
- * return result;
105
- * });
106
- * ```
107
75
  */
108
76
  declare const trace: ((server: MCPServer, options: TraceOptions) => void) & {
109
77
  /**
110
78
  * ํ˜„์žฌ active span์— ์ปค์Šคํ…€ ๋กœ๊ทธ ์ถ”๊ฐ€
111
79
  *
112
80
  * โš ๏ธ ์ฃผ์˜: Request handler ๋‚ด๋ถ€์—์„œ๋งŒ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
113
- *
114
- * @param entry - ์ปค์Šคํ…€ ๋กœ๊ทธ ์—”ํŠธ๋ฆฌ
115
- *
116
- * @example
117
- * ```typescript
118
- * trace.addLog({
119
- * level: 'info',
120
- * message: 'Database query executed',
121
- * metadata: { query: 'SELECT * FROM users', rows: 42 },
122
- * });
123
- * ```
124
81
  */
125
- addLog(entry: CustomLogEntry$1): void;
82
+ addLog(entry: CustomLogEntry): void;
126
83
  /**
127
84
  * Telemetry ์ข…๋ฃŒ
128
- *
129
- * ์ผ๋ฐ˜์ ์œผ๋กœ ๋ช…์‹œ์ ์œผ๋กœ ํ˜ธ์ถœํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค.
130
- * ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ์‹œ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
131
85
  */
132
86
  shutdown(): Promise<void>;
133
87
  };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- function e(){return S}function r(){return q}function t(){return N||"unknown"}async function s(){if(I)try{await I.shutdown(),I=null,S=null,w=!1,q=null,N=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function o(e,r=2e5){try{const t=JSON.stringify(e);return t.length>r?t.slice(0,r)+"... (truncated)":t}catch(e){return null}}function n(e,r){return e.startsWith("resources/")?{type:e.split("/")[1],uri:r?.uri||r?.path||r?.resource}:e.startsWith("prompts/")?{type:"prompt",uri:r?.name||r?.promptId}:"tools/call"===e?{type:"tool",uri:r?.name}:r?.resourceType&&r?.resourceUri?{type:r.resourceType,uri:r.resourceUri}:{type:void 0,uri:void 0}}function i(e,r,t,s,o){const i=function(e,r){const t=r;if(t){const e=t["user-agent"]||t["User-Agent"];if(e){const r=e.match(/^([^\/]+)\/([^\s]+)/);if(r)return{name:r[1],version:r[2]}}}return{name:void 0,version:void 0}}(0,o),c=function(e){if(!M.has(e)){const r=y();M.set(e,r)}return M.get(e)}(`${i.name||"unknown"}-${i.version||"0"}`),a=function(e,r){const t=`${e}-${r}`;return O.has(t)||O.set(t,"conv-"+ ++R),O.get(t)}(c,e),m=function(e,r){return e?.userId||e?.user_id?e.userId||e.user_id:r?r["x-user-id"]||r["X-User-Id"]:void 0}(r,o),u=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(r),p=n(e,r);p.uri&&function(e){const r=(L.get(e)||0)+1;L.set(e,r)}(p.uri);const l=function(e,r){return void 0!==e?.cost?e.cost:void 0!==r?.cost?r.cost:void 0}(r,t),d={};return c&&(d.sessionId=c),a&&(d.conversationId=a),m&&(d.userId=m),i.name&&(d.clientName=i.name),i.version&&(d.clientVersion=i.version),u&&(d.permissionLevel=u),void 0!==l&&(d.cost=l),d}function c(s){const c=e();if(!c)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const a="mcp."+s.method;c.startActiveSpan(a,e=>{try{const c={"mcp.source":s.source,"mcp.transport":s.transport,"mcp.request.id":s.request.id+"","mcp.request.method":s.method,"mcp.request.timestamp":s.request.timestamp,"mcp.request.params":o(s.request.params)||"{}","mcp.request.params.size":JSON.stringify(s.request.params||{}).length,"mcp.response.id":s.request.id+"","mcp.response.timestamp":s.response.timestamp,"mcp.duration_ms":s.duration};if("tools/call"===s.method&&s.request.params){const e=s.request.params.name,r=s.request.params.arguments;if(e&&(c["mcp.tool.name"]=e),r){const e=o(r);e&&(c["mcp.tool.arguments"]=e,c["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==s.response.result){const e=o(s.response.result);e&&(c["mcp.response.result"]=e,c["mcp.response.result.size"]=JSON.stringify(s.response.result).length)}if(s.request.headers){const e=o(s.request.headers);e&&(c["mcp.headers"]=e,c["mcp.headers.size"]=JSON.stringify(s.request.headers).length)}if("cli"===s.source){const e=r();e&&(c["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}));const s=t();c["mcp.server.version"]=s}const a=s.contextMetadata||i(s.method,s.request.params,s.response.result,0,s.request.headers);a.sessionId&&(c["mcp.session.id"]=a.sessionId),a.conversationId&&(c["mcp.conversation.id"]=a.conversationId),a.sessionId&&a.conversationId&&(c["mcp.trace.id"]=`${a.sessionId}:${a.conversationId}`),a.userId&&(c["mcp.user.id"]=a.userId),a.clientName&&(c["mcp.client.name"]=a.clientName),a.clientVersion&&(c["mcp.client.version"]=a.clientVersion),a.permissionLevel&&(c["mcp.permission.level"]=a.permissionLevel),void 0!==a.cost&&(c["mcp.cost"]=a.cost);const u=function(e,r){const t=n(e,r);if(!t.uri)return{};const s=L.get(t.uri)||0;return{resourceType:t.type,resourceUri:t.uri,resourceAccessCount:s}}(s.method,s.request.params);if(u.resourceType&&(c["mcp.resource.type"]=u.resourceType),u.resourceUri&&(c["mcp.resource.uri"]=u.resourceUri),u.resourceAccessCount&&(c["mcp.resource.access_count"]=u.resourceAccessCount),s.customEvents&&s.customEvents.length>0&&(c["mcp.events"]=JSON.stringify(s.customEvents),c["mcp.events.count"]=s.customEvents.length,s.customEvents.forEach(r=>{e.addEvent("custom."+(r.level||"info"),{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp||Date.now()})})),s.response.error){let r;c["mcp.error"]=!0,r=s.response.error instanceof Error?s.response.error.message:"string"==typeof s.response.error?s.response.error:o(s.response.error)||"Unknown error",c["mcp.error.message"]=r,s.response.error instanceof Error&&e.recordException(s.response.error),e.setStatus({code:m.ERROR,message:r})}else e.setStatus({code:m.OK});!function(e,r){for(const[t,s]of Object.entries(r))null!=s&&e.setAttribute(t,s)}(e,c)}catch(r){console.error("[MCP Logger] Error creating paired span:",r),e.setStatus({code:m.ERROR,message:r+""})}finally{e.end()}})}import{trace as a,SpanStatusCode as m,context as u}from"@opentelemetry/api";import{NodeSDK as p}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as l}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as d}from"@opentelemetry/resources";import{readFileSync as g}from"fs";import{fileURLToPath as v}from"url";import{dirname as f,join as h}from"path";import{ulid as y}from"ulid";const E={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=v(import.meta.url),r=f(e),t=h(r,"..","..","package.json");return JSON.parse(g(t,"utf-8")).version||"1.0.0"}catch(e){return console.error("[MCP Logger] Failed to read package version:",e),"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"};let I=null,S=null,w=!1,q=null,N=null;const M=new Map,O=new Map;let R=0;const L=new Map,C=Symbol("mcp-logger.customEvents"),b=Object.assign(function(e,r){if(!r.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");!function(e){if(w)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),S;const r=e.endpoint||E.ENDPOINT,t=e.serviceName||`${E.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+r),console.error("[MCP Logger] Service: "+t)),I=new p({resource:new d({"service.name":t,"service.version":E.SDK_VERSION}),traceExporter:new l({url:r,headers:{"x-api-key":e.apiKey}})}),I.start(),S=a.getTracer("mcp-logger",E.SDK_VERSION),w=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const o=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await s()};process.once("SIGTERM",o),process.once("SIGINT",o)}(r),function(e,r){const t=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const o=e.shape?.method,n=o?._def?.value||"unknown";return r.debug&&console.log("[mcp-logger] Instrumenting handler: "+n),t(e,async(e,t)=>{const o=Date.now(),a=`${n}-${o}-${Math.random().toString(36).slice(2,8)}`,m=[],p=u.active().setValue(C,m);try{const l=await u.with(p,async()=>await s(e,t)),d=Date.now(),g=i(n,e.params,l,0,void 0);return c({method:n,source:"sdk",transport:"stdio",request:{id:a,timestamp:o,params:e.params},response:{timestamp:d,result:l},duration:d-o,customEvents:m.length>0?m:void 0,contextMetadata:g}),r.debug&&console.log(`[mcp-logger] Request completed (${d-o}ms):`,{method:n,customEvents:m.length}),l}catch(t){const s=Date.now(),u=i(n,e.params,void 0,0,void 0);throw c({method:n,source:"sdk",transport:"stdio",request:{id:a,timestamp:o,params:e.params},response:{timestamp:s,error:t},duration:s-o,customEvents:m.length>0?m:void 0,contextMetadata:u}),r.debug&&console.error("[mcp-logger] Request failed:",t),t}})},r.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,r),r.debug&&console.log("[mcp-logger] โœ“ Initialization complete")},{addLog(e){const r=u.active().getValue(C);if(!r)return void console.warn("[mcp-logger] addLog called outside of handler context");const t={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};r.push(t);const s=a.getSpan(u.active());s&&s.addEvent("custom."+t.level,{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp})},async shutdown(){await s()}});export{b 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-dev.0",
3
+ "version": "0.0.9",
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"
@@ -1 +0,0 @@
1
- import{Transform as e}from"stream";class s extends e{buffer="";direction;logger;sessionContextStore;debug;constructor(e,s,t,r=!1){super(),this.direction=e,this.logger=s,this.sessionContextStore=t,this.debug=r}_transform(e,s,t){try{if("request"===this.direction)return this.push(e),this.buffer+=e.toString(),this.tryParseMessages(),void t();this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseAndForwardMessages(),t()}catch(e){t(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const s of e){const e=s.trim();if(e)try{const s=JSON.parse(e);this.handleMessage(s)}catch(s){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}tryParseAndForwardMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const s of e){const e=s.trim();if(e)try{const s=JSON.parse(e);"2.0"===s.jsonrpc?(this.push(e+"\n"),this.handleMessage(s)):this.debug&&console.error("[mcp-logger CLI] Non-JSON-RPC JSON ignored:",e.slice(0,100))}catch(s){this.debug&&console.error("[mcp-logger CLI] Non-JSON output filtered:",e.slice(0,100))}}}handleMessage(e){if("request"===this.direction&&"initialize"===e.method&&e.params?.clientInfo){const s=e.params;this.sessionContextStore.setClientInfo({name:s.clientInfo?.name,version:s.clientInfo?.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);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.logger.getStats();console.error("[mcp-logger CLI] Stream closed. Pending requests: "+e.pending)}e()}catch(s){e(s)}}}export{s as MessageInterceptor};
package/dist/cli/main.js DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- function e(e){return!M.includes(e)}function s(){return O}function t(){return L}function r(e){P=e}function o(){return P||"unknown"}async function n(){if(R)try{await R.shutdown(),R=null,O=null,x=!1,L=null,P=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function i(e,s){for(const[t,r]of Object.entries(s))null!=r&&e.setAttribute(t,r)}function c(e,s=2e5){try{const t=JSON.stringify(e);return t.length>s?t.slice(0,s)+"... (truncated)":t}catch(e){return null}}function a(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 p(e,s,t,r,o){const n=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,o),i=function(e){if(!T.has(e)){const s=w();T.set(e,s)}return T.get(e)}(`${n.name||"unknown"}-${n.version||"0"}`),c=function(e,s){const t=`${e}-${s}`;return k.has(t)||k.set(t,"conv-"+ ++$),k.get(t)}(i,e),p=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),m=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(s),u=a(e,s);u.uri&&function(e){const s=(_.get(e)||0)+1;_.set(e,s)}(u.uri);const d=function(e,s){return void 0!==e?.cost?e.cost:void 0!==s?.cost?s.cost:void 0}(s,t),l={};return i&&(l.sessionId=i),c&&(l.conversationId=c),p&&(l.userId=p),n.name&&(l.clientName=n.name),n.version&&(l.clientVersion=n.version),m&&(l.permissionLevel=m),void 0!==d&&(l.cost=d),l}function m(e,s){const t=a(e,s);if(!t.uri)return{};const r=_.get(t.uri)||0;return{resourceType:t.type,resourceUri:t.uri,resourceAccessCount:r}}import{spawn as u,execSync as d}from"child_process";import{program as l}from"commander";import{NodeSDK as g}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as h}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as f}from"@opentelemetry/resources";import{trace as v,SpanStatusCode as I}from"@opentelemetry/api";import{readFileSync as y}from"fs";import{fileURLToPath as S}from"url";import{dirname as q,join as C}from"path";import{Transform as N}from"stream";import{ulid as w}from"ulid";const E={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=S(import.meta.url),s=q(e),t=C(s,"..","..","package.json");return JSON.parse(y(t,"utf-8")).version||"1.0.0"}catch(e){return console.error("[MCP Logger] Failed to read package version:",e),"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"},M=["notifications/initialized"];let R=null,O=null,x=!1,L=null,P=null;class b extends N{buffer="";direction;logger;sessionContextStore;debug;constructor(e,s,t,r=!1){super(),this.direction=e,this.logger=s,this.sessionContextStore=t,this.debug=r}_transform(e,s,t){try{if("request"===this.direction)return this.push(e),this.buffer+=e.toString(),this.tryParseMessages(),void t();this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseAndForwardMessages(),t()}catch(e){t(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const s of e){const e=s.trim();if(e)try{const s=JSON.parse(e);this.handleMessage(s)}catch(s){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}tryParseAndForwardMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const s of e){const e=s.trim();if(e)try{const s=JSON.parse(e);"2.0"===s.jsonrpc?(this.push(e+"\n"),this.handleMessage(s)):this.debug&&console.error("[mcp-logger CLI] Non-JSON-RPC JSON ignored:",e.slice(0,100))}catch(s){this.debug&&console.error("[mcp-logger CLI] Non-JSON output filtered:",e.slice(0,100))}}}handleMessage(e){if("request"===this.direction&&"initialize"===e.method&&e.params?.clientInfo){const s=e.params;this.sessionContextStore.setClientInfo({name:s.clientInfo?.name,version:s.clientInfo?.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);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.logger.getStats();console.error("[mcp-logger CLI] Stream closed. Pending requests: "+e.pending)}e()}catch(s){e(s)}}}const T=new Map,k=new Map;let $=0;const _=new Map;class A{pendingRequests=new Map;debug;TIMEOUT=3e4;sessionContextStore;constructor(e,s=!1){this.debug=s,this.sessionContextStore=e}onRequest(s){e(s.method)&&(s.id?(this.pendingRequests.set(s.id,{request:s,timestamp:Date.now()}),"initialize"===s.method&&s.params?.clientInfo&&this.sessionContextStore.setClientInfo({name:s.params.clientInfo.name,version:s.params.clientInfo.version}),setTimeout(()=>this.cleanupStale(s.id),this.TIMEOUT)):this.createRequestOnlySpan(s,Date.now()))}onResponse(e){if(!e.id)return;const s=this.pendingRequests.get(e.id);if("initialize"===s?.request.method)if(e.result?.serverInfo?.version)r(e.result.serverInfo.version);else{const s=e.result?.capabilities?.implementation?.version;s&&r(s)}if(!s)return void this.createResponseOnlySpan(e,Date.now());const t=Date.now(),o=t-s.timestamp;this.createPairedSpan(s.request,e,s.timestamp,t,o),this.pendingRequests.delete(e.id)}onNotification(s){e(s.method)&&this.createResponseOnlySpan({jsonrpc:"2.0",id:`notification-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,method:s.method},Date.now())}createPairedSpan(e,r,n,a,u){const d=p(e.method,e.params,r.result,this.sessionContextStore,void 0);!function(e){const r=s();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+e.method;r.startActiveSpan(n,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":c(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=c(t);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==e.response.result){const s=c(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=c(e.request.headers);s&&(r["mcp.headers"]=s,r["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=t();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}));const s=o();r["mcp.server.version"]=s}const n=e.contextMetadata||p(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 a=m(e.method,e.request.params);if(a.resourceType&&(r["mcp.resource.type"]=a.resourceType),a.resourceUri&&(r["mcp.resource.uri"]=a.resourceUri),a.resourceAccessCount&&(r["mcp.resource.access_count"]=a.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:c(e.response.error)||"Unknown error",r["mcp.error.message"]=t,e.response.error instanceof Error&&s.recordException(e.response.error),s.setStatus({code:I.ERROR,message:t})}else s.setStatus({code:I.OK});i(s,r)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),s.setStatus({code:I.ERROR,message:e+""})}finally{s.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:n,params:e.params},response:{timestamp:a,result:r.result,error:r.error},duration:u,contextMetadata:d})}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,r){const n=p(e.method,e.params,void 0,this.sessionContextStore,void 0);!function(e){const r=s();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+e.method;r.startActiveSpan(n,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":c(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=c(t);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if("cli"===e.source){const e=t();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}));const s=o();r["mcp.server.version"]=s}const n=e.contextMetadata||p(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 a=m(e.method,e.request.params);a.resourceType&&(r["mcp.resource.type"]=a.resourceType),a.resourceUri&&(r["mcp.resource.uri"]=a.resourceUri),a.resourceAccessCount&&(r["mcp.resource.access_count"]=a.resourceAccessCount),s.setStatus({code:I.OK}),i(s,r)}catch(e){console.error("[MCP Logger] Error creating request-only span:",e),s.setStatus({code:I.ERROR,message:e+""})}finally{s.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:r,params:e.params},contextMetadata:n})}createResponseOnlySpan(e,r){const n=p(e.method||"unknown",void 0,e.result,this.sessionContextStore,void 0);!function(e){const r=s();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+e.method;r.startActiveSpan(n,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=c(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:c(e.response.error)||"Unknown error",r["mcp.error.message"]=t,e.response.error instanceof Error&&s.recordException(e.response.error),s.setStatus({code:I.ERROR,message:t})}else s.setStatus({code:I.OK});if("cli"===e.source){const e=t();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}));const s=o();r["mcp.server.version"]=s}const n=e.contextMetadata||p(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),i(s,r)}catch(e){console.error("[MCP Logger] Error creating response-only span:",e),s.setStatus({code:I.ERROR,message:e+""})}finally{s.end()}})}({method:e.method||"same_request_method",source:"cli",transport:"stdio",response:{id:e.id,timestamp:r,result:e.result,error:e.error},contextMetadata:n})}getStats(){return{pending:this.pendingRequests.size}}clear(){this.pendingRequests.clear()}}class J{context={};getClientInfo(){return this.context.clientInfo}setClientInfo(e){this.context.clientInfo=e}getHeaders(){return this.context.headers}setHeaders(e){this.context.headers=e}snapshot(){return{clientInfo:this.context.clientInfo?{...this.context.clientInfo}:void 0,headers:this.context.headers?{...this.context.headers}:void 0}}}const z=new Set(["npx","node","uvx","python","python3","pip","pipx","deno","bun"]);l.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(e,s)=>{try{const t=!1,o=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(e){return console.error("[MCP Logger CLI] aware CLI not found or mcpenv sync/keys failed, skipping env extraction"),[]}}()),[i,...c]=e,a=function(e){for(const s of e){if(z.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}(c.filter(e=>!e.startsWith("-")));let p,m;if(p=a?.packageName?a.packageName:i.split("/").pop()||i||E.SERVICE_NAME_PREFIX+"-unknown",a&&(m=a.version,"latest"===a.version)){const e="uvx"===i?"pip":"npm",s=function(e,s){try{let t;t="pip"===s?"pip index versions "+e:`npm view ${e} version --json`;const r=d(t,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]});if("pip"===s){const e=r.match(/Available versions:\s*([^\n]+)/);return e?e[1].split(",").map(e=>e.trim())[0]:null}{const e=r.trim().replace(/^["']|["']$/g,""),s=e.match(/^(\d+\.\d+\.\d+(?:-[\w.]+)?)/);return s?s[1]:e}}catch(e){return null}}(a.packageName,e);s&&(m=s)}!function(e){if(x)return O;const s=e.endpoint||E.ENDPOINT,t=e.serviceName||`${E.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;R=new g({resource:new f({"service.name":t,"service.version":E.SDK_VERSION}),traceExporter:new h({url:s,headers:{"x-api-key":e.apiKey}})}),R.start(),O=v.getTracer("mcp-logger",E.SDK_VERSION),x=!0;const r=async()=>{await n()};process.once("SIGTERM",r),process.once("SIGINT",r)}({...s,serviceName:p,debug:t}),console.error("[MCP Logger CLI] Starting MCP server:",e.join(" ")),m&&r(m),function(e,s,t){L={command:e,args:s,env:t}}(i,c,o);const l=u(i,c,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),I=new J,y=new A(I,t),S=new b("request",y,I,t);process.stdin.pipe(S).pipe(l.stdin);const q=new b("response",y,I,t);l.stdout.pipe(q).pipe(process.stdout),l.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),y.clear(),await n(),process.exit(1)}),l.on("exit",async(e,s)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${s}`),y.clear(),await n(),process.exit(e||0)});let C=!1;const N=async e=>{C||(C=!0,console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),l.kill(e),await Promise.race([new Promise(e=>l.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await n(),process.exit(0))};process.on("SIGTERM",()=>N("SIGTERM")),process.on("SIGINT",()=>N("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await n(),process.exit(1)}}),l.parse();
@@ -1 +0,0 @@
1
- function e(e){return!a.includes(e)}function t(e,t,n,o,s){const r=function(e,t){const n=e?.getClientInfo();if(n?.name||n?.version)return{name:n.name,version:n.version};const o=e?.getHeaders()||t;if(o){const e=o["user-agent"]||o["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}}(o,s),a=function(e){if(!c.has(e)){const t=i();c.set(e,t)}return c.get(e)}(`${r.name||"unknown"}-${r.version||"0"}`),m=function(e,t){const n=`${e}-${t}`;return p.has(n)||p.set(n,"conv-"+ ++u),p.get(n)}(a,e),l=function(e){if(e?.userId||e?.user_id)return e.userId||e.user_id}(t),h=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(t),f=function(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}}(e,t);f.uri&&function(e){const t=(d.get(e)||0)+1;d.set(e,t)}(f.uri);const v=function(e,t){return void 0!==e?.cost?e.cost:void 0!==t?.cost?t.cost:void 0}(t,n),g={};return a&&(g.sessionId=a),m&&(g.conversationId=m),l&&(g.userId=l),r.name&&(g.clientName=r.name),r.version&&(g.clientVersion=r.version),h&&(g.permissionLevel=h),void 0!==v&&(g.cost=v),g}import{readFileSync as n}from"fs";import{fileURLToPath as o}from"url";import{dirname as s,join as r}from"path";import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";import{ulid as i}from"ulid";!function(){try{const e=o(import.meta.url),t=s(e),i=r(t,"..","..","package.json");return JSON.parse(n(i,"utf-8")).version||"1.0.0"}catch(e){return console.error("[MCP Logger] Failed to read package version:",e),"1.0.0"}}();const a=["notifications/initialized"],c=new Map,p=new Map;let u=0;const d=new Map;class m{pendingRequests=new Map;debug;TIMEOUT=3e4;sessionContextStore;constructor(e,t=!1){this.debug=t,this.sessionContextStore=e}onRequest(t){e(t.method)&&(t.id?(this.pendingRequests.set(t.id,{request:t,timestamp:Date.now()}),"initialize"===t.method&&t.params?.clientInfo&&this.sessionContextStore.setClientInfo({name:t.params.clientInfo.name,version:t.params.clientInfo.version}),setTimeout(()=>this.cleanupStale(t.id),this.TIMEOUT)):this.createRequestOnlySpan(t,Date.now()))}onResponse(e){if(!e.id)return;const t=this.pendingRequests.get(e.id);if("initialize"===t?.request.method&&(e.result?.serverInfo?.version?e.result.serverInfo.version:e.result),!t)return void this.createResponseOnlySpan(e,Date.now());const n=Date.now(),o=n-t.timestamp;this.createPairedSpan(t.request,e,t.timestamp,n,o),this.pendingRequests.delete(e.id)}onNotification(t){e(t.method)&&this.createResponseOnlySpan({jsonrpc:"2.0",id:`notification-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,method:t.method},Date.now())}createPairedSpan(e,n,o,s,r){t(e.method,e.params,n.result,this.sessionContextStore,void 0),e.method,e.id,e.params,n.result,n.error,console.error("[MCP Logger] Tracer not initialized, cannot create span")}cleanupStale(e){const t=this.pendingRequests.get(e);t&&Date.now()-t.timestamp>this.TIMEOUT&&(this.createRequestOnlySpan(t.request,t.timestamp),this.pendingRequests.delete(e))}createRequestOnlySpan(e,n){t(e.method,e.params,void 0,this.sessionContextStore,void 0),e.method,e.id,e.params,console.error("[MCP Logger] Tracer not initialized, cannot create span")}createResponseOnlySpan(e,n){t(e.method||"unknown",void 0,e.result,this.sessionContextStore,void 0),e.method,e.id,e.result,e.error,console.error("[MCP Logger] Tracer not initialized, cannot create span")}getStats(){return{pending:this.pendingRequests.size}}clear(){this.pendingRequests.clear()}}export{m as MCPMessageLogger};
@@ -1 +0,0 @@
1
- function r(r){return!a.includes(r)}import{readFileSync as o}from"fs";import{fileURLToPath as t}from"url";import{dirname as e,join as i}from"path";const n={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const r=t(import.meta.url),n=e(r),a=i(n,"..","..","package.json");return JSON.parse(o(a,"utf-8")).version||"1.0.0"}catch(r){return console.error("[MCP Logger] Failed to read package version:",r),"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"},a=["notifications/initialized"];export{n as CONFIG,r as shouldTrackMethod};
@@ -1 +0,0 @@
1
- function r(r){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function e(r){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function o(r){console.error("[MCP Logger] Tracer not initialized, cannot create span")}import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";import{readFileSync as t}from"fs";import{fileURLToPath as n}from"url";import{dirname as i,join as a}from"path";import"ulid";!function(){try{const r=n(import.meta.url),e=i(r),o=a(e,"..","..","package.json");return JSON.parse(t(o,"utf-8")).version||"1.0.0"}catch(r){return console.error("[MCP Logger] Failed to read package version:",r),"1.0.0"}}();export{r as createPairedSpan,e as createRequestOnlySpan,o as createResponseOnlySpan};
@@ -1 +0,0 @@
1
- function t(t,r){for(const[e,n]of Object.entries(r))null!=n&&t.setAttribute(e,n)}function r(t,r,e,n){try{e?t.addEvent(r,e):t.addEvent(r)}catch(t){n&&console.error("[MCP Logger] Failed to add span event:",t)}}function e(t,r,e){const n=r instanceof Error?r.message:"Unknown error",o=r instanceof Error?r.constructor.name:"Error";r instanceof Error&&t.recordException(r),t.setStatus({code:s.ERROR,message:n}),t.setAttribute("error",!0),t.setAttribute("error.type",o),t.setAttribute("error.message",n),e&&console.error("[MCP Logger] Error recorded in span:",r)}function n(t){t.setStatus({code:s.OK})}function o(t,r=2e5){try{const e=JSON.stringify(t);return e.length>r?e.slice(0,r)+"... (truncated)":e}catch(t){return null}}function c(){const t={},r=a.active(),e=i.getSpan(r);if(e){const r=e.spanContext();r&&(t.traceparent=`00-${r.traceId}-${r.spanId}-01`)}return t}import{SpanStatusCode as s,context as a,trace as i}from"@opentelemetry/api";export{r as addSpanEvent,c as getTraceHeaders,n as markSpanSuccess,e as recordSpanError,o as safeStringify,t as setSpanAttributes};
@@ -1 +0,0 @@
1
- function e(e){if(I)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),S;const r=e.endpoint||y.ENDPOINT,o=e.serviceName||`${y.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+r),console.error("[MCP Logger] Service: "+o)),E=new s({resource:new a({"service.name":o,"service.version":y.SDK_VERSION}),traceExporter:new u({url:r,headers:{"x-api-key":e.apiKey}})}),E.start(),S=p.getTracer("mcp-logger",y.SDK_VERSION),I=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const n=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await l()};return process.once("SIGTERM",n),process.once("SIGINT",n),S}function r(){return S}function o(){return I}function n(e,r,o){M={command:e,args:r,env:o}}function t(){return M}function i(e){N=e}function c(){return N||"unknown"}async function l(){if(E)try{await E.shutdown(),E=null,S=null,I=!1,M=null,N=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}import{NodeSDK as s}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as u}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as a}from"@opentelemetry/resources";import{trace as p}from"@opentelemetry/api";import{readFileSync as g}from"fs";import{fileURLToPath as m}from"url";import{dirname as f,join as d}from"path";const y={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=m(import.meta.url),r=f(e),o=d(r,"..","..","package.json");return JSON.parse(g(o,"utf-8")).version||"1.0.0"}catch(e){return console.error("[MCP Logger] Failed to read package version:",e),"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"};let E=null,S=null,I=!1,M=null,N=null;export{t as getCLIServerInfo,c as getMCPServerVersion,r as getTracer,e as initializeTelemetry,o as isInitializedTelemetry,n as setCLIServerInfo,i as setMCPServerVersion,l as shutdownTelemetry};
@@ -1,228 +0,0 @@
1
- /**
2
- * ๊ณตํ†ต ํƒ€์ž… ์ •์˜
3
- */
4
- /**
5
- * ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ์ดˆ๊ธฐํ™” ์˜ต์…˜
6
- */
7
- interface TelemetryOptions {
8
- /**
9
- * API Key for authentication (ํ•„์ˆ˜)
10
- */
11
- apiKey: string;
12
- /**
13
- * ์„œ๋น„์Šค ์ด๋ฆ„ (์˜ต์…˜)
14
- * ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ž๋™์œผ๋กœ 'mcp-server-{random}' ํ˜•์‹์œผ๋กœ ์ƒ์„ฑ
15
- */
16
- serviceName?: string;
17
- /**
18
- * ์ปค์Šคํ…€ OTLP ์—”๋“œํฌ์ธํŠธ (์˜ต์…˜)
19
- * ๊ธฐ๋ณธ๊ฐ’: CONFIG.ENDPOINT
20
- */
21
- endpoint?: string;
22
- /**
23
- * ๋””๋ฒ„๊ทธ ๋กœ๊ทธ ํ™œ์„ฑํ™” (์˜ต์…˜)
24
- */
25
- debug?: boolean;
26
- }
27
- /**
28
- * Span ์†์„ฑ ์ธํ„ฐํŽ˜์ด์Šค
29
- */
30
- interface SpanAttributes {
31
- [key: string]: string | number | boolean | undefined;
32
- }
33
- /**
34
- * MCP ๋ฉ”์‹œ์ง€ ๋ฐฉํ–ฅ
35
- */
36
- type MessageDirection = 'request' | 'response';
37
- /**
38
- * JSON-RPC ๋ฉ”์‹œ์ง€ ํƒ€์ž…
39
- */
40
- interface JSONRPCMessage {
41
- jsonrpc: '2.0';
42
- id?: string | number;
43
- method?: string;
44
- params?: any;
45
- result?: any;
46
- error?: {
47
- code: number;
48
- message: string;
49
- data?: any;
50
- };
51
- }
52
- /**
53
- * JSON-RPC Request ํƒ€์ž…
54
- */
55
- interface JSONRPCRequest extends JSONRPCMessage {
56
- method: string;
57
- params?: any;
58
- }
59
- /**
60
- * JSON-RPC Response ํƒ€์ž…
61
- */
62
- interface JSONRPCResponse extends JSONRPCMessage {
63
- id: string | number;
64
- result?: any;
65
- error?: {
66
- code: number;
67
- message: string;
68
- data?: any;
69
- };
70
- }
71
- /**
72
- * MCP Span ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ (ES ์ตœ์ ํ™”)
73
- * Request/Response ํ•„๋“œ๊ฐ€ optional์ด๋ฏ€๋กœ log_type ๋ถˆํ•„์š”
74
- * - request๋งŒ ์žˆ์Œ = request-only
75
- * - response๋งŒ ์žˆ์Œ = response-only
76
- * - ๋‘˜ ๋‹ค ์žˆ์Œ = paired (duration ํฌํ•จ)
77
- */
78
- interface MCPSpanData {
79
- 'mcp.source': 'cli' | 'sdk';
80
- 'mcp.transport': 'stdio' | 'sse';
81
- 'mcp.request.id'?: string;
82
- 'mcp.request.method'?: string;
83
- 'mcp.request.timestamp'?: number;
84
- 'mcp.request.params'?: string;
85
- 'mcp.request.params.size'?: number;
86
- 'mcp.response.id'?: string;
87
- 'mcp.response.method'?: string;
88
- 'mcp.response.timestamp'?: number;
89
- 'mcp.response.result'?: string;
90
- 'mcp.response.result.size'?: number;
91
- 'mcp.duration_ms'?: number;
92
- 'mcp.tool.name'?: string;
93
- 'mcp.tool.arguments'?: string;
94
- 'mcp.tool.arguments.size'?: number;
95
- 'mcp.headers'?: string;
96
- 'mcp.headers.size'?: number;
97
- 'mcp.events'?: string;
98
- 'mcp.events.count'?: number;
99
- 'mcp.cli'?: string;
100
- 'mcp.server.version'?: string;
101
- 'mcp.error'?: boolean;
102
- 'mcp.error.code'?: number;
103
- 'mcp.error.message'?: string;
104
- 'mcp.session.id'?: string;
105
- 'mcp.conversation.id'?: string;
106
- 'mcp.trace.id'?: string;
107
- 'mcp.user.id'?: string;
108
- 'mcp.client.name'?: string;
109
- 'mcp.client.version'?: string;
110
- 'mcp.permission.level'?: string;
111
- 'mcp.resource.type'?: string;
112
- 'mcp.resource.uri'?: string;
113
- 'mcp.resource.access_count'?: number;
114
- 'mcp.cost'?: number;
115
- }
116
- /**
117
- * ์ปค์Šคํ…€ ๋กœ๊ทธ ์—”ํŠธ๋ฆฌ
118
- */
119
- interface CustomLogEntry {
120
- /**
121
- * ๋กœ๊ทธ ๋ ˆ๋ฒจ
122
- */
123
- level?: 'info' | 'warn' | 'error';
124
- /**
125
- * ๋กœ๊ทธ ๋ฉ”์‹œ์ง€
126
- */
127
- message: string;
128
- /**
129
- * ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ (์ž์œ  ํ˜•์‹)
130
- */
131
- metadata?: Record<string, any>;
132
- /**
133
- * ํƒ€์ž„์Šคํƒฌํ”„ (์ž๋™ ์ƒ์„ฑ ๊ฐ€๋Šฅ)
134
- */
135
- timestamp?: number;
136
- }
137
- /**
138
- * MCP ์ปจํ…์ŠคํŠธ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
139
- */
140
- interface MCPContextMetadata {
141
- sessionId?: string;
142
- conversationId?: string;
143
- userId?: string;
144
- clientName?: string;
145
- clientVersion?: string;
146
- permissionLevel?: string;
147
- cost?: number;
148
- }
149
- /**
150
- * Session-level context shared across a single MCP connection
151
- */
152
- interface SessionContext {
153
- clientInfo?: {
154
- name?: string;
155
- version?: string;
156
- };
157
- headers?: Record<string, string>;
158
- }
159
- /**
160
- * Abstraction for reading/writing session-scoped context for a single connection.
161
- */
162
- interface SessionContextStore {
163
- getClientInfo(): {
164
- name?: string;
165
- version?: string;
166
- } | undefined;
167
- setClientInfo(info: {
168
- name?: string;
169
- version?: string;
170
- } | undefined): void;
171
- getHeaders(): Record<string, string> | undefined;
172
- setHeaders(headers: Record<string, string> | undefined): void;
173
- snapshot(): SessionContext;
174
- }
175
- /**
176
- * Paired Span ์ž…๋ ฅ ๋ฐ์ดํ„ฐ
177
- */
178
- interface PairedSpanInput {
179
- method: string;
180
- source: 'cli' | 'sdk';
181
- transport: 'stdio' | 'sse';
182
- request: {
183
- id: string | number;
184
- timestamp: number;
185
- params: any;
186
- headers?: Record<string, string>;
187
- };
188
- response: {
189
- timestamp: number;
190
- result?: any;
191
- error?: any;
192
- };
193
- duration: number;
194
- customEvents?: CustomLogEntry[];
195
- contextMetadata?: MCPContextMetadata;
196
- }
197
- /**
198
- * Response-only Span ์ž…๋ ฅ ๋ฐ์ดํ„ฐ (notification ๋“ฑ response๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ)
199
- */
200
- interface ResponseOnlySpanInput {
201
- method: string;
202
- source: 'cli' | 'sdk';
203
- transport: 'stdio' | 'sse';
204
- response: {
205
- id: string | number;
206
- timestamp: number;
207
- result?: any;
208
- error?: any;
209
- };
210
- contextMetadata?: MCPContextMetadata;
211
- }
212
- /**
213
- * Request-only Span ์ž…๋ ฅ ๋ฐ์ดํ„ฐ (timeout ๋“ฑ์œผ๋กœ response๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ)
214
- */
215
- interface RequestOnlySpanInput {
216
- method: string;
217
- source: 'cli' | 'sdk';
218
- transport: 'stdio' | 'sse';
219
- request: {
220
- id: string | number;
221
- timestamp: number;
222
- params: any;
223
- headers?: Record<string, string>;
224
- };
225
- contextMetadata?: MCPContextMetadata;
226
- }
227
-
228
- export type { CustomLogEntry, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, MCPContextMetadata, MCPSpanData, MessageDirection, PairedSpanInput, RequestOnlySpanInput, ResponseOnlySpanInput, SessionContext, SessionContextStore, SpanAttributes, TelemetryOptions };
package/dist/sdk/index.js DELETED
@@ -1 +0,0 @@
1
- function e(){return S}function r(){return q}function t(){return N||"unknown"}async function s(){if(I)try{await I.shutdown(),I=null,S=null,w=!1,q=null,N=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function o(e,r=2e5){try{const t=JSON.stringify(e);return t.length>r?t.slice(0,r)+"... (truncated)":t}catch(e){return null}}function n(e,r){return e.startsWith("resources/")?{type:e.split("/")[1],uri:r?.uri||r?.path||r?.resource}:e.startsWith("prompts/")?{type:"prompt",uri:r?.name||r?.promptId}:"tools/call"===e?{type:"tool",uri:r?.name}:r?.resourceType&&r?.resourceUri?{type:r.resourceType,uri:r.resourceUri}:{type:void 0,uri:void 0}}function i(e,r,t,s,o){const i=function(e,r){const t=r;if(t){const e=t["user-agent"]||t["User-Agent"];if(e){const r=e.match(/^([^\/]+)\/([^\s]+)/);if(r)return{name:r[1],version:r[2]}}}return{name:void 0,version:void 0}}(0,o),c=function(e){if(!M.has(e)){const r=y();M.set(e,r)}return M.get(e)}(`${i.name||"unknown"}-${i.version||"0"}`),a=function(e,r){const t=`${e}-${r}`;return O.has(t)||O.set(t,"conv-"+ ++R),O.get(t)}(c,e),m=function(e,r){return e?.userId||e?.user_id?e.userId||e.user_id:r?r["x-user-id"]||r["X-User-Id"]:void 0}(r,o),u=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(r),p=n(e,r);p.uri&&function(e){const r=(L.get(e)||0)+1;L.set(e,r)}(p.uri);const l=function(e,r){return void 0!==e?.cost?e.cost:void 0!==r?.cost?r.cost:void 0}(r,t),d={};return c&&(d.sessionId=c),a&&(d.conversationId=a),m&&(d.userId=m),i.name&&(d.clientName=i.name),i.version&&(d.clientVersion=i.version),u&&(d.permissionLevel=u),void 0!==l&&(d.cost=l),d}function c(s){const c=e();if(!c)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const a="mcp."+s.method;c.startActiveSpan(a,e=>{try{const c={"mcp.source":s.source,"mcp.transport":s.transport,"mcp.request.id":s.request.id+"","mcp.request.method":s.method,"mcp.request.timestamp":s.request.timestamp,"mcp.request.params":o(s.request.params)||"{}","mcp.request.params.size":JSON.stringify(s.request.params||{}).length,"mcp.response.id":s.request.id+"","mcp.response.timestamp":s.response.timestamp,"mcp.duration_ms":s.duration};if("tools/call"===s.method&&s.request.params){const e=s.request.params.name,r=s.request.params.arguments;if(e&&(c["mcp.tool.name"]=e),r){const e=o(r);e&&(c["mcp.tool.arguments"]=e,c["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==s.response.result){const e=o(s.response.result);e&&(c["mcp.response.result"]=e,c["mcp.response.result.size"]=JSON.stringify(s.response.result).length)}if(s.request.headers){const e=o(s.request.headers);e&&(c["mcp.headers"]=e,c["mcp.headers.size"]=JSON.stringify(s.request.headers).length)}if("cli"===s.source){const e=r();e&&(c["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}));const s=t();c["mcp.server.version"]=s}const a=s.contextMetadata||i(s.method,s.request.params,s.response.result,0,s.request.headers);a.sessionId&&(c["mcp.session.id"]=a.sessionId),a.conversationId&&(c["mcp.conversation.id"]=a.conversationId),a.sessionId&&a.conversationId&&(c["mcp.trace.id"]=`${a.sessionId}:${a.conversationId}`),a.userId&&(c["mcp.user.id"]=a.userId),a.clientName&&(c["mcp.client.name"]=a.clientName),a.clientVersion&&(c["mcp.client.version"]=a.clientVersion),a.permissionLevel&&(c["mcp.permission.level"]=a.permissionLevel),void 0!==a.cost&&(c["mcp.cost"]=a.cost);const u=function(e,r){const t=n(e,r);if(!t.uri)return{};const s=L.get(t.uri)||0;return{resourceType:t.type,resourceUri:t.uri,resourceAccessCount:s}}(s.method,s.request.params);if(u.resourceType&&(c["mcp.resource.type"]=u.resourceType),u.resourceUri&&(c["mcp.resource.uri"]=u.resourceUri),u.resourceAccessCount&&(c["mcp.resource.access_count"]=u.resourceAccessCount),s.customEvents&&s.customEvents.length>0&&(c["mcp.events"]=JSON.stringify(s.customEvents),c["mcp.events.count"]=s.customEvents.length,s.customEvents.forEach(r=>{e.addEvent("custom."+(r.level||"info"),{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp||Date.now()})})),s.response.error){let r;c["mcp.error"]=!0,r=s.response.error instanceof Error?s.response.error.message:"string"==typeof s.response.error?s.response.error:o(s.response.error)||"Unknown error",c["mcp.error.message"]=r,s.response.error instanceof Error&&e.recordException(s.response.error),e.setStatus({code:m.ERROR,message:r})}else e.setStatus({code:m.OK});!function(e,r){for(const[t,s]of Object.entries(r))null!=s&&e.setAttribute(t,s)}(e,c)}catch(r){console.error("[MCP Logger] Error creating paired span:",r),e.setStatus({code:m.ERROR,message:r+""})}finally{e.end()}})}import{trace as a,SpanStatusCode as m,context as u}from"@opentelemetry/api";import{NodeSDK as p}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as l}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as d}from"@opentelemetry/resources";import{readFileSync as g}from"fs";import{fileURLToPath as v}from"url";import{dirname as f,join as h}from"path";import{ulid as y}from"ulid";const E={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=v(import.meta.url),r=f(e),t=h(r,"..","..","package.json");return JSON.parse(g(t,"utf-8")).version||"1.0.0"}catch(e){return console.error("[MCP Logger] Failed to read package version:",e),"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"};let I=null,S=null,w=!1,q=null,N=null;const M=new Map,O=new Map;let R=0;const L=new Map,C=Symbol("mcp-logger.customEvents"),b=Object.assign(function(e,r){if(!r.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");!function(e){if(w)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),S;const r=e.endpoint||E.ENDPOINT,t=e.serviceName||`${E.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+r),console.error("[MCP Logger] Service: "+t)),I=new p({resource:new d({"service.name":t,"service.version":E.SDK_VERSION}),traceExporter:new l({url:r,headers:{"x-api-key":e.apiKey}})}),I.start(),S=a.getTracer("mcp-logger",E.SDK_VERSION),w=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const o=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await s()};process.once("SIGTERM",o),process.once("SIGINT",o)}(r),function(e,r){const t=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const o=e.shape?.method,n=o?._def?.value||"unknown";return r.debug&&console.log("[mcp-logger] Instrumenting handler: "+n),t(e,async(e,t)=>{const o=Date.now(),a=`${n}-${o}-${Math.random().toString(36).slice(2,8)}`,m=[],p=u.active().setValue(C,m);try{const l=await u.with(p,async()=>await s(e,t)),d=Date.now(),g=i(n,e.params,l,0,void 0);return c({method:n,source:"sdk",transport:"stdio",request:{id:a,timestamp:o,params:e.params},response:{timestamp:d,result:l},duration:d-o,customEvents:m.length>0?m:void 0,contextMetadata:g}),r.debug&&console.log(`[mcp-logger] Request completed (${d-o}ms):`,{method:n,customEvents:m.length}),l}catch(t){const s=Date.now(),u=i(n,e.params,void 0,0,void 0);throw c({method:n,source:"sdk",transport:"stdio",request:{id:a,timestamp:o,params:e.params},response:{timestamp:s,error:t},duration:s-o,customEvents:m.length>0?m:void 0,contextMetadata:u}),r.debug&&console.error("[mcp-logger] Request failed:",t),t}})},r.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,r),r.debug&&console.log("[mcp-logger] โœ“ Initialization complete")},{addLog(e){const r=u.active().getValue(C);if(!r)return void console.warn("[mcp-logger] addLog called outside of handler context");const t={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};r.push(t);const s=a.getSpan(u.active());s&&s.addEvent("custom."+t.level,{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp})},async shutdown(){await s()}});export{b as trace};
@@ -1 +0,0 @@
1
- function e(e,t,o,r,n){const s=function(e){if(!p.has(e)){const t=a();p.set(e,t)}return p.get(e)}("unknown-0"),i=function(e,t){const o=`${e}-${t}`;return m.has(o)||m.set(o,"conv-"+ ++l),m.get(o)}(s,e),c=function(e){if(e?.userId||e?.user_id)return e.userId||e.user_id}(t),u=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(t),g=function(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}}(e,t);g.uri&&function(e){const t=(d.get(e)||0)+1;d.set(e,t)}(g.uri);const f=function(e,t){return void 0!==e?.cost?e.cost:void 0!==t?.cost?t.cost:void 0}(t,o),v={};return s&&(v.sessionId=s),i&&(v.conversationId=i),c&&(v.userId=c),u&&(v.permissionLevel=u),void 0!==f&&(v.cost=f),v}function t(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function o(o,r){const s=o.setRequestHandler.bind(o);o.setRequestHandler=function(o,i){const c=o.shape?.method,u=c?._def?.value||"unknown";return r.debug&&console.log("[mcp-logger] Instrumenting handler: "+u),s(o,async(o,s)=>{const c=Date.now(),a=(Math.random().toString(36).slice(2,8),[]),p=n.active().setValue(g,a);try{const m=await n.with(p,async()=>await i(o,s)),l=Date.now();return e(u,o.params,m),t(o.params),r.debug&&console.log(`[mcp-logger] Request completed (${l-c}ms):`,{method:u,customEvents:a.length}),m}catch(n){throw e(u,o.params,void 0),t(o.params),r.debug&&console.error("[mcp-logger] Request failed:",n),n}})},r.debug&&console.log("[mcp-logger] Server instrumentation complete")}function r(){return n.active().getValue(g)}import{context as n}from"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";import{readFileSync as s}from"fs";import{fileURLToPath as i}from"url";import{dirname as c,join as u}from"path";import{ulid as a}from"ulid";!function(){try{const e=i(import.meta.url),t=c(e),o=u(t,"..","..","package.json");return JSON.parse(s(o,"utf-8")).version||"1.0.0"}catch(e){return console.error("[MCP Logger] Failed to read package version:",e),"1.0.0"}}();const p=new Map,m=new Map;let l=0;const d=new Map,g=Symbol("mcp-logger.customEvents");export{r as getCustomEventsFromContext,o as instrumentMCPServer};
@@ -1,91 +0,0 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { ZodObject, ZodLiteral } from 'zod';
3
- import { Request, Result } from '@modelcontextprotocol/sdk/types.js';
4
-
5
- /**
6
- * ๊ณตํ†ต ํƒ€์ž… ์ •์˜
7
- */
8
- /**
9
- * ํ…”๋ ˆ๋ฉ”ํŠธ๋ฆฌ ์ดˆ๊ธฐํ™” ์˜ต์…˜
10
- */
11
- interface TelemetryOptions {
12
- /**
13
- * API Key for authentication (ํ•„์ˆ˜)
14
- */
15
- apiKey: string;
16
- /**
17
- * ์„œ๋น„์Šค ์ด๋ฆ„ (์˜ต์…˜)
18
- * ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ž๋™์œผ๋กœ 'mcp-server-{random}' ํ˜•์‹์œผ๋กœ ์ƒ์„ฑ
19
- */
20
- serviceName?: string;
21
- /**
22
- * ์ปค์Šคํ…€ OTLP ์—”๋“œํฌ์ธํŠธ (์˜ต์…˜)
23
- * ๊ธฐ๋ณธ๊ฐ’: CONFIG.ENDPOINT
24
- */
25
- endpoint?: string;
26
- /**
27
- * ๋””๋ฒ„๊ทธ ๋กœ๊ทธ ํ™œ์„ฑํ™” (์˜ต์…˜)
28
- */
29
- debug?: boolean;
30
- }
31
- /**
32
- * ์ปค์Šคํ…€ ๋กœ๊ทธ ์—”ํŠธ๋ฆฌ
33
- */
34
- interface CustomLogEntry$1 {
35
- /**
36
- * ๋กœ๊ทธ ๋ ˆ๋ฒจ
37
- */
38
- level?: 'info' | 'warn' | 'error';
39
- /**
40
- * ๋กœ๊ทธ ๋ฉ”์‹œ์ง€
41
- */
42
- message: string;
43
- /**
44
- * ์ถ”๊ฐ€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ (์ž์œ  ํ˜•์‹)
45
- */
46
- metadata?: Record<string, any>;
47
- /**
48
- * ํƒ€์ž„์Šคํƒฌํ”„ (์ž๋™ ์ƒ์„ฑ ๊ฐ€๋Šฅ)
49
- */
50
- timestamp?: number;
51
- }
52
-
53
- /**
54
- * SDK ํƒ€์ž… ์ •์˜
55
- */
56
-
57
- /**
58
- * Trace ์˜ต์…˜
59
- */
60
- interface TraceOptions extends TelemetryOptions {
61
- }
62
- /**
63
- * Custom Log Entry (re-export from core)
64
- */
65
- type CustomLogEntry = CustomLogEntry$1;
66
- /**
67
- * MCP JSON-RPC Request ํƒ€์ž…
68
- */
69
- type MCPRequest = Request;
70
- /**
71
- * MCP Result ํƒ€์ž…
72
- */
73
- type MCPResult = Result;
74
- /**
75
- * MCP Request Schema ํƒ€์ž…
76
- */
77
- type MCPRequestSchema = ZodObject<{
78
- method: ZodLiteral<string>;
79
- }>;
80
- /**
81
- * Request Handler Extra ํƒ€์ž…
82
- */
83
- interface RequestHandlerExtra {
84
- signal: AbortSignal;
85
- }
86
- /**
87
- * MCP Server ํƒ€์ž… (์žฌexport)
88
- */
89
- type MCPServer = Server;
90
-
91
- export type { CustomLogEntry, MCPRequest, MCPRequestSchema, MCPResult, MCPServer, RequestHandlerExtra, TraceOptions };