@awarecorp/mcp-logger 0.0.3-dev.2 → 0.0.3-dev.3

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/dist/cli/main.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- function e(e){return!h.includes(e)}function r(){return y}function s(){return S}async function t(){if(f)try{await f.shutdown(),f=null,y=null,q=!1,S=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function o(e,r){for(const[s,t]of Object.entries(r))null!=t&&e.setAttribute(s,t)}function n(e,r=1e4){try{const s=JSON.stringify(e);return s.length>r?s.slice(0,r)+"... (truncated)":s}catch(e){return null}}import{spawn as i}from"child_process";import{program as c}from"commander";import{NodeSDK as a}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as p}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as m}from"@opentelemetry/resources";import{trace as u,SpanStatusCode as d}from"@opentelemetry/api";import{Transform as l}from"stream";const g="1.0.0",h=["notifications/initialized"];let f=null,y=null,q=!1,S=null;class v extends l{buffer="";direction;logger;debug;constructor(e,r,s=!1){super(),this.direction=e,this.logger=r,this.debug=s}_transform(e,r,s){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),s()}catch(e){s(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const r=JSON.parse(e);this.handleMessage(r)}catch(r){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){console.error("[mcp-logger CLI] Handling message:",JSON.stringify(e).slice(0,100)),"request"===this.direction&&e.method?this.logger.onRequest(e):"response"===this.direction&&(void 0===e.id||e.method?e.method&&!e.id&&this.logger.onNotification(e):this.logger.onResponse(e))}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.logger.getStats();console.error("[mcp-logger CLI] Stream closed. Pending requests: "+e.pending)}e()}catch(r){e(r)}}}class C{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(r){if(this.debug&&console.error(`[mcp-logger CLI] 📥 REQUEST: method="${r.method}", id=${r.id}, params=${JSON.stringify(r.params)?.slice(0,100)}`),e(r.method)){if(!r.id)return this.debug&&console.error(`[mcp-logger CLI] 🔔 Notification detected: ${r.method} → Creating request-only span`),void this.createRequestOnlySpan(r,Date.now());this.pendingRequests.set(r.id,{request:r,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${r.method} (ID: ${r.id})`),setTimeout(()=>this.cleanupStale(r.id),this.TIMEOUT)}else this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+r.method)}onResponse(e){if(this.debug&&console.error(`[mcp-logger CLI] 📤 RESPONSE: id=${e.id}, hasResult=${!!e.result}, result=${JSON.stringify(e.result)?.slice(0,100)}, hasError=${!!e.error}`),!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const r=this.pendingRequests.get(e.id);if(!r)return this.debug&&console.error(`[mcp-logger CLI] ⏭️ No matching request for response ID: ${e.id} → Creating response-only span`),void this.createResponseOnlySpan(e,Date.now());const s=Date.now(),t=s-r.timestamp;this.debug&&console.error(`[mcp-logger CLI] ✅ Pairing success: ${r.request.method} (${t}ms)`),this.createPairedSpan(r.request,e,r.timestamp,s,t),this.pendingRequests.delete(e.id)}onNotification(r){this.debug&&console.error(`[mcp-logger CLI] 📢 NOTIFICATION (Server→Client): method="${r.method}", params=${JSON.stringify(r.params)?.slice(0,100)}`),e(r.method)?this.createResponseOnlySpan({jsonrpc:"2.0",id:`notification-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,method:r.method},Date.now()):this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+r.method)}createPairedSpan(e,t,i,c,a){!function(e){const t=r();if(!t)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const i="mcp."+e.method;t.startActiveSpan(i,r=>{try{const t={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.request.id":e.request.id+"","mcp.request.method":e.method,"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":n(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.id":e.request.id+"","mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const r=e.request.params.name,s=e.request.params.arguments;if(r&&(t["mcp.tool.name"]=r),s){const e=n(s);e&&(t["mcp.tool.arguments"]=e,t["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if(void 0!==e.response.result){const r=n(e.response.result);r&&(t["mcp.response.result"]=r,t["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const r=n(e.request.headers);r&&(t["mcp.headers"]=r,t["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=s();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(e.customEvents&&e.customEvents.length>0&&(t["mcp.events"]=JSON.stringify(e.customEvents),t["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{r.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.response.error){let s;t["mcp.error"]=!0,s=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:n(e.response.error)||"Unknown error",t["mcp.error.message"]=s,e.response.error instanceof Error&&r.recordException(e.response.error),r.setStatus({code:d.ERROR,message:s})}else r.setStatus({code:d.OK});o(r,t)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),r.setStatus({code:d.ERROR,message:e+""})}finally{r.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:i,params:e.params},response:{timestamp:c,result:t.result,error:t.error},duration:a})}cleanupStale(e){const r=this.pendingRequests.get(e);r&&Date.now()-r.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] ⏰ Request timeout: ${r.request.method} (ID: ${e}) → Creating request-only span`),this.createRequestOnlySpan(r.request,r.timestamp),this.pendingRequests.delete(e))}createRequestOnlySpan(e,t){!function(e){const t=r();if(!t)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const i="mcp."+e.method;t.startActiveSpan(i,r=>{try{const t={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.request.id":e.request.id+"","mcp.request.method":e.method,"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":n(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length};if("tools/call"===e.method&&e.request.params){const r=e.request.params.name,s=e.request.params.arguments;if(r&&(t["mcp.tool.name"]=r),s){const e=n(s);e&&(t["mcp.tool.arguments"]=e,t["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if("cli"===e.source){const e=s();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}r.setStatus({code:d.OK}),o(r,t)}catch(e){console.error("[MCP Logger] Error creating request-only span:",e),r.setStatus({code:d.ERROR,message:e+""})}finally{r.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:t,params:e.params}})}createResponseOnlySpan(e,t){!function(e){const t=r();if(!t)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const i="mcp."+e.method;t.startActiveSpan(i,r=>{try{const t={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.response.id":e.response.id+"","mcp.response.method":e.method,"mcp.response.timestamp":e.response.timestamp};if(void 0!==e.response.result){const r=n(e.response.result);r&&(t["mcp.response.result"]=r,t["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.response.error){let s;t["mcp.error"]=!0,s=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:n(e.response.error)||"Unknown error",t["mcp.error.message"]=s,e.response.error instanceof Error&&r.recordException(e.response.error),r.setStatus({code:d.ERROR,message:s})}else r.setStatus({code:d.OK});if("cli"===e.source){const e=s();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}o(r,t)}catch(e){console.error("[MCP Logger] Error creating response-only span:",e),r.setStatus({code:d.ERROR,message:e+""})}finally{r.end()}})}({method:e.method||"same_request_method",source:"cli",transport:"stdio",response:{id:e.id,timestamp:t,result:e.result,error:e.error}})}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}c.name("mcp-logger").description("Add observability to any MCP server without code changes").version("1.0.0").requiredOption("-k, --api-key <key>","Aware API key").option("-s, --service-name <name>","Service name for identification").option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(e,r)=>{try{const s=!1;!function(e){if(q)return y;const r=e.endpoint||"https://aware.mcypher.com/v1/traces",s=e.serviceName||"mcp-server-"+Math.random().toString(36).slice(2,8);f=new a({resource:new m({"service.name":s,"service.version":g}),traceExporter:new p({url:r,headers:{"x-api-key":e.apiKey}})}),f.start(),y=u.getTracer("mcp-logger",g),q=!0;const o=async()=>{await t()};process.once("SIGTERM",o),process.once("SIGINT",o)}({...r,debug:s}),console.error("[MCP Logger CLI] Starting MCP server:",e.join(" "));const[o,...n]=e;!function(e,r){S={command:e,args:r,env:{}}}(o,n);const c=i(o,n,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),d=new C(s),l=new v("request",d,s);process.stdin.pipe(l).pipe(c.stdin);const h=new v("response",d,s);c.stdout.pipe(h).pipe(process.stdout),c.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),d.clear(),await t(),process.exit(1)}),c.on("exit",async(e,r)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${r}`),d.clear(),await t(),process.exit(e||0)});let R=!1;const O=async e=>{R||(R=!0,console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),c.kill(e),await Promise.race([new Promise(e=>c.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await t(),process.exit(0))};process.on("SIGTERM",()=>O("SIGTERM")),process.on("SIGINT",()=>O("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await t(),process.exit(1)}}),c.parse();
2
+ function e(e){return!y.includes(e)}function s(){return C}function r(){return q}async function t(){if(I)try{await I.shutdown(),I=null,C=null,S=!1,q=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function o(e,s){for(const[r,t]of Object.entries(s))null!=t&&e.setAttribute(r,t)}function n(e,s=1e4){try{const r=JSON.stringify(e);return r.length>s?r.slice(0,s)+"... (truncated)":r}catch(e){return null}}function i(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 c(e,s,r,t){const o=function(e){if(e){const s=e["user-agent"]||e["User-Agent"];if(s){const e=s.match(/^([^\/]+)\/([^\s]+)/);if(e)return{name:e[1],version:e[2]}}}const s=process.env.MCP_CLIENT_NAME||process.env.CLIENT_NAME,r=process.env.MCP_CLIENT_VERSION||process.env.CLIENT_VERSION;return process.env.CLAUDE_DESKTOP?{name:"Claude Desktop",version:process.env.CLAUDE_DESKTOP_VERSION}:{name:s,version:r}}(t),n=(u=`${o.name||"unknown"}-${o.version||"0"}`,L.has(u)||L.set(u,"session-"+ ++O),L.get(u)),c=function(e,s){const r=`${e}-${s}`;return R.has(r)||R.set(r,"conv-"+ ++N),R.get(r)}(n,e),a=function(e,s){return e?.userId||e?.user_id?e.userId||e.user_id:s?s["x-user-id"]||s["X-User-Id"]:process.env.MCP_USER_ID||process.env.USER_ID}(s,t),p=function(e,s){if(s?.permission||s?.permissionLevel)return s.permission||s.permissionLevel;if(e.startsWith("admin/"))return"admin";if(e.includes("/write")||e.includes("/create")||e.includes("/update")||e.includes("/delete"))return"write";if(e.includes("/read")||e.includes("/list")||e.includes("/get"))return"read";if("tools/call"===e&&s?.name){const e=s.name;return e.includes("admin")||e.includes("system")?"elevated":"standard"}}(e,s),m=i(e,s);var u;m.uri&&function(e){const s=(M.get(e)||0)+1;M.set(e,s)}(m.uri);const d=function(e,s,r){let t=0;const o={"tools/call":.001,"prompts/get":5e-4,"resources/read":2e-4,"resources/write":3e-4,"completion/create":.01};return o[e]&&(t+=o[e]),s&&(t+=1e-6*JSON.stringify(s).length),r&&(t+=1e-6*JSON.stringify(r).length),(e.includes("completion")||e.includes("generate"))&&(t+=1e-5*((s?.max_tokens||s?.tokens||0)+(r?.usage?.total_tokens||0))),t>0?Math.round(1e6*t)/1e6:void 0}(e,s,r),l={};return n&&(l.sessionId=n),c&&(l.conversationId=c),a&&(l.userId=a),o.name&&(l.clientName=o.name),o.version&&(l.clientVersion=o.version),p&&(l.permissionLevel=p),void 0!==d&&(l.estimatedCost=d),l}function a(e,s){const r=i(e,s);if(!r.uri)return{};const t=M.get(r.uri)||0;return{resourceType:r.type,resourceUri:r.uri,resourceAccessCount:t}}import{spawn as p}from"child_process";import{program as m}from"commander";import{NodeSDK as u}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as d}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as l}from"@opentelemetry/resources";import{trace as g,SpanStatusCode as h}from"@opentelemetry/api";import{Transform as f}from"stream";const v="1.0.0",y=["notifications/initialized"];let I=null,C=null,S=!1,q=null;class E extends f{buffer="";direction;logger;debug;constructor(e,s,r=!1){super(),this.direction=e,this.logger=s,this.debug=r}_transform(e,s,r){try{this.push(e),this.buffer+=e.toString(),this.buffer.length>1e6&&(this.debug&&console.error("[mcp-logger CLI] Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseMessages(),r()}catch(e){r(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const s of e){const e=s.trim();if(e)try{const s=JSON.parse(e);this.handleMessage(s)}catch(s){this.debug&&console.error("[mcp-logger CLI] Failed to parse:",e.slice(0,100))}}}handleMessage(e){console.error("[mcp-logger CLI] Handling message:",JSON.stringify(e).slice(0,100)),"request"===this.direction&&e.method?this.logger.onRequest(e):"response"===this.direction&&(void 0===e.id||e.method?e.method&&!e.id&&this.logger.onNotification(e):this.logger.onResponse(e))}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);this.handleMessage(e)}catch(e){this.debug&&console.error("[mcp-logger CLI] Failed to parse final buffer")}if(this.debug){const e=this.logger.getStats();console.error("[mcp-logger CLI] Stream closed. Pending requests: "+e.pending)}e()}catch(s){e(s)}}}const L=new Map;let O=0;const R=new Map;let N=0;const M=new Map;class w{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(s){if(this.debug&&console.error(`[mcp-logger CLI] 📥 REQUEST: method="${s.method}", id=${s.id}, params=${JSON.stringify(s.params)?.slice(0,100)}`),e(s.method)){if(!s.id)return this.debug&&console.error(`[mcp-logger CLI] 🔔 Notification detected: ${s.method} → Creating request-only span`),void this.createRequestOnlySpan(s,Date.now());this.pendingRequests.set(s.id,{request:s,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${s.method} (ID: ${s.id})`),setTimeout(()=>this.cleanupStale(s.id),this.TIMEOUT)}else this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+s.method)}onResponse(e){if(this.debug&&console.error(`[mcp-logger CLI] 📤 RESPONSE: id=${e.id}, hasResult=${!!e.result}, result=${JSON.stringify(e.result)?.slice(0,100)}, hasError=${!!e.error}`),!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const s=this.pendingRequests.get(e.id);if(!s)return this.debug&&console.error(`[mcp-logger CLI] ⏭️ No matching request for response ID: ${e.id} → Creating response-only span`),void this.createResponseOnlySpan(e,Date.now());const r=Date.now(),t=r-s.timestamp;this.debug&&console.error(`[mcp-logger CLI] ✅ Pairing success: ${s.request.method} (${t}ms)`),this.createPairedSpan(s.request,e,s.timestamp,r,t),this.pendingRequests.delete(e.id)}onNotification(s){this.debug&&console.error(`[mcp-logger CLI] 📢 NOTIFICATION (Server→Client): method="${s.method}", params=${JSON.stringify(s.params)?.slice(0,100)}`),e(s.method)?this.createResponseOnlySpan({jsonrpc:"2.0",id:`notification-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,method:s.method},Date.now()):this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+s.method)}createPairedSpan(e,t,i,p,m){const u=c(e.method,e.params,t.result,void 0);!function(e){const t=s();if(!t)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const i="mcp."+e.method;t.startActiveSpan(i,s=>{try{const t={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.request.id":e.request.id+"","mcp.request.method":e.method,"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":n(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length,"mcp.response.id":e.request.id+"","mcp.response.timestamp":e.response.timestamp,"mcp.duration_ms":e.duration};if("tools/call"===e.method&&e.request.params){const s=e.request.params.name,r=e.request.params.arguments;if(s&&(t["mcp.tool.name"]=s),r){const e=n(r);e&&(t["mcp.tool.arguments"]=e,t["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if(void 0!==e.response.result){const s=n(e.response.result);s&&(t["mcp.response.result"]=s,t["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.request.headers){const s=n(e.request.headers);s&&(t["mcp.headers"]=s,t["mcp.headers.size"]=JSON.stringify(e.request.headers).length)}if("cli"===e.source){const e=r();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}const i=e.contextMetadata||c(e.method,e.request.params,e.response.result,e.request.headers);i.sessionId&&(t["mcp.session.id"]=i.sessionId),i.conversationId&&(t["mcp.conversation.id"]=i.conversationId),i.userId&&(t["mcp.user.id"]=i.userId),i.clientName&&(t["mcp.client.name"]=i.clientName),i.clientVersion&&(t["mcp.client.version"]=i.clientVersion),i.permissionLevel&&(t["mcp.permission.level"]=i.permissionLevel),void 0!==i.estimatedCost&&(t["mcp.cost.estimated"]=i.estimatedCost);const p=a(e.method,e.request.params);if(p.resourceType&&(t["mcp.resource.type"]=p.resourceType),p.resourceUri&&(t["mcp.resource.uri"]=p.resourceUri),p.resourceAccessCount&&(t["mcp.resource.access_count"]=p.resourceAccessCount),e.customEvents&&e.customEvents.length>0&&(t["mcp.events"]=JSON.stringify(e.customEvents),t["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 r;t["mcp.error"]=!0,r=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:n(e.response.error)||"Unknown error",t["mcp.error.message"]=r,e.response.error instanceof Error&&s.recordException(e.response.error),s.setStatus({code:h.ERROR,message:r})}else s.setStatus({code:h.OK});o(s,t)}catch(e){console.error("[MCP Logger] Error creating paired span:",e),s.setStatus({code:h.ERROR,message:e+""})}finally{s.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:i,params:e.params},response:{timestamp:p,result:t.result,error:t.error},duration:m,contextMetadata:u})}cleanupStale(e){const s=this.pendingRequests.get(e);s&&Date.now()-s.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] ⏰ Request timeout: ${s.request.method} (ID: ${e}) → Creating request-only span`),this.createRequestOnlySpan(s.request,s.timestamp),this.pendingRequests.delete(e))}createRequestOnlySpan(e,t){const i=c(e.method,e.params,void 0,void 0);!function(e){const t=s();if(!t)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const i="mcp."+e.method;t.startActiveSpan(i,s=>{try{const t={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.request.id":e.request.id+"","mcp.request.method":e.method,"mcp.request.timestamp":e.request.timestamp,"mcp.request.params":n(e.request.params)||"{}","mcp.request.params.size":JSON.stringify(e.request.params||{}).length};if("tools/call"===e.method&&e.request.params){const s=e.request.params.name,r=e.request.params.arguments;if(s&&(t["mcp.tool.name"]=s),r){const e=n(r);e&&(t["mcp.tool.arguments"]=e,t["mcp.tool.arguments.size"]=JSON.stringify(r).length)}}if("cli"===e.source){const e=r();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}const i=e.contextMetadata||c(e.method,e.request.params,void 0,e.request.headers);i.sessionId&&(t["mcp.session.id"]=i.sessionId),i.conversationId&&(t["mcp.conversation.id"]=i.conversationId),i.userId&&(t["mcp.user.id"]=i.userId),i.clientName&&(t["mcp.client.name"]=i.clientName),i.clientVersion&&(t["mcp.client.version"]=i.clientVersion),i.permissionLevel&&(t["mcp.permission.level"]=i.permissionLevel),void 0!==i.estimatedCost&&(t["mcp.cost.estimated"]=i.estimatedCost);const p=a(e.method,e.request.params);p.resourceType&&(t["mcp.resource.type"]=p.resourceType),p.resourceUri&&(t["mcp.resource.uri"]=p.resourceUri),p.resourceAccessCount&&(t["mcp.resource.access_count"]=p.resourceAccessCount),s.setStatus({code:h.OK}),o(s,t)}catch(e){console.error("[MCP Logger] Error creating request-only span:",e),s.setStatus({code:h.ERROR,message:e+""})}finally{s.end()}})}({method:e.method,source:"cli",transport:"stdio",request:{id:e.id,timestamp:t,params:e.params},contextMetadata:i})}createResponseOnlySpan(e,t){const i=c(e.method||"unknown",void 0,e.result,void 0);!function(e){const t=s();if(!t)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const i="mcp."+e.method;t.startActiveSpan(i,s=>{try{const t={"mcp.source":e.source,"mcp.transport":e.transport,"mcp.response.id":e.response.id+"","mcp.response.method":e.method,"mcp.response.timestamp":e.response.timestamp};if(void 0!==e.response.result){const s=n(e.response.result);s&&(t["mcp.response.result"]=s,t["mcp.response.result.size"]=JSON.stringify(e.response.result).length)}if(e.response.error){let r;t["mcp.error"]=!0,r=e.response.error instanceof Error?e.response.error.message:"string"==typeof e.response.error?e.response.error:n(e.response.error)||"Unknown error",t["mcp.error.message"]=r,e.response.error instanceof Error&&s.recordException(e.response.error),s.setStatus({code:h.ERROR,message:r})}else s.setStatus({code:h.OK});if("cli"===e.source){const e=r();e&&(t["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}const i=e.contextMetadata||c(e.method,void 0,e.response.result,void 0);i.sessionId&&(t["mcp.session.id"]=i.sessionId),i.conversationId&&(t["mcp.conversation.id"]=i.conversationId),i.userId&&(t["mcp.user.id"]=i.userId),i.clientName&&(t["mcp.client.name"]=i.clientName),i.clientVersion&&(t["mcp.client.version"]=i.clientVersion),i.permissionLevel&&(t["mcp.permission.level"]=i.permissionLevel),void 0!==i.estimatedCost&&(t["mcp.cost.estimated"]=i.estimatedCost),o(s,t)}catch(e){console.error("[MCP Logger] Error creating response-only span:",e),s.setStatus({code:h.ERROR,message:e+""})}finally{s.end()}})}({method:e.method||"same_request_method",source:"cli",transport:"stdio",response:{id:e.id,timestamp:t,result:e.result,error:e.error},contextMetadata:i})}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}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("-s, --service-name <name>","Service name for identification").option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(e,s)=>{try{const r=!1;!function(e){if(S)return C;const s=e.endpoint||"https://aware.mcypher.com/v1/traces",r=e.serviceName||"mcp-server-"+Math.random().toString(36).slice(2,8);I=new u({resource:new l({"service.name":r,"service.version":v}),traceExporter:new d({url:s,headers:{"x-api-key":e.apiKey}})}),I.start(),C=g.getTracer("mcp-logger",v),S=!0;const o=async()=>{await t()};process.once("SIGTERM",o),process.once("SIGINT",o)}({...s,debug:r}),console.error("[MCP Logger CLI] Starting MCP server:",e.join(" "));const[o,...n]=e;!function(e,s){q={command:e,args:s,env:{}}}(o,n);const i=p(o,n,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),c=new w(r),a=new E("request",c,r);process.stdin.pipe(a).pipe(i.stdin);const m=new E("response",c,r);i.stdout.pipe(m).pipe(process.stdout),i.on("error",async e=>{console.error("[MCP Logger CLI] Failed to start MCP server:",e),c.clear(),await t(),process.exit(1)}),i.on("exit",async(e,s)=>{console.error(`[MCP Logger CLI] MCP server exited with code ${e}, signal ${s}`),c.clear(),await t(),process.exit(e||0)});let h=!1;const f=async e=>{h||(h=!0,console.error(`[MCP Logger CLI] Received ${e}, shutting down...`),i.kill(e),await Promise.race([new Promise(e=>i.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await t(),process.exit(0))};process.on("SIGTERM",()=>f("SIGTERM")),process.on("SIGINT",()=>f("SIGINT"))}catch(e){console.error("[MCP Logger CLI] Fatal error:",e),await t(),process.exit(1)}}),m.parse();
@@ -1 +1 @@
1
- function e(e){return!t.includes(e)}import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const t=["notifications/initialized"];class r{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(t){if(this.debug&&console.error(`[mcp-logger CLI] 📥 REQUEST: method="${t.method}", id=${t.id}, params=${JSON.stringify(t.params)?.slice(0,100)}`),e(t.method)){if(!t.id)return this.debug&&console.error(`[mcp-logger CLI] 🔔 Notification detected: ${t.method} → Creating request-only span`),void this.createRequestOnlySpan(t,Date.now());this.pendingRequests.set(t.id,{request:t,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${t.method} (ID: ${t.id})`),setTimeout(()=>this.cleanupStale(t.id),this.TIMEOUT)}else this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+t.method)}onResponse(e){if(this.debug&&console.error(`[mcp-logger CLI] 📤 RESPONSE: id=${e.id}, hasResult=${!!e.result}, result=${JSON.stringify(e.result)?.slice(0,100)}, hasError=${!!e.error}`),!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const t=this.pendingRequests.get(e.id);if(!t)return this.debug&&console.error(`[mcp-logger CLI] ⏭️ No matching request for response ID: ${e.id} → Creating response-only span`),void this.createResponseOnlySpan(e,Date.now());const r=Date.now(),o=r-t.timestamp;this.debug&&console.error(`[mcp-logger CLI] ✅ Pairing success: ${t.request.method} (${o}ms)`),this.createPairedSpan(t.request,e,t.timestamp,r,o),this.pendingRequests.delete(e.id)}onNotification(t){this.debug&&console.error(`[mcp-logger CLI] 📢 NOTIFICATION (Server→Client): method="${t.method}", params=${JSON.stringify(t.params)?.slice(0,100)}`),e(t.method)?this.createResponseOnlySpan({jsonrpc:"2.0",id:`notification-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,method:t.method},Date.now()):this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+t.method)}createPairedSpan(e,t,r,o,s){e.method,e.id,e.params,t.result,t.error,console.error("[MCP Logger] Tracer not initialized, cannot create span")}cleanupStale(e){const t=this.pendingRequests.get(e);t&&Date.now()-t.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] ⏰ Request timeout: ${t.request.method} (ID: ${e}) → Creating request-only span`),this.createRequestOnlySpan(t.request,t.timestamp),this.pendingRequests.delete(e))}createRequestOnlySpan(e,t){e.method,e.id,e.params,console.error("[MCP Logger] Tracer not initialized, cannot create span")}createResponseOnlySpan(e,t){e.method,e.id,e.result,e.error,console.error("[MCP Logger] Tracer not initialized, cannot create span")}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}export{r as MCPMessageLogger};
1
+ function e(e){return!s.includes(e)}function t(e,t,s,c){const d=function(){const e=process.env.MCP_CLIENT_NAME||process.env.CLIENT_NAME,t=process.env.MCP_CLIENT_VERSION||process.env.CLIENT_VERSION;return process.env.CLAUDE_DESKTOP?{name:"Claude Desktop",version:process.env.CLAUDE_DESKTOP_VERSION}:{name:e,version:t}}(),u=(h=`${d.name||"unknown"}-${d.version||"0"}`,r.has(h)||r.set(h,"session-"+ ++n),r.get(h)),p=function(e,t){const s=`${e}-${t}`;return o.has(s)||o.set(s,"conv-"+ ++i),o.get(s)}(u,e),l=function(e){return e?.userId||e?.user_id?e.userId||e.user_id:process.env.MCP_USER_ID||process.env.USER_ID}(t),m=function(e,t){if(t?.permission||t?.permissionLevel)return t.permission||t.permissionLevel;if(e.startsWith("admin/"))return"admin";if(e.includes("/write")||e.includes("/create")||e.includes("/update")||e.includes("/delete"))return"write";if(e.includes("/read")||e.includes("/list")||e.includes("/get"))return"read";if("tools/call"===e&&t?.name){const e=t.name;return e.includes("admin")||e.includes("system")?"elevated":"standard"}}(e,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);var h;g.uri&&function(e){const t=(a.get(e)||0)+1;a.set(e,t)}(g.uri);const I=function(e,t,s){let r=0;const n={"tools/call":.001,"prompts/get":5e-4,"resources/read":2e-4,"resources/write":3e-4,"completion/create":.01};return n[e]&&(r+=n[e]),t&&(r+=1e-6*JSON.stringify(t).length),s&&(r+=1e-6*JSON.stringify(s).length),(e.includes("completion")||e.includes("generate"))&&(r+=1e-5*((t?.max_tokens||t?.tokens||0)+(s?.usage?.total_tokens||0))),r>0?Math.round(1e6*r)/1e6:void 0}(e,t,s),C={};return u&&(C.sessionId=u),p&&(C.conversationId=p),l&&(C.userId=l),d.name&&(C.clientName=d.name),d.version&&(C.clientVersion=d.version),m&&(C.permissionLevel=m),void 0!==I&&(C.estimatedCost=I),C}import"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const s=["notifications/initialized"],r=new Map;let n=0;const o=new Map;let i=0;const a=new Map;class c{pendingRequests=new Map;debug;TIMEOUT=3e4;constructor(e=!1){this.debug=e}onRequest(t){if(this.debug&&console.error(`[mcp-logger CLI] 📥 REQUEST: method="${t.method}", id=${t.id}, params=${JSON.stringify(t.params)?.slice(0,100)}`),e(t.method)){if(!t.id)return this.debug&&console.error(`[mcp-logger CLI] 🔔 Notification detected: ${t.method} → Creating request-only span`),void this.createRequestOnlySpan(t,Date.now());this.pendingRequests.set(t.id,{request:t,timestamp:Date.now()}),this.debug&&console.error(`[mcp-logger CLI] Request stored: ${t.method} (ID: ${t.id})`),setTimeout(()=>this.cleanupStale(t.id),this.TIMEOUT)}else this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+t.method)}onResponse(e){if(this.debug&&console.error(`[mcp-logger CLI] 📤 RESPONSE: id=${e.id}, hasResult=${!!e.result}, result=${JSON.stringify(e.result)?.slice(0,100)}, hasError=${!!e.error}`),!e.id)return void(this.debug&&console.error("[mcp-logger CLI] Response without ID, skipping"));const t=this.pendingRequests.get(e.id);if(!t)return this.debug&&console.error(`[mcp-logger CLI] ⏭️ No matching request for response ID: ${e.id} → Creating response-only span`),void this.createResponseOnlySpan(e,Date.now());const s=Date.now(),r=s-t.timestamp;this.debug&&console.error(`[mcp-logger CLI] ✅ Pairing success: ${t.request.method} (${r}ms)`),this.createPairedSpan(t.request,e,t.timestamp,s,r),this.pendingRequests.delete(e.id)}onNotification(t){this.debug&&console.error(`[mcp-logger CLI] 📢 NOTIFICATION (Server→Client): method="${t.method}", params=${JSON.stringify(t.params)?.slice(0,100)}`),e(t.method)?this.createResponseOnlySpan({jsonrpc:"2.0",id:`notification-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,method:t.method},Date.now()):this.debug&&console.error("[mcp-logger CLI] 🚫 Skipping excluded method: "+t.method)}createPairedSpan(e,s,r,n,o){t(e.method,e.params,s.result),e.method,e.id,e.params,s.result,s.error,console.error("[MCP Logger] Tracer not initialized, cannot create span")}cleanupStale(e){const t=this.pendingRequests.get(e);t&&Date.now()-t.timestamp>this.TIMEOUT&&(this.debug&&console.error(`[mcp-logger CLI] ⏰ Request timeout: ${t.request.method} (ID: ${e}) → Creating request-only span`),this.createRequestOnlySpan(t.request,t.timestamp),this.pendingRequests.delete(e))}createRequestOnlySpan(e,s){t(e.method,e.params,void 0),e.method,e.id,e.params,console.error("[MCP Logger] Tracer not initialized, cannot create span")}createResponseOnlySpan(e,s){t(e.method||"unknown",void 0,e.result),e.method,e.id,e.result,e.error,console.error("[MCP Logger] Tracer not initialized, cannot create span")}getStats(){return{pending:this.pendingRequests.size}}clear(){this.debug&&console.error(`[mcp-logger CLI] Clearing ${this.pendingRequests.size} pending requests`),this.pendingRequests.clear()}}export{c as MCPMessageLogger};
@@ -100,6 +100,16 @@ interface MCPSpanData {
100
100
  'mcp.error'?: boolean;
101
101
  'mcp.error.code'?: number;
102
102
  'mcp.error.message'?: string;
103
+ 'mcp.session.id'?: string;
104
+ 'mcp.conversation.id'?: string;
105
+ 'mcp.user.id'?: string;
106
+ 'mcp.client.name'?: string;
107
+ 'mcp.client.version'?: string;
108
+ 'mcp.permission.level'?: string;
109
+ 'mcp.resource.type'?: string;
110
+ 'mcp.resource.uri'?: string;
111
+ 'mcp.resource.access_count'?: number;
112
+ 'mcp.cost.estimated'?: number;
103
113
  }
104
114
  /**
105
115
  * 커스텀 로그 엔트리
@@ -122,6 +132,18 @@ interface CustomLogEntry {
122
132
  */
123
133
  timestamp?: number;
124
134
  }
135
+ /**
136
+ * MCP 컨텍스트 메타데이터
137
+ */
138
+ interface MCPContextMetadata {
139
+ sessionId?: string;
140
+ conversationId?: string;
141
+ userId?: string;
142
+ clientName?: string;
143
+ clientVersion?: string;
144
+ permissionLevel?: string;
145
+ estimatedCost?: number;
146
+ }
125
147
  /**
126
148
  * Paired Span 입력 데이터
127
149
  */
@@ -142,6 +164,7 @@ interface PairedSpanInput {
142
164
  };
143
165
  duration: number;
144
166
  customEvents?: CustomLogEntry[];
167
+ contextMetadata?: MCPContextMetadata;
145
168
  }
146
169
  /**
147
170
  * Response-only Span 입력 데이터 (notification 등 response가 없는 경우)
@@ -156,6 +179,7 @@ interface ResponseOnlySpanInput {
156
179
  result?: any;
157
180
  error?: any;
158
181
  };
182
+ contextMetadata?: MCPContextMetadata;
159
183
  }
160
184
  /**
161
185
  * Request-only Span 입력 데이터 (timeout 등으로 response가 없는 경우)
@@ -170,6 +194,7 @@ interface RequestOnlySpanInput {
170
194
  params: any;
171
195
  headers?: Record<string, string>;
172
196
  };
197
+ contextMetadata?: MCPContextMetadata;
173
198
  }
174
199
 
175
- export type { CustomLogEntry, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, MCPSpanData, MessageDirection, PairedSpanInput, RequestOnlySpanInput, ResponseOnlySpanInput, SpanAttributes, TelemetryOptions };
200
+ export type { CustomLogEntry, JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, MCPContextMetadata, MCPSpanData, MessageDirection, PairedSpanInput, RequestOnlySpanInput, ResponseOnlySpanInput, SpanAttributes, TelemetryOptions };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- function e(){return g}function t(){return f}async function r(){if(u)try{await u.shutdown(),u=null,g=null,d=!1,f=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function o(r){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+r.method;o.startActiveSpan(n,e=>{try{const o={"mcp.source":r.source,"mcp.transport":r.transport,"mcp.request.id":r.request.id+"","mcp.request.method":r.method,"mcp.request.timestamp":r.request.timestamp,"mcp.request.params":s(r.request.params)||"{}","mcp.request.params.size":JSON.stringify(r.request.params||{}).length,"mcp.response.id":r.request.id+"","mcp.response.timestamp":r.response.timestamp,"mcp.duration_ms":r.duration};if("tools/call"===r.method&&r.request.params){const e=r.request.params.name,t=r.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),t){const e=s(t);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==r.response.result){const e=s(r.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(r.response.result).length)}if(r.request.headers){const e=s(r.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(r.request.headers).length)}if("cli"===r.source){const e=t();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(r.customEvents&&r.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(r.customEvents),o["mcp.events.count"]=r.customEvents.length,r.customEvents.forEach(t=>{e.addEvent("custom."+(t.level||"info"),{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp||Date.now()})})),r.response.error){let t;o["mcp.error"]=!0,t=r.response.error instanceof Error?r.response.error.message:"string"==typeof r.response.error?r.response.error:s(r.response.error)||"Unknown error",o["mcp.error.message"]=t,r.response.error instanceof Error&&e.recordException(r.response.error),e.setStatus({code:a.ERROR,message:t})}else e.setStatus({code:a.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(e,o)}catch(t){console.error("[MCP Logger] Error creating paired span:",t),e.setStatus({code:a.ERROR,message:t+""})}finally{e.end()}})}import{trace as n,SpanStatusCode as a,context as c}from"@opentelemetry/api";import{NodeSDK as i}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as m}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as l}from"@opentelemetry/resources";const p="1.0.0";let u=null,g=null,d=!1,f=null;const h=Symbol("mcp-logger.customEvents"),v=Object.assign(function(e,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");!function(e){if(d)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),g;const t=e.endpoint||"https://aware.mcypher.com/v1/traces",s=e.serviceName||"mcp-server-"+Math.random().toString(36).slice(2,8);e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+t),console.error("[MCP Logger] Service: "+s)),u=new i({resource:new l({"service.name":s,"service.version":p}),traceExporter:new m({url:t,headers:{"x-api-key":e.apiKey}})}),u.start(),g=n.getTracer("mcp-logger",p),d=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const o=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",o),process.once("SIGINT",o)}(t),function(e,t){const r=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const n=e.shape?.method,a=n?._def?.value||"unknown";return t.debug&&console.log("[mcp-logger] Instrumenting handler: "+a),r(e,async(e,r)=>{const n=Date.now(),i=`${a}-${n}-${Math.random().toString(36).slice(2,8)}`,m=[],l=c.active().setValue(h,m);try{const p=await c.with(l,async()=>await s(e,r)),u=Date.now();return o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:u,result:p},duration:u-n,customEvents:m.length>0?m:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${u-n}ms):`,{method:a,customEvents:m.length}),p}catch(r){const s=Date.now();throw o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:m.length>0?m:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,t),t.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(e){const t=c.active().getValue(h);if(!t)return void console.warn("[mcp-logger] addLog called outside of handler context");const r={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(r);const s=n.getSpan(c.active());s&&s.addEvent("custom."+r.level,{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp})},async shutdown(){await r()}});export{v as trace};
1
+ function e(){return v}function t(){return h}async function r(){if(g)try{await g.shutdown(),g=null,v=null,f=!1,h=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function o(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 n(e,t,r,s){const n=function(e){if(e){const t=e["user-agent"]||e["User-Agent"];if(t){const e=t.match(/^([^\/]+)\/([^\s]+)/);if(e)return{name:e[1],version:e[2]}}}const t=process.env.MCP_CLIENT_NAME||process.env.CLIENT_NAME,r=process.env.MCP_CLIENT_VERSION||process.env.CLIENT_VERSION;return process.env.CLAUDE_DESKTOP?{name:"Claude Desktop",version:process.env.CLAUDE_DESKTOP_VERSION}:{name:t,version:r}}(s),i=(p=`${n.name||"unknown"}-${n.version||"0"}`,y.has(p)||y.set(p,"session-"+ ++E),y.get(p)),c=function(e,t){const r=`${e}-${t}`;return w.has(r)||w.set(r,"conv-"+ ++S),w.get(r)}(i,e),a=function(e,t){return e?.userId||e?.user_id?e.userId||e.user_id:t?t["x-user-id"]||t["X-User-Id"]:process.env.MCP_USER_ID||process.env.USER_ID}(t,s),m=function(e,t){if(t?.permission||t?.permissionLevel)return t.permission||t.permissionLevel;if(e.startsWith("admin/"))return"admin";if(e.includes("/write")||e.includes("/create")||e.includes("/update")||e.includes("/delete"))return"write";if(e.includes("/read")||e.includes("/list")||e.includes("/get"))return"read";if("tools/call"===e&&t?.name){const e=t.name;return e.includes("admin")||e.includes("system")?"elevated":"standard"}}(e,t),u=o(e,t);var p;u.uri&&function(e){const t=(I.get(e)||0)+1;I.set(e,t)}(u.uri);const l=function(e,t,r){let s=0;const o={"tools/call":.001,"prompts/get":5e-4,"resources/read":2e-4,"resources/write":3e-4,"completion/create":.01};return o[e]&&(s+=o[e]),t&&(s+=1e-6*JSON.stringify(t).length),r&&(s+=1e-6*JSON.stringify(r).length),(e.includes("completion")||e.includes("generate"))&&(s+=1e-5*((t?.max_tokens||t?.tokens||0)+(r?.usage?.total_tokens||0))),s>0?Math.round(1e6*s)/1e6:void 0}(e,t,r),d={};return i&&(d.sessionId=i),c&&(d.conversationId=c),a&&(d.userId=a),n.name&&(d.clientName=n.name),n.version&&(d.clientVersion=n.version),m&&(d.permissionLevel=m),void 0!==l&&(d.estimatedCost=l),d}function i(r){const i=e();if(!i)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const c="mcp."+r.method;i.startActiveSpan(c,e=>{try{const i={"mcp.source":r.source,"mcp.transport":r.transport,"mcp.request.id":r.request.id+"","mcp.request.method":r.method,"mcp.request.timestamp":r.request.timestamp,"mcp.request.params":s(r.request.params)||"{}","mcp.request.params.size":JSON.stringify(r.request.params||{}).length,"mcp.response.id":r.request.id+"","mcp.response.timestamp":r.response.timestamp,"mcp.duration_ms":r.duration};if("tools/call"===r.method&&r.request.params){const e=r.request.params.name,t=r.request.params.arguments;if(e&&(i["mcp.tool.name"]=e),t){const e=s(t);e&&(i["mcp.tool.arguments"]=e,i["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==r.response.result){const e=s(r.response.result);e&&(i["mcp.response.result"]=e,i["mcp.response.result.size"]=JSON.stringify(r.response.result).length)}if(r.request.headers){const e=s(r.request.headers);e&&(i["mcp.headers"]=e,i["mcp.headers.size"]=JSON.stringify(r.request.headers).length)}if("cli"===r.source){const e=t();e&&(i["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}const c=r.contextMetadata||n(r.method,r.request.params,r.response.result,r.request.headers);c.sessionId&&(i["mcp.session.id"]=c.sessionId),c.conversationId&&(i["mcp.conversation.id"]=c.conversationId),c.userId&&(i["mcp.user.id"]=c.userId),c.clientName&&(i["mcp.client.name"]=c.clientName),c.clientVersion&&(i["mcp.client.version"]=c.clientVersion),c.permissionLevel&&(i["mcp.permission.level"]=c.permissionLevel),void 0!==c.estimatedCost&&(i["mcp.cost.estimated"]=c.estimatedCost);const m=function(e,t){const r=o(e,t);if(!r.uri)return{};const s=I.get(r.uri)||0;return{resourceType:r.type,resourceUri:r.uri,resourceAccessCount:s}}(r.method,r.request.params);if(m.resourceType&&(i["mcp.resource.type"]=m.resourceType),m.resourceUri&&(i["mcp.resource.uri"]=m.resourceUri),m.resourceAccessCount&&(i["mcp.resource.access_count"]=m.resourceAccessCount),r.customEvents&&r.customEvents.length>0&&(i["mcp.events"]=JSON.stringify(r.customEvents),i["mcp.events.count"]=r.customEvents.length,r.customEvents.forEach(t=>{e.addEvent("custom."+(t.level||"info"),{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp||Date.now()})})),r.response.error){let t;i["mcp.error"]=!0,t=r.response.error instanceof Error?r.response.error.message:"string"==typeof r.response.error?r.response.error:s(r.response.error)||"Unknown error",i["mcp.error.message"]=t,r.response.error instanceof Error&&e.recordException(r.response.error),e.setStatus({code:a.ERROR,message:t})}else e.setStatus({code:a.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(e,i)}catch(t){console.error("[MCP Logger] Error creating paired span:",t),e.setStatus({code:a.ERROR,message:t+""})}finally{e.end()}})}import{trace as c,SpanStatusCode as a,context as m}from"@opentelemetry/api";import{NodeSDK as u}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as p}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as l}from"@opentelemetry/resources";const d="1.0.0";let g=null,v=null,f=!1,h=null;const y=new Map;let E=0;const w=new Map;let S=0;const I=new Map,q=Symbol("mcp-logger.customEvents"),C=Object.assign(function(e,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");!function(e){if(f)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),v;const t=e.endpoint||"https://aware.mcypher.com/v1/traces",s=e.serviceName||"mcp-server-"+Math.random().toString(36).slice(2,8);e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+t),console.error("[MCP Logger] Service: "+s)),g=new u({resource:new l({"service.name":s,"service.version":d}),traceExporter:new p({url:t,headers:{"x-api-key":e.apiKey}})}),g.start(),v=c.getTracer("mcp-logger",d),f=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const o=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",o),process.once("SIGINT",o)}(t),function(e,t){const r=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const o=e.shape?.method,c=o?._def?.value||"unknown";return t.debug&&console.log("[mcp-logger] Instrumenting handler: "+c),r(e,async(e,r)=>{const o=Date.now(),a=`${c}-${o}-${Math.random().toString(36).slice(2,8)}`,u=[],p=m.active().setValue(q,u);try{const l=await m.with(p,async()=>await s(e,r)),d=Date.now(),g=n(c,e.params,l,void 0);return i({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:o,params:e.params},response:{timestamp:d,result:l},duration:d-o,customEvents:u.length>0?u:void 0,contextMetadata:g}),t.debug&&console.log(`[mcp-logger] Request completed (${d-o}ms):`,{method:c,customEvents:u.length}),l}catch(r){const s=Date.now(),m=n(c,e.params,void 0,void 0);throw i({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:o,params:e.params},response:{timestamp:s,error:r},duration:s-o,customEvents:u.length>0?u:void 0,contextMetadata:m}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,t),t.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(e){const t=m.active().getValue(q);if(!t)return void console.warn("[mcp-logger] addLog called outside of handler context");const r={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(r);const s=c.getSpan(m.active());s&&s.addEvent("custom."+r.level,{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp})},async shutdown(){await r()}});export{C as trace};
package/dist/sdk/index.js CHANGED
@@ -1 +1 @@
1
- function e(){return g}function t(){return f}async function r(){if(u)try{await u.shutdown(),u=null,g=null,d=!1,f=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function o(r){const o=e();if(!o)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const n="mcp."+r.method;o.startActiveSpan(n,e=>{try{const o={"mcp.source":r.source,"mcp.transport":r.transport,"mcp.request.id":r.request.id+"","mcp.request.method":r.method,"mcp.request.timestamp":r.request.timestamp,"mcp.request.params":s(r.request.params)||"{}","mcp.request.params.size":JSON.stringify(r.request.params||{}).length,"mcp.response.id":r.request.id+"","mcp.response.timestamp":r.response.timestamp,"mcp.duration_ms":r.duration};if("tools/call"===r.method&&r.request.params){const e=r.request.params.name,t=r.request.params.arguments;if(e&&(o["mcp.tool.name"]=e),t){const e=s(t);e&&(o["mcp.tool.arguments"]=e,o["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==r.response.result){const e=s(r.response.result);e&&(o["mcp.response.result"]=e,o["mcp.response.result.size"]=JSON.stringify(r.response.result).length)}if(r.request.headers){const e=s(r.request.headers);e&&(o["mcp.headers"]=e,o["mcp.headers.size"]=JSON.stringify(r.request.headers).length)}if("cli"===r.source){const e=t();e&&(o["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}if(r.customEvents&&r.customEvents.length>0&&(o["mcp.events"]=JSON.stringify(r.customEvents),o["mcp.events.count"]=r.customEvents.length,r.customEvents.forEach(t=>{e.addEvent("custom."+(t.level||"info"),{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp||Date.now()})})),r.response.error){let t;o["mcp.error"]=!0,t=r.response.error instanceof Error?r.response.error.message:"string"==typeof r.response.error?r.response.error:s(r.response.error)||"Unknown error",o["mcp.error.message"]=t,r.response.error instanceof Error&&e.recordException(r.response.error),e.setStatus({code:a.ERROR,message:t})}else e.setStatus({code:a.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(e,o)}catch(t){console.error("[MCP Logger] Error creating paired span:",t),e.setStatus({code:a.ERROR,message:t+""})}finally{e.end()}})}import{trace as n,SpanStatusCode as a,context as c}from"@opentelemetry/api";import{NodeSDK as i}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as m}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as l}from"@opentelemetry/resources";const p="1.0.0";let u=null,g=null,d=!1,f=null;const h=Symbol("mcp-logger.customEvents"),v=Object.assign(function(e,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");!function(e){if(d)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),g;const t=e.endpoint||"https://aware.mcypher.com/v1/traces",s=e.serviceName||"mcp-server-"+Math.random().toString(36).slice(2,8);e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+t),console.error("[MCP Logger] Service: "+s)),u=new i({resource:new l({"service.name":s,"service.version":p}),traceExporter:new m({url:t,headers:{"x-api-key":e.apiKey}})}),u.start(),g=n.getTracer("mcp-logger",p),d=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const o=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",o),process.once("SIGINT",o)}(t),function(e,t){const r=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const n=e.shape?.method,a=n?._def?.value||"unknown";return t.debug&&console.log("[mcp-logger] Instrumenting handler: "+a),r(e,async(e,r)=>{const n=Date.now(),i=`${a}-${n}-${Math.random().toString(36).slice(2,8)}`,m=[],l=c.active().setValue(h,m);try{const p=await c.with(l,async()=>await s(e,r)),u=Date.now();return o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:u,result:p},duration:u-n,customEvents:m.length>0?m:void 0}),t.debug&&console.log(`[mcp-logger] Request completed (${u-n}ms):`,{method:a,customEvents:m.length}),p}catch(r){const s=Date.now();throw o({method:a,source:"sdk",transport:"stdio",request:{id:i,timestamp:n,params:e.params},response:{timestamp:s,error:r},duration:s-n,customEvents:m.length>0?m:void 0}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,t),t.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(e){const t=c.active().getValue(h);if(!t)return void console.warn("[mcp-logger] addLog called outside of handler context");const r={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(r);const s=n.getSpan(c.active());s&&s.addEvent("custom."+r.level,{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp})},async shutdown(){await r()}});export{v as trace};
1
+ function e(){return v}function t(){return h}async function r(){if(g)try{await g.shutdown(),g=null,v=null,f=!1,h=null}catch(e){console.error("[MCP Logger] Error shutting down telemetry:",e)}}function s(e,t=1e4){try{const r=JSON.stringify(e);return r.length>t?r.slice(0,t)+"... (truncated)":r}catch(e){return null}}function o(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 n(e,t,r,s){const n=function(e){if(e){const t=e["user-agent"]||e["User-Agent"];if(t){const e=t.match(/^([^\/]+)\/([^\s]+)/);if(e)return{name:e[1],version:e[2]}}}const t=process.env.MCP_CLIENT_NAME||process.env.CLIENT_NAME,r=process.env.MCP_CLIENT_VERSION||process.env.CLIENT_VERSION;return process.env.CLAUDE_DESKTOP?{name:"Claude Desktop",version:process.env.CLAUDE_DESKTOP_VERSION}:{name:t,version:r}}(s),i=(p=`${n.name||"unknown"}-${n.version||"0"}`,y.has(p)||y.set(p,"session-"+ ++E),y.get(p)),c=function(e,t){const r=`${e}-${t}`;return w.has(r)||w.set(r,"conv-"+ ++S),w.get(r)}(i,e),a=function(e,t){return e?.userId||e?.user_id?e.userId||e.user_id:t?t["x-user-id"]||t["X-User-Id"]:process.env.MCP_USER_ID||process.env.USER_ID}(t,s),m=function(e,t){if(t?.permission||t?.permissionLevel)return t.permission||t.permissionLevel;if(e.startsWith("admin/"))return"admin";if(e.includes("/write")||e.includes("/create")||e.includes("/update")||e.includes("/delete"))return"write";if(e.includes("/read")||e.includes("/list")||e.includes("/get"))return"read";if("tools/call"===e&&t?.name){const e=t.name;return e.includes("admin")||e.includes("system")?"elevated":"standard"}}(e,t),u=o(e,t);var p;u.uri&&function(e){const t=(I.get(e)||0)+1;I.set(e,t)}(u.uri);const l=function(e,t,r){let s=0;const o={"tools/call":.001,"prompts/get":5e-4,"resources/read":2e-4,"resources/write":3e-4,"completion/create":.01};return o[e]&&(s+=o[e]),t&&(s+=1e-6*JSON.stringify(t).length),r&&(s+=1e-6*JSON.stringify(r).length),(e.includes("completion")||e.includes("generate"))&&(s+=1e-5*((t?.max_tokens||t?.tokens||0)+(r?.usage?.total_tokens||0))),s>0?Math.round(1e6*s)/1e6:void 0}(e,t,r),d={};return i&&(d.sessionId=i),c&&(d.conversationId=c),a&&(d.userId=a),n.name&&(d.clientName=n.name),n.version&&(d.clientVersion=n.version),m&&(d.permissionLevel=m),void 0!==l&&(d.estimatedCost=l),d}function i(r){const i=e();if(!i)return void console.error("[MCP Logger] Tracer not initialized, cannot create span");const c="mcp."+r.method;i.startActiveSpan(c,e=>{try{const i={"mcp.source":r.source,"mcp.transport":r.transport,"mcp.request.id":r.request.id+"","mcp.request.method":r.method,"mcp.request.timestamp":r.request.timestamp,"mcp.request.params":s(r.request.params)||"{}","mcp.request.params.size":JSON.stringify(r.request.params||{}).length,"mcp.response.id":r.request.id+"","mcp.response.timestamp":r.response.timestamp,"mcp.duration_ms":r.duration};if("tools/call"===r.method&&r.request.params){const e=r.request.params.name,t=r.request.params.arguments;if(e&&(i["mcp.tool.name"]=e),t){const e=s(t);e&&(i["mcp.tool.arguments"]=e,i["mcp.tool.arguments.size"]=JSON.stringify(t).length)}}if(void 0!==r.response.result){const e=s(r.response.result);e&&(i["mcp.response.result"]=e,i["mcp.response.result.size"]=JSON.stringify(r.response.result).length)}if(r.request.headers){const e=s(r.request.headers);e&&(i["mcp.headers"]=e,i["mcp.headers.size"]=JSON.stringify(r.request.headers).length)}if("cli"===r.source){const e=t();e&&(i["mcp.cli.server"]=JSON.stringify({command:e.command,args:e.args,env:e.env}))}const c=r.contextMetadata||n(r.method,r.request.params,r.response.result,r.request.headers);c.sessionId&&(i["mcp.session.id"]=c.sessionId),c.conversationId&&(i["mcp.conversation.id"]=c.conversationId),c.userId&&(i["mcp.user.id"]=c.userId),c.clientName&&(i["mcp.client.name"]=c.clientName),c.clientVersion&&(i["mcp.client.version"]=c.clientVersion),c.permissionLevel&&(i["mcp.permission.level"]=c.permissionLevel),void 0!==c.estimatedCost&&(i["mcp.cost.estimated"]=c.estimatedCost);const m=function(e,t){const r=o(e,t);if(!r.uri)return{};const s=I.get(r.uri)||0;return{resourceType:r.type,resourceUri:r.uri,resourceAccessCount:s}}(r.method,r.request.params);if(m.resourceType&&(i["mcp.resource.type"]=m.resourceType),m.resourceUri&&(i["mcp.resource.uri"]=m.resourceUri),m.resourceAccessCount&&(i["mcp.resource.access_count"]=m.resourceAccessCount),r.customEvents&&r.customEvents.length>0&&(i["mcp.events"]=JSON.stringify(r.customEvents),i["mcp.events.count"]=r.customEvents.length,r.customEvents.forEach(t=>{e.addEvent("custom."+(t.level||"info"),{message:t.message,metadata:JSON.stringify(t.metadata||{}),timestamp:t.timestamp||Date.now()})})),r.response.error){let t;i["mcp.error"]=!0,t=r.response.error instanceof Error?r.response.error.message:"string"==typeof r.response.error?r.response.error:s(r.response.error)||"Unknown error",i["mcp.error.message"]=t,r.response.error instanceof Error&&e.recordException(r.response.error),e.setStatus({code:a.ERROR,message:t})}else e.setStatus({code:a.OK});!function(e,t){for(const[r,s]of Object.entries(t))null!=s&&e.setAttribute(r,s)}(e,i)}catch(t){console.error("[MCP Logger] Error creating paired span:",t),e.setStatus({code:a.ERROR,message:t+""})}finally{e.end()}})}import{trace as c,SpanStatusCode as a,context as m}from"@opentelemetry/api";import{NodeSDK as u}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as p}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as l}from"@opentelemetry/resources";const d="1.0.0";let g=null,v=null,f=!1,h=null;const y=new Map;let E=0;const w=new Map;let S=0;const I=new Map,q=Symbol("mcp-logger.customEvents"),C=Object.assign(function(e,t){if(!t.apiKey)throw Error("[mcp-logger] apiKey is required");if(!e)throw Error("[mcp-logger] server instance is required");!function(e){if(f)return e.debug&&console.error("[MCP Logger] Telemetry already initialized, returning existing tracer"),v;const t=e.endpoint||"https://aware.mcypher.com/v1/traces",s=e.serviceName||"mcp-server-"+Math.random().toString(36).slice(2,8);e.debug&&(console.error("[MCP Logger] Initializing telemetry..."),console.error("[MCP Logger] Endpoint: "+t),console.error("[MCP Logger] Service: "+s)),g=new u({resource:new l({"service.name":s,"service.version":d}),traceExporter:new p({url:t,headers:{"x-api-key":e.apiKey}})}),g.start(),v=c.getTracer("mcp-logger",d),f=!0,e.debug&&console.error("[MCP Logger] Telemetry initialized successfully");const o=async()=>{e.debug&&console.error("[MCP Logger] Shutting down telemetry..."),await r()};process.once("SIGTERM",o),process.once("SIGINT",o)}(t),function(e,t){const r=e.setRequestHandler.bind(e);e.setRequestHandler=function(e,s){const o=e.shape?.method,c=o?._def?.value||"unknown";return t.debug&&console.log("[mcp-logger] Instrumenting handler: "+c),r(e,async(e,r)=>{const o=Date.now(),a=`${c}-${o}-${Math.random().toString(36).slice(2,8)}`,u=[],p=m.active().setValue(q,u);try{const l=await m.with(p,async()=>await s(e,r)),d=Date.now(),g=n(c,e.params,l,void 0);return i({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:o,params:e.params},response:{timestamp:d,result:l},duration:d-o,customEvents:u.length>0?u:void 0,contextMetadata:g}),t.debug&&console.log(`[mcp-logger] Request completed (${d-o}ms):`,{method:c,customEvents:u.length}),l}catch(r){const s=Date.now(),m=n(c,e.params,void 0,void 0);throw i({method:c,source:"sdk",transport:"stdio",request:{id:a,timestamp:o,params:e.params},response:{timestamp:s,error:r},duration:s-o,customEvents:u.length>0?u:void 0,contextMetadata:m}),t.debug&&console.error("[mcp-logger] Request failed:",r),r}})},t.debug&&console.log("[mcp-logger] Server instrumentation complete")}(e,t),t.debug&&console.log("[mcp-logger] ✓ Initialization complete")},{addLog(e){const t=m.active().getValue(q);if(!t)return void console.warn("[mcp-logger] addLog called outside of handler context");const r={level:e.level||"info",message:e.message,metadata:e.metadata,timestamp:e.timestamp||Date.now()};t.push(r);const s=c.getSpan(m.active());s&&s.addEvent("custom."+r.level,{message:r.message,metadata:JSON.stringify(r.metadata||{}),timestamp:r.timestamp})},async shutdown(){await r()}});export{C as trace};
@@ -1 +1 @@
1
- function e(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function t(t,o){const c=t.setRequestHandler.bind(t);t.setRequestHandler=function(t,s){const a=t.shape?.method,l=a?._def?.value||"unknown";return o.debug&&console.log("[mcp-logger] Instrumenting handler: "+l),c(t,async(t,c)=>{const a=Date.now(),m=(Math.random().toString(36).slice(2,8),[]),i=n.active().setValue(r,m);try{const r=await n.with(i,async()=>await s(t,c)),p=Date.now();return e(t.params),o.debug&&console.log(`[mcp-logger] Request completed (${p-a}ms):`,{method:l,customEvents:m.length}),r}catch(n){throw e(t.params),o.debug&&console.error("[mcp-logger] Request failed:",n),n}})},o.debug&&console.log("[mcp-logger] Server instrumentation complete")}function o(){return n.active().getValue(r)}import{context as n}from"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const r=Symbol("mcp-logger.customEvents");export{o as getCustomEventsFromContext,t as instrumentMCPServer};
1
+ function e(e,t,n,r){const s=function(){const e=process.env.MCP_CLIENT_NAME||process.env.CLIENT_NAME,t=process.env.MCP_CLIENT_VERSION||process.env.CLIENT_VERSION;return process.env.CLAUDE_DESKTOP?{name:"Claude Desktop",version:process.env.CLAUDE_DESKTOP_VERSION}:{name:e,version:t}}(),l=(v=`${s.name||"unknown"}-${s.version||"0"}`,o.has(v)||o.set(v,"session-"+ ++i),o.get(v)),p=function(e,t){const n=`${e}-${t}`;return c.has(n)||c.set(n,"conv-"+ ++a),c.get(n)}(l,e),m=function(e){return e?.userId||e?.user_id?e.userId||e.user_id:process.env.MCP_USER_ID||process.env.USER_ID}(t),d=function(e,t){if(t?.permission||t?.permissionLevel)return t.permission||t.permissionLevel;if(e.startsWith("admin/"))return"admin";if(e.includes("/write")||e.includes("/create")||e.includes("/update")||e.includes("/delete"))return"write";if(e.includes("/read")||e.includes("/list")||e.includes("/get"))return"read";if("tools/call"===e&&t?.name){const e=t.name;return e.includes("admin")||e.includes("system")?"elevated":"standard"}}(e,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);var v;g.uri&&function(e){const t=(u.get(e)||0)+1;u.set(e,t)}(g.uri);const f=function(e,t,n){let r=0;const s={"tools/call":.001,"prompts/get":5e-4,"resources/read":2e-4,"resources/write":3e-4,"completion/create":.01};return s[e]&&(r+=s[e]),t&&(r+=1e-6*JSON.stringify(t).length),n&&(r+=1e-6*JSON.stringify(n).length),(e.includes("completion")||e.includes("generate"))&&(r+=1e-5*((t?.max_tokens||t?.tokens||0)+(n?.usage?.total_tokens||0))),r>0?Math.round(1e6*r)/1e6:void 0}(e,t,n),h={};return l&&(h.sessionId=l),p&&(h.conversationId=p),m&&(h.userId=m),s.name&&(h.clientName=s.name),s.version&&(h.clientVersion=s.version),d&&(h.permissionLevel=d),void 0!==f&&(h.estimatedCost=f),h}function t(e){console.error("[MCP Logger] Tracer not initialized, cannot create span")}function n(n,r){const o=n.setRequestHandler.bind(n);n.setRequestHandler=function(n,i){const c=n.shape?.method,a=c?._def?.value||"unknown";return r.debug&&console.log("[mcp-logger] Instrumenting handler: "+a),o(n,async(n,o)=>{const c=Date.now(),u=(Math.random().toString(36).slice(2,8),[]),p=s.active().setValue(l,u);try{const l=await s.with(p,async()=>await i(n,o)),m=Date.now();return e(a,n.params,l),t(n.params),r.debug&&console.log(`[mcp-logger] Request completed (${m-c}ms):`,{method:a,customEvents:u.length}),l}catch(s){throw e(a,n.params,void 0),t(n.params),r.debug&&console.error("[mcp-logger] Request failed:",s),s}})},r.debug&&console.log("[mcp-logger] Server instrumentation complete")}function r(){return s.active().getValue(l)}import{context as s}from"@opentelemetry/api";import"@opentelemetry/sdk-node";import"@opentelemetry/exporter-trace-otlp-http";import"@opentelemetry/resources";const o=new Map;let i=0;const c=new Map;let a=0;const u=new Map,l=Symbol("mcp-logger.customEvents");export{r as getCustomEventsFromContext,n as instrumentMCPServer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awarecorp/mcp-logger",
3
- "version": "0.0.3-dev.2",
3
+ "version": "0.0.3-dev.3",
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",
package/README.md DELETED
@@ -1,247 +0,0 @@
1
- # @awarecorp/mcp-logger
2
-
3
- **Unified MCP Observability Solution** - Monitor your Model Context Protocol servers with zero configuration.
4
-
5
- [![npm version](https://img.shields.io/npm/v/@awarecorp/mcp-logger)](https://www.npmjs.com/package/@awarecorp/mcp-logger)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
-
8
- [Aware Corp](https://awarecorp.io/)
9
-
10
- ---
11
-
12
- ## 🎯 Overview
13
-
14
- `@awarecorp/mcp-logger` provides comprehensive observability for MCP servers in two ways:
15
-
16
- - **SDK**: Direct integration for custom MCP servers
17
- - **CLI**: Zero-code wrapper for existing MCP packages
18
-
19
- All telemetry data is sent to OpenTelemetry-compatible endpoints with automatic request-response pairing.
20
-
21
- ---
22
-
23
- ## 📦 Installation
24
-
25
- ```bash
26
- npm install @awarecorp/mcp-logger
27
- ```
28
-
29
- ---
30
-
31
- ## 🚀 Quick Start
32
-
33
- ### SDK Mode
34
-
35
- Instrument your MCP server with one line of code:
36
-
37
- ```typescript
38
- import { trace } from '@awarecorp/mcp-logger';
39
-
40
- trace(server, {
41
- apiKey: 'YOUR_API_KEY',
42
- serviceName: 'my-mcp-server'
43
- });
44
- ```
45
-
46
- ### CLI Mode
47
-
48
- Wrap any existing MCP server without code changes:
49
-
50
- ```bash
51
- npx @awarecorp/mcp-logger \
52
- -k YOUR_API_KEY \
53
- -s my-server \
54
- npx your-mcp-server
55
- ```
56
-
57
- ---
58
-
59
- ## 📋 Features
60
-
61
- ### Core Capabilities
62
-
63
- - **Automatic Request-Response Pairing**: Single span per request-response cycle with intelligent message correlation
64
- - **Notification Support**: Tracks both client→server and server→client notifications
65
- - **Selective Method Tracking**: Configurable filtering to track only relevant methods (excludes noisy notifications like `notifications/initialized`)
66
- - **Custom Event Logging**: Add context-aware logs within request handlers
67
- - **Enhanced Error Handling**: Robust error serialization for complex error objects
68
- - **Custom OTLP Endpoint**: Send traces to any OTLP/HTTP compatible observability backend
69
- - **Transport Support**: stdio and SSE (Server-Sent Events)
70
- - **Zero Configuration**: Works out of the box with sensible defaults
71
-
72
- ### Telemetry Data
73
-
74
- All spans include:
75
-
76
- - Request/response timestamps and payloads
77
- - Method name and parameters
78
- - Tool names and arguments (for `tools/call`)
79
- - Custom events and logs
80
- - Error details (if any)
81
- - Duration metrics
82
- - CLI server information (command, args, env)
83
-
84
- ### Data Format
85
-
86
- Optimized for Elasticsearch with flat structure:
87
- - `mcp.request.method` / `mcp.response.method`: Method names for request/response
88
- - `mcp.source`: `sdk` or `cli`
89
- - `mcp.transport`: `stdio` or `sse`
90
- - `mcp.duration_ms`: Request duration
91
- - `mcp.tool.name`: Tool name (tools/call only)
92
- - `mcp.request.params` / `mcp.response.result`: Request/response payloads
93
- - `mcp.error.message`: Enhanced error serialization (no more `[object Object]`)
94
- - `mcp.events`: Custom log entries (JSON array)
95
- - `mcp.cli.server`: CLI execution info (JSON object)
96
-
97
- ---
98
-
99
- ## 🔧 SDK API
100
-
101
- ### `trace(server, options)`
102
-
103
- Enable automatic instrumentation on an MCP server.
104
-
105
- **Parameters:**
106
- - `server`: MCP Server instance
107
- - `options.apiKey`: API key for authentication (required)
108
- - Currently accepts any string value for testing purposes
109
- - For production use, sign up at [Aware](https://awarecorp.io/) to get your API key
110
- - `options.serviceName`: Service identifier (optional, auto-generated if omitted)
111
- - `options.endpoint`: Custom OTLP endpoint (optional, default: `https://aware.mcypher.com/v1/traces`)
112
- - Accepts any OTLP/HTTP compatible endpoint URL
113
- - Use this to send traces to your own observability backend (e.g., Jaeger, Grafana, etc.)
114
- - `options.debug`: Enable debug logging (optional, default: false)
115
-
116
- **Returns:** `void`
117
-
118
- ### `trace.addLog(entry)`
119
-
120
- Add custom log entries within request handlers.
121
-
122
- **Parameters:**
123
- - `entry.level`: Log level (`info` | `warn` | `error`)
124
- - `entry.message`: Log message (required)
125
- - `entry.metadata`: Additional data (optional)
126
- - `entry.timestamp`: Unix timestamp in milliseconds (optional, auto-generated)
127
-
128
- **Returns:** `void`
129
-
130
- ### `trace.shutdown()`
131
-
132
- Gracefully shutdown telemetry and flush pending data.
133
-
134
- **Returns:** `Promise<void>`
135
-
136
- ---
137
-
138
- ## 🖥️ CLI Usage
139
-
140
- ### Basic Syntax
141
-
142
- ```bash
143
- mcp-logger [options] <command...>
144
- ```
145
-
146
- ### Options
147
-
148
- - `-k, --api-key <key>`: API key (required)
149
- - Currently accepts any string value for testing
150
- - Sign up at [Aware](https://awarecorp.io/) to get your API key for production use
151
- - `-s, --service-name <name>`: Service name (optional)
152
- - `-e, --endpoint <url>`: Custom OTLP endpoint (optional, default: `https://aware.mcypher.com/v1/traces`)
153
- - Send traces to any OTLP/HTTP compatible backend
154
- - Examples: Jaeger, Grafana Tempo, New Relic, Honeycomb, etc.
155
- - `-d, --debug`: Enable debug logging (optional)
156
- - Shows detailed information about message processing and span creation
157
-
158
- ### Examples
159
-
160
- **Wrap an npx command:**
161
- ```bash
162
- mcp-logger -k API_KEY -s filesystem npx -y @modelcontextprotocol/server-filesystem /path
163
- ```
164
-
165
- **Use with Claude Desktop:**
166
- ```json
167
- {
168
- "mcpServers": {
169
- "my-server": {
170
- "command": "npx",
171
- "args": [
172
- "-y",
173
- "@awarecorp/mcp-logger",
174
- "-k",
175
- "YOUR_API_KEY",
176
- "-s",
177
- "my-server",
178
- "--",
179
- "npx",
180
- "-y",
181
- "your-mcp-server"
182
- ]
183
- }
184
- }
185
- }
186
- ```
187
-
188
- ---
189
-
190
- ## 🏗️ How It Works
191
-
192
- ### Request-Response Pairing
193
-
194
- Creates **one span per request-response pair** for complete transaction context:
195
-
196
- 1. Request arrives → stored in pending map
197
- 2. Response arrives → matched by ID, single span created
198
- 3. Timeout (30s) → pending requests cleared
199
- 4. Notifications → handled as single-direction spans (request-only or response-only)
200
-
201
- ### Message Type Handling
202
-
203
- - **Paired Spans**: Request-response cycles with matching IDs (most common)
204
- - **Request-Only Spans**: Client→server notifications (no response expected)
205
- - **Response-Only Spans**: Server→client notifications (no matching request)
206
-
207
- ### Tracked Methods
208
-
209
- Configurable method filtering with exclusion list:
210
- - Tracks: `tools/call`, `tools/list`, and other business-critical methods
211
- - Excludes: `notifications/initialized` and other noisy protocol notifications
212
- - Customizable via `EXCLUDED_METHODS` configuration
213
-
214
- ---
215
-
216
- ## 📊 Telemetry Data Structure
217
-
218
- Each span includes:
219
- - Request/response timestamps and payloads (with optional fields for notifications)
220
- - Method name and parameters (separate fields for request/response)
221
- - Tool names and arguments (for `tools/call`)
222
- - Custom events and logs
223
- - Enhanced error details with proper serialization
224
- - Duration metrics
225
- - CLI server information (command, args, env)
226
-
227
- ### Span Attributes
228
-
229
- All attributes use a flat structure optimized for Elasticsearch:
230
- - Request attributes: `mcp.request.*` (id, method, params, timestamp)
231
- - Response attributes: `mcp.response.*` (method, result, error, timestamp)
232
- - Error handling: Proper serialization for Error objects, strings, and complex objects
233
- - Metadata: source, transport, duration, tool information
234
-
235
- ---
236
-
237
- ## 📄 License
238
-
239
- MIT © [Aware Corp](https://awarecorp.io/)
240
-
241
- ---
242
-
243
- ## 🔗 Links
244
-
245
- - [Aware Corp](https://awarecorp.io/)
246
- - [npm Package](https://www.npmjs.com/package/@awarecorp/mcp-logger)
247
- - [Model Context Protocol](https://modelcontextprotocol.io/)