@aiscene/shared 8.0.1 → 8.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/es/cli/cli-args.mjs +95 -0
  2. package/dist/es/cli/cli-error.mjs +24 -0
  3. package/dist/es/cli/cli-runner.mjs +10 -40
  4. package/dist/es/cli/index.mjs +4 -2
  5. package/dist/es/constants/example-code.mjs +2 -2
  6. package/dist/es/constants/index.mjs +6 -1
  7. package/dist/es/env/model-config-manager.mjs +3 -0
  8. package/dist/es/env/parse-model-config.mjs +6 -1
  9. package/dist/es/env/types.mjs +2 -1
  10. package/dist/es/env/utils.mjs +1 -9
  11. package/dist/es/extractor/index.mjs +2 -2
  12. package/dist/es/img/index.mjs +2 -2
  13. package/dist/es/img/info.mjs +7 -1
  14. package/dist/es/key-alias-utils.mjs +19 -0
  15. package/dist/es/mcp/base-server.mjs +10 -9
  16. package/dist/es/mcp/base-tools.mjs +72 -4
  17. package/dist/es/mcp/cli-report-session.mjs +78 -0
  18. package/dist/es/mcp/error-formatter.mjs +19 -0
  19. package/dist/es/mcp/index.mjs +2 -0
  20. package/dist/es/mcp/init-arg-utils.mjs +38 -0
  21. package/dist/es/mcp/inject-report-html-plugin.mjs +1 -1
  22. package/dist/es/mcp/tool-generator.mjs +65 -17
  23. package/dist/es/node/fs.mjs +1 -1
  24. package/dist/lib/cli/cli-args.js +138 -0
  25. package/dist/lib/cli/cli-error.js +61 -0
  26. package/dist/lib/cli/cli-runner.js +19 -46
  27. package/dist/lib/cli/index.js +8 -3
  28. package/dist/lib/constants/example-code.js +2 -2
  29. package/dist/lib/constants/index.js +23 -3
  30. package/dist/lib/env/model-config-manager.js +3 -0
  31. package/dist/lib/env/parse-model-config.js +6 -1
  32. package/dist/lib/env/types.js +4 -0
  33. package/dist/lib/env/utils.js +0 -11
  34. package/dist/lib/extractor/index.js +3 -0
  35. package/dist/lib/img/index.js +3 -0
  36. package/dist/lib/img/info.js +10 -1
  37. package/dist/lib/key-alias-utils.js +62 -0
  38. package/dist/lib/mcp/base-server.js +11 -10
  39. package/dist/lib/mcp/base-tools.js +74 -6
  40. package/dist/lib/mcp/cli-report-session.js +121 -0
  41. package/dist/lib/mcp/error-formatter.js +53 -0
  42. package/dist/lib/mcp/index.js +24 -10
  43. package/dist/lib/mcp/init-arg-utils.js +78 -0
  44. package/dist/lib/mcp/inject-report-html-plugin.js +1 -1
  45. package/dist/lib/mcp/tool-generator.js +74 -26
  46. package/dist/lib/node/fs.js +1 -1
  47. package/dist/types/cli/cli-args.d.ts +8 -0
  48. package/dist/types/cli/cli-error.d.ts +5 -0
  49. package/dist/types/cli/cli-runner.d.ts +4 -7
  50. package/dist/types/cli/index.d.ts +3 -1
  51. package/dist/types/constants/example-code.d.ts +1 -1
  52. package/dist/types/constants/index.d.ts +5 -0
  53. package/dist/types/env/types.d.ts +10 -2
  54. package/dist/types/env/utils.d.ts +0 -31
  55. package/dist/types/extractor/index.d.ts +1 -1
  56. package/dist/types/img/index.d.ts +1 -1
  57. package/dist/types/img/info.d.ts +5 -0
  58. package/dist/types/key-alias-utils.d.ts +9 -0
  59. package/dist/types/mcp/base-tools.d.ts +74 -5
  60. package/dist/types/mcp/cli-report-session.d.ts +12 -0
  61. package/dist/types/mcp/error-formatter.d.ts +12 -0
  62. package/dist/types/mcp/index.d.ts +2 -0
  63. package/dist/types/mcp/init-arg-utils.d.ts +13 -0
  64. package/dist/types/mcp/inject-report-html-plugin.d.ts +2 -2
  65. package/dist/types/mcp/launcher-helper.d.ts +3 -3
  66. package/dist/types/mcp/tool-generator.d.ts +3 -3
  67. package/dist/types/mcp/types.d.ts +9 -0
  68. package/package.json +1 -1
  69. package/src/cli/cli-args.ts +173 -0
  70. package/src/cli/cli-error.ts +24 -0
  71. package/src/cli/cli-runner.ts +37 -56
  72. package/src/cli/index.ts +3 -7
  73. package/src/constants/example-code.ts +2 -2
  74. package/src/constants/index.ts +10 -0
  75. package/src/env/model-config-manager.ts +3 -0
  76. package/src/env/parse-model-config.ts +19 -1
  77. package/src/env/types.ts +11 -2
  78. package/src/env/utils.ts +0 -50
  79. package/src/extractor/index.ts +1 -1
  80. package/src/img/index.ts +2 -0
  81. package/src/img/info.ts +27 -0
  82. package/src/key-alias-utils.ts +23 -0
  83. package/src/mcp/base-server.ts +10 -12
  84. package/src/mcp/base-tools.ts +218 -11
  85. package/src/mcp/cli-report-session.ts +130 -0
  86. package/src/mcp/error-formatter.ts +52 -0
  87. package/src/mcp/index.ts +2 -0
  88. package/src/mcp/init-arg-utils.ts +105 -0
  89. package/src/mcp/inject-report-html-plugin.ts +3 -3
  90. package/src/mcp/launcher-helper.ts +3 -3
  91. package/src/mcp/tool-generator.ts +98 -20
  92. package/src/mcp/types.ts +16 -1
