11const fs = require ( "node:fs" ) ;
22const path = require ( "node:path" ) ;
3- const { spawnSync } = require ( "node:child_process" ) ;
3+ const { spawn , spawnSync } = require ( "node:child_process" ) ;
44
55const secrets = new Set ( [ process . env . GITHUB_TOKEN ] ) ;
66const report = { cliVersion : "1.0.104" , inferenceRequests : 0 , attempts : [ ] } ;
@@ -104,6 +104,8 @@ async function discoverTools(config) {
104104 }
105105 const headers = { ...config . headers } ;
106106 keepHeaderSecrets ( headers ) ;
107+ // Listing omits runtime-injected credentials; test this token explicitly.
108+ headers . Authorization = `Bearer ${ process . env . GITHUB_TOKEN } ` ;
107109 const initialized = await rpc (
108110 url ,
109111 headers ,
@@ -116,6 +118,7 @@ async function discoverTools(config) {
116118 1 ,
117119 ) ;
118120 const result = {
121+ authentication : "explicit Actions GITHUB_TOKEN" ,
119122 endpoint : `${ url . origin } ${ url . pathname } ` ,
120123 headerNames : Object . keys ( headers ) . filter (
121124 ( name ) => name . toLowerCase ( ) !== "mcp-session-id" ,
@@ -150,6 +153,143 @@ async function discoverTools(config) {
150153 return result ;
151154}
152155
156+ async function inspectSession ( config ) {
157+ const child = spawn ( "copilot" , [ ...flags , "--server" , "--stdio" ] , {
158+ stdio : [ "pipe" , "pipe" , "pipe" ] ,
159+ } ) ;
160+ let buffer = Buffer . alloc ( 0 ) ;
161+ let nextId = 0 ;
162+ const pending = new Map ( ) ;
163+ const result = { stderr : "" } ;
164+ report . session = result ;
165+ child . stderr . setEncoding ( "utf8" ) . on ( "data" , ( chunk ) => {
166+ result . stderr += chunk ;
167+ } ) ;
168+ function send ( message ) {
169+ const body = JSON . stringify ( message ) ;
170+ child . stdin . write (
171+ `Content-Length: ${ Buffer . byteLength ( body ) } \r\n\r\n${ body } ` ,
172+ ) ;
173+ }
174+ child . stdout . on ( "data" , ( chunk ) => {
175+ try {
176+ buffer = Buffer . concat ( [ buffer , chunk ] ) ;
177+ while ( true ) {
178+ const end = buffer . indexOf ( "\r\n\r\n" ) ;
179+ if ( end < 0 ) return ;
180+ const length = Number (
181+ / C o n t e n t - L e n g t h : \s * ( \d + ) / i. exec (
182+ buffer . subarray ( 0 , end ) . toString ( ) ,
183+ ) ?. [ 1 ] ,
184+ ) ;
185+ if ( ! Number . isFinite ( length ) )
186+ throw new Error ( "Missing JSON-RPC frame length" ) ;
187+ if ( buffer . length < end + 4 + length ) return ;
188+ const message = JSON . parse (
189+ buffer . subarray ( end + 4 , end + 4 + length ) . toString ( ) ,
190+ ) ;
191+ buffer = buffer . subarray ( end + 4 + length ) ;
192+ if ( message . method && message . id !== undefined ) {
193+ send ( {
194+ jsonrpc : "2.0" ,
195+ id : message . id ,
196+ error : { code : - 32601 , message : "No client callbacks installed" } ,
197+ } ) ;
198+ } else if ( pending . has ( message . id ) ) {
199+ const request = pending . get ( message . id ) ;
200+ pending . delete ( message . id ) ;
201+ clearTimeout ( request . timer ) ;
202+ if ( message . error )
203+ request . reject ( new Error ( JSON . stringify ( message . error ) ) ) ;
204+ else request . resolve ( message . result ) ;
205+ }
206+ }
207+ } catch ( error ) {
208+ rejectPending ( error ) ;
209+ }
210+ } ) ;
211+ function rejectPending ( error ) {
212+ for ( const request of pending . values ( ) ) {
213+ clearTimeout ( request . timer ) ;
214+ request . reject ( error ) ;
215+ }
216+ pending . clear ( ) ;
217+ }
218+ child . on ( "error" , rejectPending ) ;
219+ child . stdin . on ( "error" , rejectPending ) ;
220+ child . on ( "exit" , ( code ) =>
221+ rejectPending ( new Error ( `CLI server exited with ${ code } ` ) ) ,
222+ ) ;
223+ function request ( method , params ) {
224+ if (
225+ ! [
226+ "ping" ,
227+ "session.create" ,
228+ "session.mcp.list" ,
229+ "session.mcp.listTools" ,
230+ "session.destroy" ,
231+ ] . includes ( method )
232+ ) {
233+ throw new Error ( "Refusing non-discovery RPC" ) ;
234+ }
235+ return new Promise ( ( resolve , reject ) => {
236+ const id = ++ nextId ;
237+ const timer = setTimeout ( ( ) => {
238+ pending . delete ( id ) ;
239+ reject ( new Error ( `Timed out waiting for ${ method } ` ) ) ;
240+ } , 90000 ) ;
241+ pending . set ( id , { resolve, reject, timer } ) ;
242+ send ( { jsonrpc : "2.0" , id, method, params } ) ;
243+ } ) ;
244+ }
245+ try {
246+ result . ping = await request ( "ping" , { message : "no-inference-diagnostic" } ) ;
247+ const session = await request ( "session.create" , {
248+ model : "gpt-6.1-terra" ,
249+ workingDirectory : process . cwd ( ) ,
250+ availableTools : [
251+ "apply_patch" ,
252+ "bash" ,
253+ "glob" ,
254+ "rg" ,
255+ "view" ,
256+ "web_fetch" ,
257+ "web_search" ,
258+ ] ,
259+ mcpServers : config . mcpServers ,
260+ disabledMcpServers : [ "githubiq" ] ,
261+ } ) ;
262+ result . sessionId = session . sessionId ;
263+ const listed = await request ( "session.mcp.list" , {
264+ sessionId : session . sessionId ,
265+ } ) ;
266+ result . servers = listed . servers . map ( ( server ) => ( {
267+ name : server . name ,
268+ status : server . status ,
269+ error : server . error ,
270+ toolCount : server . toolCount ,
271+ } ) ) ;
272+ const tools = await request ( "session.mcp.listTools" , {
273+ sessionId : session . sessionId ,
274+ serverName : "github-mcp-server" ,
275+ } ) ;
276+ result . toolNames = tools . tools . map ( ( tool ) => tool . name ) . sort ( ) ;
277+ result . webSearchPresent = result . toolNames . includes ( "web_search" ) ;
278+ await request ( "session.destroy" , { sessionId : session . sessionId } ) ;
279+ result . complete = true ;
280+ } catch ( error ) {
281+ result . complete = false ;
282+ result . error = error . stack ;
283+ throw error ;
284+ } finally {
285+ const exited = new Promise ( ( resolve ) => child . once ( "exit" , resolve ) ) ;
286+ child . kill ( "SIGTERM" ) ;
287+ const timer = setTimeout ( ( ) => child . kill ( "SIGKILL" ) , 3000 ) ;
288+ if ( child . exitCode === null ) await exited ;
289+ clearTimeout ( timer ) ;
290+ }
291+ }
292+
153293function logFiles ( directory ) {
154294 if ( ! fs . existsSync ( directory ) ) return [ ] ;
155295 return fs . readdirSync ( directory , { withFileTypes : true } ) . flatMap ( ( entry ) => {
@@ -163,6 +303,7 @@ function logFiles(directory) {
163303}
164304
165305async function main ( ) {
306+ let lastConfig ;
166307 for ( let attempt = 1 ; attempt <= 2 ; attempt ++ ) {
167308 const child = spawnSync ( "copilot" , [ ...flags , "mcp" , "list" , "--json" ] , {
168309 cwd : process . cwd ( ) ,
@@ -175,6 +316,7 @@ async function main() {
175316 if ( child . error ) throw child . error ;
176317 if ( child . status !== 0 ) throw new Error ( "copilot mcp list failed" ) ;
177318 const config = JSON . parse ( child . stdout ) ;
319+ lastConfig = config ;
178320 for ( const server of Object . values ( config . mcpServers || { } ) )
179321 keepHeaderSecrets ( server . headers ) ;
180322 result . servers = Object . fromEntries (
@@ -185,13 +327,16 @@ async function main() {
185327 type : server . type ,
186328 source : server . source ,
187329 selectedTools : server . tools ,
330+ configurationKeys : Object . keys ( server ) ,
331+ toolSelector : server . headers ?. [ "X-MCP-Tools" ] ,
188332 } ,
189333 ] ) ,
190334 ) ;
191335 const github = config . mcpServers ?. [ "github-mcp-server" ] ;
192336 if ( github && github . enabled !== false )
193337 result . github = await discoverTools ( github ) ;
194338 }
339+ await inspectSession ( lastConfig ) ;
195340 report . complete = true ;
196341}
197342
0 commit comments