- connectStdio(server): wires the MCP server to StdioServerTransport. - connectHttp(server, port): wires the MCP server to StreamableHTTPServerTransport over node:http. Returns a handle with port and close(). - bin/pascal-mcp.ts: CLI entrypoint with --stdio (default), --http, --port, --scene, --help, --version. Loads node-shims before any core import. Logs to stderr (stdio transport uses stdout for JSON-RPC). - scripts/smoke.ts: end-to-end smoke — spawns the stdio bin, connects as an MCP Client, exercises get_scene, create_level, validate_scene, undo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
/**
|
|
* End-to-end smoke test for @pascal-app/mcp.
|
|
*
|
|
* Spawns the compiled stdio binary as a child process, connects as an MCP
|
|
* client, and exercises a handful of representative tools. This test requires
|
|
* the package to be built first (`bun run build`) — the compiled bin is what
|
|
* `package.json`'s `bin` entry ships to users.
|
|
*
|
|
* Run with: bun run scripts/smoke.ts
|
|
*/
|
|
import { existsSync } from 'node:fs'
|
|
import { dirname, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
|
|
const __filename = fileURLToPath(import.meta.url)
|
|
const __dirname = dirname(__filename)
|
|
const BIN_PATH = resolve(__dirname, '../dist/bin/pascal-mcp.js')
|
|
|
|
async function main(): Promise<void> {
|
|
if (!existsSync(BIN_PATH)) {
|
|
console.error(`[smoke] bin not found at ${BIN_PATH}`)
|
|
console.error('[smoke] run `bun run build` first')
|
|
process.exit(1)
|
|
}
|
|
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [BIN_PATH, '--stdio'],
|
|
stderr: 'inherit',
|
|
})
|
|
const client = new Client({ name: 'pascal-mcp-smoke', version: '0.0.0' })
|
|
|
|
try {
|
|
await client.connect(transport)
|
|
|
|
const tools = await client.listTools()
|
|
console.log(`[smoke] tools registered: ${tools.tools.length}`)
|
|
if (tools.tools.length === 0) {
|
|
throw new Error('no tools registered')
|
|
}
|
|
|
|
const getScene = await client.callTool({ name: 'get_scene', arguments: {} })
|
|
if (getScene.isError) {
|
|
throw new Error(`get_scene failed: ${JSON.stringify(getScene)}`)
|
|
}
|
|
console.log('[smoke] get_scene: OK')
|
|
|
|
// create_level — buildingId may not match a real node depending on the
|
|
// default scene; we just verify the tool returns a structured response
|
|
// rather than crash.
|
|
const createLevel = await client.callTool({
|
|
name: 'create_level',
|
|
arguments: { buildingId: 'tbd', elevation: 1, height: 3 },
|
|
})
|
|
console.log('[smoke] create_level:', createLevel.isError ? 'structured error (ok)' : 'OK')
|
|
|
|
const validate = await client.callTool({
|
|
name: 'validate_scene',
|
|
arguments: {},
|
|
})
|
|
console.log('[smoke] validate_scene:', validate.isError ? 'ERROR' : 'OK')
|
|
|
|
const undone = await client.callTool({ name: 'undo', arguments: {} })
|
|
console.log('[smoke] undo:', undone.isError ? 'ERROR' : 'OK')
|
|
|
|
console.log('[smoke] passed')
|
|
} finally {
|
|
try {
|
|
await client.close()
|
|
} catch {
|
|
// client may already be closed; ignore.
|
|
}
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('[smoke] failed:', err)
|
|
process.exit(1)
|
|
})
|