Skip to content

Commit b8d2592

Browse files
Exercise polyglot candidates with the actual MCP probe
Measure protocol errors separately from stderr diagnostics and test Windows without Git or Unix shells on PATH. Compare Node and shell polyglots on Node 22 and 24 across all three platforms.
1 parent 27aad47 commit b8d2592

2 files changed

Lines changed: 59 additions & 3 deletions

File tree

‎.github/workflows/polyglot-prototype.yml‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,20 @@ jobs:
1111
fail-fast: false
1212
matrix:
1313
os: [ubuntu-latest, windows-latest, macos-latest]
14+
node: [22, 24]
1415
runs-on: ${{ matrix.os }}
1516
steps:
1617
- uses: actions/checkout@v7.0.1
1718
- uses: pnpm/action-setup@v6.1.0
1819
- uses: actions/setup-node@v7.0.0
1920
with:
20-
node-version: 24
21+
node-version: ${{ matrix.node }}
2122
cache: pnpm
2223
- run: pnpm install --frozen-lockfile
2324
- name: Probe launcher routes
2425
shell: bash
25-
run: node scripts/polyglot-probe.mjs > prototype-result.json
26+
run: node scripts/polyglot-mcp-probe.mjs > prototype-result.json
2627
- uses: actions/upload-artifact@v4
2728
with:
28-
name: result-${{ matrix.os }}
29+
name: result-${{ matrix.os }}-node${{ matrix.node }}
2930
path: prototype-result.json

‎scripts/polyglot-mcp-probe.mjs‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import assert from 'node:assert/strict';
2+
import {mkdtemp, mkdir, writeFile, cp, chmod, rm, realpath} from 'node:fs/promises';
3+
import {tmpdir} from 'node:os';
4+
import {dirname,join,delimiter} from 'node:path';
5+
import {fileURLToPath} from 'node:url';
6+
import {Client} from '@modelcontextprotocol/sdk/client/index.js';
7+
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';
8+
const repo=fileURLToPath(new URL('..',import.meta.url));
9+
const parent=await mkdtemp(join(tmpdir(),'apc polyglot MCP '));
10+
const root=join(parent,'plugin with spaces');
11+
await cp(join(repo,'plugins/agent-plugins-conformance-core'),root,{recursive:true});
12+
await mkdir(join(root,'bin'));
13+
await writeFile(join(root,'bin/package.json'),'{"type":"commonjs"}\n');
14+
const jsBody='0</* :\n@node "%~dp0..\\dist\\probe.mjs" %*\n@exit /b %errorlevel%\n*/0;\nimport("../dist/probe.mjs");\n';
15+
const shBody=':; exec node "$(dirname "$0")/../dist/probe.mjs" "$@"\n@echo off\nnode "%~dp0..\\dist\\probe.mjs" %*\nexit /b %errorlevel%\n';
16+
const variants={
17+
node_plain:'#!/usr/bin/env node\n'+jsBody,
18+
node_envS:'#!/usr/bin/env -S node # 2>nul\n'+jsBody,
19+
node_envS_double_space:'#!/usr/bin/env -S node # 2>nul\n'+jsBody,
20+
shell_envS:'#!/usr/bin/env -S sh # 2>nul\n'+shBody,
21+
};
22+
const env={...process.env,PLUGIN_ROOT:root,PLUGIN_DATA:parent};
23+
if(process.platform==='win32') {
24+
for(const key of Object.keys(env)) if(key.toLowerCase()==='path') delete env[key];
25+
env.PATH=[dirname(process.execPath),join(process.env.SystemRoot,'System32'),process.env.SystemRoot].join(delimiter);
26+
}
27+
const results=[];
28+
try {
29+
for(const [name,body] of Object.entries(variants)) {
30+
const file=join(root,'bin',name+'.cmd'); await writeFile(file,body);await chmod(file,0o755);
31+
const routes={sdk:{command:file,args:['relative']}};
32+
if(process.platform==='win32') for(const quiet of [false,true]) routes[quiet?'cmdQuiet':'cmd']={command:process.env.ComSpec||'cmd.exe',args:['/d',...(quiet?['/q']:[]),'/s','/c',`"${file}" relative`]};
33+
for(const [route,launch] of Object.entries(routes)){
34+
const client=new Client({name:'polyglot-prototype',version:'1'});
35+
const transport=new StdioClientTransport({...launch,cwd:join(root,'probe-workdir'),env,stderr:'pipe'});
36+
const row={name,route,launch,stderr:'',protocolErrors:[],success:false};
37+
client.onerror=error=>row.protocolErrors.push(String(error));
38+
transport.stderr?.on('data',data=>row.stderr+=data.toString());
39+
try{
40+
await client.connect(transport,{timeout:10000});
41+
const result=await client.callTool({name:'observe',arguments:{}},undefined,{timeout:10000});
42+
assert.ok(!result.isError);
43+
const observation=result.structuredContent;
44+
assert.equal(observation.evidence.root,await realpath(root));
45+
assert.equal(observation.evidence.cwd,await realpath(join(root,'probe-workdir')));
46+
assert.deepEqual(observation.evidence.argv,['relative']);
47+
row.observation=observation; row.success=true;
48+
}catch(error){row.error=String(error)}
49+
finally{await client.close();}
50+
row.protocolClean=row.success&&row.protocolErrors.length===0;
51+
results.push(row);
52+
}
53+
}
54+
}finally{await rm(parent,{recursive:true,force:true});}
55+
console.log(JSON.stringify({platform:process.platform,node:process.version,path:env.PATH,results},null,2));

0 commit comments

Comments
 (0)