@awarecorp/mcp-logger 0.0.7 β†’ 0.0.8

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
@@ -87,7 +86,8 @@ Optimized for Elasticsearch with flat structure:
87
86
  - `mcp.request.params` / `mcp.response.result`: Request/response payloads
88
87
  - `mcp.error.message`: Enhanced error serialization (no more `[object Object]`)
89
88
  - `mcp.events`: Custom log entries (JSON array)
90
- - `mcp.cli.server`: CLI execution info (JSON object)
89
+ - `mcp.sdk.name`: MCP SDK/framework name (e.g., `FastMCP`)
90
+ - `mcp.sdk.version`: MCP SDK/framework version
91
91
 
92
92
  ## πŸ”§ SDK API
93
93
 
@@ -99,13 +99,8 @@ Enable automatic instrumentation on an MCP server.
99
99
 
100
100
  - `server`: MCP Server instance
101
101
  - `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
102
  - `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)
103
+ - `options.endpoint`: Custom OTLP endpoint (optional)
109
104
 
110
105
  **Returns:** `void`
111
106
 
@@ -139,21 +134,14 @@ mcp-logger [options] <command...>
139
134
  ### Options
140
135
 
141
136
  - `-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
137
+ - `-e, --endpoint <url>`: Custom OTLP endpoint (optional)
150
138
 
151
139
  ### Examples
152
140
 
153
141
  **Wrap an npx command:**
154
142
 
155
143
  ```bash
