@awarecorp/mcp-logger 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- function e(...e){console.error("[mcp-logger]",...e)}function t(...e){x&&console.error("[mcp-logger]",...e)}function s(...e){E&&console.error("[mcp-logger]",...e)}function r(e){return!N.includes(e)}async function n(){await R.shutdown()}function o(e,t=2e5){try{const s=JSON.stringify(e);return s.length>t?s.slice(0,t)+"... (truncated)":s}catch{return null}}function i(e,t){return e.startsWith("resources/")?{type:e.split("/")[1],uri:t?.uri||t?.path||t?.resource}:e.startsWith("prompts/")?{type:"prompt",uri:t?.name||t?.promptId}:"tools/call"===e?{type:"tool",uri:t?.name}:t?.resourceType&&t?.resourceUri?{type:t.resourceType,uri:t.resourceUri}:{type:void 0,uri:void 0}}function c(e,t,s,r,n){const o=function(e,t){const s=e?.getClientInfo();if(s?.name||s?.version)return{name:s.name,version:s.version};const r=e?.getHeaders()||t;if(r){const e=r["user-agent"]||r["User-Agent"];if(e){const t=e.match(/^([^\/]+)\/([^\s]+)/);if(t)return{name:t[1],version:t[2]}}}return{name:void 0,version:void 0}}(r,n),c=`${o.name||"unknown"}-${o.version||"0"}`,a=T.getOrCreateSessionId(c),p=T.getOrCreateConversationId(a,e),u=function(e,t){return e?.userId||e?.user_id?e.userId||e.user_id:t?t["x-user-id"]||t["X-User-Id"]:void 0}(t,n),m=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(t),d=i(e,t);d.uri&&T.trackResourceAccess(d.uri);const l=function(e,t){return void 0!==e?.cost?e.cost:void 0!==t?.cost?t.cost:void 0}(t,s),h={};return a&&(h.sessionId=a),p&&(h.conversationId=p),u&&(h.userId=u),o.name&&(h.clientName=o.name),o.version&&(h.clientVersion=o.version),m&&(h.permissionLevel=m),void 0!==l&&(h.cost=l),h}function a(e){const t=R.getTracer();if(!t)return;const r="mcp."+e.method;t.startActiveSpan(r,t=>{try{const r={"ingest.type":"mcp","mcp.type":e.type,"mcp.transport":e.transport,"mcp.method":e.method,"mcp.source":e.source},n=e.contextMetadata||c(e.method,e.params,e.result,void 0,e.headers);if(n.sessionId&&n.conversationId&&(r["mcp.trace.id"]=`${n.sessionId}:${n.conversationId}`),"paired"===e.type||"request"===e.type){if(void 0!==e.requestId&&(r["mcp.request.id"]=e.requestId+""),r["mcp.request.method"]=e.method,void 0!==e.requestTimestamp&&(r["mcp.request.timestamp"]=e.requestTimestamp),void 0!==e.params&&(r["mcp.request.params"]=o(e.params)||"{}",r["mcp.request.params.size"]=JSON.stringify(e.params||{}).length),"tools/call"===e.method&&e.params){const t=e.params.name,s=e.params.arguments;if(t&&(r["mcp.tool.name"]=t),s){const e=o(s);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if(e.headers){const t=o(e.headers);t&&(r["mcp.headers"]=t,r["mcp.headers.size"]=JSON.stringify(e.headers).length)}}if(("paired"===e.type||"response"===e.type)&&(void 0!==e.responseId&&(r["mcp.response.id"]=e.responseId+""),r["mcp.response.method"]=e.method,void 0!==e.responseTimestamp&&(r["mcp.response.timestamp"]=e.responseTimestamp),void 0!==e.result)){const t=o(e.result);t&&(r["mcp.response.result"]=t,r["mcp.response.result.size"]=JSON.stringify(e.result).length)}void 0!==e.durationMs&&(r["mcp.duration_ms"]=e.durationMs);const a=R.getCLIServerInfo();a&&(r["mcp.cli"]=JSON.stringify({command:a.command,args:a.args,env:a.env})),e.serverInfo&&(e.serverInfo.name&&(r["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(r["mcp.sdk.version"]=e.serverInfo.version)),n.sessionId&&(r["mcp.session.id"]=n.sessionId),n.conversationId&&(r["mcp.conversation.id"]=n.conversationId),n.userId&&(r["mcp.user.id"]=n.userId),n.clientName&&(r["mcp.client.name"]=n.clientName),n.clientVersion&&(r["mcp.client.version"]=n.clientVersion),n.permissionLevel&&(r["mcp.permission.level"]=n.permissionLevel),void 0!==n.cost&&(r["mcp.cost"]=n.cost);const p=function(e,t){const s=i(e,t);if(!s.uri)return{};const r=T.getResourceAccessCount(s.uri);return{resourceType:s.type,resourceUri:s.uri,resourceAccessCount:r}}(e.method,e.params);if(p.resourceType&&(r["mcp.resource.type"]=p.resourceType),p.resourceUri&&(r["mcp.resource.uri"]=p.resourceUri),p.resourceAccessCount&&(r["mcp.resource.access_count"]=p.resourceAccessCount),e.customEvents&&e.customEvents.length>0&&(r["mcp.events"]=JSON.stringify(e.customEvents),r["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{t.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.error){let s;r["mcp.error"]=!0,e.error instanceof Error?(s=e.error.message,t.recordException(e.error)):s="string"==typeof e.error?e.error:o(e.error)||"Unknown error",r["mcp.error.message"]=s,t.setStatus({code:v.ERROR,message:s})}else t.setStatus({code:v.OK});!function(e,t){for(const[s,r]of Object.entries(t))null!=r&&e.setAttribute(s,r)}(t,r),s("[OTLP] Span data:",JSON.stringify(r,null,2))}catch{t.setStatus({code:v.ERROR,message:"Error creating span"})}finally{t.end()}})}import{execSync as p,spawn as u}from"child_process";import{program as m}from"commander";import{NodeSDK as d}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as l}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as h}from"@opentelemetry/resources";import{trace as f,SpanStatusCode as v}from"@opentelemetry/api";import{readFileSync as g}from"fs";import{fileURLToPath as I}from"url";import{dirname as y,join as S}from"path";import{Transform as C}from"stream";import{ulid as w}from"ulid";const x="true"===process.env.AWARE_DEBUG||"1"===process.env.AWARE_DEBUG,E="true"===process.env.AWARE_RAW_DEBUG||"1"===process.env.AWARE_RAW_DEBUG,M={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=I(import.meta.url),t=y(e),s=S(t,"..","..","package.json");return JSON.parse(g(s,"utf-8")).version||"1.0.0"}catch{return"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"},N=["notifications/initialized"],R=new class{sdk=null;tracer=null;initialized=!1;cliServerInfo=null;initialize(e){if(this.initialized)return t("Telemetry already initialized, returning existing tracer"),this.tracer;const s=e.endpoint||M.ENDPOINT,r=e.serviceName||`${M.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`,n=e.serviceVersion||"unknown";t("Initializing telemetry..."),t("Endpoint: "+s),t("Service: "+r),t("Version: "+n),this.sdk=new d({resource:new h({"service.name":r,"service.version":n}),traceExporter:new l({url:s,headers:{"x-api-key":e.apiKey}})}),this.sdk.start(),this.tracer=f.getTracer("mcp-logger",M.SDK_VERSION),this.initialized=!0,t("Telemetry initialized successfully");const o=async()=>{t("Shutting down telemetry..."),await this.shutdown()};return process.once("SIGTERM",o),process.once("SIGINT",o),this.tracer}getTracer(){return this.tracer}isInitialized(){return this.initialized}setCLIServerInfo(e,t,s){this.cliServerInfo={command:e,args:t,env:s}}getCLIServerInfo(){return this.cliServerInfo}async shutdown(){if(this.sdk)try{await this.sdk.shutdown(),this.sdk=null,this.tracer=null,this.initialized=!1,this.cliServerInfo=null}catch(e){console.error("[mcp-logger] Error shutting down telemetry:",e)}}};class q extends C{buffer="";direction;logger;sessionContextStore;constructor(e,t,s){super(),this.direction=e,this.logger=t,this.sessionContextStore=s}_transform(e,s,r){try{if("request"===this.direction)return this.push(e),this.buffer+=e.toString(),this.tryParseMessages(),void r();this.buffer+=e.toString(),this.buffer.length>1e6&&(t("Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseAndForwardMessages(),r()}catch(e){r(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const t=JSON.parse(e);s(`[${this.direction}] Raw message:`,e),this.handleMessage(t)}catch{t("Failed to parse:",e.slice(0,100))}}}tryParseAndForwardMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const r=JSON.parse(e);"2.0"===r.jsonrpc?(this.push(e+"\n"),s(`[${this.direction}] Raw message:`,e),this.handleMessage(r)):t("Non-JSON-RPC JSON ignored:",e.slice(0,100))}catch{t("Non-JSON output filtered:",e.slice(0,100))}}}isValidInfo(e){return!(!e?.name||!e?.version)}handleMessage(e){if("request"===this.direction&&"initialize"===e.method&&e.params?.clientInfo&&!this.isValidInfo(this.sessionContextStore.getClientInfo())){const t=e.params;this.sessionContextStore.setClientInfo({name:t.clientInfo?.name,version:t.clientInfo?.version})}if("response"===this.direction&&void 0!==e.id&&!e.method){const t=e;if(t.result?.serverInfo&&!this.isValidInfo(this.sessionContextStore.getServerInfo())){const e=t.result.serverInfo;this.sessionContextStore.setServerInfo({name:e.name,version:e.version})}}"request"===this.direction&&e.method?this.logger.onRequest(e):"response"===this.direction&&(void 0===e.id||e.method?e.method&&!e.id&&this.logger.onNotification(e):this.logger.onResponse(e))}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);"response"===this.direction&&"2.0"===e.jsonrpc&&this.push(this.buffer.trim()+"\n"),this.handleMessage(e)}catch{"request"===this.direction&&this.push(this.buffer),t("Failed to parse final buffer")}x&&t("Stream closed. Pending requests: "+this.logger.getStats().pending),e()}catch(t){e(t)}}}const T=new class{sessionMap=new Map;sessionCounter=0;conversationMap=new Map;conversationCounter=0;resourceAccessCount=new Map;getOrCreateSessionId(e){if(!this.sessionMap.has(e)){const t=w();this.sessionMap.set(e,t),this.sessionCounter++}return this.sessionMap.get(e)}getOrCreateConversationId(e,t){const s=`${e}-${t}`;return this.conversationMap.has(s)||this.conversationMap.set(s,"conv-"+ ++this.conversationCounter),this.conversationMap.get(s)}trackResourceAccess(e){const t=(this.resourceAccessCount.get(e)||0)+1;return this.resourceAccessCount.set(e,t),t}getResourceAccessCount(e){return this.resourceAccessCount.get(e)||0}clear(){this.sessionMap.clear(),this.conversationMap.clear(),this.resourceAccessCount.clear(),this.sessionCounter=0,this.conversationCounter=0}};class A{pendingRequests=new Map;TIMEOUT=3e4;sessionContextStore;constructor(e){this.sessionContextStore=e}onRequest(e){if(r(e.method)){if(!e.id){const t=c(e.method,e.params,void 0,this.sessionContextStore,void 0);return void a({type:"response",method:e.method,transport:"stdio",source:"cli",params:e.params,contextMetadata:t})}"initialize"===e.method&&e.params?.clientInfo&&this.sessionContextStore.setClientInfo({name:e.params.clientInfo.name,version:e.params.clientInfo.version}),this.pendingRequests.set(e.id,{request:e,timestamp:Date.now()}),setTimeout(()=>this.handleTimeout(e.id),this.TIMEOUT)}}onResponse(e){if(!e.id)return;const t=this.pendingRequests.get(e.id),s=Date.now(),r=t?.request.method||e.method||"unknown";let n=this.sessionContextStore.getServerInfo();"initialize"===t?.request.method&&e.result?.serverInfo&&(n={name:e.result.serverInfo.name,version:e.result.serverInfo.version});const o=c(r,t?.request.params,e.result,this.sessionContextStore,void 0);t?(a({type:"paired",method:r,transport:"stdio",source:"cli",requestId:t.request.id,requestTimestamp:t.timestamp,params:t.request.params,responseId:e.id,responseTimestamp:s,result:e.result,error:e.error,durationMs:s-t.timestamp,contextMetadata:o,serverInfo:n}),this.pendingRequests.delete(e.id)):a({type:"response",method:r,transport:"stdio",source:"cli",responseId:e.id,responseTimestamp:s,result:e.result,error:e.error,contextMetadata:o,serverInfo:n})}onNotification(e){if(!r(e.method))return;const t=c(e.method,e.params,void 0,this.sessionContextStore,void 0);a({type:"response",method:e.method,transport:"stdio",source:"cli",params:e.params,contextMetadata:t})}handleTimeout(e){const t=this.pendingRequests.get(e);if(t&&Date.now()-t.timestamp>this.TIMEOUT){const s=c(t.request.method,t.request.params,void 0,this.sessionContextStore,void 0);a({type:"request",method:t.request.method,transport:"stdio",source:"cli",requestId:t.request.id,requestTimestamp:t.timestamp,params:t.request.params,contextMetadata:s}),this.pendingRequests.delete(e)}}getStats(){return{pending:this.pendingRequests.size}}clear(){this.pendingRequests.clear()}}class O{context={};getClientInfo(){return this.context.clientInfo}setClientInfo(e){this.context.clientInfo=e}getServerInfo(){return this.context.serverInfo}setServerInfo(e){this.context.serverInfo=e}getHeaders(){return this.context.headers}setHeaders(e){this.context.headers=e}snapshot(){return{clientInfo:this.context.clientInfo?{...this.context.clientInfo}:void 0,serverInfo:this.context.serverInfo?{...this.context.serverInfo}:void 0,headers:this.context.headers?{...this.context.headers}:void 0}}}const k=new Set(["npx","node","uvx","python","python3","pip","pipx","deno","bun"]),P=new Set(["uvx","python","python3","pip","pipx"]);m.name("mcp-logger").description("Add observability to any MCP server without code changes").version("1.0.0").requiredOption("-k, --api-key <key>","Aware API key").option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(s,r)=>{try{const o=function(e){const t={};for(const s of e){const e=process.env[s];void 0!==e&&(t[s]=e)}return t}(function(){try{return p("aware mcpenv sync",{encoding:"utf-8",timeout:1e4,stdio:["pipe","pipe","pipe"]}),p("aware mcpenv keys",{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim().split("\n").filter(e=>e.length>0)}catch{return[]}}()),[i,...c]=s,a=function(e,t){const s=function(e){for(const t of e){if(k.has(t))continue;const e=t.match(/^(@[^@]+\/[^@]+)@(.+)$/);if(e)return{packageName:e[1],version:e[2]};const s=t.match(/^([^@]+)@(.+)$/);if(s)return{packageName:s[1],version:s[2]};const r=t.match(/^(@[^@]+\/[^@]+)$/);if(r)return{packageName:r[1],version:"latest"};if(t.match(/^[a-z0-9-]+$/)&&!t.includes("/"))return{packageName:t,version:"latest"}}return null}(t.filter(e=>!e.startsWith("-")));let r,n;if(r=s?.packageName?s.packageName:e.split("/").pop()||e||M.SERVICE_NAME_PREFIX+"-unknown",s&&(n=s.version,"latest"===s.version)){const t=P.has(e)?"pip":"npm",r=function(e,t){try{if("pip"===t){let t=null;for(const s of["pip3","pip"])try{t=p(`${s} index versions ${e}`,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]});break}catch{continue}if(!t)return null;const s=t.match(/\(([^)]+)\)/);if(s)return s[1];const r=t.match(/Available versions:\s*([^\s,]+)/);return r?r[1]:null}{const t=p(`npm view ${e} version --json`,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim().replace(/^["']|["']$/g,""),s=t.match(/^(\d+\.\d+\.\d+(?:-[\w.]+)?)/);return s?s[1]:t}}catch{return null}}(s.packageName,t);r&&(n=r)}return{name:r,version:n}}(i,c);!function(e){R.initialize(e)}({...r,serviceName:a.name,serviceVersion:a.version}),e("Starting MCP server: "+s.join(" ")),e(`Service: ${a.name} = ${a.version||"unknown"}`),function(e,t,s){R.setCLIServerInfo(e,t,s)}(i,c,o);const m=u(i,c,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),d=new O,l=new A(d),h=new q("request",l,d);process.stdin.pipe(h).pipe(m.stdin);const f=new q("response",l,d);m.stdout.pipe(f).pipe(process.stdout),m.on("error",async t=>{e("Failed to start MCP server:",t),l.clear(),await n(),process.exit(1)}),m.on("exit",async(e,s)=>{t(`MCP server exited with code ${e}, signal ${s}`),l.clear(),await n(),process.exit(e||0)});let v=!1;const g=async e=>{v||(v=!0,t(`Received ${e}, shutting down...`),m.kill(e),await Promise.race([new Promise(e=>m.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await n(),process.exit(0))};process.on("SIGTERM",()=>g("SIGTERM")),process.on("SIGINT",()=>g("SIGINT"))}catch(t){e("Fatal error:",t),await n(),process.exit(1)}}),m.parse();
2
+ function e(...e){console.error("[mcp-logger]",...e)}function t(...e){x&&console.error("[mcp-logger]",...e)}function s(...e){E&&console.error("[mcp-logger]",...e)}function r(e){return!N.includes(e)}async function n(){await R.shutdown()}function o(e,t=2e5){try{const s=JSON.stringify(e);return s.length>t?s.slice(0,t)+"... (truncated)":s}catch{return null}}function i(e,t){return e.startsWith("resources/")?{type:e.split("/")[1],uri:t?.uri||t?.path||t?.resource}:e.startsWith("prompts/")?{type:"prompt",uri:t?.name||t?.promptId}:"tools/call"===e?{type:"tool",uri:t?.name}:t?.resourceType&&t?.resourceUri?{type:t.resourceType,uri:t.resourceUri}:{type:void 0,uri:void 0}}function c(e,t,s,r,n){const o=function(e,t){const s=e?.getClientInfo();if(s?.name||s?.version)return{name:s.name,version:s.version};const r=e?.getHeaders()||t;if(r){const e=r["user-agent"]||r["User-Agent"];if(e){const t=e.match(/^([^\/]+)\/([^\s]+)/);if(t)return{name:t[1],version:t[2]}}}return{name:void 0,version:void 0}}(r,n),c=`${o.name||"unknown"}-${o.version||"0"}`,a=T.getOrCreateSessionId(c),p=T.getOrCreateConversationId(a,e),u=function(e,t){return e?.userId||e?.user_id?e.userId||e.user_id:t?t["x-user-id"]||t["X-User-Id"]:void 0}(t,n),m=function(e){if(e?.permission||e?.permissionLevel)return e.permission||e.permissionLevel}(t),d=i(e,t);d.uri&&T.trackResourceAccess(d.uri);const l=function(e,t){return void 0!==e?.cost?e.cost:void 0!==t?.cost?t.cost:void 0}(t,s),h={};return a&&(h.sessionId=a),p&&(h.conversationId=p),u&&(h.userId=u),o.name&&(h.clientName=o.name),o.version&&(h.clientVersion=o.version),m&&(h.permissionLevel=m),void 0!==l&&(h.cost=l),h}function a(e){const t=R.getTracer();if(!t)return;const r="mcp."+e.method;t.startActiveSpan(r,t=>{try{const r={"ingest.type":"mcp","mcp.type":e.type,"mcp.transport":e.transport,"mcp.method":e.method,"mcp.source":e.source},n=e.contextMetadata||c(e.method,e.params,e.result,void 0,e.headers);if(n.sessionId&&n.conversationId&&(r["mcp.trace.id"]=`${n.sessionId}:${n.conversationId}`),"paired"===e.type||"request"===e.type){if(void 0!==e.requestId&&(r["mcp.request.id"]=e.requestId+""),r["mcp.request.method"]=e.method,void 0!==e.requestTimestamp&&(r["mcp.request.timestamp"]=e.requestTimestamp),void 0!==e.params&&(r["mcp.request.params"]=o(e.params)||"{}",r["mcp.request.params.size"]=JSON.stringify(e.params||{}).length),"tools/call"===e.method&&e.params){const t=e.params.name,s=e.params.arguments;if(t&&(r["mcp.tool.name"]=t),s){const e=o(s);e&&(r["mcp.tool.arguments"]=e,r["mcp.tool.arguments.size"]=JSON.stringify(s).length)}}if(e.headers){const t=o(e.headers);t&&(r["mcp.headers"]=t,r["mcp.headers.size"]=JSON.stringify(e.headers).length)}}if(("paired"===e.type||"response"===e.type)&&(void 0!==e.responseId&&(r["mcp.response.id"]=e.responseId+""),r["mcp.response.method"]=e.method,void 0!==e.responseTimestamp&&(r["mcp.response.timestamp"]=e.responseTimestamp),void 0!==e.result)){const t=o(e.result);t&&(r["mcp.response.result"]=t,r["mcp.response.result.size"]=JSON.stringify(e.result).length)}void 0!==e.durationMs&&(r["mcp.duration_ms"]=e.durationMs);const a=R.getCLIServerInfo();a&&(r["mcp.cli"]=JSON.stringify({command:a.command,args:a.args,env:a.env})),e.serverInfo&&(e.serverInfo.name&&(r["mcp.sdk.name"]=e.serverInfo.name),e.serverInfo.version&&(r["mcp.sdk.version"]=e.serverInfo.version)),n.sessionId&&(r["mcp.session.id"]=n.sessionId),n.conversationId&&(r["mcp.conversation.id"]=n.conversationId),n.userId&&(r["mcp.user.id"]=n.userId),n.clientName&&(r["mcp.client.name"]=n.clientName),n.clientVersion&&(r["mcp.client.version"]=n.clientVersion),n.permissionLevel&&(r["mcp.permission.level"]=n.permissionLevel),void 0!==n.cost&&(r["mcp.cost"]=n.cost);const p=function(e,t){const s=i(e,t);if(!s.uri)return{};const r=T.getResourceAccessCount(s.uri);return{resourceType:s.type,resourceUri:s.uri,resourceAccessCount:r}}(e.method,e.params);if(p.resourceType&&(r["mcp.resource.type"]=p.resourceType),p.resourceUri&&(r["mcp.resource.uri"]=p.resourceUri),p.resourceAccessCount&&(r["mcp.resource.access_count"]=p.resourceAccessCount),e.customEvents&&e.customEvents.length>0&&(r["mcp.events"]=JSON.stringify(e.customEvents),r["mcp.events.count"]=e.customEvents.length,e.customEvents.forEach(e=>{t.addEvent("custom."+(e.level||"info"),{message:e.message,metadata:JSON.stringify(e.metadata||{}),timestamp:e.timestamp||Date.now()})})),e.error){let s;r["mcp.error"]=!0,e.error instanceof Error?(s=e.error.message,t.recordException(e.error)):s="string"==typeof e.error?e.error:o(e.error)||"Unknown error",r["mcp.error.message"]=s,t.setStatus({code:v.ERROR,message:s})}else t.setStatus({code:v.OK});!function(e,t){for(const[s,r]of Object.entries(t))null!=r&&e.setAttribute(s,r)}(t,r),s("[OTLP] Span data:",JSON.stringify(r,null,2))}catch{t.setStatus({code:v.ERROR,message:"Error creating span"})}finally{t.end()}})}import{execSync as p,spawn as u}from"child_process";import{program as m}from"commander";import{NodeSDK as d}from"@opentelemetry/sdk-node";import{OTLPTraceExporter as l}from"@opentelemetry/exporter-trace-otlp-http";import{Resource as h}from"@opentelemetry/resources";import{trace as f,SpanStatusCode as v}from"@opentelemetry/api";import{readFileSync as g}from"fs";import{fileURLToPath as I}from"url";import{dirname as y,join as S}from"path";import{Transform as C}from"stream";import{ulid as w}from"ulid";const x="true"===process.env.AWARE_DEBUG||"1"===process.env.AWARE_DEBUG,E="true"===process.env.AWARE_RAW_DEBUG||"1"===process.env.AWARE_RAW_DEBUG,M={ENDPOINT:"https://api.awarecorp.io/traces",SDK_VERSION:function(){try{const e=I(import.meta.url),t=y(e),s=S(t,"..","..","package.json");return JSON.parse(g(s,"utf-8")).version||"1.0.0"}catch{return"1.0.0"}}(),SERVICE_NAME_PREFIX:"mcp-server"},N=["notifications/initialized"],R=new class{sdk=null;tracer=null;initialized=!1;cliServerInfo=null;initialize(e){if(this.initialized)return t("Telemetry already initialized, returning existing tracer"),this.tracer;const s=e.endpoint||M.ENDPOINT,r=e.serviceName||`${M.SERVICE_NAME_PREFIX}-${Math.random().toString(36).slice(2,8)}`,n=e.serviceVersion||"unknown";t("Initializing telemetry..."),t("Endpoint: "+s),t("Service: "+r),t("Version: "+n),this.sdk=new d({resource:new h({"service.name":r,"service.version":n}),traceExporter:new l({url:s,headers:{"x-api-key":e.apiKey}})}),this.sdk.start(),this.tracer=f.getTracer("mcp-logger",M.SDK_VERSION),this.initialized=!0,t("Telemetry initialized successfully");const o=async()=>{t("Shutting down telemetry..."),await this.shutdown()};return process.once("SIGTERM",o),process.once("SIGINT",o),this.tracer}getTracer(){return this.tracer}isInitialized(){return this.initialized}setCLIServerInfo(e,t,s){this.cliServerInfo={command:e,args:t,env:s}}getCLIServerInfo(){return this.cliServerInfo}async shutdown(){if(this.sdk)try{await this.sdk.shutdown(),this.sdk=null,this.tracer=null,this.initialized=!1,this.cliServerInfo=null}catch(e){console.error("[mcp-logger] Error shutting down telemetry:",e)}}};class q extends C{buffer="";direction;logger;sessionContextStore;constructor(e,t,s){super(),this.direction=e,this.logger=t,this.sessionContextStore=s}_transform(e,s,r){try{if("request"===this.direction)return this.push(e),this.buffer+=e.toString(),this.tryParseMessages(),void r();this.buffer+=e.toString(),this.buffer.length>1e6&&(t("Buffer size exceeded, clearing..."),this.buffer=""),this.tryParseAndForwardMessages(),r()}catch(e){r(e)}}tryParseMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const t=JSON.parse(e);s(`[${this.direction}] Raw message:`,e),this.handleMessage(t)}catch{t("Failed to parse:",e.slice(0,100))}}}tryParseAndForwardMessages(){const e=this.buffer.split("\n");this.buffer=e.pop()||"";for(const r of e){const e=r.trim();if(e)try{const r=JSON.parse(e);"2.0"===r.jsonrpc?(this.push(e+"\n"),s(`[${this.direction}] Raw message:`,e),this.handleMessage(r)):t("Non-JSON-RPC JSON ignored:",e.slice(0,100))}catch{t("Non-JSON output filtered:",e.slice(0,100))}}}isValidInfo(e){return!(!e?.name||!e?.version)}handleMessage(e){if("request"===this.direction&&"initialize"===e.method&&e.params?.clientInfo&&!this.isValidInfo(this.sessionContextStore.getClientInfo())){const t=e.params;this.sessionContextStore.setClientInfo({name:t.clientInfo?.name,version:t.clientInfo?.version})}if("response"===this.direction&&void 0!==e.id&&!e.method){const t=e;if(t.result?.serverInfo&&!this.isValidInfo(this.sessionContextStore.getServerInfo())){const e=t.result.serverInfo;this.sessionContextStore.setServerInfo({name:e.name,version:e.version})}}"request"===this.direction&&e.method?this.logger.onRequest(e):"response"===this.direction&&(void 0===e.id||e.method?e.method&&null==e.id&&this.logger.onNotification(e):this.logger.onResponse(e))}_flush(e){try{if(this.buffer.trim())try{const e=JSON.parse(this.buffer);"response"===this.direction&&"2.0"===e.jsonrpc&&this.push(this.buffer.trim()+"\n"),this.handleMessage(e)}catch{"request"===this.direction&&this.push(this.buffer),t("Failed to parse final buffer")}x&&t("Stream closed. Pending requests: "+this.logger.getStats().pending),e()}catch(t){e(t)}}}const T=new class{sessionMap=new Map;sessionCounter=0;conversationMap=new Map;conversationCounter=0;resourceAccessCount=new Map;getOrCreateSessionId(e){if(!this.sessionMap.has(e)){const t=w();this.sessionMap.set(e,t),this.sessionCounter++}return this.sessionMap.get(e)}getOrCreateConversationId(e,t){const s=`${e}-${t}`;return this.conversationMap.has(s)||this.conversationMap.set(s,"conv-"+ ++this.conversationCounter),this.conversationMap.get(s)}trackResourceAccess(e){const t=(this.resourceAccessCount.get(e)||0)+1;return this.resourceAccessCount.set(e,t),t}getResourceAccessCount(e){return this.resourceAccessCount.get(e)||0}clear(){this.sessionMap.clear(),this.conversationMap.clear(),this.resourceAccessCount.clear(),this.sessionCounter=0,this.conversationCounter=0}};class A{pendingRequests=new Map;TIMEOUT=3e4;sessionContextStore;constructor(e){this.sessionContextStore=e}onRequest(e){if(r(e.method)){if(null==e.id){const t=c(e.method,e.params,void 0,this.sessionContextStore,void 0);return void a({type:"response",method:e.method,transport:"stdio",source:"cli",params:e.params,contextMetadata:t})}"initialize"===e.method&&e.params?.clientInfo&&this.sessionContextStore.setClientInfo({name:e.params.clientInfo.name,version:e.params.clientInfo.version}),this.pendingRequests.set(e.id,{request:e,timestamp:Date.now()}),setTimeout(()=>this.handleTimeout(e.id),this.TIMEOUT)}}onResponse(e){if(null==e.id)return;const t=this.pendingRequests.get(e.id),s=Date.now(),r=t?.request.method||e.method||"unknown";let n=this.sessionContextStore.getServerInfo();"initialize"===t?.request.method&&e.result?.serverInfo&&(n={name:e.result.serverInfo.name,version:e.result.serverInfo.version});const o=c(r,t?.request.params,e.result,this.sessionContextStore,void 0);t?(a({type:"paired",method:r,transport:"stdio",source:"cli",requestId:t.request.id,requestTimestamp:t.timestamp,params:t.request.params,responseId:e.id,responseTimestamp:s,result:e.result,error:e.error,durationMs:s-t.timestamp,contextMetadata:o,serverInfo:n}),this.pendingRequests.delete(e.id)):a({type:"response",method:r,transport:"stdio",source:"cli",responseId:e.id,responseTimestamp:s,result:e.result,error:e.error,contextMetadata:o,serverInfo:n})}onNotification(e){if(!r(e.method))return;const t=c(e.method,e.params,void 0,this.sessionContextStore,void 0);a({type:"response",method:e.method,transport:"stdio",source:"cli",params:e.params,contextMetadata:t})}handleTimeout(e){const t=this.pendingRequests.get(e);if(t&&Date.now()-t.timestamp>this.TIMEOUT){const s=c(t.request.method,t.request.params,void 0,this.sessionContextStore,void 0);a({type:"request",method:t.request.method,transport:"stdio",source:"cli",requestId:t.request.id,requestTimestamp:t.timestamp,params:t.request.params,contextMetadata:s}),this.pendingRequests.delete(e)}}getStats(){return{pending:this.pendingRequests.size}}clear(){this.pendingRequests.clear()}}class O{context={};getClientInfo(){return this.context.clientInfo}setClientInfo(e){this.context.clientInfo=e}getServerInfo(){return this.context.serverInfo}setServerInfo(e){this.context.serverInfo=e}getHeaders(){return this.context.headers}setHeaders(e){this.context.headers=e}snapshot(){return{clientInfo:this.context.clientInfo?{...this.context.clientInfo}:void 0,serverInfo:this.context.serverInfo?{...this.context.serverInfo}:void 0,headers:this.context.headers?{...this.context.headers}:void 0}}}const k=new Set(["npx","node","uvx","python","python3","pip","pipx","deno","bun"]),P=new Set(["uvx","python","python3","pip","pipx"]);m.name("mcp-logger").description("Add observability to any MCP server without code changes").version("1.0.0").requiredOption("-k, --api-key <key>","Aware API key").option("-e, --endpoint <url>","Custom OTLP endpoint").argument("<command...>","MCP server command to wrap").action(async(s,r)=>{try{const o=function(e){const t={};for(const s of e){const e=process.env[s];void 0!==e&&(t[s]=e)}return t}(function(){try{return p("aware mcpenv sync",{encoding:"utf-8",timeout:1e4,stdio:["pipe","pipe","pipe"]}),p("aware mcpenv keys",{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim().split("\n").filter(e=>e.length>0)}catch{return[]}}()),[i,...c]=s,a=function(e,t){const s=function(e){for(const t of e){if(k.has(t))continue;const e=t.match(/^(@[^@]+\/[^@]+)@(.+)$/);if(e)return{packageName:e[1],version:e[2]};const s=t.match(/^([^@]+)@(.+)$/);if(s)return{packageName:s[1],version:s[2]};const r=t.match(/^(@[^@]+\/[^@]+)$/);if(r)return{packageName:r[1],version:"latest"};if(t.match(/^[a-z0-9-]+$/)&&!t.includes("/"))return{packageName:t,version:"latest"}}return null}(t.filter(e=>!e.startsWith("-")));let r,n;if(r=s?.packageName?s.packageName:e.split("/").pop()||e||M.SERVICE_NAME_PREFIX+"-unknown",s&&(n=s.version,"latest"===s.version)){const t=P.has(e)?"pip":"npm",r=function(e,t){try{if("pip"===t){let t=null;for(const s of["pip3","pip"])try{t=p(`${s} index versions ${e}`,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]});break}catch{continue}if(!t)return null;const s=t.match(/\(([^)]+)\)/);if(s)return s[1];const r=t.match(/Available versions:\s*([^\s,]+)/);return r?r[1]:null}{const t=p(`npm view ${e} version --json`,{encoding:"utf-8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim().replace(/^["']|["']$/g,""),s=t.match(/^(\d+\.\d+\.\d+(?:-[\w.]+)?)/);return s?s[1]:t}}catch{return null}}(s.packageName,t);r&&(n=r)}return{name:r,version:n}}(i,c);!function(e){R.initialize(e)}({...r,serviceName:a.name,serviceVersion:a.version}),e("Starting MCP server: "+s.join(" ")),e(`Service: ${a.name} = ${a.version||"unknown"}`),function(e,t,s){R.setCLIServerInfo(e,t,s)}(i,c,o);const m=u(i,c,{stdio:["pipe","pipe","inherit"],env:{...process.env,MCP_LOGGER_ENABLED:"true"}}),d=new O,l=new A(d),h=new q("request",l,d);process.stdin.pipe(h).pipe(m.stdin);const f=new q("response",l,d);m.stdout.pipe(f).pipe(process.stdout),m.on("error",async t=>{e("Failed to start MCP server:",t),l.clear(),await n(),process.exit(1)}),m.on("exit",async(e,s)=>{t(`MCP server exited with code ${e}, signal ${s}`),l.clear(),await n(),process.exit(e||0)});let v=!1;const g=async e=>{v||(v=!0,t(`Received ${e}, shutting down...`),m.kill(e),await Promise.race([new Promise(e=>m.once("exit",e)),new Promise(e=>setTimeout(e,5e3))]),await n(),process.exit(0))};process.on("SIGTERM",()=>g("SIGTERM")),process.on("SIGINT",()=>g("SIGINT"))}catch(t){e("Fatal error:",t),await n(),process.exit(1)}}),m.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awarecorp/mcp-logger",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "Unified MCP observability solution - SDK for code integration and CLI for zero-config wrapping",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",