perf: speed up daemon fast-path calls
This commit is contained in:
parent
f412d42122
commit
e6b451aee3
@ -8,6 +8,7 @@
|
||||
- Increase the default OAuth browser wait from 60 seconds to 5 minutes so hosted MCP sign-ins have enough time for account and permission review.
|
||||
- Skip the redundant daemon `status` preflight for warm keep-alive access, cutting one socket round-trip from each routed list/call/resource request while preserving stale-config and dead-daemon recovery.
|
||||
- Route explicit default keep-alive calls like `chrome-devtools.list_pages` through a daemon-only fast path, avoiding full runtime startup on warm calls.
|
||||
- Further reduce warm keep-alive call startup by avoiding runtime/config schema imports on CLI boot and using a narrower daemon call path for simple explicit calls.
|
||||
|
||||
### Config
|
||||
|
||||
|
||||
54
src/cli.ts
54
src/cli.ts
@ -5,7 +5,7 @@ import { CliUsageError } from './cli/errors.js';
|
||||
import { consumeHelpTokens, isHelpToken, isVersionToken, printHelp, printVersion } from './cli/help-output.js';
|
||||
import { logError, logInfo } from './cli/logger-context.js';
|
||||
import { DEBUG_HANG, dumpActiveHandles, terminateChildProcesses } from './cli/runtime-debug.js';
|
||||
import { resolveConfigPath } from './config.js';
|
||||
import { resolveConfigPath } from './config/path-discovery.js';
|
||||
import type { Runtime, RuntimeOptions } from './runtime.js';
|
||||
|
||||
export { parseCallArguments } from './cli/call-arguments.js';
|
||||
@ -323,6 +323,9 @@ async function maybeHandleDaemonFastCall(
|
||||
if (!server || !DAEMON_FAST_PATH_SERVERS.has(server) || isFastPathKeepAliveDisabled(server)) {
|
||||
return false;
|
||||
}
|
||||
if (await maybeHandleSimpleDaemonFastCall(callArgs, configResolution, rootDir)) {
|
||||
return true;
|
||||
}
|
||||
const [{ DaemonClient }, { handleCall: importedHandleCall }] = await Promise.all([
|
||||
import('./daemon/client.js'),
|
||||
import('./cli/call-command.js'),
|
||||
@ -336,6 +339,55 @@ async function maybeHandleDaemonFastCall(
|
||||
return true;
|
||||
}
|
||||
|
||||
async function maybeHandleSimpleDaemonFastCall(
|
||||
callArgs: string[],
|
||||
configResolution: { path: string; explicit: boolean },
|
||||
rootDir: string | undefined
|
||||
): Promise<boolean> {
|
||||
const [{ parseCallArguments }, { resolveCallTimeout }] = await Promise.all([
|
||||
import('./cli/call-arguments.js'),
|
||||
import('./cli/timeouts.js'),
|
||||
]);
|
||||
let parsed: ReturnType<typeof parseCallArguments>;
|
||||
try {
|
||||
parsed = parseCallArguments([...callArgs]);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!parsed.server ||
|
||||
!parsed.tool ||
|
||||
parsed.ephemeral ||
|
||||
parsed.tailLog ||
|
||||
parsed.saveImagesDir ||
|
||||
(parsed.positionalArgs?.length ?? 0) > 0 ||
|
||||
parsed.schemaStringCoercionCandidates ||
|
||||
parsed.schemaArrayCoercionCandidates
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [{ DaemonClient }, { wrapCallResult }, { printCallOutput }] = await Promise.all([
|
||||
import('./daemon/client.js'),
|
||||
import('./result-utils.js'),
|
||||
import('./cli/output-utils.js'),
|
||||
]);
|
||||
const daemonClient = new DaemonClient({
|
||||
configPath: configResolution.path,
|
||||
configExplicit: configResolution.explicit,
|
||||
rootDir,
|
||||
});
|
||||
const result = await daemonClient.callTool({
|
||||
server: parsed.server,
|
||||
tool: parsed.tool,
|
||||
args: Object.keys(parsed.args).length > 0 ? parsed.args : undefined,
|
||||
timeoutMs: resolveCallTimeout(parsed.timeoutMs),
|
||||
});
|
||||
const { callResult } = wrapCallResult(result);
|
||||
printCallOutput(callResult, result, parsed.output);
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolveDaemonFastCallArgs(command: string, args: string[]): string[] | undefined {
|
||||
if (command === 'call') {
|
||||
return args;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { resolveConfigPath } from '../config.js';
|
||||
import { resolveConfigPath } from '../config/path-discovery.js';
|
||||
import { parseLogLevel } from '../logging.js';
|
||||
import { extractFlags } from './flag-utils.js';
|
||||
import { getActiveLogger, getActiveLogLevel, logError, setLogLevel } from './logger-context.js';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import { loadServerDefinitions } from '../../config.js';
|
||||
import { MCPORTER_VERSION } from '../../runtime.js';
|
||||
import { MCPORTER_VERSION } from '../../version.js';
|
||||
import { logConfigLocations, resolveConfigLocations } from './shared.js';
|
||||
import type { ConfigCliOptions } from './types.js';
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import type { RolldownPlugin } from 'rolldown';
|
||||
import { MCPORTER_VERSION } from '../../runtime.js';
|
||||
import { MCPORTER_VERSION } from '../../version.js';
|
||||
import { markExecutable, safeCopyFile } from './fs-helpers.js';
|
||||
import { verifyBunAvailable } from './runtime.js';
|
||||
|
||||
@ -76,6 +76,7 @@ async function bundleWithRolldown({
|
||||
await bundle.write({
|
||||
file: absTarget,
|
||||
format: runtimeKind === 'bun' ? 'esm' : 'cjs',
|
||||
codeSplitting: false,
|
||||
sourcemap: false,
|
||||
minify,
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import type { CliArtifactMetadata } from '../../cli-metadata.js';
|
||||
import type { ServerDefinition } from '../../config.js';
|
||||
import { MCPORTER_VERSION } from '../../runtime.js';
|
||||
import { MCPORTER_VERSION } from '../../version.js';
|
||||
import { buildToolDoc, type ToolOptionDoc } from '../list-detail-helpers.js';
|
||||
import { markExecutable } from './fs-helpers.js';
|
||||
import { renderEmbeddedHelpSource } from './template-help.js';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import fsPromises from 'node:fs/promises';
|
||||
import { MCPORTER_VERSION } from '../runtime.js';
|
||||
import { MCPORTER_VERSION } from '../version.js';
|
||||
import { boldText, dimText, extraDimText, supportsAnsiColor } from './terminal.js';
|
||||
|
||||
type HelpEntry = {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import ora from 'ora';
|
||||
import type { ServerDefinition } from '../config.js';
|
||||
import { MCPORTER_VERSION } from '../runtime.js';
|
||||
import { setStdioLogMode } from '../sdk-patches.js';
|
||||
import { MCPORTER_VERSION } from '../version.js';
|
||||
import { persistPreparedEphemeralServer, prepareEphemeralServerTarget } from './ephemeral-target.js';
|
||||
import { splitHttpToolSelector } from './http-utils.js';
|
||||
import { chooseClosestIdentifier, renderIdentifierResolutionMessages } from './identifier-helpers.js';
|
||||
|
||||
@ -3,7 +3,6 @@ import fs from 'node:fs/promises';
|
||||
import net from 'node:net';
|
||||
import path from 'node:path';
|
||||
import { listConfigLayerPaths } from '../config/path-discovery.js';
|
||||
import { launchDaemonDetached } from './launch.js';
|
||||
import { getDaemonMetadataPath, getDaemonSocketPath } from './paths.js';
|
||||
import type {
|
||||
CallToolParams,
|
||||
@ -143,7 +142,8 @@ export class DaemonClient {
|
||||
return;
|
||||
}
|
||||
this.startingPromise = Promise.resolve()
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
const { launchDaemonDetached } = await import('./launch.js');
|
||||
launchDaemonDetached({
|
||||
configPath: this.options.configPath,
|
||||
configExplicit: this.options.configExplicit,
|
||||
@ -351,7 +351,12 @@ function delay(ms: number): Promise<void> {
|
||||
function normalizeLayers(
|
||||
layers: Array<{ path: string; mtimeMs: number | null }>
|
||||
): Array<{ path: string; mtimeMs: number | null }> {
|
||||
return layers
|
||||
.map((entry) => ({ path: path.resolve(entry.path), mtimeMs: entry.mtimeMs ?? null }))
|
||||
.toSorted((a, b) => a.path.localeCompare(b.path));
|
||||
const normalized = layers.map((entry) => ({
|
||||
path: path.isAbsolute(entry.path) ? entry.path : path.resolve(entry.path),
|
||||
mtimeMs: entry.mtimeMs ?? null,
|
||||
}));
|
||||
if (normalized.length < 2) {
|
||||
return normalized;
|
||||
}
|
||||
return normalized.toSorted((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
||||
}
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
import type { CallToolRequest, ListResourcesRequest, ReadResourceRequest } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { loadServerDefinitions, type ServerDefinition } from './config.js';
|
||||
import { createPrefixedConsoleLogger, type Logger, type LogLevel, resolveLogLevelFromEnv } from './logging.js';
|
||||
@ -10,17 +8,11 @@ import { resolveOAuthTimeoutFromEnv } from './runtime/oauth.js';
|
||||
import { type ClientContext, createClientContext } from './runtime/transport.js';
|
||||
import { normalizeTimeout, raceWithTimeout } from './runtime/utils.js';
|
||||
import { filterTools, isToolAllowed, validateToolFilters } from './tool-filters.js';
|
||||
import { MCPORTER_VERSION } from './version.js';
|
||||
|
||||
export { MCPORTER_VERSION } from './version.js';
|
||||
|
||||
const PACKAGE_NAME = 'mcporter';
|
||||
// Keep version in one place by reading package.json; fall back gracefully when bundled without it (e.g., bun bundle).
|
||||
const CLIENT_VERSION = (() => {
|
||||
try {
|
||||
return createRequire(import.meta.url)('../package.json').version as string;
|
||||
} catch {
|
||||
return process.env.MCPORTER_VERSION ?? '0.0.0-dev';
|
||||
}
|
||||
})();
|
||||
export const MCPORTER_VERSION = CLIENT_VERSION;
|
||||
const OAUTH_CODE_TIMEOUT_MS = resolveOAuthTimeoutFromEnv();
|
||||
|
||||
export interface RuntimeOptions {
|
||||
@ -121,7 +113,7 @@ class McpRuntime implements Runtime {
|
||||
this.logger = options.logger ?? createConsoleLogger();
|
||||
this.clientInfo = options.clientInfo ?? {
|
||||
name: PACKAGE_NAME,
|
||||
version: CLIENT_VERSION,
|
||||
version: MCPORTER_VERSION,
|
||||
};
|
||||
this.oauthTimeoutMs = options.oauthTimeoutMs;
|
||||
}
|
||||
|
||||
9
src/version.ts
Normal file
9
src/version.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
export const MCPORTER_VERSION = (() => {
|
||||
try {
|
||||
return createRequire(import.meta.url)('../package.json').version as string;
|
||||
} catch {
|
||||
return process.env.MCPORTER_VERSION ?? '0.0.0-dev';
|
||||
}
|
||||
})();
|
||||
@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { makeShortTempDir } from './fixtures/test-helpers.js';
|
||||
|
||||
const sentMethods: string[] = [];
|
||||
let launchDaemonDetached: ReturnType<typeof vi.fn>;
|
||||
const launchDaemonDetached = vi.hoisted(() => vi.fn());
|
||||
let createConnection: ReturnType<typeof vi.fn>;
|
||||
|
||||
class MockSocket extends EventEmitter {
|
||||
@ -73,7 +73,6 @@ vi.mock('node:net', () => {
|
||||
});
|
||||
|
||||
vi.mock('../src/daemon/launch.js', () => {
|
||||
launchDaemonDetached = vi.fn();
|
||||
return { launchDaemonDetached };
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user