fix: MCP server entrypoint not found in packaged builds
In packaged Electron apps process.cwd() does not point to the app directory so the mcp-server bundle was never found. Additionally the mcp-server dist was not included in the package at all. Changes: - Bundle mcp-server into a single self-contained ESM file (tsup noExternal + createRequire banner for CJS compat) - Ship mcp-server/dist/index.js and package.json via extraResources - Resolve entry via process.resourcesPath when app.isPackaged is true - Build controller + mcp-server in prebuild so dist exists before electron-builder runs - Add mcp-server/dist/index.js to CI verify steps on all platforms - Improve error message to list checked paths for easier debugging
This commit is contained in:
parent
24a398bccf
commit
1d15f5f4d9
4 changed files with 72 additions and 4 deletions
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
|
|
@ -113,6 +113,7 @@ jobs:
|
||||||
test -f dist-electron/main/index.cjs
|
test -f dist-electron/main/index.cjs
|
||||||
test -f dist-electron/preload/index.js
|
test -f dist-electron/preload/index.js
|
||||||
test -f out/renderer/index.html
|
test -f out/renderer/index.html
|
||||||
|
test -f mcp-server/dist/index.js
|
||||||
|
|
||||||
- name: Package (macOS ${{ matrix.arch }})
|
- name: Package (macOS ${{ matrix.arch }})
|
||||||
env:
|
env:
|
||||||
|
|
@ -180,6 +181,7 @@ jobs:
|
||||||
test -f dist-electron/main/index.cjs
|
test -f dist-electron/main/index.cjs
|
||||||
test -f dist-electron/preload/index.js
|
test -f dist-electron/preload/index.js
|
||||||
test -f out/renderer/index.html
|
test -f out/renderer/index.html
|
||||||
|
test -f mcp-server/dist/index.js
|
||||||
|
|
||||||
- name: Package (Windows)
|
- name: Package (Windows)
|
||||||
env:
|
env:
|
||||||
|
|
@ -246,6 +248,7 @@ jobs:
|
||||||
test -f dist-electron/main/index.cjs
|
test -f dist-electron/main/index.cjs
|
||||||
test -f dist-electron/preload/index.js
|
test -f dist-electron/preload/index.js
|
||||||
test -f out/renderer/index.html
|
test -f out/renderer/index.html
|
||||||
|
test -f mcp-server/dist/index.js
|
||||||
|
|
||||||
- name: Package (Linux)
|
- name: Package (Linux)
|
||||||
env:
|
env:
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,28 @@ export default defineConfig({
|
||||||
entry: ['src/index.ts'],
|
entry: ['src/index.ts'],
|
||||||
format: ['esm'],
|
format: ['esm'],
|
||||||
target: 'node20',
|
target: 'node20',
|
||||||
|
platform: 'node',
|
||||||
outDir: 'dist',
|
outDir: 'dist',
|
||||||
clean: true,
|
clean: true,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
dts: false,
|
dts: false,
|
||||||
|
// Bundle all dependencies into a single self-contained file so the packaged
|
||||||
|
// Electron app can run the MCP server without a node_modules tree.
|
||||||
|
noExternal: [/.*/],
|
||||||
|
splitting: false,
|
||||||
|
// Provide a real `require` function for CJS dependencies (e.g. undici)
|
||||||
|
// that use require() for Node built-in modules.
|
||||||
|
banner: {
|
||||||
|
js: `import { createRequire as __bundled_createRequire } from 'module';\nconst require = __bundled_createRequire(import.meta.url);`,
|
||||||
|
},
|
||||||
|
esbuildOptions(options) {
|
||||||
|
// Optional peer deps of xsschema (pulled in by fastmcp) — we only use zod.
|
||||||
|
// Mark as external at the esbuild level to avoid resolution errors.
|
||||||
|
options.external = [
|
||||||
|
...(options.external ?? []),
|
||||||
|
'sury',
|
||||||
|
'@valibot/to-json-schema',
|
||||||
|
'effect',
|
||||||
|
];
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
10
package.json
10
package.json
|
|
@ -20,7 +20,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "electron-vite dev",
|
"dev": "electron-vite dev",
|
||||||
"dev:kill": "node bin/kill-dev.js",
|
"dev:kill": "node bin/kill-dev.js",
|
||||||
"prebuild": "tsx scripts/fetch-pricing-data.ts",
|
"prebuild": "tsx scripts/fetch-pricing-data.ts && pnpm --filter agent-teams-controller build && pnpm --filter agent-teams-mcp build",
|
||||||
"build": "electron-vite build",
|
"build": "electron-vite build",
|
||||||
"dist": "electron-builder --mac --win --linux",
|
"dist": "electron-builder --mac --win --linux",
|
||||||
"dist:mac": "electron-builder --mac",
|
"dist:mac": "electron-builder --mac",
|
||||||
|
|
@ -223,6 +223,14 @@
|
||||||
{
|
{
|
||||||
"from": "resources/pricing.json",
|
"from": "resources/pricing.json",
|
||||||
"to": "pricing.json"
|
"to": "pricing.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "mcp-server/dist/index.js",
|
||||||
|
"to": "mcp-server/index.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "mcp-server/package.json",
|
||||||
|
"to": "mcp-server/package.json"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"npmRebuild": false,
|
"npmRebuild": false,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,24 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
return !!value && typeof value === 'object' && !Array.isArray(value);
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPackagedApp(): boolean {
|
||||||
|
try {
|
||||||
|
const { app } = require('electron') as typeof import('electron');
|
||||||
|
return app.isPackaged;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In a packaged Electron build the mcp-server bundle lives under
|
||||||
|
* `process.resourcesPath/mcp-server/index.js` (copied via extraResources).
|
||||||
|
* In dev mode we resolve relative to the workspace root (process.cwd()).
|
||||||
|
*/
|
||||||
|
function getPackagedServerEntry(): string {
|
||||||
|
return path.join(process.resourcesPath, 'mcp-server', 'index.js');
|
||||||
|
}
|
||||||
|
|
||||||
function getWorkspaceRoot(): string {
|
function getWorkspaceRoot(): string {
|
||||||
return process.cwd();
|
return process.cwd();
|
||||||
}
|
}
|
||||||
|
|
@ -82,17 +100,34 @@ async function resolveNodePath(): Promise<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveMcpLaunchSpec(): Promise<McpLaunchSpec> {
|
async function resolveMcpLaunchSpec(): Promise<McpLaunchSpec> {
|
||||||
|
const checked: string[] = [];
|
||||||
|
|
||||||
|
// 1. Packaged Electron app — use extraResources bundle
|
||||||
|
if (isPackagedApp()) {
|
||||||
|
const packagedEntry = getPackagedServerEntry();
|
||||||
|
checked.push(packagedEntry);
|
||||||
|
if (await pathExists(packagedEntry)) {
|
||||||
|
return {
|
||||||
|
command: await resolveNodePath(),
|
||||||
|
args: [packagedEntry],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
logger.warn(`Packaged MCP entry not found at ${packagedEntry}, falling back to workspace`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Dev mode — prefer source for hot changes
|
||||||
const sourceEntry = getSourceServerEntry();
|
const sourceEntry = getSourceServerEntry();
|
||||||
|
checked.push(sourceEntry);
|
||||||
if (await pathExists(sourceEntry)) {
|
if (await pathExists(sourceEntry)) {
|
||||||
// Prefer source in workspace/dev runs so newly added MCP tools are available
|
|
||||||
// immediately and we do not accidentally serve a stale built dist bundle.
|
|
||||||
return {
|
return {
|
||||||
command: 'pnpm',
|
command: 'pnpm',
|
||||||
args: ['--dir', getMcpServerDir(), 'exec', 'tsx', sourceEntry],
|
args: ['--dir', getMcpServerDir(), 'exec', 'tsx', sourceEntry],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Dev mode — built dist
|
||||||
const builtEntry = getBuiltServerEntry();
|
const builtEntry = getBuiltServerEntry();
|
||||||
|
checked.push(builtEntry);
|
||||||
if (await pathExists(builtEntry)) {
|
if (await pathExists(builtEntry)) {
|
||||||
return {
|
return {
|
||||||
command: await resolveNodePath(),
|
command: await resolveNodePath(),
|
||||||
|
|
@ -100,7 +135,9 @@ async function resolveMcpLaunchSpec(): Promise<McpLaunchSpec> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('agent-teams-mcp entrypoint not found in mcp-server package');
|
throw new Error(
|
||||||
|
`agent-teams-mcp entrypoint not found. Checked paths:\n${checked.map((p) => ` - ${p}`).join('\n')}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TeamMcpConfigBuilder {
|
export class TeamMcpConfigBuilder {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue