browser-devtools-mcp 0.3.1 → 0.3.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/README.md +17 -24
- package/dist/cli/runner.js +1 -1
- package/dist/core-24BPAITI.js +5 -0
- package/dist/{core-RRWTV5B5.js → core-BD5K3BG4.js} +19 -34
- package/dist/{core-LAPVCKSF.js → core-GW4S4YVO.js} +1 -1
- package/dist/{core-DOXUXYCD.js → core-PF26XSPV.js} +1 -1
- package/dist/daemon-server.js +1 -1
- package/dist/index.js +2 -2
- package/dist/platform/browser/tools/index.d.ts +2 -1
- package/dist/platform/node/index.d.ts +1 -1
- package/dist/platform/node/tools/index.d.ts +3 -2
- package/dist/platform/types.d.ts +3 -2
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/tools/types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/core-PIEDD6UN.js +0 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DEFAULT_NODE_DEBUG_CONFIG,ProbeKind,addWatchExpression,clearProbes,clearSnapshots,clearSnapshotsByProbe,clearWatchExpressions,createProbe,detachDebugging,disableDebugging,enableDebugging,evaluateInNode,getConsoleMessages,getExceptionBreakpoint,getOriginalSources,getScripts,getSnapshotStats,getSnapshots,getSnapshotsByProbe,getStore,getStoreKey,hasSourceMaps,isDebuggingEnabled,listProbes,listStoreKeys,listWatchExpressions,loadSourceMaps,removeProbe,removeWatchExpression,resolveSourceLocation,setExceptionBreakpoint}from"./core-
|
|
1
|
+
import{DEFAULT_NODE_DEBUG_CONFIG,ProbeKind,addWatchExpression,clearProbes,clearSnapshots,clearSnapshotsByProbe,clearWatchExpressions,createProbe,detachDebugging,disableDebugging,enableDebugging,evaluateInNode,getConsoleMessages,getExceptionBreakpoint,getOriginalSources,getScripts,getSnapshotStats,getSnapshots,getSnapshotsByProbe,getStore,getStoreKey,hasSourceMaps,isDebuggingEnabled,listProbes,listStoreKeys,listWatchExpressions,loadSourceMaps,removeProbe,removeWatchExpression,resolveSourceLocation,setExceptionBreakpoint}from"./core-PF26XSPV.js";export{DEFAULT_NODE_DEBUG_CONFIG,ProbeKind,addWatchExpression,clearProbes,clearSnapshots,clearSnapshotsByProbe,clearWatchExpressions,createProbe,detachDebugging,disableDebugging,enableDebugging,evaluateInNode,getConsoleMessages,getExceptionBreakpoint,getOriginalSources,getScripts,getSnapshotStats,getSnapshots,getSnapshotsByProbe,getStore,getStoreKey,hasSourceMaps,isDebuggingEnabled,listProbes,listStoreKeys,listWatchExpressions,loadSourceMaps,removeProbe,removeWatchExpression,resolveSourceLocation,setExceptionBreakpoint};
|
|
@@ -12,4 +12,4 @@ try {
|
|
|
12
12
|
}
|
|
13
13
|
})()`;try{let result=await session.send("Runtime.evaluate",{expression,returnByValue:!0,timeout:500});if(result.exceptionDetails)return null;let value=result.result?.value;if(value&&typeof value.traceId=="string"&&typeof value.spanId=="string")return{traceId:value.traceId,spanId:value.spanId}}catch{}return null}function _createNodeSourceMapFetcher(){return async(url,baseUrl)=>{let filePath=null;if(url.startsWith("file://"))filePath=url.replace("file://","");else if(url.startsWith("/")||url.match(/^[a-zA-Z]:\\/))filePath=url;else if(baseUrl.startsWith("file://")){let baseDir=path2.dirname(baseUrl.replace("file://",""));filePath=path2.resolve(baseDir,url)}else if(!url.startsWith("http://")&&!url.startsWith("https://")){let baseDir=path2.dirname(baseUrl.replace(/^file:\/\//,""));filePath=path2.resolve(baseDir,url)}if(filePath)try{return fs.readFileSync(filePath,"utf-8")}catch{}return url.startsWith("http://")||url.startsWith("https://")?new Promise(resolve2=>{let req=(url.startsWith("https://")?https:http).get(url,res=>{if(res.statusCode!==200){resolve2(null);return}let data="";res.on("data",chunk=>{data+=chunk}),res.on("end",()=>resolve2(data))});req.on("error",()=>resolve2(null)),req.setTimeout(5e3,()=>{req.destroy(),resolve2(null)})}):null}}async function _captureScopes(v8Api,callFrame,maxDepth=3){let scopeSnapshots=[];for(let scope of callFrame.scopeChain)if(!(scope.type==="global"||scope.type==="script"||scope.type==="with"||scope.type==="eval"||scope.type==="wasm-expression-stack")){if(scopeSnapshots.length>=maxDepth)break;try{let variables=await v8Api.getScopeVariables(scope),scopeVariables=[];for(let[name,value]of Object.entries(variables))scopeVariables.push({name,value,type:typeof value});scopeSnapshots.push({type:scope.type,name:scope.name,variables:scopeVariables})}catch{}}return scopeSnapshots}async function _callFrameToSnapshot(v8Api,frame,captureScopes,sourceMapResolver){let scopes=[];captureScopes&&(scopes=await _captureScopes(v8Api,frame));let originalLocation;if(sourceMapResolver){let resolved=sourceMapResolver.generatedToOriginal(frame.location.scriptId,frame.location.lineNumber,frame.location.columnNumber??0);resolved&&(originalLocation={source:resolved.source,line:resolved.line+1,column:resolved.column!==void 0?resolved.column+1:void 0,name:resolved.name})}return{functionName:frame.functionName||"(anonymous)",url:frame.url||"",lineNumber:frame.location.lineNumber+1,columnNumber:frame.location.columnNumber!==void 0?frame.location.columnNumber+1:void 0,scriptId:frame.location.scriptId,scopes,originalLocation}}function _convertAsyncStackTrace(asyncTrace,sourceMapResolver,maxSegments,maxFramesPerSegment){if(!asyncTrace)return;let maxSegs=maxSegments??DEFAULT_NODE_DEBUG_CONFIG.maxAsyncStackSegments,maxFrames=maxFramesPerSegment??DEFAULT_NODE_DEBUG_CONFIG.maxFramesPerAsyncSegment,segments=[],currentTrace=asyncTrace,segmentCount=0;for(;currentTrace&&segmentCount<maxSegs;){let asyncFrames=[];for(let frame of currentTrace.callFrames.slice(0,maxFrames)){let originalLocation;if(sourceMapResolver){let resolved=sourceMapResolver.generatedToOriginal(frame.location.scriptId,frame.location.lineNumber,frame.location.columnNumber??0);resolved&&(originalLocation={source:resolved.source,line:resolved.line+1,column:resolved.column!==void 0?resolved.column+1:void 0,name:resolved.name})}asyncFrames.push({functionName:frame.functionName||"(anonymous)",url:frame.url||"",lineNumber:frame.location.lineNumber+1,columnNumber:frame.location.columnNumber!==void 0?frame.location.columnNumber+1:void 0,originalLocation})}asyncFrames.length>0&&segments.push({description:currentTrace.description,callFrames:asyncFrames}),currentTrace=currentTrace.parent,segmentCount++}return segments.length===0?void 0:{segments}}async function _evaluateWatchExpressionsOnFrame(v8Api,callFrameId,watchExpressions){let results={};for(let watch of watchExpressions.values())try{let result=await v8Api.evaluateOnCallFrame(callFrameId,watch.expression);result.exceptionDetails?results[watch.expression]=`[Error: ${result.exceptionDetails.text||"Evaluation failed"}]`:results[watch.expression]=await v8Api.extractValueDeep(result.result,2)}catch(e){results[watch.expression]=`[Error: ${e.message||"Unknown error"}]`}return results}function getStoreKey(pid,wsUrl){return wsUrl||`pid:${pid}`}async function enableDebugging(storeKey,session,processInfo,v8Options,config){if(STORES.get(storeKey)?.enabled)return;let v8Api=new V8Api(session,v8Options),sourceMapResolver=new SourceMapResolver(_createNodeSourceMapFetcher()),mergedConfig={...DEFAULT_NODE_DEBUG_CONFIG,...config},store={v8Api,sourceMapResolver,probes:new Map,locationIndex:new Map,watchExpressions:new Map,snapshots:[],snapshotSequence:0,consoleMessages:[],consoleMessageSequence:0,config:mergedConfig,enabled:!1,sourceMapsLoaded:!1,exceptionBreakpoint:"none",session,processInfo};STORES.set(storeKey,store),await store.v8Api.enable(),session.on("Runtime.consoleAPICalled",params=>{let type=params.type||"log",args=params.args||[],text=_cdpArgsToText(args),levelName=_cdpConsoleTypeToLevelName(type),levelCode=ConsoleMessageLevel[levelName]?.code??1,location,stackTrace=params.stackTrace;if(stackTrace?.callFrames?.length){let frame=stackTrace.callFrames[0];location={url:frame.url||"",lineNumber:frame.lineNumber??0,columnNumber:frame.columnNumber??0}}store.consoleMessageSequence++,store.consoleMessages.push({type,text,level:{name:levelName,code:levelCode},location,timestamp:Date.now(),sequenceNumber:store.consoleMessageSequence}),store.consoleMessages.length>NODE_CONSOLE_MESSAGES_BUFFER_SIZE&&store.consoleMessages.splice(0,store.consoleMessages.length-NODE_CONSOLE_MESSAGES_BUFFER_SIZE)}),store.v8Api.on("scriptParsed",script=>{store.sourceMapResolver.registerScript(script)});for(let script of store.v8Api.getScripts())store.sourceMapResolver.registerScript(script);store.v8Api.on("paused",async event=>{let startTime=Date.now();try{let isException=event.reason==="exception"||event.reason==="promiseRejection",hitBreakpointIds=event.hitBreakpoints||[],hitProbes=[];for(let probe of store.probes.values()){if(!probe.enabled)continue;if(probe.v8BreakpointIds.some(id=>hitBreakpointIds.includes(id))){let conditionMet=!0;probe.hitCondition&&(conditionMet=_evaluateHitCondition(probe.hitCondition,probe.hitCount+1)),hitProbes.push({probe,conditionMet})}}let shouldCaptureBreakpoint=hitProbes.some(p=>p.conditionMet),shouldCaptureException=isException&&store.exceptionBreakpoint!=="none";for(let{probe}of hitProbes)probe.hitCount++,probe.lastHitAt=Date.now();if((shouldCaptureBreakpoint||shouldCaptureException)&&event.callFrames.length>0){let topFrame=event.callFrames[0],exceptionInfo;if(isException&&event.data){let excData=event.data;exceptionInfo={type:event.reason==="promiseRejection"?"promiseRejection":"exception",message:excData.description||excData.value||String(excData),name:excData.className,stack:excData.description}}let originalLocation,resolvedLoc=store.sourceMapResolver.generatedToOriginal(topFrame.location.scriptId,topFrame.location.lineNumber,topFrame.location.columnNumber??0);resolvedLoc&&(originalLocation={source:resolvedLoc.source,line:resolvedLoc.line+1,column:resolvedLoc.column!==void 0?resolvedLoc.column+1:void 0,name:resolvedLoc.name});let callStackFull=[],framesToProcess=event.callFrames.slice(0,store.config.maxCallStackDepth);for(let i=0;i<framesToProcess.length;i++)callStackFull.push(await _callFrameToSnapshot(store.v8Api,framesToProcess[i],i<store.config.maxFramesWithScopes,store.sourceMapResolver));let asyncStackTraceFull=_convertAsyncStackTrace(event.asyncStackTrace,store.sourceMapResolver,store.config.maxAsyncStackSegments,store.config.maxFramesPerAsyncSegment),traceContext=await _getOtelTraceContext(store.session),probesToCapture=hitProbes.filter(p=>p.conditionMet),snapshotCount=probesToCapture.length+(shouldCaptureException?1:0);for(let s=0;s<snapshotCount;s++){let probeId,isLogpoint,logResult,callStack,asyncStackTrace,watchResults;if(s<probesToCapture.length){let{probe}=probesToCapture[s];if(probeId=probe.id,isLogpoint=probe.kind==="logpoint",isLogpoint&&probe.logExpression)try{let evalResult=await store.v8Api.evaluateOnCallFrame(topFrame.callFrameId,probe.logExpression,{returnByValue:!0,generatePreview:!0});logResult=store.v8Api.extractValue(evalResult.result)}catch{logResult="[evaluation error]"}else logResult=void 0;isLogpoint?(callStack=[],asyncStackTrace=void 0,watchResults=void 0):(callStack=callStackFull,asyncStackTrace=asyncStackTraceFull,watchResults=store.watchExpressions.size>0?await _evaluateWatchExpressionsOnFrame(store.v8Api,topFrame.callFrameId,store.watchExpressions):void 0)}else probeId="__exception__",isLogpoint=!1,logResult=void 0,callStack=callStackFull,asyncStackTrace=asyncStackTraceFull,watchResults=store.watchExpressions.size>0?await _evaluateWatchExpressionsOnFrame(store.v8Api,topFrame.callFrameId,store.watchExpressions):void 0;let snapshot={id:_generateId(),probeId,timestamp:Date.now(),sequenceNumber:++store.snapshotSequence,url:topFrame.url||"",lineNumber:topFrame.location.lineNumber+1,columnNumber:topFrame.location.columnNumber!==void 0?topFrame.location.columnNumber+1:void 0,originalLocation,exception:exceptionInfo,callStack,asyncStackTrace,logResult,watchResults,captureTimeMs:Date.now()-startTime,traceContext:traceContext??void 0};store.snapshots.push(snapshot)}store.snapshots.length>store.config.maxSnapshots&&store.snapshots.splice(0,store.snapshots.length-store.config.maxSnapshots)}}finally{await store.v8Api.resume()}}),store.enabled=!0,store.sourceMapResolver.loadAllSourceMaps().then(()=>{store.sourceMapsLoaded=!0}).catch(()=>{})}async function disableDebugging(storeKey){let store=STORES.get(storeKey);if(store?.enabled){for(let entry of store.locationIndex.values())try{await store.v8Api.removeBreakpoint(entry.breakpointId)}catch{}store.locationIndex.clear(),store.probes.clear(),store.snapshots.length=0,store.snapshotSequence=0,store.consoleMessages.length=0,store.consoleMessageSequence=0,await store.v8Api.disable(),store.enabled=!1}}async function detachDebugging(storeKey){let store=STORES.get(storeKey);store&&(await disableDebugging(storeKey),await store.v8Api.detach(),STORES.delete(storeKey))}function isDebuggingEnabled(storeKey){return STORES.get(storeKey)?.enabled??!1}function getStore(storeKey){return STORES.get(storeKey)}async function resolveSourceLocation(storeKey,url,line,column=1){let store=STORES.get(storeKey);if(!store?.enabled)throw new Error("Not connected to Node.js process or debugging not enabled. Call debug_connect first.");let resolved=await store.sourceMapResolver.resolveLocationByUrl(url,line,column);return resolved?{source:resolved.source,line:resolved.line+1,column:resolved.column+1,name:resolved.name}:null}function listStoreKeys(){return Array.from(STORES.keys())}async function setExceptionBreakpoint(storeKey,state){let store=STORES.get(storeKey);if(!store?.enabled)throw new Error("Debugging is not enabled");await store.v8Api.setPauseOnExceptions(state),store.exceptionBreakpoint=state}function getExceptionBreakpoint(storeKey){return STORES.get(storeKey)?.exceptionBreakpoint??"none"}async function createProbe(storeKey,options){let store=STORES.get(storeKey);if(!store?.enabled)throw new Error("Debugging is not enabled");let probeId=_generateId(),columnForKey=_columnForLocationKey(options.columnNumber),locationKey=_locationKey(options.urlPattern,options.lineNumber,columnForKey),existingEntry=store.locationIndex.get(locationKey);if(existingEntry){existingEntry.refCount++;let probe2={id:probeId,kind:options.kind,enabled:!0,urlPattern:options.urlPattern,lineNumber:options.lineNumber,columnNumber:options.columnNumber,condition:options.condition,logExpression:options.logExpression,hitCondition:options.hitCondition,v8BreakpointIds:[existingEntry.breakpointId],resolvedLocations:existingEntry.resolvedLocations,hitCount:0,createdAt:Date.now()};return store.probes.set(probeId,probe2),probe2}let fullCondition=options.condition?`(${options.condition})`:"true",line0based=options.lineNumber-1,column0based=columnForKey-1,resolved=store.sourceMapResolver.originalToGenerated(options.urlPattern,line0based,column0based),breakpointId,resolvedLocationsCount=0;if(resolved)try{breakpointId=(await store.v8Api.setBreakpoint({scriptId:resolved.scriptId,lineNumber:resolved.location.line,columnNumber:resolved.location.column},fullCondition)).breakpointId,resolvedLocationsCount=1}catch{let scriptUrl=store.sourceMapResolver.getScriptUrl(resolved.scriptId);if(scriptUrl){let result=await store.v8Api.setBreakpointByUrl({url:scriptUrl,lineNumber:resolved.location.line,columnNumber:resolved.location.column,condition:fullCondition});breakpointId=result.breakpointId,resolvedLocationsCount=result.locations.length}else throw new Error("Failed to set breakpoint at resolved location and could not fall back (script URL unknown). A probe may already exist at this line; remove it first or use a different line.")}else{let urlRegex=options.urlPattern.replace(/\\([.*+?^${}()|[\]\\/-])/g,"$1").replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/\\\*/g,".*").replace(/\\\?/g,"."),result=await store.v8Api.setBreakpointByUrl({urlRegex,lineNumber:options.lineNumber-1,columnNumber:options.columnNumber!=null?Math.max(0,options.columnNumber-1):void 0,condition:fullCondition});breakpointId=result.breakpointId,resolvedLocationsCount=result.locations.length}store.locationIndex.set(locationKey,{breakpointId,resolvedLocations:resolvedLocationsCount,refCount:1});let probe={id:probeId,kind:options.kind,enabled:!0,urlPattern:options.urlPattern,lineNumber:options.lineNumber,columnNumber:options.columnNumber,condition:options.condition,logExpression:options.logExpression,hitCondition:options.hitCondition,v8BreakpointIds:[breakpointId],resolvedLocations:resolvedLocationsCount,hitCount:0,createdAt:Date.now()};return store.probes.set(probeId,probe),probe}async function removeProbe(storeKey,probeId){let store=STORES.get(storeKey);if(!store)return!1;let probe=store.probes.get(probeId);if(!probe)return!1;let locationKey=_locationKey(probe.urlPattern,probe.lineNumber,_columnForLocationKey(probe.columnNumber)),entry=store.locationIndex.get(locationKey);if(entry&&(entry.refCount--,entry.refCount===0)){try{await store.v8Api.removeBreakpoint(entry.breakpointId)}catch{}store.locationIndex.delete(locationKey)}return store.probes.delete(probeId),!0}function listProbes(storeKey){return Array.from(STORES.get(storeKey)?.probes.values()??[])}async function clearProbes(storeKey){let store=STORES.get(storeKey);if(!store)return 0;let count=store.probes.size;for(let entry of store.locationIndex.values())try{await store.v8Api.removeBreakpoint(entry.breakpointId)}catch{}return store.locationIndex.clear(),store.probes.clear(),count}function getSnapshots(storeKey){return[...STORES.get(storeKey)?.snapshots??[]]}function getSnapshotsByProbe(storeKey,probeId){return(STORES.get(storeKey)?.snapshots??[]).filter(s=>s.probeId===probeId)}function clearSnapshots(storeKey){let store=STORES.get(storeKey);if(!store)return 0;let count=store.snapshots.length;return store.snapshots.length=0,count}function clearSnapshotsByProbe(storeKey,probeId){let store=STORES.get(storeKey);if(!store)return 0;let before=store.snapshots.length;return store.snapshots=store.snapshots.filter(s=>s.probeId!==probeId),before-store.snapshots.length}function getSnapshotStats(storeKey){let store=STORES.get(storeKey);if(!store||store.snapshots.length===0)return{totalSnapshots:0,snapshotsByProbe:{},averageCaptureTimeMs:0};let snapshotsByProbe={},totalCaptureTime=0;for(let s of store.snapshots)snapshotsByProbe[s.probeId]=(snapshotsByProbe[s.probeId]||0)+1,totalCaptureTime+=s.captureTimeMs;return{totalSnapshots:store.snapshots.length,snapshotsByProbe,oldestTimestamp:store.snapshots[0].timestamp,newestTimestamp:store.snapshots[store.snapshots.length-1].timestamp,averageCaptureTimeMs:totalCaptureTime/store.snapshots.length}}function getConsoleMessages(storeKey){return[...STORES.get(storeKey)?.consoleMessages??[]]}function addWatchExpression(storeKey,expression){let store=STORES.get(storeKey);if(!store)throw new Error("Debug store not initialized");let id=_generateId(),watchExpr={id,expression,createdAt:Date.now()};return store.watchExpressions.set(id,watchExpr),watchExpr}function removeWatchExpression(storeKey,watchExpressionId){return STORES.get(storeKey)?.watchExpressions.delete(watchExpressionId)??!1}function listWatchExpressions(storeKey){return Array.from(STORES.get(storeKey)?.watchExpressions.values()??[])}function clearWatchExpressions(storeKey){let store=STORES.get(storeKey);if(!store)return 0;let count=store.watchExpressions.size;return store.watchExpressions.clear(),count}async function loadSourceMaps(storeKey){let store=STORES.get(storeKey);if(!store?.enabled)throw new Error("Debugging is not enabled");let loaded=await store.sourceMapResolver.loadAllSourceMaps(),sources=store.sourceMapResolver.getOriginalSources();return store.sourceMapsLoaded=!0,{loaded,sources}}function hasSourceMaps(storeKey){return STORES.get(storeKey)?.sourceMapResolver.hasSourceMaps()??!1}function getOriginalSources(storeKey){return STORES.get(storeKey)?.sourceMapResolver.getOriginalSources()??[]}function getScripts(storeKey){return STORES.get(storeKey)?.v8Api.getScripts()??[]}function wrapScriptInAsyncIIFE(script){return`(async function() { ${script} })()`}function formatEvaluationException(exc){let parts=[],text=exc.text??exc.description;text&&parts.push(text);let desc=exc.exception?.description;desc&&desc!==text&&parts.push(desc);let className=exc.exception?.className;className&&!parts.some(p=>p.includes(className))&&parts.push(className),parts.length===0&&parts.push(JSON.stringify(exc));let stack=exc.stackTrace?.callFrames?.slice(0,5).map(f=>` at ${f.functionName??"?"} (${f.url??"?"}:${f.lineNumber??0})`).join(`
|
|
14
14
|
`);return stack&&parts.push(`Stack:
|
|
15
|
-
${stack}`),parts.join(" \u2014 ")}async function evaluateInNode(storeKey,expression,timeoutMs=5e3){let store=STORES.get(storeKey);if(!store?.enabled)throw new Error("Not connected to Node.js process or debugging not enabled. Call debug_connect first.");let codeToRun=wrapScriptInAsyncIIFE(expression),result=await store.session.send("Runtime.evaluate",{expression:codeToRun,returnByValue:!0,awaitPromise:!0,timeout:Math.min(timeoutMs,3e4)});if(result.exceptionDetails){let exc=result.exceptionDetails,message=formatEvaluationException(exc);throw new Error(`Node evaluation failed: ${message}`)}return store.v8Api.extractValue(result.result)}export{PORT,TOOL_OUTPUT_SCHEMA_DISABLE,AVAILABLE_TOOL_DOMAINS,PLATFORM,SESSION_IDLE_SECONDS,SESSION_IDLE_CHECK_SECONDS,SESSION_CLOSE_ON_SOCKET_CLOSE,BROWSER_HEADLESS_ENABLE,BROWSER_PERSISTENT_ENABLE,BROWSER_PERSISTENT_USER_DATA_DIR,BROWSER_USE_INSTALLED_ON_SYSTEM,BROWSER_EXECUTABLE_PATH,BROWSER_LOCALE,BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE,BROWSER_HTTP_REQUESTS_BUFFER_SIZE,NODE_CONSOLE_MESSAGES_BUFFER_SIZE,NODE_INSPECTOR_HOST,OTEL_ENABLE,OTEL_SERVICE_NAME,OTEL_SERVICE_VERSION,OTEL_ASSETS_DIR,OTEL_INSTRUMENTATION_USER_INTERACTION_EVENTS,OTEL_EXPORTER_TYPE,OTEL_EXPORTER_HTTP_URL,OTEL_EXPORTER_HTTP_HEADERS,AWS_REGION,AWS_PROFILE,AMAZON_BEDROCK_ENABLE,AMAZON_BEDROCK_IMAGE_EMBED_MODEL_ID,AMAZON_BEDROCK_TEXT_EMBED_MODEL_ID,AMAZON_BEDROCK_VISION_MODEL_ID,FIGMA_ACCESS_TOKEN,FIGMA_API_BASE_URL,DAEMON_PORT,DAEMON_SESSION_IDLE_SECONDS,DAEMON_SESSION_IDLE_CHECK_SECONDS,enable,disable,isDebugEnabled,debug,info,warn,error,toJson,V8Api,SourceMapResolver,
|
|
15
|
+
${stack}`),parts.join(" \u2014 ")}async function evaluateInNode(storeKey,expression,timeoutMs=5e3){let store=STORES.get(storeKey);if(!store?.enabled)throw new Error("Not connected to Node.js process or debugging not enabled. Call debug_connect first.");let codeToRun=wrapScriptInAsyncIIFE(expression),result=await store.session.send("Runtime.evaluate",{expression:codeToRun,returnByValue:!0,awaitPromise:!0,timeout:Math.min(timeoutMs,3e4)});if(result.exceptionDetails){let exc=result.exceptionDetails,message=formatEvaluationException(exc);throw new Error(`Node evaluation failed: ${message}`)}return store.v8Api.extractValue(result.result)}export{PORT,TOOL_OUTPUT_SCHEMA_DISABLE,AVAILABLE_TOOL_DOMAINS,PLATFORM,SESSION_IDLE_SECONDS,SESSION_IDLE_CHECK_SECONDS,SESSION_CLOSE_ON_SOCKET_CLOSE,BROWSER_HEADLESS_ENABLE,BROWSER_PERSISTENT_ENABLE,BROWSER_PERSISTENT_USER_DATA_DIR,BROWSER_USE_INSTALLED_ON_SYSTEM,BROWSER_EXECUTABLE_PATH,BROWSER_LOCALE,BROWSER_CONSOLE_MESSAGES_BUFFER_SIZE,BROWSER_HTTP_REQUESTS_BUFFER_SIZE,NODE_CONSOLE_MESSAGES_BUFFER_SIZE,NODE_INSPECTOR_HOST,OTEL_ENABLE,OTEL_SERVICE_NAME,OTEL_SERVICE_VERSION,OTEL_ASSETS_DIR,OTEL_INSTRUMENTATION_USER_INTERACTION_EVENTS,OTEL_EXPORTER_TYPE,OTEL_EXPORTER_HTTP_URL,OTEL_EXPORTER_HTTP_HEADERS,AWS_REGION,AWS_PROFILE,AMAZON_BEDROCK_ENABLE,AMAZON_BEDROCK_IMAGE_EMBED_MODEL_ID,AMAZON_BEDROCK_TEXT_EMBED_MODEL_ID,AMAZON_BEDROCK_VISION_MODEL_ID,FIGMA_ACCESS_TOKEN,FIGMA_API_BASE_URL,DAEMON_PORT,DAEMON_SESSION_IDLE_SECONDS,DAEMON_SESSION_IDLE_CHECK_SECONDS,enable,disable,isDebugEnabled,debug,info,warn,error,toJson,ConsoleMessageLevelName,ConsoleMessageLevel,V8Api,SourceMapResolver,ProbeKind,DEFAULT_NODE_DEBUG_CONFIG,getStoreKey,enableDebugging,disableDebugging,detachDebugging,isDebuggingEnabled,getStore,resolveSourceLocation,listStoreKeys,setExceptionBreakpoint,getExceptionBreakpoint,createProbe,removeProbe,listProbes,clearProbes,getSnapshots,getSnapshotsByProbe,clearSnapshots,clearSnapshotsByProbe,getSnapshotStats,getConsoleMessages,addWatchExpression,removeWatchExpression,listWatchExpressions,clearWatchExpressions,loadSourceMaps,hasSourceMaps,getOriginalSources,getScripts,evaluateInNode};
|
package/dist/daemon-server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{init,shutdown,trackToolCalled}from"./core-
|
|
1
|
+
import{init,shutdown,trackToolCalled}from"./core-24BPAITI.js";import{isToolEnabled,platformInfo}from"./core-BD5K3BG4.js";import{AVAILABLE_TOOL_DOMAINS,DAEMON_PORT,DAEMON_SESSION_IDLE_CHECK_SECONDS,DAEMON_SESSION_IDLE_SECONDS,debug,enable,error,info,isDebugEnabled}from"./core-PF26XSPV.js";import{createRequire}from"node:module";import{Command,Option,InvalidOptionArgumentError}from"commander";import{serve}from"@hono/node-server";import{Hono}from"hono";import{cors}from"hono/cors";import{z}from"zod";var require2=createRequire(import.meta.url),daemonStartTime=0,daemonPort=0,app=new Hono,sessions=new Map,DEFAULT_SESSION_ID="#default",ERRORS={get sessionNotFound(){return _buildErrorResponse(404,"Session Not Found")},get toolNotFound(){return _buildErrorResponse(404,"Tool Not Found")},get internalServerError(){return _buildErrorResponse(500,"Internal Server Error")}};function _buildErrorResponse(code,message){return{error:{code,message}}}async function _closeSession(session){session.closed=!0;try{await session.context.close(),debug("Closed MCP session context")}catch(err){error("Error occurred while closing MCP session context",err)}sessions.delete(session.id)}async function _createSession(ctx,sessionId){let now=Date.now(),session={id:sessionId,context:await platformInfo.toolsInfo.createToolSessionContext(()=>sessionId),toolExecutor:platformInfo.toolsInfo.createToolExecutor(),closed:!1,createdAt:now,lastActiveAt:now};return debug(`Created session with id ${sessionId}`),session}function _getSessionInfo(session){let now=Date.now();return{id:session.id,createdAt:session.createdAt,lastActiveAt:session.lastActiveAt,idleSeconds:Math.floor((now-session.lastActiveAt)/1e3)}}async function _getSession(ctx){let sessionId=ctx.req.header("session-id")||DEFAULT_SESSION_ID;return sessions.get(sessionId)}async function _getOrCreateSession(ctx){let sessionId=ctx.req.header("session-id")||DEFAULT_SESSION_ID,session=sessions.get(sessionId);return session?debug(`Reusing session with id ${sessionId}`):(debug(`No session could be found with id ${sessionId}`),session=await _createSession(ctx,sessionId),sessions.set(sessionId,session)),session}function _scheduleIdleSessionCheck(){let noActiveSession=!1;setInterval(()=>{let currentTime=Date.now();noActiveSession&&sessions.size===0&&(info("No active session found, so terminating daemon server"),process.exit(0));for(let[sessionId,session]of sessions)debug(`Checking whether session with id ${sessionId} is idle or not ...`),currentTime-session.lastActiveAt>DAEMON_SESSION_IDLE_SECONDS*1e3&&(debug(`Session with id ${sessionId} is idle, so it will be closing ...`),_closeSession(session).then(()=>{debug(`Session with id ${sessionId} was idle, so it has been closed`)}).catch(err=>{error(`Unable to delete idle session with id ${sessionId}`,err)}));noActiveSession=sessions.size===0},DAEMON_SESSION_IDLE_CHECK_SECONDS*1e3)}async function _logRequest(ctx){let reqClone=ctx.req.raw.clone();debug(`Got request: ${await reqClone.json()}`)}async function startDaemonHTTPServer(port){init({source:"cli"});let allowedDomains=AVAILABLE_TOOL_DOMAINS,allTools=platformInfo.toolsInfo.tools.filter(tool=>isToolEnabled(tool)),toolsToExpose=allowedDomains===void 0?allTools:allTools.filter(tool=>{let domain=tool.name().split("_")[0]?.toLowerCase()??"";return allowedDomains.has(domain)}),toolMap=Object.fromEntries(toolsToExpose.map(tool=>[tool.name(),tool]));app.use("*",cors({origin:"*",allowMethods:["GET","POST","DELETE","OPTIONS"],allowHeaders:["Content-Type","Authorization","session-id"]})),daemonPort=port,daemonStartTime=Date.now();let gracefulShutdown=async signal=>{info(`Received ${signal}, initiating graceful shutdown...`);let closePromises=[];for(let session of sessions.values())closePromises.push(_closeSession(session));await Promise.allSettled(closePromises),await shutdown(),info("All sessions closed, exiting..."),process.exit(0)};process.on("SIGTERM",()=>gracefulShutdown("SIGTERM")),process.on("SIGINT",()=>gracefulShutdown("SIGINT")),process.on("uncaughtException",err=>{error("Uncaught exception",err)}),process.on("unhandledRejection",reason=>{error("Unhandled rejection",reason)}),app.get("/health",ctx=>ctx.json({status:"ok"})),app.get("/info",ctx=>{let info2={version:require2("../package.json").version,uptime:Math.floor((Date.now()-daemonStartTime)/1e3),sessionCount:sessions.size,port:daemonPort};return ctx.json(info2)}),app.get("/sessions",ctx=>{let sessionList=[];for(let session of sessions.values())sessionList.push(_getSessionInfo(session));return ctx.json({sessions:sessionList})}),app.get("/session",async ctx=>{let session=await _getSession(ctx);return session?ctx.json(_getSessionInfo(session)):ctx.json(ERRORS.sessionNotFound,404)}),app.post("/shutdown",async ctx=>{info("Shutdown request received, closing all sessions...");let closePromises=[];for(let session of sessions.values())closePromises.push(_closeSession(session));return await Promise.allSettled(closePromises),await shutdown(),info("All sessions closed, shutting down daemon server..."),setTimeout(()=>{process.exit(0)},500),ctx.json({status:"shutting_down"},200)}),app.post("/call",async ctx=>{try{isDebugEnabled()&&await _logRequest(ctx);let session=await _getOrCreateSession(ctx);session.lastActiveAt=Date.now();let toolCallRequest=await ctx.req.json(),tool=toolMap[toolCallRequest.toolName];if(!tool)return ctx.json(ERRORS.toolNotFound,404);let toolInput;try{toolInput=z.object(tool.inputSchema()).parse(toolCallRequest.toolInput)}catch(err){let errorMessage=err.errors&&Array.isArray(err.errors)?err.errors.map(e=>`${e.path?.join(".")||"input"}: ${e.message}`).join("; "):"Invalid tool input";return ctx.json(_buildErrorResponse(400,`Invalid Tool Request: ${errorMessage}`),400)}let toolStartTime=Date.now();try{let toolOutput=await session.toolExecutor.executeTool(session.context,tool,toolInput);trackToolCalled({toolName:tool.name(),durationMs:Date.now()-toolStartTime,success:!0,sessionId:session.id});let toolCallResponse={toolOutput};return ctx.json(toolCallResponse,200)}catch(err){trackToolCalled({toolName:toolCallRequest.toolName,durationMs:Date.now()-toolStartTime,success:!1,sessionId:session.id,error:err});let toolCallResponse={toolError:{code:err.code,message:err.message}};return ctx.json(toolCallResponse,500)}}catch(err){return error("Error occurred while handling tool call request",err),ctx.json(ERRORS.internalServerError,500)}}),app.delete("/session",async ctx=>{try{let session=await _getSession(ctx);return session?(await _closeSession(session),ctx.json({ok:!0},200)):ctx.json(ERRORS.sessionNotFound,404)}catch(err){return error("Error occurred while deleting session",err),ctx.json(ERRORS.internalServerError,500)}}),app.onError((err,ctx)=>(error("Unhandled error in request handler",err),ctx.json({error:{code:500,message:"Internal Server Error"}},500))),app.notFound(ctx=>ctx.json({error:"Not Found",status:404},404)),serve({fetch:app.fetch,port},()=>info(`Listening on port ${port}`)),_scheduleIdleSessionCheck()}var isMainModule=import.meta.url===`file://${process.argv[1]}`||import.meta.url===`file://${process.argv[1]}.mjs`||process.argv[1]?.endsWith("daemon-server.js")||process.argv[1]?.endsWith("daemon-server.mjs");if(isMainModule){let parsePort=function(value){let n=Number(value);if(!Number.isInteger(n)||n<1||n>65535)throw new InvalidOptionArgumentError("port must be an integer between 1 and 65535");return n};parsePort2=parsePort;let options=new Command().addOption(new Option("--port <number>","port for daemon HTTP server").argParser(parsePort).default(DAEMON_PORT)).allowUnknownOption().parse(process.argv).opts();enable(),info("Starting daemon HTTP server..."),startDaemonHTTPServer(options.port).then(()=>{info("Daemon HTTP server started")}).catch(err=>{error("Failed to start daemon HTTP server",err),process.exit(1)})}var parsePort2;export{startDaemonHTTPServer};
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{init,
|
|
2
|
+
import{init,shutdown,trackToolCalled}from"./core-24BPAITI.js";import{isToolEnabled,platformInfo}from"./core-BD5K3BG4.js";import{AVAILABLE_TOOL_DOMAINS,PORT,SESSION_CLOSE_ON_SOCKET_CLOSE,SESSION_IDLE_CHECK_SECONDS,SESSION_IDLE_SECONDS,TOOL_OUTPUT_SCHEMA_DISABLE,debug,disable,enable,error,info,isDebugEnabled}from"./core-PF26XSPV.js";import{createRequire}from"node:module";var require2=createRequire(import.meta.url),SERVER_NAME="browser-devtools-mcp",SERVER_VERSION=require2("../package.json").version;function getServerInstructions(){let parts=[];return parts.push(platformInfo.serverInfo.instructions),parts.join(`
|
|
3
3
|
|
|
4
|
-
`).trim()}function getServerPolicies(){return platformInfo.serverInfo.policies}import crypto from"node:crypto";import{StreamableHTTPTransport}from"@hono/mcp";import{serve}from"@hono/node-server";import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";import{Hono}from"hono";import{cors}from"hono/cors";var MCP_TEMPLATE={jsonrpc:"2.0",error:{code:0,message:"N/A"},id:null},MCP_ERRORS={get sessionNotFound(){return _buildMCPErrorResponse(-32001,"Session Not Found")},get unauthorized(){return _buildMCPErrorResponse(-32001,"Unauthorized")},get internalServerError(){return _buildMCPErrorResponse(-32603,"Internal Server Error")}},sessions=new Map;function _buildMCPErrorResponse(code,message){let result={...MCP_TEMPLATE};return result.error.code=code,result.error.message=message,result}function _getImage(response){if("image"in response&&response.image!==null&&typeof response.image=="object"&&"data"in response.image&&"mimeType"in response.image&&Buffer.isBuffer(response.image.data)&&typeof response.image.mimeType=="string"){let image=response.image;return delete response.image,image}}function _toResponse(response){let image=_getImage(response),contents=[];return contents.push({type:"text",text:JSON.stringify(response,null,2)}),image&&(image.mimeType==="image/svg+xml"?contents.push({type:"text",text:image.data.toString(),mimeType:image.mimeType}):contents.push({type:"image",data:image.data.toString("base64"),mimeType:image.mimeType})),{content:contents,structuredContent:response,isError:!1}}function _createServer(opts){let server=new McpServer({name:SERVER_NAME,version:SERVER_VERSION},{capabilities:{resources:{},tools:{}},instructions:getServerInstructions()}),messages=[],policies=getServerPolicies();if(policies)for(let policy of policies)messages.push({role:"user",content:{type:"text",text:policy}});server.registerPrompt("default_system",{title:"Default System Prompt",description:"General behavior for the AI assistant"},async()=>({description:"Defines the assistant's general reasoning and tool usage rules.",messages}));let toolExecutor=platformInfo.toolsInfo.createToolExecutor(()=>opts.sessionIdProvider?opts.sessionIdProvider():"");server.server.oninitialized=()=>{refineSource(server.server.getClientVersion()),opts.transportType&&trackMcpServerStarted(opts.transportType)};let createToolCallback=tool=>async args=>{let startTime=Date.now(),sessionId=opts.sessionIdProvider?.()||void 0,clientName=server.server.getClientVersion()?.name;try{let response=await toolExecutor.executeTool(tool,args);return trackToolCalled({toolName:tool.name(),durationMs:Date.now()-startTime,success:!0,sessionId,clientName}),_toResponse(response)}catch(error2){return trackToolCalled({toolName:tool.name(),durationMs:Date.now()-startTime,success:!1,error:error2,sessionId,clientName}),{content:[{type:"text",text:`Error: ${error2.message}`}],isError:!0}}},includeOutputSchema=!TOOL_OUTPUT_SCHEMA_DISABLE,allowedDomains=AVAILABLE_TOOL_DOMAINS;return platformInfo.toolsInfo.tools.forEach(t=>{if(!isToolEnabled(t)){debug(`Skipping tool ${t.name()} (isEnabled returned false)`);return}let domain=t.name().split("_")[0]?.toLowerCase()??"";if(allowedDomains&&!allowedDomains.has(domain)){debug(`Skipping tool ${t.name()} (domain ${domain} not in AVAILABLE_TOOL_DOMAINS)`);return}debug(`Registering tool ${t.name()} ...`);let toolOptions=includeOutputSchema?{description:t.description(),inputSchema:t.inputSchema(),outputSchema:t.outputSchema()}:{description:t.description(),inputSchema:t.inputSchema()};server.registerTool(t.name(),toolOptions,createToolCallback(t))}),server}async function _createAndConnectServer(transport,opts){let fallbackSessionId=crypto.randomUUID(),server=_createServer({config:opts.config,transportType:opts.transportType,sessionIdProvider:()=>transport.sessionId??fallbackSessionId});return await server.connect(transport),server}function _getConfig(){return{}}function _createSession(ctx,transport,server){let session={transport,server,closed:!1,lastActiveAt:Date.now()},socket=ctx.env.incoming.socket;return socket._mcpRegistered||(socket._mcpRegistered=!0,socket.on("close",async()=>{debug(`Socket, which is for MCP session with id ${transport.sessionId}, has been closed`),SESSION_CLOSE_ON_SOCKET_CLOSE&&await transport.close()})),_registerMCPSessionClose(transport,session.server),debug(`Created MCP server session with id ${transport.sessionId}`),session}async function _createTransport(ctx){let serverConfig=_getConfig(),holder={},transport=new StreamableHTTPTransport({enableJsonResponse:!0,sessionIdGenerator:()=>crypto.randomUUID(),onsessioninitialized:async sessionId=>{let session=_createSession(ctx,transport,holder.server);sessions.set(sessionId,session),debug(`MCP session initialized with id ${sessionId}`)},onsessionclosed:async sessionId=>{debug(`Closing MCP session closed with id ${sessionId} ...`),await transport.close(),debug(`MCP session closed with id ${sessionId}`)}});return holder.server=await _createAndConnectServer(transport,{config:serverConfig,transportType:"streamable-http"}),transport}async function _getTransport(ctx){let sessionId=ctx.req.header("mcp-session-id");if(sessionId){let session=sessions.get(sessionId);if(session)return debug(`Reusing MCP session with id ${sessionId}`),session.transport}}async function _getOrCreateTransport(ctx){let sessionId=ctx.req.header("mcp-session-id");if(sessionId){let session=sessions.get(sessionId);if(session)return debug(`Reusing MCP session with id ${sessionId}`),session.transport;debug(`No MCP session could be found with id ${sessionId}`);return}return await _createTransport(ctx)}function _registerMCPSessionClose(transport,mcpServer){let closed=!1;transport.onclose=async()=>{if(debug(`Closing MCP session with id ${transport.sessionId} ...`),closed){debug(`MCP session with id ${transport.sessionId} has already been closed`);return}closed=!0;try{await mcpServer.close(),debug("Closed MCP server")}catch(err){error("Error occurred while closing MCP server",err)}if(transport.sessionId){let session=sessions.get(transport.sessionId);if(session&&(session.closed=!0,session.context))try{await session.context.close(),debug("Closed MCP session context")}catch(err){error("Error occurred while closing MCP session context",err)}sessions.delete(transport.sessionId)}debug(`Closing MCP session with id ${transport.sessionId} ...`)}}function _scheduleIdleSessionCheck(){setInterval(()=>{let currentTime=Date.now();for(let[sessionId,session]of sessions)debug(`Checking whether session with id ${sessionId} is idle or not ...`),currentTime-session.lastActiveAt>SESSION_IDLE_SECONDS*1e3&&(debug(`Session with id ${sessionId} is idle, so it will be closing ...`),session.transport.close().then(()=>{debug(`Session with id ${sessionId} was idle, so it has been closed`)}).catch(err=>{error(`Unable to delete idle session with id ${sessionId}`,err)}))},SESSION_IDLE_CHECK_SECONDS*1e3)}async function _logRequest(ctx){let reqClone=ctx.req.raw.clone();debug(`Got request: ${await reqClone.json()}`)}function _markSessionAsActive(ctx){let sessionId=ctx.req.header("mcp-session-id");if(sessionId){let session=sessions.get(sessionId);session&&(session.lastActiveAt=Date.now())}}async function startStdioServer(){init({source:"mcp"});let transport=new StdioServerTransport;await _createAndConnectServer(transport,{config:_getConfig(),transportType:"stdio"})}var app=new Hono;async function startStreamableHTTPServer(port){init({source:"mcp"}),app.use("*",cors({origin:"*",allowMethods:["GET","POST","OPTIONS"],allowHeaders:["Content-Type","Authorization","MCP-Protocol-Version"]})),app.get("/health",ctx=>ctx.json({status:"ok"})),app.get("/ping",ctx=>ctx.json({status:"ok",message:"pong"})),app.get("/mcp",ctx=>ctx.json({status:"ok",protocol:"model-context-protocol",version:"1.0"})),app.post("/mcp",async ctx=>{try{isDebugEnabled()&&await _logRequest(ctx);let transport=await _getOrCreateTransport(ctx);return transport?(_markSessionAsActive(ctx),await transport.handleRequest(ctx)):ctx.json(MCP_ERRORS.sessionNotFound,400)}catch(err){return error("Error occurred while handling MCP request",err),ctx.json(MCP_ERRORS.internalServerError,500)}}),app.delete("/mcp",async ctx=>{try{let transport=await _getTransport(ctx);return transport?(await transport.close(),ctx.json({ok:!0},200)):ctx.json(MCP_ERRORS.sessionNotFound,400)}catch(err){return error("Error occurred while deleting MCP session",err),ctx.json(MCP_ERRORS.internalServerError,500)}}),app.notFound(ctx=>ctx.json({error:"Not Found",status:404},404)),serve({fetch:app.fetch,port},()=>info(`Listening on port ${port}`)),_scheduleIdleSessionCheck()}import{Command,Option,InvalidOptionArgumentError}from"commander";function _parsePort(value){let n=Number(value);if(!Number.isInteger(n)||n<1||n>65535)throw new InvalidOptionArgumentError("port must be an integer between 1 and 65535");return n}function _getOptions(){return new Command().addOption(new Option("--transport <type>","transport type").choices(["stdio","streamable-http"]).default("stdio")).addOption(new Option("--port <number>","port for Streamable HTTP transport").argParser(_parsePort).default(PORT)).allowUnknownOption().parse(process.argv).opts()}async function _shutdown(){trackMcpServerStopped(),await shutdown()}async function main(){let options=_getOptions();for(let signal of["SIGTERM","SIGINT"])process.on(signal,async()=>{await _shutdown(),process.exit(0)});options.transport==="stdio"?(disable(),await startStdioServer()):options.transport==="streamable-http"?(info("Starting MCP server..."),await startStreamableHTTPServer(options.port),info("Started MCP Server")):(error(`Invalid transport: ${options.transport}`),process.exit(1))}main().catch(async err=>{enable(),error("MCP server error",err),await _shutdown(),process.exit(1)});
|
|
4
|
+
`).trim()}function getServerPolicies(){return platformInfo.serverInfo.policies}import crypto from"node:crypto";import{StreamableHTTPTransport}from"@hono/mcp";import{serve}from"@hono/node-server";import{McpServer}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport}from"@modelcontextprotocol/sdk/server/stdio.js";import{Hono}from"hono";import{cors}from"hono/cors";var MCP_TEMPLATE={jsonrpc:"2.0",error:{code:0,message:"N/A"},id:null},MCP_ERRORS={get sessionNotFound(){return _buildMCPErrorResponse(-32001,"Session Not Found")},get unauthorized(){return _buildMCPErrorResponse(-32001,"Unauthorized")},get internalServerError(){return _buildMCPErrorResponse(-32603,"Internal Server Error")}},sessions=new Map;function _buildMCPErrorResponse(code,message){let result={...MCP_TEMPLATE};return result.error.code=code,result.error.message=message,result}function _getImage(response){if("image"in response&&response.image!==null&&typeof response.image=="object"&&"data"in response.image&&"mimeType"in response.image&&Buffer.isBuffer(response.image.data)&&typeof response.image.mimeType=="string"){let image=response.image;return delete response.image,image}}function _toResponse(response){let image=_getImage(response),contents=[];return contents.push({type:"text",text:JSON.stringify(response,null,2)}),image&&(image.mimeType==="image/svg+xml"?contents.push({type:"text",text:image.data.toString(),mimeType:image.mimeType}):contents.push({type:"image",data:image.data.toString("base64"),mimeType:image.mimeType})),{content:contents,structuredContent:response,isError:!1}}function _createServer(opts){let server=new McpServer({name:SERVER_NAME,version:SERVER_VERSION},{capabilities:{resources:{},tools:{}},instructions:getServerInstructions()}),messages=[],policies=getServerPolicies();if(policies)for(let policy of policies)messages.push({role:"user",content:{type:"text",text:policy}});server.registerPrompt("default_system",{title:"Default System Prompt",description:"General behavior for the AI assistant"},async()=>({description:"Defines the assistant's general reasoning and tool usage rules.",messages}));let toolExecutor=platformInfo.toolsInfo.createToolExecutor(),fallbackSessionId=crypto.randomUUID(),toolSessionContext,createToolCallback=tool=>async args=>{let startTime=Date.now(),session=opts.sessionProvider?opts.sessionProvider():void 0,sessionId=session?.id||fallbackSessionId,clientName=server.server.getClientVersion()?.name;toolSessionContext||(session&&(toolSessionContext=session.context),toolSessionContext||(toolSessionContext=await platformInfo.toolsInfo.createToolSessionContext(()=>sessionId),session&&(session.context=toolSessionContext)));try{let response=await toolExecutor.executeTool(toolSessionContext,tool,args);return trackToolCalled({toolName:tool.name(),durationMs:Date.now()-startTime,success:!0,sessionId,clientName}),_toResponse(response)}catch(error2){return trackToolCalled({toolName:tool.name(),durationMs:Date.now()-startTime,success:!1,error:error2,sessionId,clientName}),{content:[{type:"text",text:`Error: ${error2.message}`}],isError:!0}}},includeOutputSchema=!TOOL_OUTPUT_SCHEMA_DISABLE,allowedDomains=AVAILABLE_TOOL_DOMAINS;return platformInfo.toolsInfo.tools.forEach(t=>{if(!isToolEnabled(t)){debug(`Skipping tool ${t.name()} (isEnabled returned false)`);return}let domain=t.name().split("_")[0]?.toLowerCase()??"";if(allowedDomains&&!allowedDomains.has(domain)){debug(`Skipping tool ${t.name()} (domain ${domain} not in AVAILABLE_TOOL_DOMAINS)`);return}debug(`Registering tool ${t.name()} ...`);let toolOptions=includeOutputSchema?{description:t.description(),inputSchema:t.inputSchema(),outputSchema:t.outputSchema()}:{description:t.description(),inputSchema:t.inputSchema()};server.registerTool(t.name(),toolOptions,createToolCallback(t))}),server}async function _createAndConnectServer(transport,opts){let server=_createServer({config:opts.config,sessionProvider:()=>sessions.get(transport.sessionId),transportType:opts.transportType});return await server.connect(transport),server}function _getConfig(){return{}}function _createSession(id,ctx,transport,server,closeOnTransportClose=!0){let session={id,transport,server,closed:!1,lastActiveAt:Date.now()},socket=ctx.env.incoming.socket;return socket._mcpRegistered||(socket._mcpRegistered=!0,socket.on("close",async()=>{debug(`Socket, which is for MCP session with id ${transport.sessionId}, has been closed`),SESSION_CLOSE_ON_SOCKET_CLOSE&&await transport.close()})),_registerMCPSessionClose(transport,session.server,closeOnTransportClose),debug(`Created MCP server session with id ${transport.sessionId}`),session}function _createOrUpdateSession(id,ctx,transport,server,closeOnTransportClose=!0){let session=sessions.get(id);return session?(session.transport=transport,session.server=server,session.closed=!1,session.lastActiveAt=Date.now(),session):_createSession(id,ctx,transport,server,closeOnTransportClose)}async function _createTransport(ctx){let serverConfig=_getConfig(),holder={},useSessionId=ctx.req.header("mcp-use-session-id"),transport=new StreamableHTTPTransport({enableJsonResponse:!0,sessionIdGenerator:()=>useSessionId||crypto.randomUUID(),onsessioninitialized:async sessionId=>{let session=_createOrUpdateSession(sessionId,ctx,transport,holder.server);sessions.set(sessionId,session),debug(`MCP session initialized with id ${sessionId}`)},onsessionclosed:async sessionId=>{debug(`Closing MCP session closed with id ${sessionId} ...`),await transport.close(),debug(`MCP session closed with id ${sessionId}`)}});return holder.server=await _createAndConnectServer(transport,{config:serverConfig,transportType:"streamable-http"}),transport}function _getSessionId(ctx){return ctx.req.header("mcp-session-id")}async function _getTransport(ctx){let sessionId=_getSessionId(ctx);if(sessionId){let session=sessions.get(sessionId);if(session)return debug(`Reusing MCP session with id ${sessionId}`),session.transport}}async function _getOrCreateTransport(ctx){let sessionId=_getSessionId(ctx);if(sessionId){let session=sessions.get(sessionId);if(session)return debug(`Reusing MCP session with id ${sessionId}`),session.transport;debug(`No MCP session could be found with id ${sessionId}`);return}return await _createTransport(ctx)}function _registerMCPSessionClose(transport,mcpServer,closeSession=!0){let closed=!1;transport.onclose=async()=>{if(debug(`Closing MCP session with id ${transport.sessionId} ...`),closed){debug(`MCP session with id ${transport.sessionId} has already been closed`);return}closed=!0;try{await mcpServer.close(),debug("Closed MCP server")}catch(err){error("Error occurred while closing MCP server",err)}if(closeSession&&transport.sessionId){let session=sessions.get(transport.sessionId);if(session&&(session.closed=!0,session.context))try{await session.context.close(),debug("Closed MCP session context")}catch(err){error("Error occurred while closing MCP session context",err)}sessions.delete(transport.sessionId),debug(`Closing MCP session with id ${transport.sessionId} ...`)}}}function _scheduleIdleSessionCheck(){setInterval(()=>{let currentTime=Date.now();for(let[sessionId,session]of sessions)debug(`Checking whether session with id ${sessionId} is idle or not ...`),currentTime-session.lastActiveAt>SESSION_IDLE_SECONDS*1e3&&(debug(`Session with id ${sessionId} is idle, so it will be closing ...`),session.transport.close().then(()=>{debug(`Session with id ${sessionId} was idle, so it has been closed`)}).catch(err=>{error(`Unable to delete idle session with id ${sessionId}`,err)}))},SESSION_IDLE_CHECK_SECONDS*1e3)}async function _logRequest(ctx){let reqClone=ctx.req.raw.clone();debug(`Got request: ${await reqClone.text()}`)}function _markSessionAsActive(ctx){let sessionId=_getSessionId(ctx);if(sessionId){let session=sessions.get(sessionId);session&&(session.lastActiveAt=Date.now())}}async function startStdioServer(){init({source:"mcp"});let transport=new StdioServerTransport;await _createAndConnectServer(transport,{config:_getConfig(),transportType:"stdio"})}var app=new Hono;async function startStreamableHTTPServer(port){init({source:"mcp"}),app.use("*",cors({origin:"*",allowMethods:["GET","POST","OPTIONS"],allowHeaders:["Content-Type","Authorization","MCP-Protocol-Version"]})),app.get("/health",ctx=>ctx.json({status:"ok"})),app.get("/ping",ctx=>ctx.json({status:"ok",message:"pong"})),app.get("/mcp",ctx=>ctx.json({status:"ok",protocol:"model-context-protocol",version:"1.0"})),app.post("/mcp",async ctx=>{try{isDebugEnabled()&&await _logRequest(ctx);let transport=await _getOrCreateTransport(ctx);return transport?(_markSessionAsActive(ctx),await transport.handleRequest(ctx)):ctx.json(MCP_ERRORS.sessionNotFound,400)}catch(err){return error("Error occurred while handling MCP request",err),ctx.json(MCP_ERRORS.internalServerError,500)}}),app.delete("/mcp",async ctx=>{try{let transport=await _getTransport(ctx);return transport?(await transport.close(),ctx.json({ok:!0},200)):ctx.json(MCP_ERRORS.sessionNotFound,400)}catch(err){return error("Error occurred while deleting MCP session",err),ctx.json(MCP_ERRORS.internalServerError,500)}}),app.notFound(ctx=>ctx.json({error:"Not Found",status:404},404)),serve({fetch:app.fetch,port},()=>info(`Listening on port ${port}`)),_scheduleIdleSessionCheck()}import{Command,Option,InvalidOptionArgumentError}from"commander";function _parsePort(value){let n=Number(value);if(!Number.isInteger(n)||n<1||n>65535)throw new InvalidOptionArgumentError("port must be an integer between 1 and 65535");return n}function _getOptions(){return new Command().addOption(new Option("--transport <type>","transport type").choices(["stdio","streamable-http"]).default("stdio")).addOption(new Option("--port <number>","port for Streamable HTTP transport").argParser(_parsePort).default(PORT)).allowUnknownOption().parse(process.argv).opts()}async function _shutdown(){await shutdown()}async function main(){let options=_getOptions();for(let signal of["SIGTERM","SIGINT"])process.on(signal,async()=>{await _shutdown(),process.exit(0)});options.transport==="stdio"?(disable(),await startStdioServer()):options.transport==="streamable-http"?(info("Starting MCP server..."),await startStreamableHTTPServer(options.port),info("Started MCP Server")):(error(`Invalid transport: ${options.transport}`),process.exit(1))}main().catch(async err=>{enable(),error("MCP server error",err),await _shutdown(),process.exit(1)});
|
|
@@ -2,4 +2,5 @@ import type { Tool } from '../../../tools';
|
|
|
2
2
|
import { BrowserToolSessionContext } from './context';
|
|
3
3
|
import { BrowserToolExecutor } from './tool-executor';
|
|
4
4
|
export declare const tools: Tool<BrowserToolSessionContext>[];
|
|
5
|
-
export declare function
|
|
5
|
+
export declare function createToolSessionContext(sessionIdProvider: () => string): Promise<BrowserToolSessionContext>;
|
|
6
|
+
export declare function createToolExecutor(): BrowserToolExecutor;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Exports Node.js platform info for debugging Node.js backend processes.
|
|
5
5
|
*/
|
|
6
6
|
import { PlatformInfo } from '../types';
|
|
7
|
-
export { tools, createToolExecutor } from './tools';
|
|
8
7
|
export { SERVER_INSTRUCTIONS, NODE_DEBUGGING_POLICY } from './server-info';
|
|
8
|
+
export { tools, createToolExecutor, createToolSessionContext } from './tools';
|
|
9
9
|
export { NodeToolSessionContext } from './tools/context';
|
|
10
10
|
export { NodeToolExecutor } from './tools/tool-executor';
|
|
11
11
|
export declare const nodePlatformInfo: PlatformInfo;
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Aggregates all Node.js debugging tools and provides the tool executor factory.
|
|
5
5
|
*/
|
|
6
|
-
import type { Tool } from '../../../tools';
|
|
7
6
|
import { NodeToolSessionContext } from './context';
|
|
7
|
+
import type { Tool } from '../../../tools';
|
|
8
8
|
import { NodeToolExecutor } from './tool-executor';
|
|
9
9
|
export declare const tools: Tool<NodeToolSessionContext>[];
|
|
10
|
-
export declare function
|
|
10
|
+
export declare function createToolSessionContext(sessionIdProvider: () => string): Promise<NodeToolSessionContext>;
|
|
11
|
+
export declare function createToolExecutor(): NodeToolExecutor;
|
package/dist/platform/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Provider } from '../cli';
|
|
2
|
-
import { Tool, ToolExecutor } from '../tools';
|
|
2
|
+
import { Tool, ToolExecutor, ToolSessionContext } from '../tools';
|
|
3
3
|
export type PlatformInfo = {
|
|
4
4
|
serverInfo: {
|
|
5
5
|
instructions: string;
|
|
@@ -7,7 +7,8 @@ export type PlatformInfo = {
|
|
|
7
7
|
};
|
|
8
8
|
toolsInfo: {
|
|
9
9
|
tools: Tool[];
|
|
10
|
-
|
|
10
|
+
createToolSessionContext: (sessionIdProvider: () => string) => Promise<ToolSessionContext>;
|
|
11
|
+
createToolExecutor: () => ToolExecutor;
|
|
11
12
|
};
|
|
12
13
|
cliInfo: {
|
|
13
14
|
cliProvider: Provider;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { init, refineSource, trackToolCalled, trackMcpServerStarted,
|
|
1
|
+
export { init, refineSource, trackToolCalled, trackMcpServerStarted, trackCliCommandExecuted, shutdown, } from './telemetry-manager';
|
|
2
2
|
export type { InitOptions, TrackToolCalledOpts } from './telemetry-manager';
|
package/dist/tools/types.d.ts
CHANGED
|
@@ -29,5 +29,5 @@ export interface ToolSessionContext {
|
|
|
29
29
|
close(): Promise<boolean>;
|
|
30
30
|
}
|
|
31
31
|
export interface ToolExecutor<SC extends ToolSessionContext = ToolSessionContext> {
|
|
32
|
-
executeTool(tool: Tool<SC>, args: ToolInput): Promise<ToolOutput>;
|
|
32
|
+
executeTool(context: SC, tool: Tool<SC>, args: ToolInput): Promise<ToolOutput>;
|
|
33
33
|
}
|
package/package.json
CHANGED
package/dist/core-PIEDD6UN.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import*as fs2 from"node:fs";import{fileURLToPath}from"node:url";import{PostHog}from"posthog-node";import*as crypto from"node:crypto";import*as fs from"node:fs";import*as os from"node:os";import*as path from"node:path";var CONFIG_DIR=path.join(os.homedir(),".browser-devtools-mcp"),CONFIG_FILE=path.join(CONFIG_DIR,"config.json");function readConfig(){let existing={};if(fs.existsSync(CONFIG_FILE))try{let raw=fs.readFileSync(CONFIG_FILE,"utf-8");existing=JSON.parse(raw)}catch{}let dirty=!1;return existing.anonymousId||(existing.anonymousId=crypto.randomUUID(),dirty=!0),existing.telemetryEnabled===void 0&&(existing.telemetryEnabled=!0,dirty=!0),existing.telemetryNoticeShown===void 0&&(existing.telemetryNoticeShown=!1,dirty=!0),dirty&&_writeConfig(existing),existing}function updateConfig(updates){let current=readConfig();_writeConfig({...current,...updates})}function _writeConfig(config){try{fs.existsSync(CONFIG_DIR)||fs.mkdirSync(CONFIG_DIR,{recursive:!0}),fs.writeFileSync(CONFIG_FILE,JSON.stringify(config,null,2),"utf-8")}catch{}}var POSTHOG_API_KEY=process.env.POSTHOG_API_KEY??"phc_ekFEnQ9ipk0F1BbO0KCkaD8OaYPa4bIqqUoxsCfeFsy",POSTHOG_HOST="https://us.posthog.com",_initialized=!1,_client=null,_anonymousId="",_enabled=!1,_source="mcp-unknown",_mcpTransport,_browserDevtoolsVersion=(()=>{let candidates=[new URL("../package.json",import.meta.url),new URL("../../package.json",import.meta.url)];for(let candidate of candidates)try{let raw=fs2.readFileSync(fileURLToPath(candidate),"utf-8"),ver=JSON.parse(raw).version;if(ver)return ver}catch{}return"unknown"})();function _detectMcpClientSource(clientInfo){if(process.env.CURSOR_TRACE_ID)return"mcp-cursor";if(process.env.CLAUDE_DESKTOP)return"mcp-claude";if(clientInfo?.name){let name=clientInfo.name.toLowerCase();if(name.includes("cursor"))return"mcp-cursor";if(name.includes("claude")||name.includes("local-agent-mode"))return"mcp-claude";if(name.includes("codex")||name.includes("openai"))return"mcp-codex"}return"mcp-unknown"}function _detectCLISource(){return"cli"}function _buildBaseProperties(){return{source:_source,browser_devtools_version:_browserDevtoolsVersion,node_version:process.version,os_platform:process.platform,os_arch:process.arch,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,timestamp:new Date().toUTCString()}}function init(opts={}){if(!_initialized){_initialized=!0;try{let config=readConfig();_anonymousId=config.anonymousId;let telemetryEnabled=process.env.TELEMETRY_ENABLE!=="false";if(_enabled=config.telemetryEnabled&&!opts.disabled&&telemetryEnabled,opts.source==="cli")_source=_detectCLISource();else if(opts.source==="mcp"){let detectedSource=_detectMcpClientSource(opts.clientInfo);_source=detectedSource==="cli"?"mcp-unknown":detectedSource}else _source=_detectMcpClientSource(opts.clientInfo);if(!config.telemetryNoticeShown&&telemetryEnabled&&(process.stderr.write(`
|
|
2
|
-
Telemetry is enabled by default to help improve this tool.
|
|
3
|
-
Run with --no-telemetry for CLI and set TELEMETRY_ENABLE=false for MCP Servers to disable.
|
|
4
|
-
|
|
5
|
-
`),updateConfig({telemetryNoticeShown:!0})),!_enabled)return;_client=new PostHog(POSTHOG_API_KEY,{host:POSTHOG_HOST,flushAt:20,flushInterval:1e4});let _beforeExitHandled=!1;process.on("beforeExit",async()=>{if(!(_beforeExitHandled||!_client)){_beforeExitHandled=!0;try{_mcpTransport&&trackMcpServerStopped(),await _client.shutdown(),_client=null}catch{_client=null}}})}catch{}}}function trackToolCalled(opts){if(!(!_enabled||!_client))try{_source==="mcp-unknown"&&opts.clientName&&refineSource({name:opts.clientName});let properties={..._buildBaseProperties(),tool_name:opts.toolName,duration_ms:opts.durationMs,success:opts.success};if(opts.clientName&&(properties.client_name=opts.clientName),!opts.success&&opts.error!=null){let err=opts.error;properties.error_type=err?.constructor?.name??"Error",err?.code!==void 0&&(properties.error_code=err.code)}opts.sessionId&&(properties.session_id=opts.sessionId),_client.capture({distinctId:_anonymousId,event:"tool_called",properties})}catch{}}function trackMcpServerStarted(transport){if(_mcpTransport=transport,!(!_enabled||!_client))try{_client.capture({distinctId:_anonymousId,event:"mcp_server_started",properties:{..._buildBaseProperties(),transport}})}catch{}}function trackMcpServerStopped(){if(!(!_enabled||!_client))try{_client.capture({distinctId:_anonymousId,event:"mcp_server_stopped",properties:{..._buildBaseProperties(),transport:_mcpTransport}})}catch{}}function refineSource(clientInfo){if(_source!=="mcp-unknown")return;let refined=_detectMcpClientSource(clientInfo);refined!=="mcp-unknown"&&(_source=refined)}async function shutdown(){if(_client){try{await _client.shutdown()}catch{}_client=null}}export{init,trackToolCalled,trackMcpServerStarted,trackMcpServerStopped,refineSource,shutdown};
|