@xiuchang-midscene/shared 2.0.2
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.
- package/README.md +9 -0
- package/dist/es/baseDB.mjs +109 -0
- package/dist/es/baseDB.mjs.bak +109 -0
- package/dist/es/build/copy-static.mjs +31 -0
- package/dist/es/build/copy-static.mjs.bak +31 -0
- package/dist/es/build/rspack-config.mjs +4 -0
- package/dist/es/build/rspack-config.mjs.bak +4 -0
- package/dist/es/cli/cli-runner.mjs +140 -0
- package/dist/es/cli/cli-runner.mjs.bak +140 -0
- package/dist/es/cli/index.mjs +2 -0
- package/dist/es/cli/index.mjs.bak +2 -0
- package/dist/es/common.mjs +37 -0
- package/dist/es/common.mjs.bak +37 -0
- package/dist/es/constants/example-code.mjs +223 -0
- package/dist/es/constants/example-code.mjs.bak +223 -0
- package/dist/es/constants/index.mjs +23 -0
- package/dist/es/constants/index.mjs.bak +23 -0
- package/dist/es/env/basic.mjs +6 -0
- package/dist/es/env/basic.mjs.bak +6 -0
- package/dist/es/env/constants.mjs +70 -0
- package/dist/es/env/constants.mjs.bak +70 -0
- package/dist/es/env/global-config-manager.mjs +94 -0
- package/dist/es/env/global-config-manager.mjs.bak +94 -0
- package/dist/es/env/helper.mjs +43 -0
- package/dist/es/env/helper.mjs.bak +43 -0
- package/dist/es/env/index.mjs +5 -0
- package/dist/es/env/index.mjs.bak +5 -0
- package/dist/es/env/init-debug.mjs +18 -0
- package/dist/es/env/init-debug.mjs.bak +18 -0
- package/dist/es/env/model-config-manager.mjs +79 -0
- package/dist/es/env/model-config-manager.mjs.bak +79 -0
- package/dist/es/env/parse-model-config.mjs +132 -0
- package/dist/es/env/parse-model-config.mjs.bak +132 -0
- package/dist/es/env/types.mjs +220 -0
- package/dist/es/env/types.mjs.bak +220 -0
- package/dist/es/env/utils.mjs +26 -0
- package/dist/es/env/utils.mjs.bak +26 -0
- package/dist/es/extractor/constants.mjs +2 -0
- package/dist/es/extractor/constants.mjs.bak +2 -0
- package/dist/es/extractor/debug.mjs +6 -0
- package/dist/es/extractor/debug.mjs.bak +6 -0
- package/dist/es/extractor/dom-util.mjs +92 -0
- package/dist/es/extractor/dom-util.mjs.bak +92 -0
- package/dist/es/extractor/index.mjs +5 -0
- package/dist/es/extractor/index.mjs.bak +5 -0
- package/dist/es/extractor/locator.mjs +250 -0
- package/dist/es/extractor/locator.mjs.bak +250 -0
- package/dist/es/extractor/tree.mjs +78 -0
- package/dist/es/extractor/tree.mjs.bak +78 -0
- package/dist/es/extractor/util.mjs +245 -0
- package/dist/es/extractor/util.mjs.bak +245 -0
- package/dist/es/extractor/web-extractor.mjs +303 -0
- package/dist/es/extractor/web-extractor.mjs.bak +303 -0
- package/dist/es/img/box-select.mjs +824 -0
- package/dist/es/img/box-select.mjs.bak +824 -0
- package/dist/es/img/canvas-fallback.mjs +238 -0
- package/dist/es/img/canvas-fallback.mjs.bak +238 -0
- package/dist/es/img/get-photon.mjs +45 -0
- package/dist/es/img/get-photon.mjs.bak +45 -0
- package/dist/es/img/get-sharp.mjs +11 -0
- package/dist/es/img/get-sharp.mjs.bak +11 -0
- package/dist/es/img/index.mjs +4 -0
- package/dist/es/img/index.mjs.bak +4 -0
- package/dist/es/img/info.mjs +29 -0
- package/dist/es/img/info.mjs.bak +29 -0
- package/dist/es/img/transform.mjs +295 -0
- package/dist/es/img/transform.mjs.bak +295 -0
- package/dist/es/index.mjs +4 -0
- package/dist/es/index.mjs.bak +4 -0
- package/dist/es/logger.mjs +64 -0
- package/dist/es/logger.mjs.bak +64 -0
- package/dist/es/mcp/base-server.mjs +281 -0
- package/dist/es/mcp/base-server.mjs.bak +281 -0
- package/dist/es/mcp/base-tools.mjs +91 -0
- package/dist/es/mcp/base-tools.mjs.bak +91 -0
- package/dist/es/mcp/chrome-path.mjs +35 -0
- package/dist/es/mcp/chrome-path.mjs.bak +35 -0
- package/dist/es/mcp/index.mjs +7 -0
- package/dist/es/mcp/index.mjs.bak +7 -0
- package/dist/es/mcp/inject-report-html-plugin.mjs +53 -0
- package/dist/es/mcp/inject-report-html-plugin.mjs.bak +53 -0
- package/dist/es/mcp/launcher-helper.mjs +52 -0
- package/dist/es/mcp/launcher-helper.mjs.bak +52 -0
- package/dist/es/mcp/tool-generator.mjs +297 -0
- package/dist/es/mcp/tool-generator.mjs.bak +297 -0
- package/dist/es/mcp/types.mjs +3 -0
- package/dist/es/mcp/types.mjs.bak +3 -0
- package/dist/es/node/fs.mjs +44 -0
- package/dist/es/node/fs.mjs.bak +44 -0
- package/dist/es/node/index.mjs +2 -0
- package/dist/es/node/index.mjs.bak +2 -0
- package/dist/es/node/port.mjs +24 -0
- package/dist/es/node/port.mjs.bak +24 -0
- package/dist/es/oss/demo.mjs +30 -0
- package/dist/es/oss/demo.mjs.bak +30 -0
- package/dist/es/oss/index.mjs +90 -0
- package/dist/es/oss/index.mjs.bak +90 -0
- package/dist/es/polyfills/async-hooks.mjs +2 -0
- package/dist/es/polyfills/async-hooks.mjs.bak +2 -0
- package/dist/es/polyfills/index.mjs +1 -0
- package/dist/es/polyfills/index.mjs.bak +1 -0
- package/dist/es/types/index.mjs +3 -0
- package/dist/es/types/index.mjs.bak +3 -0
- package/dist/es/us-keyboard-layout.mjs +1414 -0
- package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
- package/dist/es/us-keyboard-layout.mjs.bak +1414 -0
- package/dist/es/utils.mjs +72 -0
- package/dist/es/utils.mjs.bak +72 -0
- package/dist/es/zod-schema-utils.mjs +54 -0
- package/dist/es/zod-schema-utils.mjs.bak +54 -0
- package/dist/lib/baseDB.js +149 -0
- package/dist/lib/baseDB.js.bak +149 -0
- package/dist/lib/build/copy-static.js +79 -0
- package/dist/lib/build/copy-static.js.bak +79 -0
- package/dist/lib/build/rspack-config.js +38 -0
- package/dist/lib/build/rspack-config.js.bak +38 -0
- package/dist/lib/cli/cli-runner.js +196 -0
- package/dist/lib/cli/cli-runner.js.bak +196 -0
- package/dist/lib/cli/index.js +48 -0
- package/dist/lib/cli/index.js.bak +48 -0
- package/dist/lib/common.js +93 -0
- package/dist/lib/common.js.bak +93 -0
- package/dist/lib/constants/example-code.js +260 -0
- package/dist/lib/constants/example-code.js.bak +260 -0
- package/dist/lib/constants/index.js +96 -0
- package/dist/lib/constants/index.js.bak +96 -0
- package/dist/lib/env/basic.js +40 -0
- package/dist/lib/env/basic.js.bak +40 -0
- package/dist/lib/env/constants.js +113 -0
- package/dist/lib/env/constants.js.bak +113 -0
- package/dist/lib/env/global-config-manager.js +128 -0
- package/dist/lib/env/global-config-manager.js.bak +128 -0
- package/dist/lib/env/helper.js +80 -0
- package/dist/lib/env/helper.js.bak +80 -0
- package/dist/lib/env/index.js +90 -0
- package/dist/lib/env/index.js.bak +90 -0
- package/dist/lib/env/init-debug.js +52 -0
- package/dist/lib/env/init-debug.js.bak +52 -0
- package/dist/lib/env/model-config-manager.js +113 -0
- package/dist/lib/env/model-config-manager.js.bak +113 -0
- package/dist/lib/env/parse-model-config.js +178 -0
- package/dist/lib/env/parse-model-config.js.bak +178 -0
- package/dist/lib/env/types.js +554 -0
- package/dist/lib/env/types.js.bak +554 -0
- package/dist/lib/env/utils.js +72 -0
- package/dist/lib/env/utils.js.bak +72 -0
- package/dist/lib/extractor/constants.js +42 -0
- package/dist/lib/extractor/constants.js.bak +42 -0
- package/dist/lib/extractor/debug.js +12 -0
- package/dist/lib/extractor/debug.js.bak +12 -0
- package/dist/lib/extractor/dom-util.js +153 -0
- package/dist/lib/extractor/dom-util.js.bak +153 -0
- package/dist/lib/extractor/index.js +81 -0
- package/dist/lib/extractor/index.js.bak +81 -0
- package/dist/lib/extractor/locator.js +296 -0
- package/dist/lib/extractor/locator.js.bak +296 -0
- package/dist/lib/extractor/tree.js +124 -0
- package/dist/lib/extractor/tree.js.bak +124 -0
- package/dist/lib/extractor/util.js +336 -0
- package/dist/lib/extractor/util.js.bak +336 -0
- package/dist/lib/extractor/web-extractor.js +349 -0
- package/dist/lib/extractor/web-extractor.js.bak +349 -0
- package/dist/lib/img/box-select.js +875 -0
- package/dist/lib/img/box-select.js.bak +875 -0
- package/dist/lib/img/canvas-fallback.js +305 -0
- package/dist/lib/img/canvas-fallback.js.bak +305 -0
- package/dist/lib/img/get-photon.js +82 -0
- package/dist/lib/img/get-photon.js.bak +82 -0
- package/dist/lib/img/get-sharp.js +45 -0
- package/dist/lib/img/get-sharp.js.bak +45 -0
- package/dist/lib/img/index.js +95 -0
- package/dist/lib/img/index.js.bak +95 -0
- package/dist/lib/img/info.js +83 -0
- package/dist/lib/img/info.js.bak +83 -0
- package/dist/lib/img/transform.js +387 -0
- package/dist/lib/img/transform.js.bak +387 -0
- package/dist/lib/index.js +47 -0
- package/dist/lib/index.js.bak +47 -0
- package/dist/lib/logger.js +114 -0
- package/dist/lib/logger.js.bak +114 -0
- package/dist/lib/mcp/base-server.js +331 -0
- package/dist/lib/mcp/base-server.js.bak +331 -0
- package/dist/lib/mcp/base-tools.js +125 -0
- package/dist/lib/mcp/base-tools.js.bak +125 -0
- package/dist/lib/mcp/chrome-path.js +72 -0
- package/dist/lib/mcp/chrome-path.js.bak +72 -0
- package/dist/lib/mcp/index.js +100 -0
- package/dist/lib/mcp/index.js.bak +100 -0
- package/dist/lib/mcp/inject-report-html-plugin.js +98 -0
- package/dist/lib/mcp/inject-report-html-plugin.js.bak +98 -0
- package/dist/lib/mcp/launcher-helper.js +86 -0
- package/dist/lib/mcp/launcher-helper.js.bak +86 -0
- package/dist/lib/mcp/tool-generator.js +334 -0
- package/dist/lib/mcp/tool-generator.js.bak +334 -0
- package/dist/lib/mcp/types.js +40 -0
- package/dist/lib/mcp/types.js.bak +40 -0
- package/dist/lib/node/fs.js +97 -0
- package/dist/lib/node/fs.js.bak +97 -0
- package/dist/lib/node/index.js +65 -0
- package/dist/lib/node/index.js.bak +65 -0
- package/dist/lib/node/port.js +61 -0
- package/dist/lib/node/port.js.bak +61 -0
- package/dist/lib/oss/demo.js +36 -0
- package/dist/lib/oss/demo.js.bak +36 -0
- package/dist/lib/oss/index.js +138 -0
- package/dist/lib/oss/index.js.bak +138 -0
- package/dist/lib/polyfills/async-hooks.js +36 -0
- package/dist/lib/polyfills/async-hooks.js.bak +36 -0
- package/dist/lib/polyfills/index.js +58 -0
- package/dist/lib/polyfills/index.js.bak +58 -0
- package/dist/lib/types/index.js +37 -0
- package/dist/lib/types/index.js.bak +37 -0
- package/dist/lib/us-keyboard-layout.js +1457 -0
- package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
- package/dist/lib/us-keyboard-layout.js.bak +1457 -0
- package/dist/lib/utils.js +148 -0
- package/dist/lib/utils.js.bak +148 -0
- package/dist/lib/zod-schema-utils.js +97 -0
- package/dist/lib/zod-schema-utils.js.bak +97 -0
- package/dist/types/baseDB.d.ts +25 -0
- package/dist/types/baseDB.d.ts.bak +25 -0
- package/dist/types/build/copy-static.d.ts +31 -0
- package/dist/types/build/copy-static.d.ts.bak +31 -0
- package/dist/types/build/rspack-config.d.ts +8 -0
- package/dist/types/build/rspack-config.d.ts.bak +8 -0
- package/dist/types/cli/cli-runner.d.ts +14 -0
- package/dist/types/cli/cli-runner.d.ts.bak +14 -0
- package/dist/types/cli/index.d.ts +2 -0
- package/dist/types/cli/index.d.ts.bak +2 -0
- package/dist/types/common.d.ts +12 -0
- package/dist/types/common.d.ts.bak +12 -0
- package/dist/types/constants/example-code.d.ts +2 -0
- package/dist/types/constants/example-code.d.ts.bak +2 -0
- package/dist/types/constants/index.d.ts +21 -0
- package/dist/types/constants/index.d.ts.bak +21 -0
- package/dist/types/env/basic.d.ts +6 -0
- package/dist/types/env/basic.d.ts.bak +6 -0
- package/dist/types/env/constants.d.ts +40 -0
- package/dist/types/env/constants.d.ts.bak +40 -0
- package/dist/types/env/global-config-manager.d.ts +32 -0
- package/dist/types/env/global-config-manager.d.ts.bak +32 -0
- package/dist/types/env/helper.d.ts +4 -0
- package/dist/types/env/helper.d.ts.bak +4 -0
- package/dist/types/env/index.d.ts +4 -0
- package/dist/types/env/index.d.ts.bak +4 -0
- package/dist/types/env/init-debug.d.ts +1 -0
- package/dist/types/env/init-debug.d.ts.bak +1 -0
- package/dist/types/env/model-config-manager.d.ts +25 -0
- package/dist/types/env/model-config-manager.d.ts.bak +25 -0
- package/dist/types/env/parse-model-config.d.ts +31 -0
- package/dist/types/env/parse-model-config.d.ts.bak +31 -0
- package/dist/types/env/types.d.ts +318 -0
- package/dist/types/env/types.d.ts.bak +318 -0
- package/dist/types/env/utils.d.ts +38 -0
- package/dist/types/env/utils.d.ts.bak +38 -0
- package/dist/types/extractor/constants.d.ts +1 -0
- package/dist/types/extractor/constants.d.ts.bak +1 -0
- package/dist/types/extractor/debug.d.ts +1 -0
- package/dist/types/extractor/debug.d.ts.bak +1 -0
- package/dist/types/extractor/dom-util.d.ts +56 -0
- package/dist/types/extractor/dom-util.d.ts.bak +56 -0
- package/dist/types/extractor/index.d.ts +32 -0
- package/dist/types/extractor/index.d.ts.bak +32 -0
- package/dist/types/extractor/locator.d.ts +9 -0
- package/dist/types/extractor/locator.d.ts.bak +9 -0
- package/dist/types/extractor/tree.d.ts +6 -0
- package/dist/types/extractor/tree.d.ts.bak +6 -0
- package/dist/types/extractor/util.d.ts +47 -0
- package/dist/types/extractor/util.d.ts.bak +47 -0
- package/dist/types/extractor/web-extractor.d.ts +19 -0
- package/dist/types/extractor/web-extractor.d.ts.bak +19 -0
- package/dist/types/img/box-select.d.ts +26 -0
- package/dist/types/img/box-select.d.ts.bak +26 -0
- package/dist/types/img/canvas-fallback.d.ts +105 -0
- package/dist/types/img/canvas-fallback.d.ts.bak +105 -0
- package/dist/types/img/get-photon.d.ts +19 -0
- package/dist/types/img/get-photon.d.ts.bak +19 -0
- package/dist/types/img/get-sharp.d.ts +3 -0
- package/dist/types/img/get-sharp.d.ts.bak +3 -0
- package/dist/types/img/index.d.ts +3 -0
- package/dist/types/img/index.d.ts.bak +3 -0
- package/dist/types/img/info.d.ts +29 -0
- package/dist/types/img/info.d.ts.bak +29 -0
- package/dist/types/img/transform.d.ts +107 -0
- package/dist/types/img/transform.d.ts.bak +107 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.bak +4 -0
- package/dist/types/logger.d.ts +5 -0
- package/dist/types/logger.d.ts.bak +5 -0
- package/dist/types/mcp/base-server.d.ts +93 -0
- package/dist/types/mcp/base-server.d.ts.bak +93 -0
- package/dist/types/mcp/base-tools.d.ts +79 -0
- package/dist/types/mcp/base-tools.d.ts.bak +79 -0
- package/dist/types/mcp/chrome-path.d.ts +2 -0
- package/dist/types/mcp/chrome-path.d.ts.bak +2 -0
- package/dist/types/mcp/index.d.ts +7 -0
- package/dist/types/mcp/index.d.ts.bak +7 -0
- package/dist/types/mcp/inject-report-html-plugin.d.ts +18 -0
- package/dist/types/mcp/inject-report-html-plugin.d.ts.bak +18 -0
- package/dist/types/mcp/launcher-helper.d.ts +94 -0
- package/dist/types/mcp/launcher-helper.d.ts.bak +94 -0
- package/dist/types/mcp/tool-generator.d.ts +10 -0
- package/dist/types/mcp/tool-generator.d.ts.bak +10 -0
- package/dist/types/mcp/types.d.ts +103 -0
- package/dist/types/mcp/types.d.ts.bak +103 -0
- package/dist/types/node/fs.d.ts +15 -0
- package/dist/types/node/fs.d.ts.bak +15 -0
- package/dist/types/node/index.d.ts +2 -0
- package/dist/types/node/index.d.ts.bak +2 -0
- package/dist/types/node/port.d.ts +8 -0
- package/dist/types/node/port.d.ts.bak +8 -0
- package/dist/types/oss/demo.d.ts +1 -0
- package/dist/types/oss/demo.d.ts.bak +1 -0
- package/dist/types/oss/index.d.ts +34 -0
- package/dist/types/oss/index.d.ts.bak +34 -0
- package/dist/types/polyfills/async-hooks.d.ts +6 -0
- package/dist/types/polyfills/async-hooks.d.ts.bak +6 -0
- package/dist/types/polyfills/index.d.ts +4 -0
- package/dist/types/polyfills/index.d.ts.bak +4 -0
- package/dist/types/types/index.d.ts +34 -0
- package/dist/types/types/index.d.ts.bak +34 -0
- package/dist/types/us-keyboard-layout.d.ts +32 -0
- package/dist/types/us-keyboard-layout.d.ts.bak +32 -0
- package/dist/types/utils.d.ts +34 -0
- package/dist/types/utils.d.ts.bak +34 -0
- package/dist/types/zod-schema-utils.d.ts +23 -0
- package/dist/types/zod-schema-utils.d.ts.bak +23 -0
- package/package.json +132 -0
- package/src/baseDB.ts +158 -0
- package/src/build/copy-static.ts +68 -0
- package/src/build/rspack-config.ts +12 -0
- package/src/cli/cli-runner.ts +224 -0
- package/src/cli/index.ts +8 -0
- package/src/common.ts +67 -0
- package/src/constants/example-code.ts +223 -0
- package/src/constants/index.ts +29 -0
- package/src/env/basic.ts +12 -0
- package/src/env/constants.ts +234 -0
- package/src/env/global-config-manager.ts +191 -0
- package/src/env/helper.ts +58 -0
- package/src/env/index.ts +4 -0
- package/src/env/init-debug.ts +34 -0
- package/src/env/model-config-manager.ts +149 -0
- package/src/env/parse-model-config.ts +294 -0
- package/src/env/types.ts +547 -0
- package/src/env/utils.ts +89 -0
- package/src/extractor/constants.ts +5 -0
- package/src/extractor/debug.ts +10 -0
- package/src/extractor/dom-util.ts +226 -0
- package/src/extractor/index.ts +48 -0
- package/src/extractor/locator.ts +469 -0
- package/src/extractor/tree.ts +179 -0
- package/src/extractor/util.ts +482 -0
- package/src/extractor/web-extractor.ts +481 -0
- package/src/img/box-select.ts +588 -0
- package/src/img/canvas-fallback.ts +393 -0
- package/src/img/get-photon.ts +108 -0
- package/src/img/get-sharp.ts +18 -0
- package/src/img/index.ts +26 -0
- package/src/img/info.ts +75 -0
- package/src/img/transform.ts +594 -0
- package/src/index.ts +8 -0
- package/src/logger.ts +96 -0
- package/src/mcp/base-server.ts +502 -0
- package/src/mcp/base-tools.ts +185 -0
- package/src/mcp/chrome-path.ts +48 -0
- package/src/mcp/index.ts +7 -0
- package/src/mcp/inject-report-html-plugin.ts +119 -0
- package/src/mcp/launcher-helper.ts +200 -0
- package/src/mcp/tool-generator.ts +429 -0
- package/src/mcp/types.ts +112 -0
- package/src/node/fs.ts +84 -0
- package/src/node/index.ts +2 -0
- package/src/node/port.ts +37 -0
- package/src/oss/demo.ts +61 -0
- package/src/oss/index.ts +187 -0
- package/src/polyfills/async-hooks.ts +6 -0
- package/src/polyfills/index.ts +4 -0
- package/src/types/index.ts +52 -0
- package/src/us-keyboard-layout.ts +723 -0
- package/src/utils.ts +149 -0
- package/src/zod-schema-utils.ts +133 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import type { ParseArgsConfig } from 'node:util';
|
|
3
|
+
import { setIsMcp } from '@midscene/shared/utils';
|
|
4
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
7
|
+
import express, {
|
|
8
|
+
type Application,
|
|
9
|
+
type Request,
|
|
10
|
+
type Response,
|
|
11
|
+
} from 'express';
|
|
12
|
+
import type { IMidsceneTools } from './types';
|
|
13
|
+
|
|
14
|
+
export interface BaseMCPServerConfig {
|
|
15
|
+
name: string;
|
|
16
|
+
version: string;
|
|
17
|
+
description: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface HttpLaunchOptions {
|
|
21
|
+
port: number;
|
|
22
|
+
host?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface LaunchMCPServerResult {
|
|
26
|
+
/**
|
|
27
|
+
* The MCP server port (for HTTP mode)
|
|
28
|
+
*/
|
|
29
|
+
port?: number;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The server host (for HTTP mode)
|
|
33
|
+
*/
|
|
34
|
+
host?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Function to gracefully shutdown the MCP server
|
|
38
|
+
*/
|
|
39
|
+
close: () => Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface SessionData {
|
|
43
|
+
transport: StreamableHTTPServerTransport;
|
|
44
|
+
createdAt: Date;
|
|
45
|
+
lastAccessedAt: Date;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* CLI argument configuration for MCP servers
|
|
50
|
+
*/
|
|
51
|
+
export const CLI_ARGS_CONFIG: ParseArgsConfig['options'] = {
|
|
52
|
+
mode: { type: 'string', default: 'stdio' },
|
|
53
|
+
port: { type: 'string', default: '3000' },
|
|
54
|
+
host: { type: 'string', default: 'localhost' },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export interface CLIArgs {
|
|
58
|
+
mode?: string;
|
|
59
|
+
port?: string;
|
|
60
|
+
host?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Launch an MCP server based on CLI arguments
|
|
65
|
+
* Shared helper to reduce duplication across platform CLI entry points
|
|
66
|
+
*/
|
|
67
|
+
export function launchMCPServer(
|
|
68
|
+
server: BaseMCPServer,
|
|
69
|
+
args: CLIArgs,
|
|
70
|
+
): Promise<LaunchMCPServerResult> {
|
|
71
|
+
if (args.mode === 'http') {
|
|
72
|
+
return server.launchHttp({
|
|
73
|
+
port: Number.parseInt(args.port || '3000', 10),
|
|
74
|
+
host: args.host || 'localhost',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return server.launch();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
81
|
+
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
82
|
+
const MAX_SESSIONS = 100; // Maximum concurrent sessions to prevent DoS
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Base MCP Server class with programmatic launch() API
|
|
86
|
+
* Each platform extends this to provide their own tools manager
|
|
87
|
+
*/
|
|
88
|
+
export abstract class BaseMCPServer {
|
|
89
|
+
protected mcpServer: McpServer;
|
|
90
|
+
protected toolsManager?: IMidsceneTools;
|
|
91
|
+
protected config: BaseMCPServerConfig;
|
|
92
|
+
protected providedToolsManager?: IMidsceneTools;
|
|
93
|
+
|
|
94
|
+
constructor(config: BaseMCPServerConfig, toolsManager?: IMidsceneTools) {
|
|
95
|
+
this.config = config;
|
|
96
|
+
this.mcpServer = new McpServer({
|
|
97
|
+
name: config.name,
|
|
98
|
+
version: config.version,
|
|
99
|
+
description: config.description,
|
|
100
|
+
});
|
|
101
|
+
this.providedToolsManager = toolsManager;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Platform-specific: create tools manager instance
|
|
106
|
+
* This is only called if no tools manager was provided in constructor
|
|
107
|
+
*/
|
|
108
|
+
protected abstract createToolsManager(): IMidsceneTools;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Initialize tools manager and attach to MCP server
|
|
112
|
+
*/
|
|
113
|
+
private async initializeToolsManager(): Promise<void> {
|
|
114
|
+
setIsMcp(true);
|
|
115
|
+
|
|
116
|
+
// Use provided tools manager if available, otherwise create new one
|
|
117
|
+
this.toolsManager = this.providedToolsManager || this.createToolsManager();
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await this.toolsManager.initTools();
|
|
121
|
+
} catch (error: unknown) {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
console.error(`Failed to initialize tools: ${message}`);
|
|
124
|
+
console.error('Tools will be initialized on first use');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.toolsManager.attachToServer(this.mcpServer);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Perform cleanup on shutdown
|
|
132
|
+
*/
|
|
133
|
+
private async performCleanup(): Promise<void> {
|
|
134
|
+
console.error(`${this.config.name} closing...`);
|
|
135
|
+
this.mcpServer.close();
|
|
136
|
+
await this.toolsManager?.destroy?.().catch(console.error);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Initialize and launch the MCP server with stdio transport
|
|
141
|
+
*/
|
|
142
|
+
public async launch(): Promise<LaunchMCPServerResult> {
|
|
143
|
+
// Hijack stdout-based console methods to stderr for stdio mode
|
|
144
|
+
// This prevents them from breaking MCP JSON-RPC protocol on stdout
|
|
145
|
+
// Note: console.warn and console.error already output to stderr
|
|
146
|
+
console.log = (...args: unknown[]) => {
|
|
147
|
+
console.error('[LOG]', ...args);
|
|
148
|
+
};
|
|
149
|
+
console.info = (...args: unknown[]) => {
|
|
150
|
+
console.error('[INFO]', ...args);
|
|
151
|
+
};
|
|
152
|
+
console.debug = (...args: unknown[]) => {
|
|
153
|
+
console.error('[DEBUG]', ...args);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
await this.initializeToolsManager();
|
|
157
|
+
|
|
158
|
+
const transport = new StdioServerTransport();
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
await this.mcpServer.connect(transport);
|
|
162
|
+
} catch (error: unknown) {
|
|
163
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
164
|
+
console.error(`Failed to connect MCP stdio transport: ${message}`);
|
|
165
|
+
throw new Error(`Failed to initialize MCP stdio transport: ${message}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Setup signal handlers for graceful shutdown
|
|
169
|
+
let isShuttingDown = false;
|
|
170
|
+
const cleanup = () => {
|
|
171
|
+
if (isShuttingDown) return;
|
|
172
|
+
isShuttingDown = true;
|
|
173
|
+
console.error(`${this.config.name} shutting down...`);
|
|
174
|
+
this.performCleanup().finally(() => process.exit(0));
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Setup process-level error handlers to prevent crashes
|
|
178
|
+
process.on('uncaughtException', (error: Error & { code?: string }) => {
|
|
179
|
+
// Exit on pipe errors — parent process is gone
|
|
180
|
+
if (error.code === 'EPIPE' || error.code === 'ERR_STREAM_DESTROYED') {
|
|
181
|
+
cleanup();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
console.error(`[${this.config.name}] Uncaught Exception:`, error);
|
|
185
|
+
console.error('Stack:', error.stack);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
process.on('unhandledRejection', (reason: unknown) => {
|
|
189
|
+
console.error(`[${this.config.name}] Unhandled Rejection:`, reason);
|
|
190
|
+
if (reason instanceof Error) {
|
|
191
|
+
console.error('Stack:', reason.stack);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Exit when stdin closes (parent process gone) or reaches EOF
|
|
196
|
+
process.stdin.on('close', cleanup);
|
|
197
|
+
process.stdin.on('end', cleanup);
|
|
198
|
+
|
|
199
|
+
// Exit when stdout/stderr pipe breaks (parent process gone)
|
|
200
|
+
process.stdout.on('error', cleanup);
|
|
201
|
+
|
|
202
|
+
process.once('SIGINT', cleanup);
|
|
203
|
+
process.once('SIGTERM', cleanup);
|
|
204
|
+
process.once('SIGHUP', cleanup);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
close: async () => {
|
|
208
|
+
this.performCleanup();
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Launch MCP server with HTTP transport
|
|
215
|
+
* Supports stateful sessions for web applications and service integration
|
|
216
|
+
*/
|
|
217
|
+
public async launchHttp(
|
|
218
|
+
options: HttpLaunchOptions,
|
|
219
|
+
): Promise<LaunchMCPServerResult> {
|
|
220
|
+
// Validate port number
|
|
221
|
+
if (
|
|
222
|
+
!Number.isInteger(options.port) ||
|
|
223
|
+
options.port < 1 ||
|
|
224
|
+
options.port > 65535
|
|
225
|
+
) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Invalid port number: ${options.port}. Port must be between 1 and 65535.`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
await this.initializeToolsManager();
|
|
232
|
+
|
|
233
|
+
const app: Application = express();
|
|
234
|
+
|
|
235
|
+
// Add JSON body parser with size limit
|
|
236
|
+
app.use(express.json({ limit: '10mb' }));
|
|
237
|
+
|
|
238
|
+
const sessions = new Map<string, SessionData>();
|
|
239
|
+
|
|
240
|
+
app.all('/mcp', async (req: Request, res: Response) => {
|
|
241
|
+
const startTime = Date.now();
|
|
242
|
+
const requestId = randomUUID().substring(0, 8);
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const rawSessionId = req.headers['mcp-session-id'];
|
|
246
|
+
const sessionId = Array.isArray(rawSessionId)
|
|
247
|
+
? rawSessionId[0]
|
|
248
|
+
: rawSessionId;
|
|
249
|
+
let session = sessionId ? sessions.get(sessionId) : undefined;
|
|
250
|
+
|
|
251
|
+
if (!session && req.method === 'POST') {
|
|
252
|
+
// Check session limit to prevent DoS
|
|
253
|
+
if (sessions.size >= MAX_SESSIONS) {
|
|
254
|
+
console.error(
|
|
255
|
+
`[${new Date().toISOString()}] [${requestId}] Session limit reached: ${sessions.size}/${MAX_SESSIONS}`,
|
|
256
|
+
);
|
|
257
|
+
res.status(503).json({
|
|
258
|
+
error: 'Too many active sessions',
|
|
259
|
+
message: 'Server is at maximum capacity. Please try again later.',
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
session = await this.createHttpSession(sessions);
|
|
264
|
+
console.log(
|
|
265
|
+
`[${new Date().toISOString()}] [${requestId}] New session created: ${session.transport.sessionId}`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (session) {
|
|
270
|
+
session.lastAccessedAt = new Date();
|
|
271
|
+
await session.transport.handleRequest(req, res, req.body);
|
|
272
|
+
const duration = Date.now() - startTime;
|
|
273
|
+
console.log(
|
|
274
|
+
`[${new Date().toISOString()}] [${requestId}] Request completed in ${duration}ms`,
|
|
275
|
+
);
|
|
276
|
+
} else {
|
|
277
|
+
console.error(
|
|
278
|
+
`[${new Date().toISOString()}] [${requestId}] Invalid session or GET without session`,
|
|
279
|
+
);
|
|
280
|
+
res
|
|
281
|
+
.status(400)
|
|
282
|
+
.json({ error: 'Invalid session or GET without session' });
|
|
283
|
+
}
|
|
284
|
+
} catch (error: unknown) {
|
|
285
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
286
|
+
const duration = Date.now() - startTime;
|
|
287
|
+
console.error(
|
|
288
|
+
`[${new Date().toISOString()}] [${requestId}] MCP request error after ${duration}ms: ${message}`,
|
|
289
|
+
);
|
|
290
|
+
if (!res.headersSent) {
|
|
291
|
+
res.status(500).json({
|
|
292
|
+
error: 'Internal server error',
|
|
293
|
+
message: 'Failed to process MCP request',
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const host = options.host || 'localhost';
|
|
300
|
+
|
|
301
|
+
// Create server with error handling
|
|
302
|
+
const server = app
|
|
303
|
+
.listen(options.port, host, () => {
|
|
304
|
+
console.log(
|
|
305
|
+
`${this.config.name} HTTP server listening on http://${host}:${options.port}/mcp`,
|
|
306
|
+
);
|
|
307
|
+
})
|
|
308
|
+
.on('error', (error: NodeJS.ErrnoException) => {
|
|
309
|
+
if (error.code === 'EADDRINUSE') {
|
|
310
|
+
console.error(
|
|
311
|
+
`ERROR: Port ${options.port} is already in use.\nPlease try a different port: --port=<number>\nExample: --mode=http --port=${options.port + 1}`,
|
|
312
|
+
);
|
|
313
|
+
} else if (error.code === 'EACCES') {
|
|
314
|
+
console.error(
|
|
315
|
+
`ERROR: Permission denied to bind to port ${options.port}.\nPorts below 1024 require root/admin privileges.\nPlease use a port above 1024 or run with elevated privileges.`,
|
|
316
|
+
);
|
|
317
|
+
} else {
|
|
318
|
+
console.error(
|
|
319
|
+
`ERROR: Failed to start HTTP server on ${host}:${options.port}\n` +
|
|
320
|
+
`Reason: ${error.message}\n` +
|
|
321
|
+
`Code: ${error.code || 'unknown'}`,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
process.exit(1);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const cleanupInterval = this.startSessionCleanup(sessions);
|
|
328
|
+
this.setupHttpShutdownHandlers(server, sessions, cleanupInterval);
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
port: options.port,
|
|
332
|
+
host,
|
|
333
|
+
close: async () => {
|
|
334
|
+
clearInterval(cleanupInterval);
|
|
335
|
+
for (const session of sessions.values()) {
|
|
336
|
+
try {
|
|
337
|
+
await session.transport.close();
|
|
338
|
+
} catch (error: unknown) {
|
|
339
|
+
const message =
|
|
340
|
+
error instanceof Error ? error.message : String(error);
|
|
341
|
+
console.error(
|
|
342
|
+
`Failed to close session ${session.transport.sessionId}: ${message}`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
sessions.clear();
|
|
347
|
+
|
|
348
|
+
return new Promise<void>((resolve) => {
|
|
349
|
+
server.close(async (err) => {
|
|
350
|
+
if (err) {
|
|
351
|
+
console.error('Error closing HTTP server:', err);
|
|
352
|
+
}
|
|
353
|
+
await this.performCleanup();
|
|
354
|
+
resolve();
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Create a new HTTP session with transport
|
|
363
|
+
*/
|
|
364
|
+
private async createHttpSession(
|
|
365
|
+
sessions: Map<string, SessionData>,
|
|
366
|
+
): Promise<SessionData> {
|
|
367
|
+
const transport = new StreamableHTTPServerTransport({
|
|
368
|
+
sessionIdGenerator: () => randomUUID(),
|
|
369
|
+
onsessioninitialized: (sid: string) => {
|
|
370
|
+
sessions.set(sid, {
|
|
371
|
+
transport,
|
|
372
|
+
createdAt: new Date(),
|
|
373
|
+
lastAccessedAt: new Date(),
|
|
374
|
+
});
|
|
375
|
+
console.log(
|
|
376
|
+
`[${new Date().toISOString()}] Session ${sid} initialized (total: ${sessions.size})`,
|
|
377
|
+
);
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
transport.onclose = () => {
|
|
382
|
+
if (transport.sessionId) {
|
|
383
|
+
sessions.delete(transport.sessionId);
|
|
384
|
+
console.log(
|
|
385
|
+
`[${new Date().toISOString()}] Session ${transport.sessionId} closed (remaining: ${sessions.size})`,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
await this.mcpServer.connect(transport);
|
|
392
|
+
} catch (error: unknown) {
|
|
393
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
394
|
+
console.error(
|
|
395
|
+
`[${new Date().toISOString()}] Failed to connect MCP transport: ${message}`,
|
|
396
|
+
);
|
|
397
|
+
// Clean up the failed transport
|
|
398
|
+
if (transport.sessionId) {
|
|
399
|
+
sessions.delete(transport.sessionId);
|
|
400
|
+
}
|
|
401
|
+
throw new Error(`Failed to initialize MCP session: ${message}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
transport,
|
|
406
|
+
createdAt: new Date(),
|
|
407
|
+
lastAccessedAt: new Date(),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Start periodic session cleanup for inactive sessions
|
|
413
|
+
*/
|
|
414
|
+
private startSessionCleanup(
|
|
415
|
+
sessions: Map<string, SessionData>,
|
|
416
|
+
): ReturnType<typeof setInterval> {
|
|
417
|
+
return setInterval(() => {
|
|
418
|
+
const now = Date.now();
|
|
419
|
+
for (const [sid, session] of sessions) {
|
|
420
|
+
if (now - session.lastAccessedAt.getTime() > SESSION_TIMEOUT_MS) {
|
|
421
|
+
try {
|
|
422
|
+
session.transport.close();
|
|
423
|
+
sessions.delete(sid);
|
|
424
|
+
console.log(
|
|
425
|
+
`[${new Date().toISOString()}] Session ${sid} cleaned up due to inactivity (remaining: ${sessions.size})`,
|
|
426
|
+
);
|
|
427
|
+
} catch (error: unknown) {
|
|
428
|
+
const message =
|
|
429
|
+
error instanceof Error ? error.message : String(error);
|
|
430
|
+
console.error(
|
|
431
|
+
`[${new Date().toISOString()}] Failed to close session ${sid} during cleanup: ${message}`,
|
|
432
|
+
);
|
|
433
|
+
// Still delete from map to prevent retry loops
|
|
434
|
+
sessions.delete(sid);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}, CLEANUP_INTERVAL_MS);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Setup shutdown handlers for HTTP server
|
|
443
|
+
*/
|
|
444
|
+
private setupHttpShutdownHandlers(
|
|
445
|
+
server: ReturnType<Application['listen']>,
|
|
446
|
+
sessions: Map<string, SessionData>,
|
|
447
|
+
cleanupInterval: ReturnType<typeof setInterval>,
|
|
448
|
+
): void {
|
|
449
|
+
const cleanup = () => {
|
|
450
|
+
console.error(`${this.config.name} shutting down...`);
|
|
451
|
+
clearInterval(cleanupInterval);
|
|
452
|
+
|
|
453
|
+
// Close all sessions with error handling
|
|
454
|
+
for (const session of sessions.values()) {
|
|
455
|
+
try {
|
|
456
|
+
session.transport.close();
|
|
457
|
+
} catch (error: unknown) {
|
|
458
|
+
const message =
|
|
459
|
+
error instanceof Error ? error.message : String(error);
|
|
460
|
+
console.error(`Error closing session during shutdown: ${message}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
sessions.clear();
|
|
464
|
+
|
|
465
|
+
// Close HTTP server gracefully
|
|
466
|
+
try {
|
|
467
|
+
server.close(() => {
|
|
468
|
+
// Server closed callback - all connections finished
|
|
469
|
+
this.performCleanup().finally(() => process.exit(0));
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Set a timeout in case server.close() hangs
|
|
473
|
+
setTimeout(() => {
|
|
474
|
+
console.error('Forcefully shutting down after timeout');
|
|
475
|
+
this.performCleanup().finally(() => process.exit(1));
|
|
476
|
+
}, 5000);
|
|
477
|
+
} catch (error: unknown) {
|
|
478
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
479
|
+
console.error(`Error closing HTTP server: ${message}`);
|
|
480
|
+
this.performCleanup().finally(() => process.exit(1));
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
// Use once() to prevent multiple registrations
|
|
485
|
+
process.once('SIGINT', cleanup);
|
|
486
|
+
process.once('SIGTERM', cleanup);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Get the underlying MCP server instance
|
|
491
|
+
*/
|
|
492
|
+
public getServer(): McpServer {
|
|
493
|
+
return this.mcpServer;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Get the tools manager instance
|
|
498
|
+
*/
|
|
499
|
+
public getToolsManager(): IMidsceneTools | undefined {
|
|
500
|
+
return this.toolsManager;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { parseBase64 } from '@midscene/shared/img';
|
|
2
|
+
import { getDebug } from '@midscene/shared/logger';
|
|
3
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import {
|
|
5
|
+
generateCommonTools,
|
|
6
|
+
generateToolsFromActionSpace,
|
|
7
|
+
} from './tool-generator';
|
|
8
|
+
import type {
|
|
9
|
+
ActionSpaceItem,
|
|
10
|
+
BaseAgent,
|
|
11
|
+
BaseDevice,
|
|
12
|
+
IMidsceneTools,
|
|
13
|
+
ToolDefinition,
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
const debug = getDebug('mcp:base-tools');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Base class for platform-specific MCP tools
|
|
20
|
+
* Generic type TAgent allows subclasses to use their specific agent types
|
|
21
|
+
*/
|
|
22
|
+
export abstract class BaseMidsceneTools<TAgent extends BaseAgent = BaseAgent>
|
|
23
|
+
implements IMidsceneTools
|
|
24
|
+
{
|
|
25
|
+
protected mcpServer?: McpServer;
|
|
26
|
+
protected agent?: TAgent;
|
|
27
|
+
protected toolDefinitions: ToolDefinition[] = [];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Ensure agent is initialized and ready for use.
|
|
31
|
+
* Must be implemented by subclasses to create platform-specific agent.
|
|
32
|
+
* @param initParam Optional initialization parameter (platform-specific, e.g., URL, device ID)
|
|
33
|
+
* @returns Promise resolving to initialized agent instance
|
|
34
|
+
* @throws Error if agent initialization fails
|
|
35
|
+
*/
|
|
36
|
+
protected abstract ensureAgent(initParam?: string): Promise<TAgent>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Optional: prepare platform-specific tools (e.g., device connection)
|
|
40
|
+
*/
|
|
41
|
+
protected preparePlatformTools(): ToolDefinition[] {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Must be implemented by subclasses to create a temporary device instance
|
|
47
|
+
* This allows getting real actionSpace without connecting to device
|
|
48
|
+
*/
|
|
49
|
+
protected abstract createTemporaryDevice(): BaseDevice;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Initialize all tools by querying actionSpace
|
|
53
|
+
* Uses two-layer fallback strategy:
|
|
54
|
+
* 1. Try to get actionSpace from connected agent (if available)
|
|
55
|
+
* 2. Create temporary device instance to read actionSpace (always succeeds)
|
|
56
|
+
*/
|
|
57
|
+
public async initTools(): Promise<void> {
|
|
58
|
+
this.toolDefinitions = [];
|
|
59
|
+
|
|
60
|
+
// 1. Add platform-specific tools first (device connection, etc.)
|
|
61
|
+
// These don't require an agent and should always be available
|
|
62
|
+
const platformTools = this.preparePlatformTools();
|
|
63
|
+
this.toolDefinitions.push(...platformTools);
|
|
64
|
+
|
|
65
|
+
// 2. Get action space: use pre-set agent if available, otherwise temp device.
|
|
66
|
+
// When called via mcpKitForAgent(), agent is set before initTools().
|
|
67
|
+
// For CLI usage, agent is deferred to the first real command.
|
|
68
|
+
let actionSpace: ActionSpaceItem[];
|
|
69
|
+
if (this.agent) {
|
|
70
|
+
actionSpace = await this.agent.getActionSpace();
|
|
71
|
+
debug(
|
|
72
|
+
'Action space from agent:',
|
|
73
|
+
actionSpace.map((a) => a.name).join(', '),
|
|
74
|
+
);
|
|
75
|
+
} else {
|
|
76
|
+
const tempDevice = this.createTemporaryDevice();
|
|
77
|
+
actionSpace = tempDevice.actionSpace();
|
|
78
|
+
await tempDevice.destroy?.();
|
|
79
|
+
debug(
|
|
80
|
+
'Action space from temporary device:',
|
|
81
|
+
actionSpace.map((a) => a.name).join(', '),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 3. Generate tools from action space (core innovation)
|
|
86
|
+
const actionTools = generateToolsFromActionSpace(actionSpace, () =>
|
|
87
|
+
this.ensureAgent(),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// 4. Add common tools (screenshot, waitFor)
|
|
91
|
+
const commonTools = generateCommonTools(() => this.ensureAgent());
|
|
92
|
+
|
|
93
|
+
this.toolDefinitions.push(...actionTools, ...commonTools);
|
|
94
|
+
|
|
95
|
+
debug('Total tools prepared:', this.toolDefinitions.length);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Attach to MCP server and register all tools
|
|
100
|
+
*/
|
|
101
|
+
public attachToServer(server: McpServer): void {
|
|
102
|
+
this.mcpServer = server;
|
|
103
|
+
|
|
104
|
+
if (this.toolDefinitions.length === 0) {
|
|
105
|
+
debug('Warning: No tools to register. Tools may be initialized lazily.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
for (const toolDef of this.toolDefinitions) {
|
|
109
|
+
this.mcpServer.tool(
|
|
110
|
+
toolDef.name,
|
|
111
|
+
toolDef.description,
|
|
112
|
+
toolDef.schema,
|
|
113
|
+
toolDef.handler,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
debug('Registered', this.toolDefinitions.length, 'tools');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Cleanup method - destroy agent and release resources
|
|
122
|
+
*/
|
|
123
|
+
public async destroy(): Promise<void> {
|
|
124
|
+
await this.agent?.destroy?.();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get tool definitions
|
|
129
|
+
*/
|
|
130
|
+
public getToolDefinitions(): ToolDefinition[] {
|
|
131
|
+
return this.toolDefinitions;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Set agent for the tools manager
|
|
136
|
+
*/
|
|
137
|
+
public setAgent(agent: TAgent): void {
|
|
138
|
+
this.agent = agent;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Helper: Convert base64 screenshot to image content array
|
|
143
|
+
*/
|
|
144
|
+
protected buildScreenshotContent(screenshot: string) {
|
|
145
|
+
const { mimeType, body } = parseBase64(screenshot);
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
type: 'image' as const,
|
|
149
|
+
data: body,
|
|
150
|
+
mimeType,
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Helper: Build a simple text result for tool responses
|
|
157
|
+
*/
|
|
158
|
+
protected buildTextResult(text: string) {
|
|
159
|
+
return {
|
|
160
|
+
content: [{ type: 'text' as const, text }],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create a disconnect handler for releasing platform resources
|
|
166
|
+
* @param platformName Human-readable platform name for the response message
|
|
167
|
+
* @returns Handler function that destroys the agent and returns appropriate response
|
|
168
|
+
*/
|
|
169
|
+
protected createDisconnectHandler(platformName: string) {
|
|
170
|
+
return async () => {
|
|
171
|
+
if (!this.agent) {
|
|
172
|
+
return this.buildTextResult('No active connection to disconnect');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
await this.agent.destroy?.();
|
|
177
|
+
} catch (error) {
|
|
178
|
+
debug('Failed to destroy agent during disconnect:', error);
|
|
179
|
+
}
|
|
180
|
+
this.agent = undefined;
|
|
181
|
+
|
|
182
|
+
return this.buildTextResult(`Disconnected from ${platformName}`);
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|