diff --git a/packages/cli/package.json b/packages/cli/package.json index 9e1ce63e..7c9b9fc0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,7 +56,6 @@ "dependencies": { "@kernlang/agon-engines": "^0.1.1", "@kernlang/agon-dedup": "^0.1.6", - "@kernlang/agon-mcp": "^0.1.6", "@ai-sdk/anthropic": "^3.0.67", "@ai-sdk/openai-compatible": "^2.0.40", "@kernlang/protocol": "~3.5.8", diff --git a/packages/cli/src/generated/cesar/session.ts b/packages/cli/src/generated/cesar/session.ts index 42273f44..c3f697cb 100644 --- a/packages/cli/src/generated/cesar/session.ts +++ b/packages/cli/src/generated/cesar/session.ts @@ -999,7 +999,7 @@ export function mcpConfigFingerprint(config: any): string { } /** - * Resolve the agon-orchestration MCP server entry (packages/mcp/dist/index.js). The CLI ships as a tsup BUNDLE (chunks at packages/cli/dist/), so the old fixed '../../../../mcp/dist/index.js' relative to import.meta.url resolved to a NONEXISTENT path — the MCP server never spawned and Cesar saw zero tools (slow scraping fallback). Resolution order: (1) node module resolution of @kernlang/agon-mcp (works installed AND monorepo-via-symlink), (2) walk up to the repo root containing packages/mcp/dist/index.js (monorepo without a symlink), (3) the original relative guess as a last resort. `fromUrl` is for tests; defaults to this module's URL. + * Resolve the agon-orchestration MCP server entry. The CLI ships as a tsup BUNDLE that ALSO emits the MCP server to /mcp/index.js (see tsup.config.ts), so the published install is self-contained — no @kernlang/agon-mcp npm dependency. Resolution order: (0) the bundled sibling /mcp/index.js (the published, self-contained path), (1) node module resolution of @kernlang/agon-mcp (monorepo-via-symlink / legacy installs), (2) walk up to the repo root containing packages/mcp/dist/index.js (monorepo without a symlink), (3) the original relative guess as a last resort. `fromUrl` is for tests; defaults to this module's URL. */ // @kern-source: session:961 export function resolveAgonMcpServerPath(fromUrl?: string): string { @@ -1007,6 +1007,12 @@ export function resolveAgonMcpServerPath(fromUrl?: string): string { // Accept either a file: URL (normal) or a bare path (defensive): fileURLToPath // throws ERR_INVALID_URL_SCHEME on a bare path, so normalise first. const url = raw.startsWith('file:') ? raw : pathToFileURL(raw).href; + // 0) Bundled sibling — cli's tsup build emits the MCP server to + // /mcp/index.js (see tsup.config.ts). This is the PUBLISHED + // path: self-contained, no @kernlang/agon-mcp dependency. The importing + // chunk lives in the cli dist dir, so dist/mcp/index.js sits beside it. + const bundledSibling = join(dirname(fileURLToPath(url)), 'mcp', 'index.js'); + if (existsSync(bundledSibling)) return bundledSibling; // 1) Installed/published OR monorepo workspace symlink — node resolution. try { const req = createRequire(url); @@ -1029,7 +1035,7 @@ export function resolveAgonMcpServerPath(fromUrl?: string): string { /** * Single source of truth for which backend a Cesar engine will actually use. Honours config.cesarBackend preference ('auto' | 'cli' | 'api'). Pure — no side effects beyond registry lookups. Returns backend='none' when the engine has neither a usable binary nor an API key; callers decide how to handle that. */ -// @kern-source: session:987 +// @kern-source: session:993 export function resolveCesarBackend(ctx: HandlerContext, engineId?: string): { backend: 'cli'|'api'|'none', binaryPath: string, hasBinary: boolean, hasApi: boolean, engine: any } { const config = ctx.config; const cesarEngineId = engineId ?? (config as any).cesarEngine ?? config.forgeFixedStarter ?? 'claude'; @@ -1054,7 +1060,7 @@ export function resolveCesarBackend(ctx: HandlerContext, engineId?: string): { b return { backend: 'none', binaryPath: '', hasBinary, hasApi, engine }; } -// @kern-source: session:1013 +// @kern-source: session:1019 export async function ensureCesarSession(ctx: HandlerContext): Promise { const config = ctx.config; const cesarEngineId = (config as any).cesarEngine ?? config.forgeFixedStarter ?? 'claude'; diff --git a/packages/cli/src/kern/cesar/session.kern b/packages/cli/src/kern/cesar/session.kern index e8cb058f..324b6638 100644 --- a/packages/cli/src/kern/cesar/session.kern +++ b/packages/cli/src/kern/cesar/session.kern @@ -959,12 +959,18 @@ fn name=mcpConfigFingerprint params="config:any" returns=string >>> fn name=resolveAgonMcpServerPath params="fromUrl?:string" returns=string export=true - doc "Resolve the agon-orchestration MCP server entry (packages/mcp/dist/index.js). The CLI ships as a tsup BUNDLE (chunks at packages/cli/dist/), so the old fixed '../../../../mcp/dist/index.js' relative to import.meta.url resolved to a NONEXISTENT path — the MCP server never spawned and Cesar saw zero tools (slow scraping fallback). Resolution order: (1) node module resolution of @kernlang/agon-mcp (works installed AND monorepo-via-symlink), (2) walk up to the repo root containing packages/mcp/dist/index.js (monorepo without a symlink), (3) the original relative guess as a last resort. `fromUrl` is for tests; defaults to this module's URL." + doc "Resolve the agon-orchestration MCP server entry. The CLI ships as a tsup BUNDLE that ALSO emits the MCP server to /mcp/index.js (see tsup.config.ts), so the published install is self-contained — no @kernlang/agon-mcp npm dependency. Resolution order: (0) the bundled sibling /mcp/index.js (the published, self-contained path), (1) node module resolution of @kernlang/agon-mcp (monorepo-via-symlink / legacy installs), (2) walk up to the repo root containing packages/mcp/dist/index.js (monorepo without a symlink), (3) the original relative guess as a last resort. `fromUrl` is for tests; defaults to this module's URL." handler <<< const raw = fromUrl ?? import.meta.url; // Accept either a file: URL (normal) or a bare path (defensive): fileURLToPath // throws ERR_INVALID_URL_SCHEME on a bare path, so normalise first. const url = raw.startsWith('file:') ? raw : pathToFileURL(raw).href; + // 0) Bundled sibling — cli's tsup build emits the MCP server to + // /mcp/index.js (see tsup.config.ts). This is the PUBLISHED + // path: self-contained, no @kernlang/agon-mcp dependency. The importing + // chunk lives in the cli dist dir, so dist/mcp/index.js sits beside it. + const bundledSibling = join(dirname(fileURLToPath(url)), 'mcp', 'index.js'); + if (existsSync(bundledSibling)) return bundledSibling; // 1) Installed/published OR monorepo workspace symlink — node resolution. try { const req = createRequire(url); diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts index d73a2625..a5e24627 100644 --- a/packages/cli/tsup.config.ts +++ b/packages/cli/tsup.config.ts @@ -14,7 +14,12 @@ function copyEngines(outDir: string) { } export default defineConfig({ - entry: ['src/index.ts'], + // Two entries from ONE build: the CLI itself, and the agon-orchestration MCP + // server (../mcp/src/index.ts) emitted to dist/mcp/index.js. Bundling the MCP + // server in keeps `npm i -g @kernlang/agon` self-contained — Cesar spawns the + // bundled copy (see resolveAgonMcpServerPath) instead of an unpublished + // @kernlang/agon-mcp dependency. Both entries inline agon-core and share chunks. + entry: { index: 'src/index.ts', 'mcp/index': '../mcp/src/index.ts' }, format: ['esm'], dts: false, sourcemap: true, @@ -48,5 +53,10 @@ export default defineConfig({ }, async onSuccess() { copyEngines(join(process.cwd(), 'dist')); + // The bundled MCP server resolves engines via /engines + // (resolveBuiltinEnginesDir), so it needs a copy next to dist/mcp/index.js — + // otherwise the spawned server starts with ZERO engines and Cesar silently + // loses its orchestration tools. + copyEngines(join(process.cwd(), 'dist', 'mcp')); }, }); diff --git a/packages/mcp/package.json b/packages/mcp/package.json index c3e8efc3..e5742f76 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,10 +1,8 @@ { "name": "@kernlang/agon-mcp", "version": "0.1.6", + "private": true, "type": "module", - "bin": { - "agon-mcp": "./dist/index.js" - }, "main": "./dist/index.js", "types": "./dist/index.d.ts", "files": [ diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts index df22cc28..a74fd3d5 100644 --- a/packages/mcp/src/index.ts +++ b/packages/mcp/src/index.ts @@ -1,4 +1,9 @@ -#!/usr/bin/env node +// NOTE: no `#!/usr/bin/env node` shebang here on purpose. This entry is bundled +// into the cli package (cli/dist/mcp/index.js) by cli's tsup, whose `banner` +// already prepends the shebang — a second one here lands on line 2 of the bundle +// and is a hard SyntaxError. The server is always spawned via `node ` +// (see resolveAgonMcpServerPath / the agon-orchestration mcpServer config), so it +// never needs to be directly executable. // Agon Orchestration MCP Server — entry point // Spawned by companion engines (Codex, Gemini, OpenCode) to expose // Tribunal, Brainstorm, Campfire, Forge, etc. as MCP tools.