156
- mcp-logger -k API_KEY -s filesystem npx -y @modelcontextprotocol/server-filesystem /path
144
+ mcp-logger -k API_KEY -- npx -y @modelcontextprotocol/server-filesystem /path
157
145
  ```
158
146
 
159
147
  **Use with Claude Desktop:**
@@ -168,8 +156,6 @@ mcp-logger -k API_KEY -s filesystem npx -y @modelcontextprotocol/server-filesyst
168
156
  "@awarecorp/mcp-logger",
169
157
  "-k",
170
158
  "YOUR_API_KEY",
171
- "-s",
172
- "my-server",
173
159
  "--",
174
160
  "npx",
175
161
  "-y",
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 s(...e){N&&console.error("[mcp-logger]",...e)}function t(...e){x&&console.error("[mcp-logger]",...e)}function r(e){return!M.includes(e)}function n(){return A.getTracer()}function o(){return A.getCLIServerInfo()}async function i(){await A.shutdown()}function c(e,s){for(const[t,r]of Object.entries(s))null!=r&&e.setAttribute(t,r)}function a(e,s=2e5){try{const t=JSON.stringify(e);return t.length>s?t.slice(0,s)+"... (truncated)":t}catch{return null}}function p(e,s){return e.startsWith("resources/")?{type:e.split("/")[1],uri:s?.uri||s?.path||s?.resource}:e.startsWith("prompts/")?{type:"prompt",uri:s?.name||s?.promptId}:"tools/call"===e?{type:"tool",uri:s?.name}:s?.resourceType&&s?.resourceUri?{type:s.resourceType,uri:s.resourceUri}:{type:void 0,uri:void 0}}function m(e,s,t,r,n){const o=function(e,s){const t=e?.getClientInfo();if(t?.name||t?.version)return{name:t.name,version:t.version};const r=e?.getHeaders()||s;if(r){const e=r["user-agent"]||r["User-Agent"];if(e){const s=e.match(/^([^\/]+)\/([^\s]+)/);if(s)return{name:s[1],version:s[2]}}}return{name:void 0,version:void 0}}(r,n),i=`${o.name||"unknown"}-${o.version||"0"}`,c=T.getOrCreateSessionId(i),a=T.getOrCreateConversationId(c,e),m=function(e,s){return e?.userId||e?.user_id?e.userId||e.user_id:s?s["x-user-id"]||s["X-User-Id"]:void 0}(s,n),u=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(s),d=p(e,s);d.uri&&T.trackResourceAccess(d.uri);const l=function(e,s){return void 0!==e?.cost?e.cost:void 0!==s?.cost?s.cost:void 0}(s,t),h={};return c&&(h.sessionId=c),a&&(h.conversationId=a),m&&(h.userId=m),o.name&&(h.clientName=o.name),o.version&&(h.clientVersion=o.version),u&&(h.permissionLevel=u),void 0!==l&&(h.cost=l),h}function u(e,s){const t=p(e,s);if(!t.uri)return{};const r=T.getResourceAccessCount(t.uri);return{resourceType:t.type,resourceUri:t.uri,resourceAccessCount:r}}import{execSync as d,spawn as l}from"child_process";import{program as h}from"commander";import{NodeSDK as f}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as v}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as g}from"@opentelemetry/resources";import{trace as I,SpanStatusCode as S}from"@opentelemetry/api";import{readFileSync as y}from"fs";import{fileURLToPath as q}from"url";import{dirname as w,join as C}from"path";import{Transform as R}from"stream";import{ulid as E}from"ulid";const N="true"===process.env.AWARE_DEBUG||"1"===process.env.AWARE_DEBUG,x="true"===process.env.AWARE_RAW_DEBUG||"1"===process.env.AWARE_RAW_DEBUG,O={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=q(import.meta.url),s=w(e),t=C(s,"..","..","package.json");return JSON.parse(y(t,"utf-8")).version||"1.0.0"}catch{return"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"},M=["notifications/initialized"],A=new class{sdk=null;tracer=null;initialized=!1;cliServerInfo=null;initialize(e){if(this.initialized)return s("Telemetry already initialized, returning existing tracer"),this.tracer;const t=e.endpoint||O.ENDPOINT,r=e.serviceName||`${O.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`,n=e.serviceVersion||"unknown";s("Initializing telemetry..."),s("Endpoint: "+t),s("Service: "+r),s("Version: "+n),this.sdk=new f({resource:new g({"service.name":r,"service.version":n}),traceExporter:new v({url:t,headers:{"x-api-key":e.apiKey}})}),this.sdk.start(),this.tracer=I.getTracer("mcp-logger",O.SDK_VERSION),this.initialized=!0,s("Telemetry initialized successfully");const o=async()=>{s("Shutting down telemetry..."),await this.shutdown()};return process.once("SIGTERM",o),process.once("SIGINT",o),this.tracer}getTracer(){return this.tracer}isInitialized(){return this.initialized}setCLIServerInfo(e,s,t){this.cliServerInfo={command:e,args:s,env:t}}getCLIServerInfo(){return this.cliServerInfo}async shutdown(){if(this.sdk)try{await this.sdk.shutdown(),this.sdk=null,this.tracer=null,this.initialized=!1,this.cliServerInfo=null}catch(e){console.error("[mcp-logger] Error shutting down telemetry:",e)}}};class k extends R{buffer="";direction;logger;sessionContextStore;constructor(e,s,t){super(),this.direction=e,this.logger=s,this.sessionContextStore=t}_transform(e,t,r){try{if("request"===this.direction)return this.push(e),this.buffer+=e.toString(),this.tryParseMessages(),void r();this.buffer+=e.toString(),this.buffer.length>1e6&&(s("Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseAndForwardMessages(),r()}catch(e){r(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const s=JSON.parse(e);t(`[${this.direction}] Raw message:`,e),this.handleMessage(s)}catch{s("Failed to parse:",e.slice(0,100))}}}tryParseAndForwardMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const r=JSON.parse(e);"2.0"===r.jsonrpc?(this.push(e+"\n"),t(`[${this.direction}] Raw message:`,e),this.handleMessage(r)):s("Non-JSON-RPC JSON ignored:",e.slice(0,100))}catch{s("Non-JSON output filtered:",e.slice(0,100))}}}isValidInfo(e){return!(!e?.name||!e?.version)}handleMessage(e){if("request"===this.direction&&"initialize"===e.method&&e.params?.clientInfo&&!this.isValidInfo(this.sessionContextStore.getClientInfo())){const s=e.params;this.sessionContextStore.setClientInfo({name:s.clientInfo?.name,version:s.clientInfo?.version})}if("response"===this.direction&&void 0!==e.id&&!e.method){const s=e;if(s.result?.serverInfo&&!this.isValidInfo(this.sessionContextStore.getServerInfo())){const e=s.result.serverInfo;this.sessionContextStore.setServerInfo({name:e.name,version:e.version})}}"request"===this.direction&&e.method?this.logger.onRequest(e):"response"===this.direction&&(void 0===e.id||e.method?e.method&&!e.id&&this.logger.onNotification(e):this.logger.onResponse(e))}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);"response"===this.direction&&"2.0"===e.jsonrpc&&this.push(this.buffer.trim()+"\n"),this.handleMessage(e)}catch{"request"===this.direction&&this.push(this.buffer),s("Failed to parse final buffer")}N&&s("Stream closed. Pending requests: "+this.logger.getStats().pending),e()}catch(s){e(s)}}}const T=new class{sessionMap=new Map;sessionCounter=0;conversationMap=new Map;conversationCounter=0;resourceAccessCount=new Map;getOrCreateSessionId(e){if(!this.sessionMap.has(e)){const s=E();this.sessionMap.set(e,s),this.sessionCounter++}return this.sessionMap.get(e)}getOrCreateConversationId(e,s){const t=`${e}-${s}`;return this.conversationMap.has(t)||this.conversationMap.set(t,"conv-"+ ++this.conversationCounter),this.conversationMap.get(t)}trackResourceAccess(e){const s=(this.resourceAccessCount.get(e)||0)+1;return this.resourceAccessCount.set(e,s),s}getResourceAccessCount(e){return this.resourceAccessCount.get(e)||0}clear(){this.sessionMap.clear(),this.conversationMap.clear(),this.resourceAccessCount.clear(),this.sessionCounter=0,this.conversationCounter=0}};class ${pendingRequests=new Map;TIMEOUT=3e4;sessionContextStore;constructor(e){this.sessionContextStore=e}onRequest(e){r(e.method)&&(e.id?(this.pendingRequests.set(e.id,{request:e,timestamp:Date.now()}),"initialize"===e.method&&e.params?.clientInfo&&this.sessionContextStore.setClientInfo({name:e.params.clientInfo.name,version:e.params.clientInfo.version}),setTimeout(()=>this.cleanupStale(e.id),this.TIMEOUT)):this.createRequestOnlySpan(e,Date.now()))}onResponse(e){if(!e.id)return;const s=this.pendingRequests.get(e.id);if(!s)return void this.createResponseOnlySpan(e,Date.now());const t=Date.now(),r=t-s.timestamp;this.createPairedSpan(s.request,e,s.timestamp,t,r),this.pendingRequests.delete(e.id)}onNotification(e){r(e.method)&&this.createResponseOnlySpan({jsonrpc:"2.0",id:`notification-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,method:e.method},Date.now())}createPairedSpan(e,s,r,i,p){const d=m(e.method,e.params,s.result,this.sessionContextStore,void 0);let l=this.sessionContextStore.getServerInfo();"initialize"===e.method&&s.result?.serverInfo&&(l={name:s.result.serverInfo.name,version:s.result.serverInfo.version}),function(e){const s=n();if(!s)return;const r="mcp."+e.method;s.startActiveSpan(r,s=>{try{const r={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.request.id":e.request.id+"","mcp.request.method":e.method,"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":a(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.id":e.request.id+"","mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const s=e.request.params.name,t=e.request.params.arguments;if(s&&(r["mcp.tool.name"]=s),t){const e=a(t);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==e.response.result){const s=a(e.response.result);s&&(r["mcp.response.result"]=s,r["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const s=a(e.request.headers);s&&(r["mcp.headers"]=s,r["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=o();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}e.serverInfo&&(e.serverInfo.name&&(r["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(r["mcp.sdk.version"]=e.serverInfo.version));const n=e.contextMetadata||m(e.method,e.request.params,e.response.result,void 0,e.request.headers);n.sessionId&&(r["mcp.session.id"]=n.sessionId),n.conversationId&&(r["mcp.conversation.id"]=n.conversationId),n.sessionId&&n.conversationId&&(r["mcp.trace.id"]=`${n.sessionId}:${n.conversationId}`),n.userId&&(r["mcp.user.id"]=n.userId),n.clientName&&(r["mcp.client.name"]=n.clientName),n.clientVersion&&(r["mcp.client.version"]=n.clientVersion),n.permissionLevel&&(r["mcp.permission.level"]=n.permissionLevel),void 0!==n.cost&&(r["mcp.cost"]=n.cost);const i=u(e.method,e.request.params);if(i.resourceType&&(r["mcp.resource.type"]=i.resourceType),i.resourceUri&&(r["mcp.resource.uri"]=i.resourceUri),i.resourceAccessCount&&(r["mcp.resource.access_count"]=i.resourceAccessCount),e.customEvents&&e.customEvents.length>0&&(r["mcp.events"]=JSON.stringify(e.customEvents),r["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{s.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){let t;r["mcp.error"]=!0,t=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:a(e.response.error)||"Unknown error",r["mcp.error.message"]=t,e.response.error instanceof Error&&s.recordException(e.response.error),s.setStatus({code:S.ERROR,message:t})}else s.setStatus({code:S.OK});c(s,r),t("[OTLP] Span data:",JSON.stringify(r,null,2))}catch{s.setStatus({code:S.ERROR,message:"Error creating span"})}finally{s.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:r,params:e.params},response:{timestamp:i,result:s.result,error:s.error},duration:p,contextMetadata:d,serverInfo:l})}cleanupStale(e){const s=this.pendingRequests.get(e);s&&Date.now()-s.timestamp>this.TIMEOUT&&(this.createRequestOnlySpan(s.request,s.timestamp),this.pendingRequests.delete(e))}createRequestOnlySpan(e,s){const r=m(e.method,e.params,void 0,this.sessionContextStore,void 0);!function(e){const s=n();if(!s)return;const r="mcp."+e.method;s.startActiveSpan(r,s=>{try{const r={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.request.id":e.request.id+"","mcp.request.method":e.method,"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":a(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length};if("tools/call"===e.method&&e.request.params){const s=e.request.params.name,t=e.request.params.arguments;if(s&&(r["mcp.tool.name"]=s),t){const e=a(t);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if("cli"===e.source){const e=o();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}e.serverInfo&&(e.serverInfo.name&&(r["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(r["mcp.sdk.version"]=e.serverInfo.version));const n=e.contextMetadata||m(e.method,e.request.params,void 0,void 0,e.request.headers);n.sessionId&&(r["mcp.session.id"]=n.sessionId),n.conversationId&&(r["mcp.conversation.id"]=n.conversationId),n.sessionId&&n.conversationId&&(r["mcp.trace.id"]=`${n.sessionId}:${n.conversationId}`),n.userId&&(r["mcp.user.id"]=n.userId),n.clientName&&(r["mcp.client.name"]=n.clientName),n.clientVersion&&(r["mcp.client.version"]=n.clientVersion),n.permissionLevel&&(r["mcp.permission.level"]=n.permissionLevel),void 0!==n.cost&&(r["mcp.cost"]=n.cost);const i=u(e.method,e.request.params);i.resourceType&&(r["mcp.resource.type"]=i.resourceType),i.resourceUri&&(r["mcp.resource.uri"]=i.resourceUri),i.resourceAccessCount&&(r["mcp.resource.access_count"]=i.resourceAccessCount),s.setStatus({code:S.OK}),c(s,r),t("[OTLP] Span data:",JSON.stringify(r,null,2))}catch{s.setStatus({code:S.ERROR,message:"Error creating span"})}finally{s.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:s,params:e.params},contextMetadata:r})}createResponseOnlySpan(e,s){const r=m(e.method||"unknown",void 0,e.result,this.sessionContextStore,void 0),i=this.sessionContextStore.getServerInfo();!function(e){const s=n();if(!s)return;const r="mcp."+e.method;s.startActiveSpan(r,s=>{try{const r={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.response.id":e.response.id+"","mcp.response.method":e.method,"mcp.response.timestamp":e.response.timestamp};if(void 0!==e.response.result){const s=a(e.response.result);s&&(r["mcp.response.result"]=s,r["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.response.error){let t;r["mcp.error"]=!0,t=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:a(e.response.error)||"Unknown error",r["mcp.error.message"]=t,e.response.error instanceof Error&&s.recordException(e.response.error),s.setStatus({code:S.ERROR,message:t})}else s.setStatus({code:S.OK});if("cli"===e.source){const e=o();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}e.serverInfo&&(e.serverInfo.name&&(r["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(r["mcp.sdk.version"]=e.serverInfo.version));const n=e.contextMetadata||m(e.method,void 0,e.response.result,void 0,void 0);n.sessionId&&(r["mcp.session.id"]=n.sessionId),n.conversationId&&(r["mcp.conversation.id"]=n.conversationId),n.sessionId&&n.conversationId&&(r["mcp.trace.id"]=`${n.sessionId}:${n.conversationId}`),n.userId&&(r["mcp.user.id"]=n.userId),n.clientName&&(r["mcp.client.name"]=n.clientName),n.clientVersion&&(r["mcp.client.version"]=n.clientVersion),n.permissionLevel&&(r["mcp.permission.level"]=n.permissionLevel),void 0!==n.cost&&(r["mcp.cost"]=n.cost),c(s,r),t("[OTLP] Span data:",JSON.stringify(r,null,2))}catch{s.setStatus({code:S.ERROR,message:"Error creating span"})}finally{s.end()}})}({method:e.method||"same_request_method",source:"cli",transport:"stdio",response:{id:e.id,timestamp:s,result:e.result,error:e.error},contextMetadata:r,serverInfo:i})}getStats(){return{pending:this.pendingRequests.size}}clear(){this.pendingRequests.clear()}}class P{context={};getClientInfo(){return this.context.clientInfo}setClientInfo(e){this.context.clientInfo=e}getServerInfo(){return this.context.serverInfo}setServerInfo(e){this.context.serverInfo=e}getHeaders(){return this.context.headers}setHeaders(e){this.context.headers=e}snapshot(){return{clientInfo:this.context.clientInfo?{...this.context.clientInfo}:void 0,serverInfo:this.context.serverInfo?{...this.context.serverInfo}:void 0,headers:this.context.headers?{...this.context.headers}:void 0}}}const _=new Set(["npx","node","uvx","python","python3","pip","pipx","deno","bun"]),z=new Set(["uvx","python","python3","pip","pipx"]);h.name("mcp-logger").description("Add observability to any MCP server without code changes").version("1.0.0").requiredOption("-k, --api-key <key>","Aware API key").option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(t,r)=>{try{const n=function(e){const s={};for(const t of e){const e=process.env[t];void 0!==e&&(s[t]=e)}return s}(function(){try{return d("aware mcpenv sync",{encoding:"utf-8",timeout:1e4,stdio:["pipe","pipe","pipe"]}),d("aware mcpenv keys",{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim().split("\n").filter(e=>e.length>0)}catch{return[]}}()),[o,...c]=t,a=function(e,s){const t=function(e){for(const s of e){if(_.has(s))continue;const e=s.match(/^(@[^@]+\/[^@]+)@(.+)$/);if(e)return{packageName:e[1],version:e[2]};const t=s.match(/^([^@]+)@(.+)$/);if(t)return{packageName:t[1],version:t[2]};const r=s.match(/^(@[^@]+\/[^@]+)$/);if(r)return{packageName:r[1],version:"latest"};if(s.match(/^[a-z0-9-]+$/)&&!s.includes("/"))return{packageName:s,version:"latest"}}return null}(s.filter(e=>!e.startsWith("-")));let r,n;if(r=t?.packageName?t.packageName:e.split("/").pop()||e||O.SERVICE_NAME_PREFIX+"-unknown",t&&(n=t.version,"latest"===t.version)){const s=z.has(e)?"pip":"npm",r=function(e,s){try{if("pip"===s){let s=null;for(const t of["pip3","pip"])try{s=d(`${t} index versions ${e}`,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]});break}catch{continue}if(!s)return null;const t=s.match(/\(([^)]+)\)/);if(t)return t[1];const r=s.match(/Available versions:\s*([^\s,]+)/);return r?r[1]:null}{const s=d(`npm view ${e} version --json`,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim().replace(/^["']|["']$/g,""),t=s.match(/^(\d+\.\d+\.\d+(?:-[\w.]+)?)/);return t?t[1]:s}}catch{return null}}(t.packageName,s);r&&(n=r)}return{name:r,version:n}}(o,c);!function(e){A.initialize(e)}({...r,serviceName:a.name,serviceVersion:a.version}),e("Starting MCP server: "+t.join(" ")),e(`Service: ${a.name} = ${a.version||"unknown"}`),function(e,s,t){A.setCLIServerInfo(e,s,t)}(o,c,n);const p=l(o,c,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),m=new P,u=new $(m),h=new k("request",u,m);process.stdin.pipe(h).pipe(p.stdin);const f=new k("response",u,m);p.stdout.pipe(f).pipe(process.stdout),p.on("error",async s=>{e("Failed to start MCP server:",s),u.clear(),await i(),process.exit(1)}),p.on("exit",async(e,t)=>{s(`MCP server exited with code ${e}, signal ${t}`),u.clear(),await i(),process.exit(e||0)});let v=!1;const g=async e=>{v||(v=!0,s(`Received ${e}, shutting down...`),p.kill(e),await Promise.race([new Promise(e=>p.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await i(),process.exit(0))};process.on("SIGTERM",()=>g("SIGTERM")),process.on("SIGINT",()=>g("SIGINT"))}catch(s){e("Fatal error:",s),await i(),process.exit(1)}}),h.parse();
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 s(e,s=2e5){try{const t=JSON.stringify(e);return t.length>s?t.slice(0,s)+"... (truncated)":t}catch{return null}}function t(e,s){return e.startsWith("resources/")?{type:e.split("/")[1],uri:s?.uri||s?.path||s?.resource}:e.startsWith("prompts/")?{type:"prompt",uri:s?.name||s?.promptId}:"tools/call"===e?{type:"tool",uri:s?.name}:s?.resourceType&&s?.resourceUri?{type:s.resourceType,uri:s.resourceUri}:{type:void 0,uri:void 0}}function r(e,s,r,n,o){const i=function(e,s){const t=s;if(t){const e=t["user-agent"]||t["User-Agent"];if(e){const s=e.match(/^([^\/]+)\/([^\s]+)/);if(s)return{name:s[1],version:s[2]}}}return{name:void 0,version:void 0}}(0,o),c=`${i.name||"unknown"}-${i.version||"0"}`,a=E.getOrCreateSessionId(c),u=E.getOrCreateConversationId(a,e),m=function(e,s){return e?.userId||e?.user_id?e.userId||e.user_id:s?s["x-user-id"]||s["X-User-Id"]:void 0}(s,o),p=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(s),d=t(e,s);d.uri&&E.trackResourceAccess(d.uri);const l=function(e,s){return void 0!==e?.cost?e.cost:void 0!==s?.cost?s.cost:void 0}(s,r),v={};return a&&(v.sessionId=a),u&&(v.conversationId=u),m&&(v.userId=m),i.name&&(v.clientName=i.name),i.version&&(v.clientVersion=i.version),p&&(v.permissionLevel=p),void 0!==l&&(v.cost=l),v}function n(e){const n=I.getTracer();if(!n)return;const o="mcp."+e.method;n.startActiveSpan(o,n=>{try{const o={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.request.id":e.request.id+"","mcp.request.method":e.method,"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":s(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.id":e.request.id+"","mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const t=e.request.params.name,r=e.request.params.arguments;if(t&&(o["mcp.tool.name"]=t),r){const e=s(r);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==e.response.result){const t=s(e.response.result);t&&(o["mcp.response.result"]=t,o["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const t=s(e.request.headers);t&&(o["mcp.headers"]=t,o["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=I.getCLIServerInfo();e&&(o["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}e.serverInfo&&(e.serverInfo.name&&(o["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(o["mcp.sdk.version"]=e.serverInfo.version));const c=e.contextMetadata||r(e.method,e.request.params,e.response.result,0,e.request.headers);c.sessionId&&(o["mcp.session.id"]=c.sessionId),c.conversationId&&(o["mcp.conversation.id"]=c.conversationId),c.sessionId&&c.conversationId&&(o["mcp.trace.id"]=`${c.sessionId}:${c.conversationId}`),c.userId&&(o["mcp.user.id"]=c.userId),c.clientName&&(o["mcp.client.name"]=c.clientName),c.clientVersion&&(o["mcp.client.version"]=c.clientVersion),c.permissionLevel&&(o["mcp.permission.level"]=c.permissionLevel),void 0!==c.cost&&(o["mcp.cost"]=c.cost);const a=function(e,s){const r=t(e,s);if(!r.uri)return{};const n=E.getResourceAccessCount(r.uri);return{resourceType:r.type,resourceUri:r.uri,resourceAccessCount:n}}(e.method,e.request.params);if(a.resourceType&&(o["mcp.resource.type"]=a.resourceType),a.resourceUri&&(o["mcp.resource.uri"]=a.resourceUri),a.resourceAccessCount&&(o["mcp.resource.access_count"]=a.resourceAccessCount),e.customEvents&&e.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(e.customEvents),o["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{n.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){let t;o["mcp.error"]=!0,t=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:s(e.response.error)||"Unknown error",o["mcp.error.message"]=t,e.response.error instanceof Error&&n.recordException(e.response.error),n.setStatus({code:i.ERROR,message:t})}else n.setStatus({code:i.OK});!function(e,s){for(const[t,r]of Object.entries(s))null!=r&&e.setAttribute(t,r)}(n,o),function(...e){f&&console.error("[mcp-logger]",...e)}("[OTLP] Span data:",JSON.stringify(o,null,2))}catch{n.setStatus({code:i.ERROR,message:"Error creating span"})}finally{n.end()}})}import{trace as o,SpanStatusCode as i,context as c}from"@opentelemetry/api";import{NodeSDK as a}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as u}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as m}from"@opentelemetry/resources";import{readFileSync as p}from"fs";import{fileURLToPath as d}from"url";import{dirname as l,join as v}from"path";import{ulid as h}from"ulid";const g="true"===process.env.AWARE_DEBUG||"1"===process.env.AWARE_DEBUG,f="true"===process.env.AWARE_RAW_DEBUG||"1"===process.env.AWARE_RAW_DEBUG,y={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=d(import.meta.url),s=l(e),t=v(s,"..","..","package.json");return JSON.parse(p(t,"utf-8")).version||"1.0.0"}catch{return"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"},I=new class{sdk=null;tracer=null;initialized=!1;cliServerInfo=null;initialize(s){if(this.initialized)return e("Telemetry already initialized, returning existing tracer"),this.tracer;const t=s.endpoint||y.ENDPOINT,r=s.serviceName||`${y.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`,n=s.serviceVersion||"unknown";e("Initializing telemetry..."),e("Endpoint: "+t),e("Service: "+r),e("Version: "+n),this.sdk=new a({resource:new m({"service.name":r,"service.version":n}),traceExporter:new u({url:t,headers:{"x-api-key":s.apiKey}})}),this.sdk.start(),this.tracer=o.getTracer("mcp-logger",y.SDK_VERSION),this.initialized=!0,e("Telemetry initialized successfully");const i=async()=>{e("Shutting down telemetry..."),await this.shutdown()};return process.once("SIGTERM",i),process.once("SIGINT",i),this.tracer}getTracer(){return this.tracer}isInitialized(){return this.initialized}setCLIServerInfo(e,s,t){this.cliServerInfo={command:e,args:s,env:t}}getCLIServerInfo(){return this.cliServerInfo}async shutdown(){if(this.sdk)try{await this.sdk.shutdown(),this.sdk=null,this.tracer=null,this.initialized=!1,this.cliServerInfo=null}catch(e){console.error("[mcp-logger] Error shutting down telemetry:",e)}}},E=new class{sessionMap=new Map;sessionCounter=0;conversationMap=new Map;conversationCounter=0;resourceAccessCount=new Map;getOrCreateSessionId(e){if(!this.sessionMap.has(e)){const s=h();this.sessionMap.set(e,s),this.sessionCounter++}return this.sessionMap.get(e)}getOrCreateConversationId(e,s){const t=`${e}-${s}`;return this.conversationMap.has(t)||this.conversationMap.set(t,"conv-"+ ++this.conversationCounter),this.conversationMap.get(t)}trackResourceAccess(e){const s=(this.resourceAccessCount.get(e)||0)+1;return this.resourceAccessCount.set(e,s),s}getResourceAccessCount(e){return this.resourceAccessCount.get(e)||0}clear(){this.sessionMap.clear(),this.conversationMap.clear(),this.resourceAccessCount.clear(),this.sessionCounter=0,this.conversationCounter=0}},S=Symbol("mcp-logger.customEvents"),w=Object.assign(function(s,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!s)throw Error("[mcp-logger] server instance is required");!function(e){I.initialize(e)}(t),function(s){const t=s.setRequestHandler.bind(s);s.setRequestHandler=function(s,o){const i=s.shape?.method,a=i?._def?.value||"unknown";return e("Instrumenting handler: "+a),t(s,async(s,t)=>{const i=Date.now(),u=`${a}-${i}-${Math.random().toString(36).slice(2,8)}`,m=[],p=c.active().setValue(S,m);try{const d=await c.with(p,async()=>await o(s,t)),l=Date.now(),v=r(a,s.params,d,0,void 0);return n({method:a,source:"sdk",transport:"stdio",request:{id:u,timestamp:i,params:s.params},response:{timestamp:l,result:d},duration:l-i,customEvents:m.length>0?m:void 0,contextMetadata:v}),e(`Request completed (${l-i}ms):`,{method:a,customEvents:m.length}),d}catch(t){const o=Date.now(),c=r(a,s.params,void 0,0,void 0);throw n({method:a,source:"sdk",transport:"stdio",request:{id:u,timestamp:i,params:s.params},response:{timestamp:o,error:t},duration:o-i,customEvents:m.length>0?m:void 0,contextMetadata:c}),e("Request failed:",t),t}})},e("Server instrumentation complete")}(s)},{addLog(e){const s=c.active().getValue(S);if(!s)return;const t={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};s.push(t);const r=o.getSpan(c.active());r&&r.addEvent("custom."+t.level,{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp})},async shutdown(){await async function(){await I.shutdown()}()}});export{w as trace};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awarecorp/mcp-logger",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Unified MCP observability solution - SDK for code integration and CLI for zero-config wrapping",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1 +0,0 @@
1
- import{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{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),t()}catch(e){t(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const s of e){const e=s.trim();if(e)try{const s=JSON.parse(e);this.handleMessage(s)}catch(s){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){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!R.includes(e)}function t(){return x}function s(){return L}function r(e){P=e}function o(){return P||"unknown"}async function n(){if(M)try{await M.shutdown(),M=null,x=null,O=!1,L=null,P=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function i(e,t){for(const[s,r]of Object.entries(t))null!=r&&e.setAttribute(s,r)}function c(e,t=2e5){try{const s=JSON.stringify(e);return s.length>t?s.slice(0,t)+"... (truncated)":s}catch(e){return null}}function a(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 p(e,t,s,r,o){const n=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,o),i=function(e){if(!b.has(e)){const t=w();b.set(e,t)}return b.get(e)}(`${n.name||"unknown"}-${n.version||"0"}`),c=function(e,t){const s=`${e}-${t}`;return k.has(s)||k.set(s,"conv-"+ ++$),k.get(s)}(i,e),p=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),m=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(t),u=a(e,t);u.uri&&function(e){const t=(_.get(e)||0)+1;_.set(e,t)}(u.uri);const d=function(e,t){return void 0!==e?.cost?e.cost:void 0!==t?.cost?t.cost:void 0}(t,s),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,t){const s=a(e,t);if(!s.uri)return{};const r=_.get(s.uri)||0;return{resourceType:s.type,resourceUri:s.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 E}from"stream";import{ulid as w}from"ulid";const N={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=S(import.meta.url),t=q(e),s=C(t,"..","..","package.json");return JSON.parse(y(s,"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"},R=["notifications/initialized"];let M=null,x=null,O=!1,L=null,P=null;class T extends E{buffer="";direction;logger;sessionContextStore;debug;constructor(e,t,s,r=!1){super(),this.direction=e,this.logger=t,this.sessionContextStore=s,this.debug=r}_transform(e,t,s){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),s()}catch(e){s(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const t of e){const e=t.trim();if(e)try{const t=JSON.parse(e);this.handleMessage(t)}catch(t){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){if("request"===this.direction&&"initialize"===e.method&&e.params?.clientInfo){const t=e.params;this.sessionContextStore.setClientInfo({name:t.clientInfo?.name,version:t.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(t){e(t)}}}const b=new Map,k=new Map;let $=0;const _=new Map;class A{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)if(e.result?.serverInfo?.version)r(e.result.serverInfo.version);else{const t=e.result?.capabilities?.implementation?.version;t&&r(t)}if(!t)return void this.createResponseOnlySpan(e,Date.now());const s=Date.now(),o=s-t.timestamp;this.createPairedSpan(t.request,e,t.timestamp,s,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,r,n,a,u){const d=p(e.method,e.params,r.result,this.sessionContextStore,void 0);!function(e){const r=t();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+e.method;r.startActiveSpan(n,t=>{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 t=e.request.params.name,s=e.request.params.arguments;if(t&&(r["mcp.tool.name"]=t),s){const e=c(s);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if(void 0!==e.response.result){const t=c(e.response.result);t&&(r["mcp.response.result"]=t,r["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const t=c(e.request.headers);t&&(r["mcp.headers"]=t,r["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=s();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}));const t=o();r["mcp.server.version"]=t}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=>{t.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){let s;r["mcp.error"]=!0,s=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"]=s,e.response.error instanceof Error&&t.recordException(e.response.error),t.setStatus({code:I.ERROR,message:s})}else t.setStatus({code:I.OK});i(t,r)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),t.setStatus({code:I.ERROR,message:e+""})}finally{t.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 t=this.pendingRequests.get(e);t&&Date.now()-t.timestamp>this.TIMEOUT&&(this.createRequestOnlySpan(t.request,t.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=t();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+e.method;r.startActiveSpan(n,t=>{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 t=e.request.params.name,s=e.request.params.arguments;if(t&&(r["mcp.tool.name"]=t),s){const e=c(s);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if("cli"===e.source){const e=s();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}));const t=o();r["mcp.server.version"]=t}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),t.setStatus({code:I.OK}),i(t,r)}catch(e){console.error("[MCP Logger] Error creating request-only span:",e),t.setStatus({code:I.ERROR,message:e+""})}finally{t.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=t();if(!r)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+e.method;r.startActiveSpan(n,t=>{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 t=c(e.response.result);t&&(r["mcp.response.result"]=t,r["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.response.error){let s;r["mcp.error"]=!0,s=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"]=s,e.response.error instanceof Error&&t.recordException(e.response.error),t.setStatus({code:I.ERROR,message:s})}else t.setStatus({code:I.OK});if("cli"===e.source){const e=s();e&&(r["mcp.cli"]=JSON.stringify({command:e.command,args:e.args,env:e.env}));const t=o();r["mcp.server.version"]=t}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(t,r)}catch(e){console.error("[MCP Logger] Error creating response-only span:",e),t.setStatus({code:I.ERROR,message:e+""})}finally{t.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 z{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 J=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,t)=>{try{const s=!1,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 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 t of e){if(J.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}(c.filter(e=>!e.startsWith("-")));let p,m;if(p=a?.packageName?a.packageName:i.split("/").pop()||i||N.SERVICE_NAME_PREFIX+"-unknown",a&&(m=a.version,"latest"===a.version)){const e="uvx"===i?"pip":"npm",t=function(e,t){try{let s;s="pip"===t?"pip index versions "+e:`npm view ${e} version --json`;const r=d(s,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]});if("pip"===t){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,""),t=e.match(/^(\d+\.\d+\.\d+(?:-[\w.]+)?)/);return t?t[1]:e}}catch(e){return null}}(a.packageName,e);t&&(m=t)}!function(e){if(O)return x;const t=e.endpoint||N.ENDPOINT,s=e.serviceName||`${N.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`;M=new g({resource:new f({"service.name":s,"service.version":N.SDK_VERSION}),traceExporter:new h({url:t,headers:{"x-api-key":e.apiKey}})}),M.start(),x=v.getTracer("mcp-logger",N.SDK_VERSION),O=!0;const r=async()=>{await n()};process.once("SIGTERM",r),process.once("SIGINT",r)}({...t,serviceName:p,debug:s}),console.error("[MCP Logger CLI] Starting MCP server:",e.join(" ")),m&&r(m),function(e,t,s){L={command:e,args:t,env:s}}(i,c,o);const l=u(i,c,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),I=new z,y=new A(I,s),S=new T("request",y,I,s);process.stdin.pipe(S).pipe(l.stdin);const q=new T("response",y,I,s);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,t)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${t}`),y.clear(),await n(),process.exit(e||0)});let C=!1;const E=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",()=>E("SIGTERM")),process.on("SIGINT",()=>E("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 };