@@ -9,6 +9,12 @@ import type {
9
9
  ToolResult,
10
10
  ToolResultContent,
11
11
  } from '../mcp/types';
12
+ import {
13
+ formatCliValidationError,
14
+ getCliOptionDisplay,
15
+ parseCliArgs,
16
+ } from './cli-args';
17
+ import { CLIError } from './cli-error';
12
18
 
13
19
  const debug = getDebug('cli-runner');
14
20
 
@@ -32,58 +38,8 @@ export interface CLIRunnerOptions {
32
38
  extraCommands?: CLIExtraCommand[];
33
39
  }
34
40
 
35
- export class CLIError extends Error {
36
- constructor(
37
- message: string,
38
- public exitCode = 1,
39
- ) {
40
- super(message);
41
- }
42
- }
43
-
44
- export function parseValue(raw: string): unknown {
45
- // JSON objects/arrays
46
- if (raw.startsWith('{') || raw.startsWith('[')) {
47
- try {
48
- return JSON.parse(raw);
49
- } catch {
50
- // Not valid JSON, treat as string below
51
- }
52
- }
53
-
54
- // Numbers
55
- if (/^-?\d+(\.\d+)?$/.test(raw)) {
56
- return Number(raw);
57
- }
58
-
59
- return raw;
60
- }
61
-
62
- export function parseCliArgs(args: string[]): Record<string, unknown> {
63
- const result: Record<string, unknown> = {};
64
-
65
- for (let i = 0; i < args.length; i++) {
66
- const arg = args[i];
67
- if (!arg.startsWith('--')) continue;
68
-
69
- const body = arg.slice(2);
70
- const eqIdx = body.indexOf('=');
71
-
72
- if (eqIdx >= 0) {
73
- // --key=value
74
- result[body.slice(0, eqIdx)] = parseValue(body.slice(eqIdx + 1));
75
- } else if (args[i + 1] && !args[i + 1].startsWith('--')) {
76
- // --key value
77
- i++;
78
- result[body] = parseValue(args[i]);
79
- } else {
80
- // --flag (boolean)
81
- result[body] = true;
82
- }
83
- }
84
-
85
- return result;
86
- }
41
+ export { parseCliArgs, parseValue } from './cli-args';
42
+ export { CLIError, reportCLIError } from './cli-error';
87
43
 
