Session scoped mcp stdio server
Skill kjuhwa/skills-hub/skills/mcp-integration/session-scoped-mcp-stdio-server
Ship a per-session MCP stdio server bundled as CJS that provides session-scoped tools (SubmitPlan, config_validate) and uses stderr __CALLBACK__ lines to pause execution for async host-process callbacks.From its SKILL.md
npx -y skills add kjuhwa/skills-hub --skill session-scoped-mcp-stdio-serverAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
3.5 KB, 716 tokens by cl100k_base, as published. Nobody here has run it
Session-scoped MCP stdio server with stderr callbacks
When to use
- Running an SDK (Codex, Copilot) that expects MCP tools via stdio.
- Need per-session context (session ID, workspace root, plans folder) baked into each tool invocation.
- Some tools need to pause and wait for a host decision (e.g. OAuth consent UI, plan approval). Stdio is synchronous-looking; you need a sidechannel.
How it works
- Build the server as a CJS bundle:
bun build src/index.ts --target=node --format=cjs --outfile dist/index.js. Ship with abinentry so it'snpx-able. - The host spawns it once per session with session config on argv:
--session-id X --workspace-root /.. --plans-folder /... StdioServerTransport+@modelcontextprotocol/sdk/serverregisterCallToolRequestSchema/ListToolsRequestSchema.- Each tool handler gets a
SessionToolContext(workspace path + plans path + session id). - For tools that need host interaction, write a single-line JSON to stderr prefixed with
__CALLBACK__:
The host (Electron main) reads the subprocess stderr line-by-line and parses anything starting with that prefix as a structured callback.__CALLBACK__{"type":"auth_required","sourceSlug":"gmail","authUrl":"https://..."} - Use
CALLBACK_TOOL_TIMEOUT_MS(e.g. 120_000) - if host doesn't respond within the window, return an error from the tool. - Read cached credentials the host has written to
<workspaceRoot>/sources/<slug>/.credential-cache.json- decrypted by host, shared with subprocess through the filesystem not env vars.
Example
// In the MCP server
function sendCallback(callback) {
console.error(`__CALLBACK__${JSON.stringify(callback)}`);
}
server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (req.params.name === 'SubmitPlan') {
sendCallback({ type: 'plan_ready', sessionId, planPath: '…' });
// now wait for host decision by polling a response file or a port
}
...
});
Host side:
subprocess.stderr.on('data', (chunk) => {
for (const line of chunk.toString().split('\n')) {
if (line.startsWith('__CALLBACK__')) {
const msg = JSON.parse(line.slice('__CALLBACK__'.length));
handleCallback(msg);
} else {
realLogger.info(line); // pass-through logging
}
}
});
Gotchas
- stderr is line-buffered, stdout is the JSON-RPC channel - mixing them breaks the protocol. Always use stderr for sidechannel.
- Bundle to CJS even if host is ESM - avoids
ERR_REQUIRE_ESMwhenruntime-resolver.tsspawns this at packaged-app runtime. - Ship via
dist/index.jsin the app resources and electron-builderfiles:glob or you'll get "Cannot find module" in production only. - Pass session paths on argv, not env, so you can see them in
psoutput for debugging. CALLBACK_TOOL_TIMEOUT_MSmust be longer than any expected user interaction (120s+).
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.