88
44
  function outputContentItem(item: ToolResultContent, isError: boolean): void {
89
45
  switch (item.type) {
@@ -128,10 +84,23 @@ function printCommandHelp(scriptName: string, cmd: CLICommand): void {
128
84
 
129
85
  const schemaEntries = Object.entries(def.schema);
130
86
  if (schemaEntries.length > 0) {
87
+ const optionWidth = Math.max(
88
+ 22,
89
+ ...schemaEntries.map(
90
+ ([key]) =>
91
+ getCliOptionDisplay(key, def.cli?.options?.[key]).label.length,
92
+ ),
93
+ );
131
94
  console.log('\nOptions:');
132
95
  for (const [key, zodType] of schemaEntries) {
96
+ const { label, aliases } = getCliOptionDisplay(
97
+ key,
98
+ def.cli?.options?.[key],
99
+ );
133
100
  const desc = zodType.description ?? '';
134
- console.log(` --${key.padEnd(20)} ${desc}`);
101
+ const aliasText =
102
+ aliases.length > 0 ? ` (aliases: ${aliases.join(', ')})` : '';
103
+ console.log(` ${label.padEnd(optionWidth)} ${desc}${aliasText}`);
135
104
  }
136
105
  }
137
106
  }
@@ -158,8 +127,10 @@ function printHelp(
158
127
  console.log(`\nRun "${scriptName} <command> --help" for more info.`);
159
128
  }
160
129
 
130
+ type AnyMidsceneTools = BaseMidsceneTools<any, any>;
131
+
161
132
  export async function runToolsCLI(
162
- tools: BaseMidsceneTools,
133
+ tools: AnyMidsceneTools,
163
134
  scriptName: string,
164
135
  options?: CLIRunnerOptions,
165
136
  ): Promise<void> {
@@ -227,14 +198,24 @@ export async function runToolsCLI(
227
198
  }
228
199
 
229
200
  const parsedArgs = parseCliArgs(restArgs);
230
- debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
231
-
232
201
  if (parsedArgs.help === true) {
233
202
  debug('showing command help for: %s', match.name);
234
203
  printCommandHelp(scriptName, match);
235
204
  return;
236
205
  }
237
206
 
207
+ const cliValidationError = formatCliValidationError(
208
+ scriptName,
209
+ match.name,
210
+ match.def,
211
+ parsedArgs,
212
+ );
213
+ if (cliValidationError) {
214
+ throw new CLIError(cliValidationError);
215
+ }
216
+
217
+ debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
218
+
238
219
  const result = await match.def.handler(parsedArgs);
239
220
  debug(
240
221
  'command %s completed, isError: %s',
package/src/cli/index.ts CHANGED
@@ -1,8 +1,4 @@
1
- export {
2
- runToolsCLI,
3
- CLIError,
4
- parseValue,
5
- parseCliArgs,
6
- removePrefix,
7
- } from './cli-runner';
1
+ export { CLIError, reportCLIError } from './cli-error';
2
+ export { parseCliArgs, parseValue } from './cli-args';
3
+ export { runToolsCLI, removePrefix } from './cli-runner';
8
4
  export type { CLIRunnerOptions, CLIExtraCommand } from './cli-runner';
@@ -28,8 +28,8 @@ aiQuery<T>(queryObject: Record<string, string>): Promise<T> // Extracts data fro
28
28
  // The following is test code for Midscene AI, for reference
29
29
  // The following is Playwright syntax, you can use Playwright to assist in test generation
30
30
  import { test as base } from '@playwright/test';
31
- import type { PlayWrightAiFixtureType } from '@midscene/web/playwright';
32
- import { PlaywrightAiFixture } from '@midscene/web/playwright';
31
+ import type { PlayWrightAiFixtureType } from '@aiscene/web/playwright';
32
+ import { PlaywrightAiFixture } from '@aiscene/web/playwright';
33
33
 
34
34
  const test = base.extend<PlayWrightAiFixtureType>(PlaywrightAiFixture({
35
35
  waitForNetworkIdleTimeout: 2000, // optional, the timeout for waiting for network idle between each action, default is 2000ms
@@ -17,6 +17,16 @@ export enum NodeType {
17
17
 
18
18
  export const PLAYGROUND_SERVER_PORT = 5800;
19
19
  export const SCRCPY_SERVER_PORT = 5700;
20
+ export const SCRCPY_ADB_CONNECT_TIMEOUT_MS = 10_000;
21
+ export const SCRCPY_PUSH_TIMEOUT_MS = 10_000;
22
+ export const SCRCPY_START_TIMEOUT_MS = 15_000;
23
+ export const SCRCPY_VIDEO_STREAM_TIMEOUT_MS = 15_000;
24
+ export const SCRCPY_PREVIEW_METADATA_TIMEOUT_MS =
25
+ SCRCPY_ADB_CONNECT_TIMEOUT_MS +
26
+ SCRCPY_PUSH_TIMEOUT_MS +
27
+ SCRCPY_START_TIMEOUT_MS +
28
+ SCRCPY_VIDEO_STREAM_TIMEOUT_MS +
29
+ 5_000;
20
30
 
21
31
  // WebDriver constants
22
32
  export const WEBDRIVER_ELEMENT_ID_KEY = 'element-6066-11e4-a52e-4f735466cecf';
@@ -68,14 +68,17 @@ export class ModelConfigManager {
68
68
  this.modelConfigMap = {
69
69
  default: {
70
70
  ...defaultConfig,
71
+ intent: 'default',
71
72
  createOpenAIClient: this.createOpenAIClientFn,
72
73
  },
73
74
  insight: {
74
75
  ...(insightConfig || defaultConfig),
76
+ intent: 'insight',
75
77
  createOpenAIClient: this.createOpenAIClientFn,
76
78
  },
77
79
  planning: {
78
80
  ...(planningConfig || defaultConfig),
81
+ intent: 'planning',
79
82
  createOpenAIClient: this.createOpenAIClientFn,
80
83
  },
81
84
  };
@@ -24,10 +24,26 @@ import {
24
24
  } from './types';
25
25
 
26
26
  import { getDebug } from '../logger';
27
+
27
28
  import { assert } from '../utils';
28
29
  import { maskConfig, parseJson } from './helper';
29
30
  import { initDebugConfig } from './init-debug';
30
31
 
32
+ declare const __VERSION__: string | undefined;
33
+
34
+ const MODEL_CONFIG_DOC_URL = 'https://midscenejs.com/model-common-config.html';
35
+
36
+ const getCurrentVersion = (): string => {
37
+ if (typeof __VERSION__ !== 'undefined' && __VERSION__) {
38
+ return __VERSION__;
39
+ }
40
+
41
+ return 'unknown';
42
+ };
43
+
44
+ const getInvalidModelFamilyMessage = (modelFamily: TModelFamily): string =>
45
+ `Invalid MIDSCENE_MODEL_FAMILY value: ${modelFamily}. Current version v${getCurrentVersion()} accepts the following model families: ${MODEL_FAMILY_VALUES.join(', ')}. You can also visit ${MODEL_CONFIG_DOC_URL} for the latest configuration information.`;
46
+
31
47
  type TModelConfigKeys =
32
48
  | typeof INSIGHT_MODEL_CONFIG_KEYS
33
49
  | typeof PLANNING_MODEL_CONFIG_KEYS
@@ -70,7 +86,7 @@ export const getUITarsModelVersion = (
70
86
  */
71
87
  export const validateModelFamily = (modelFamily?: TModelFamily): void => {
72
88
  if (modelFamily && !MODEL_FAMILY_VALUES.includes(modelFamily as any)) {
73
- throw new Error(`Invalid MIDSCENE_MODEL_FAMILY value: ${modelFamily}`);
89
+ throw new Error(getInvalidModelFamilyMessage(modelFamily));
74
90
  }
75
91
  };
76
92
 
@@ -228,6 +244,7 @@ export const parseOpenaiSdkConfig = ({
228
244
  modelName: modelName!,
229
245
  modelDescription,
230
246
  intent: '-' as any,
247
+ slot: '-' as any,
231
248
  timeout: provider[keys.timeout]
232
249
  ? Number(provider[keys.timeout])
233
250
  : undefined,
@@ -292,6 +309,7 @@ export const decideModelConfigFromIntentConfig = (
292
309
  useLegacyLogic: intent === 'default',
293
310
  });
294
311
  finalResult.intent = intent;
312
+ finalResult.slot = intent;
295
313
 
296
314
  debugLog(
297
315
  'decideModelConfig result by agent.modelConfig() with intent',
package/src/env/types.ts CHANGED
@@ -69,6 +69,8 @@ export const MIDSCENE_ANDROID_IME_STRATEGY = 'MIDSCENE_ANDROID_IME_STRATEGY';
69
69
 
70
70
  export const MIDSCENE_IOS_DEVICE_UDID = 'MIDSCENE_IOS_DEVICE_UDID';
71
71
  export const MIDSCENE_IOS_SIMULATOR_UDID = 'MIDSCENE_IOS_SIMULATOR_UDID';
72
+ export const MIDSCENE_IOS_DEVICE_CLASS_OVERRIDE =
73
+ 'MIDSCENE_IOS_DEVICE_CLASS_OVERRIDE';
72
74
 
73
75
  export const MIDSCENE_CACHE = 'MIDSCENE_CACHE';
74
76
  export const MIDSCENE_USE_VLM_UI_TARS = 'MIDSCENE_USE_VLM_UI_TARS';
@@ -425,7 +427,7 @@ export enum UITarsModelVersion {
425
427
 
426
428
  /**
427
429
  * Callback to create custom OpenAI client instance
428
- * @param config - Resolved model configuration including apiKey, baseURL, modelName, intent, etc.
430
+ * @param config - Resolved model configuration including apiKey, baseURL, modelName, intent, slot, etc.
429
431
  * @returns OpenAI client instance (can be wrapped with langsmith, langfuse, etc.)
430
432
  *
431
433
  * Note: Wrapper functions like langsmith's wrapOpenAI() return the same OpenAI instance
@@ -519,9 +521,16 @@ export interface IModelConfig {
519
521
  uiTarsModelVersion?: UITarsModelVersion;
520
522
  modelDescription: string;
521
523
  /**
522
- * original intent from the config
524
+ * The semantic intent this config is requested for.
525
+ * For example, getModelConfig('planning') always returns intent === 'planning'.
523
526
  */
524
527
  intent: TIntent;
528
+ /**
529
+ * The model-config slot this config was resolved from.
530
+ * For example, getModelConfig('planning') may resolve from slot === 'default'
531
+ * when MIDSCENE_PLANNING_MODEL_NAME is not configured.
532
+ */
533
+ slot: TIntent;
525
534
  /**
526
535
  * Custom OpenAI client factory function
527
536
  *
package/src/env/utils.ts CHANGED
@@ -13,56 +13,6 @@ export const globalConfigManager = new GlobalConfigManager();
13
13
  globalConfigManager.registerModelConfigManager(globalModelConfigManager);
14
14
  globalModelConfigManager.registerGlobalConfigManager(globalConfigManager);
15
15
 
16
- /**
17
- * Interface for devices that support getTimestamp method.
18
- * This is a minimal interface to avoid circular dependencies with @midscene/core.
19
- */
20
- export interface DeviceWithTimestamp {
21
- getTimestamp?: () => Promise<number>;
22
- }
23
-
24
- /**
25
- * Get the current timestamp, optionally from the target device.
26
- *
27
- * When useDeviceTimestamp is enabled and a device with getTimestamp is provided,
28
- * this function will return the device's time. Otherwise, it returns the system time.
29
- *
30
- * This is useful when:
31
- * - Testing on devices with different time zones
32
- * - Debugging time-sensitive features
33
- * - The system clock and device clock are not synchronized
34
- *
35
- * @param device Optional device interface that supports getTimestamp
36
- * @param useDeviceTimestamp Whether to use device timestamp (from agent config)
37
- * @returns Timestamp in milliseconds
38
- *
39
- * @example
40
- * // Without device - always returns system time
41
- * const systemTime = await getCurrentTime();
42
- *
43
- * @example
44
- * // With device and config enabled - returns device time
45
- * const deviceTime = await getCurrentTime(androidDevice, true);
46
- */
47
- export async function getCurrentTime(
48
- device?: DeviceWithTimestamp,
49
- useDeviceTimestamp?: boolean,
50
- ): Promise<number> {
51
- if (useDeviceTimestamp && device?.getTimestamp) {
52
- try {
53
- return await device.getTimestamp();
54
- } catch (error) {
55
- // Fall back to system time if device time retrieval fails
56
- console.warn(
57
- `Failed to get device time, falling back to system time: ${error}`,
58
- );
59
- return Date.now();
60
- }
61
- }
62
-
63
- return Date.now();
64
- }
65
-
66
16
  export const getPreferredLanguage = () => {
67
17
  const prefer = globalConfigManager.getEnvConfigValue(
68
18
  MIDSCENE_PREFERRED_LANGUAGE,
@@ -43,6 +43,6 @@ export {
43
43
  getElementXpath,
44
44
  } from './locator';
45
45
 
46
- export { generateElementByRect } from './dom-util';
46
+ export { generateElementByPoint, generateElementByRect } from './dom-util';
47
47
 
48
48
  export { isNotContainerElement } from './dom-util';
package/src/img/index.ts CHANGED
@@ -3,6 +3,8 @@ export {
3
3
  isValidPNGImageBuffer,
4
4
  isValidJPEGImageBuffer,
5
5
  isValidImageBuffer,
6
+ validateScreenshotBuffer,
7
+ type ValidateScreenshotBufferOptions,
6
8
  } from './info';
7
9
  export {
8
10
  resizeAndConvertImgBuffer,
package/src/img/info.ts CHANGED
@@ -73,3 +73,30 @@ export function isValidJPEGImageBuffer(buffer: Buffer): boolean {
73
73
  export function isValidImageBuffer(buffer: Buffer): boolean {
74
74
  return isValidPNGImageBuffer(buffer) || isValidJPEGImageBuffer(buffer);
75
75
  }
76
+
77
+ export interface ValidateScreenshotBufferOptions {
78
+ label: string;
79
+ minBufferSize?: number;
80
+ }
81
+
82
+ export function validateScreenshotBuffer(
83
+ screenshotBuffer: Buffer | undefined,
84
+ { label, minBufferSize = 0 }: ValidateScreenshotBufferOptions,
85
+ ): asserts screenshotBuffer is Buffer {
86
+ const bufferSize = screenshotBuffer?.length ?? 0;
87
+ if (!screenshotBuffer || bufferSize === 0) {
88
+ throw new Error(
89
+ `${label} validation failed: buffer size ${bufferSize} bytes`,
90
+ );
91
+ }
92
+
93
+ if (!isValidImageBuffer(screenshotBuffer)) {
94
+ throw new Error(`${label} buffer has invalid image format`);
95
+ }
96
+
97
+ if (minBufferSize > 0 && bufferSize < minBufferSize) {
98
+ throw new Error(
99
+ `${label} validation failed: buffer size ${bufferSize} bytes (minimum: ${minBufferSize})`,
100
+ );
101
+ }
102
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Internal-only helpers for CLI/MCP argument key aliasing.
3
+ * Not re-exported from the package entry point — keep consumers within
4
+ * `cli/` and `mcp/`.
5
+ */
6
+
7
+ export function kebabToCamel(str: string): string {
8
+ return str.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
9
+ }
10
+
11
+ export function camelToKebab(str: string): string {
12
+ return str
13
+ .replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)
14
+ .replace(/^-/, '');
15
+ }
16
+
17
+ export function getKeyAliases(key: string): string[] {
18
+ return [...new Set([key, kebabToCamel(key), camelToKebab(key)])];
19
+ }
20
+
21
+ export function isRecord(value: unknown): value is Record<string, unknown> {
22
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
23
+ }
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import type { ParseArgsConfig } from 'node:util';
3
- import { setIsMcp } from '../utils';
3
+ import { setIsMcp } from '@aiscene/shared/utils';
4
4
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
6
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
@@ -9,6 +9,7 @@ import express, {
9
9
  type Request,
10
10
  type Response,
11
11
  } from 'express';
12
+ import { getErrorMessage } from './error-formatter';
12
13
  import type { IMidsceneTools } from './types';
13
14
 
14
15
  export interface BaseMCPServerConfig {
@@ -119,7 +120,7 @@ export abstract class BaseMCPServer {
119
120
  try {
120
121
  await this.toolsManager.initTools();
121
122
  } catch (error: unknown) {
122
- const message = error instanceof Error ? error.message : String(error);
123
+ const message = getErrorMessage(error);
123
124
  console.error(`Failed to initialize tools: ${message}`);
124
125
  console.error('Tools will be initialized on first use');
125
126
  }
@@ -160,7 +161,7 @@ export abstract class BaseMCPServer {
160
161
  try {
161
162
  await this.mcpServer.connect(transport);
162
163
  } catch (error: unknown) {
163
- const message = error instanceof Error ? error.message : String(error);
164
+ const message = getErrorMessage(error);
164
165
  console.error(`Failed to connect MCP stdio transport: ${message}`);
165
166
  throw new Error(`Failed to initialize MCP stdio transport: ${message}`);
166
167
  }
@@ -282,7 +283,7 @@ export abstract class BaseMCPServer {
282
283
  .json({ error: 'Invalid session or GET without session' });
283
284
  }
284
285
  } catch (error: unknown) {
285
- const message = error instanceof Error ? error.message : String(error);
286
+ const message = getErrorMessage(error);
286
287
  const duration = Date.now() - startTime;
287
288
  console.error(
288
289
  `[${new Date().toISOString()}] [${requestId}] MCP request error after ${duration}ms: ${message}`,
@@ -336,8 +337,7 @@ export abstract class BaseMCPServer {
336
337
  try {
337
338
  await session.transport.close();
338
339
  } catch (error: unknown) {
339
- const message =
340
- error instanceof Error ? error.message : String(error);
340
+ const message = getErrorMessage(error);
341
341
  console.error(
342
342
  `Failed to close session ${session.transport.sessionId}: ${message}`,
343
343
  );
@@ -390,7 +390,7 @@ export abstract class BaseMCPServer {
390
390
  try {
391
391
  await this.mcpServer.connect(transport);
392
392
  } catch (error: unknown) {
393
- const message = error instanceof Error ? error.message : String(error);
393
+ const message = getErrorMessage(error);
394
394
  console.error(
395
395
  `[${new Date().toISOString()}] Failed to connect MCP transport: ${message}`,
396
396
  );
@@ -425,8 +425,7 @@ export abstract class BaseMCPServer {
425
425
  `[${new Date().toISOString()}] Session ${sid} cleaned up due to inactivity (remaining: ${sessions.size})`,
426
426
  );
427
427
  } catch (error: unknown) {
428
- const message =
429
- error instanceof Error ? error.message : String(error);
428
+ const message = getErrorMessage(error);
430
429
  console.error(
431
430
  `[${new Date().toISOString()}] Failed to close session ${sid} during cleanup: ${message}`,
432
431
  );
@@ -455,8 +454,7 @@ export abstract class BaseMCPServer {
455
454
  try {
456
455
  session.transport.close();
457
456
  } catch (error: unknown) {
458
- const message =
459
- error instanceof Error ? error.message : String(error);
457
+ const message = getErrorMessage(error);
460
458
  console.error(`Error closing session during shutdown: ${message}`);
461
459
  }
462
460
  }
@@ -475,7 +473,7 @@ export abstract class BaseMCPServer {
475
473
  this.performCleanup().finally(() => process.exit(1));
476
474
  }, 5000);
477
475
  } catch (error: unknown) {
478
- const message = error instanceof Error ? error.message : String(error);
476
+ const message = getErrorMessage(error);
479
477
  console.error(`Error closing HTTP server: ${message}`);
480
478
  this.performCleanup().finally(() => process.exit(1));
481
479
  }