@godscene/shared 1.7.11

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 (236) hide show
  1. package/README.md +9 -0
  2. package/dist/es/baseDB.mjs +109 -0
  3. package/dist/es/cli/cli-args.mjs +95 -0
  4. package/dist/es/cli/cli-error.mjs +24 -0
  5. package/dist/es/cli/cli-runner.mjs +122 -0
  6. package/dist/es/cli/index.mjs +4 -0
  7. package/dist/es/common.mjs +37 -0
  8. package/dist/es/constants/example-code.mjs +227 -0
  9. package/dist/es/constants/index.mjs +124 -0
  10. package/dist/es/env/basic.mjs +6 -0
  11. package/dist/es/env/constants.mjs +110 -0
  12. package/dist/es/env/global-config-manager.mjs +94 -0
  13. package/dist/es/env/helper.mjs +43 -0
  14. package/dist/es/env/index.mjs +5 -0
  15. package/dist/es/env/init-debug.mjs +18 -0
  16. package/dist/es/env/model-config-manager.mjs +79 -0
  17. package/dist/es/env/parse-model-config.mjs +165 -0
  18. package/dist/es/env/types.mjs +232 -0
  19. package/dist/es/env/utils.mjs +18 -0
  20. package/dist/es/extractor/constants.mjs +2 -0
  21. package/dist/es/extractor/cs_postmessage.mjs +61 -0
  22. package/dist/es/extractor/customLocator.mjs +641 -0
  23. package/dist/es/extractor/debug.mjs +6 -0
  24. package/dist/es/extractor/dom-util.mjs +96 -0
  25. package/dist/es/extractor/index.mjs +5 -0
  26. package/dist/es/extractor/locator.mjs +250 -0
  27. package/dist/es/extractor/tree.mjs +78 -0
  28. package/dist/es/extractor/util.mjs +245 -0
  29. package/dist/es/extractor/web-extractor.mjs +393 -0
  30. package/dist/es/img/box-select.mjs +824 -0
  31. package/dist/es/img/canvas-fallback.mjs +238 -0
  32. package/dist/es/img/get-photon.mjs +45 -0
  33. package/dist/es/img/get-sharp.mjs +11 -0
  34. package/dist/es/img/index.mjs +4 -0
  35. package/dist/es/img/info.mjs +35 -0
  36. package/dist/es/img/transform.mjs +275 -0
  37. package/dist/es/index.mjs +2 -0
  38. package/dist/es/key-alias-utils.mjs +19 -0
  39. package/dist/es/logger.mjs +64 -0
  40. package/dist/es/mcp/base-server.mjs +282 -0
  41. package/dist/es/mcp/base-tools.mjs +159 -0
  42. package/dist/es/mcp/chrome-path.mjs +35 -0
  43. package/dist/es/mcp/cli-report-session.mjs +78 -0
  44. package/dist/es/mcp/error-formatter.mjs +19 -0
  45. package/dist/es/mcp/index.mjs +9 -0
  46. package/dist/es/mcp/init-arg-utils.mjs +38 -0
  47. package/dist/es/mcp/inject-report-html-plugin.mjs +53 -0
  48. package/dist/es/mcp/launcher-helper.mjs +52 -0
  49. package/dist/es/mcp/tool-generator.mjs +419 -0
  50. package/dist/es/mcp/types.mjs +3 -0
  51. package/dist/es/node/fs.mjs +44 -0
  52. package/dist/es/node/index.mjs +2 -0
  53. package/dist/es/node/port.mjs +24 -0
  54. package/dist/es/polyfills/async-hooks.mjs +2 -0
  55. package/dist/es/polyfills/index.mjs +1 -0
  56. package/dist/es/types/index.mjs +3 -0
  57. package/dist/es/us-keyboard-layout.mjs +1414 -0
  58. package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
  59. package/dist/es/utils.mjs +72 -0
  60. package/dist/es/zod-schema-utils.mjs +54 -0
  61. package/dist/lib/baseDB.js +149 -0
  62. package/dist/lib/cli/cli-args.js +138 -0
  63. package/dist/lib/cli/cli-error.js +61 -0
  64. package/dist/lib/cli/cli-runner.js +181 -0
  65. package/dist/lib/cli/index.js +53 -0
  66. package/dist/lib/common.js +93 -0
  67. package/dist/lib/constants/example-code.js +264 -0
  68. package/dist/lib/constants/index.js +221 -0
  69. package/dist/lib/env/basic.js +40 -0
  70. package/dist/lib/env/constants.js +153 -0
  71. package/dist/lib/env/global-config-manager.js +128 -0
  72. package/dist/lib/env/helper.js +80 -0
  73. package/dist/lib/env/index.js +90 -0
  74. package/dist/lib/env/init-debug.js +52 -0
  75. package/dist/lib/env/model-config-manager.js +113 -0
  76. package/dist/lib/env/parse-model-config.js +211 -0
  77. package/dist/lib/env/types.js +572 -0
  78. package/dist/lib/env/utils.js +61 -0
  79. package/dist/lib/extractor/constants.js +42 -0
  80. package/dist/lib/extractor/cs_postmessage.js +98 -0
  81. package/dist/lib/extractor/customLocator.js +693 -0
  82. package/dist/lib/extractor/debug.js +12 -0
  83. package/dist/lib/extractor/dom-util.js +157 -0
  84. package/dist/lib/extractor/index.js +87 -0
  85. package/dist/lib/extractor/locator.js +296 -0
  86. package/dist/lib/extractor/tree.js +124 -0
  87. package/dist/lib/extractor/util.js +336 -0
  88. package/dist/lib/extractor/web-extractor.js +442 -0
  89. package/dist/lib/img/box-select.js +875 -0
  90. package/dist/lib/img/canvas-fallback.js +305 -0
  91. package/dist/lib/img/get-photon.js +82 -0
  92. package/dist/lib/img/get-sharp.js +45 -0
  93. package/dist/lib/img/index.js +95 -0
  94. package/dist/lib/img/info.js +92 -0
  95. package/dist/lib/img/transform.js +364 -0
  96. package/dist/lib/index.js +36 -0
  97. package/dist/lib/key-alias-utils.js +62 -0
  98. package/dist/lib/logger.js +114 -0
  99. package/dist/lib/mcp/base-server.js +332 -0
  100. package/dist/lib/mcp/base-tools.js +193 -0
  101. package/dist/lib/mcp/chrome-path.js +72 -0
  102. package/dist/lib/mcp/cli-report-session.js +121 -0
  103. package/dist/lib/mcp/error-formatter.js +53 -0
  104. package/dist/lib/mcp/index.js +114 -0
  105. package/dist/lib/mcp/init-arg-utils.js +78 -0
  106. package/dist/lib/mcp/inject-report-html-plugin.js +98 -0
  107. package/dist/lib/mcp/launcher-helper.js +86 -0
  108. package/dist/lib/mcp/tool-generator.js +456 -0
  109. package/dist/lib/mcp/types.js +40 -0
  110. package/dist/lib/node/fs.js +97 -0
  111. package/dist/lib/node/index.js +65 -0
  112. package/dist/lib/node/port.js +61 -0
  113. package/dist/lib/polyfills/async-hooks.js +36 -0
  114. package/dist/lib/polyfills/index.js +58 -0
  115. package/dist/lib/types/index.js +37 -0
  116. package/dist/lib/us-keyboard-layout.js +1457 -0
  117. package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
  118. package/dist/lib/utils.js +148 -0
  119. package/dist/lib/zod-schema-utils.js +97 -0
  120. package/dist/types/baseDB.d.ts +25 -0
  121. package/dist/types/cli/cli-args.d.ts +8 -0
  122. package/dist/types/cli/cli-error.d.ts +5 -0
  123. package/dist/types/cli/cli-runner.d.ts +19 -0
  124. package/dist/types/cli/index.d.ts +4 -0
  125. package/dist/types/common.d.ts +12 -0
  126. package/dist/types/constants/example-code.d.ts +2 -0
  127. package/dist/types/constants/index.d.ts +61 -0
  128. package/dist/types/env/basic.d.ts +6 -0
  129. package/dist/types/env/constants.d.ts +50 -0
  130. package/dist/types/env/global-config-manager.d.ts +32 -0
  131. package/dist/types/env/helper.d.ts +4 -0
  132. package/dist/types/env/index.d.ts +4 -0
  133. package/dist/types/env/init-debug.d.ts +1 -0
  134. package/dist/types/env/model-config-manager.d.ts +25 -0
  135. package/dist/types/env/parse-model-config.d.ts +31 -0
  136. package/dist/types/env/types.d.ts +339 -0
  137. package/dist/types/env/utils.d.ts +7 -0
  138. package/dist/types/extractor/constants.d.ts +1 -0
  139. package/dist/types/extractor/cs_postmessage.d.ts +2 -0
  140. package/dist/types/extractor/customLocator.d.ts +69 -0
  141. package/dist/types/extractor/debug.d.ts +1 -0
  142. package/dist/types/extractor/dom-util.d.ts +57 -0
  143. package/dist/types/extractor/index.d.ts +33 -0
  144. package/dist/types/extractor/locator.d.ts +9 -0
  145. package/dist/types/extractor/tree.d.ts +6 -0
  146. package/dist/types/extractor/util.d.ts +47 -0
  147. package/dist/types/extractor/web-extractor.d.ts +24 -0
  148. package/dist/types/img/box-select.d.ts +26 -0
  149. package/dist/types/img/canvas-fallback.d.ts +105 -0
  150. package/dist/types/img/get-photon.d.ts +19 -0
  151. package/dist/types/img/get-sharp.d.ts +3 -0
  152. package/dist/types/img/index.d.ts +3 -0
  153. package/dist/types/img/info.d.ts +34 -0
  154. package/dist/types/img/transform.d.ts +98 -0
  155. package/dist/types/index.d.ts +2 -0
  156. package/dist/types/key-alias-utils.d.ts +9 -0
  157. package/dist/types/logger.d.ts +5 -0
  158. package/dist/types/mcp/base-server.d.ts +93 -0
  159. package/dist/types/mcp/base-tools.d.ts +148 -0
  160. package/dist/types/mcp/chrome-path.d.ts +2 -0
  161. package/dist/types/mcp/cli-report-session.d.ts +12 -0
  162. package/dist/types/mcp/error-formatter.d.ts +12 -0
  163. package/dist/types/mcp/index.d.ts +9 -0
  164. package/dist/types/mcp/init-arg-utils.d.ts +13 -0
  165. package/dist/types/mcp/inject-report-html-plugin.d.ts +18 -0
  166. package/dist/types/mcp/launcher-helper.d.ts +94 -0
  167. package/dist/types/mcp/tool-generator.d.ts +10 -0
  168. package/dist/types/mcp/types.d.ts +113 -0
  169. package/dist/types/node/fs.d.ts +15 -0
  170. package/dist/types/node/index.d.ts +2 -0
  171. package/dist/types/node/port.d.ts +8 -0
  172. package/dist/types/polyfills/async-hooks.d.ts +6 -0
  173. package/dist/types/polyfills/index.d.ts +4 -0
  174. package/dist/types/types/index.d.ts +36 -0
  175. package/dist/types/us-keyboard-layout.d.ts +32 -0
  176. package/dist/types/utils.d.ts +34 -0
  177. package/dist/types/zod-schema-utils.d.ts +23 -0
  178. package/package.json +125 -0
  179. package/src/baseDB.ts +158 -0
  180. package/src/cli/cli-args.ts +173 -0
  181. package/src/cli/cli-error.ts +24 -0
  182. package/src/cli/cli-runner.ts +230 -0
  183. package/src/cli/index.ts +4 -0
  184. package/src/common.ts +67 -0
  185. package/src/constants/example-code.ts +227 -0
  186. package/src/constants/index.ts +139 -0
  187. package/src/env/basic.ts +12 -0
  188. package/src/env/constants.ts +303 -0
  189. package/src/env/global-config-manager.ts +191 -0
  190. package/src/env/helper.ts +58 -0
  191. package/src/env/index.ts +4 -0
  192. package/src/env/init-debug.ts +34 -0
  193. package/src/env/model-config-manager.ts +149 -0
  194. package/src/env/parse-model-config.ts +357 -0
  195. package/src/env/types.ts +583 -0
  196. package/src/env/utils.ts +39 -0
  197. package/src/extractor/constants.ts +5 -0
  198. package/src/extractor/cs_postmessage.ts +136 -0
  199. package/src/extractor/customLocator.ts +1245 -0
  200. package/src/extractor/debug.ts +10 -0
  201. package/src/extractor/dom-util.ts +231 -0
  202. package/src/extractor/index.ts +50 -0
  203. package/src/extractor/locator.ts +469 -0
  204. package/src/extractor/tree.ts +179 -0
  205. package/src/extractor/util.ts +482 -0
  206. package/src/extractor/web-extractor.ts +617 -0
  207. package/src/img/box-select.ts +588 -0
  208. package/src/img/canvas-fallback.ts +393 -0
  209. package/src/img/get-photon.ts +108 -0
  210. package/src/img/get-sharp.ts +18 -0
  211. package/src/img/index.ts +27 -0
  212. package/src/img/info.ts +102 -0
  213. package/src/img/transform.ts +553 -0
  214. package/src/index.ts +1 -0
  215. package/src/key-alias-utils.ts +23 -0
  216. package/src/logger.ts +96 -0
  217. package/src/mcp/base-server.ts +500 -0
  218. package/src/mcp/base-tools.ts +391 -0
  219. package/src/mcp/chrome-path.ts +48 -0
  220. package/src/mcp/cli-report-session.ts +130 -0
  221. package/src/mcp/error-formatter.ts +52 -0
  222. package/src/mcp/index.ts +9 -0
  223. package/src/mcp/init-arg-utils.ts +105 -0
  224. package/src/mcp/inject-report-html-plugin.ts +119 -0
  225. package/src/mcp/launcher-helper.ts +200 -0
  226. package/src/mcp/tool-generator.ts +658 -0
  227. package/src/mcp/types.ts +131 -0
  228. package/src/node/fs.ts +84 -0
  229. package/src/node/index.ts +2 -0
  230. package/src/node/port.ts +37 -0
  231. package/src/polyfills/async-hooks.ts +6 -0
  232. package/src/polyfills/index.ts +4 -0
  233. package/src/types/index.ts +54 -0
  234. package/src/us-keyboard-layout.ts +723 -0
  235. package/src/utils.ts +149 -0
  236. package/src/zod-schema-utils.ts +133 -0
@@ -0,0 +1,64 @@
1
+ import node_fs from "node:fs";
2
+ import node_path from "node:path";
3
+ import node_util from "node:util";
4
+ import debug from "debug";
5
+ import { getMidsceneRunSubDir } from "./common.mjs";
6
+ import { ifInNode } from "./utils.mjs";
7
+ const topicPrefix = 'midscene';
8
+ const logStreams = new Map();
9
+ const debugInstances = new Map();
10
+ function getLogStream(topic) {
11
+ const topicFileName = topic.replace(/:/g, '-');
12
+ if (!logStreams.has(topicFileName)) {
13
+ const logFile = node_path.join(getMidsceneRunSubDir('log'), `${topicFileName}.log`);
14
+ const stream = node_fs.createWriteStream(logFile, {
15
+ flags: 'a'
16
+ });
17
+ logStreams.set(topicFileName, stream);
18
+ }
19
+ return logStreams.get(topicFileName);
20
+ }
21
+ function writeLogToFile(topic, message) {
22
+ if (!ifInNode) return;
23
+ const stream = getLogStream(topic);
24
+ const now = new Date();
25
+ const isoDate = now.toLocaleDateString('sv-SE');
26
+ const isoTime = now.toLocaleTimeString('sv-SE');
27
+ const milliseconds = now.getMilliseconds().toString().padStart(3, '0');
28
+ const timezoneOffsetMinutes = now.getTimezoneOffset();
29
+ const sign = timezoneOffsetMinutes <= 0 ? '+' : '-';
30
+ const hours = Math.floor(Math.abs(timezoneOffsetMinutes) / 60).toString().padStart(2, '0');
31
+ const minutes = (Math.abs(timezoneOffsetMinutes) % 60).toString().padStart(2, '0');
32
+ const timezoneString = `${sign}${hours}:${minutes}`;
33
+ const localISOTime = `${isoDate}T${isoTime}.${milliseconds}${timezoneString}`;
34
+ stream.write(`[${localISOTime}] ${message}\n`);
35
+ }
36
+ function getDebug(topic, options) {
37
+ const fullTopic = `${topicPrefix}:${topic}`;
38
+ const withConsole = options?.console ?? false;
39
+ const cacheKey = withConsole ? `${fullTopic}:withConsole` : fullTopic;
40
+ if (!debugInstances.has(cacheKey)) if (withConsole) {
41
+ const baseFn = getDebug(topic);
42
+ const wrapper = (...args)=>{
43
+ baseFn(...args);
44
+ console.warn('[Midscene]', ...args);
45
+ };
46
+ debugInstances.set(cacheKey, wrapper);
47
+ } else {
48
+ const debugFn = debug(fullTopic);
49
+ const wrapper = (...args)=>{
50
+ if (ifInNode) {
51
+ const message = node_util.format(...args);
52
+ writeLogToFile(topic, message);
53
+ }
54
+ debugFn(...args);
55
+ };
56
+ debugInstances.set(cacheKey, wrapper);
57
+ }
58
+ return debugInstances.get(cacheKey);
59
+ }
60
+ function enableDebug(topic) {
61
+ if (ifInNode) return;
62
+ debug.enable(`${topicPrefix}:${topic}`);
63
+ }
64
+ export { enableDebug, getDebug };
@@ -0,0 +1,282 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { setIsMcp } from "@godscene/shared/utils";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
+ import express from "express";
7
+ import { getErrorMessage } from "./error-formatter.mjs";
8
+ function _define_property(obj, key, value) {
9
+ if (key in obj) Object.defineProperty(obj, key, {
10
+ value: value,
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true
14
+ });
15
+ else obj[key] = value;
16
+ return obj;
17
+ }
18
+ const CLI_ARGS_CONFIG = {
19
+ mode: {
20
+ type: 'string',
21
+ default: 'stdio'
22
+ },
23
+ port: {
24
+ type: 'string',
25
+ default: '3000'
26
+ },
27
+ host: {
28
+ type: 'string',
29
+ default: 'localhost'
30
+ }
31
+ };
32
+ function launchMCPServer(server, args) {
33
+ if ('http' === args.mode) return server.launchHttp({
34
+ port: Number.parseInt(args.port || '3000', 10),
35
+ host: args.host || 'localhost'
36
+ });
37
+ return server.launch();
38
+ }
39
+ const SESSION_TIMEOUT_MS = 1800000;
40
+ const CLEANUP_INTERVAL_MS = 300000;
41
+ const MAX_SESSIONS = 100;
42
+ class BaseMCPServer {
43
+ async initializeToolsManager() {
44
+ setIsMcp(true);
45
+ this.toolsManager = this.providedToolsManager || this.createToolsManager();
46
+ try {
47
+ await this.toolsManager.initTools();
48
+ } catch (error) {
49
+ const message = getErrorMessage(error);
50
+ console.error(`Failed to initialize tools: ${message}`);
51
+ console.error('Tools will be initialized on first use');
52
+ }
53
+ this.toolsManager.attachToServer(this.mcpServer);
54
+ }
55
+ async performCleanup() {
56
+ console.error(`${this.config.name} closing...`);
57
+ this.mcpServer.close();
58
+ await this.toolsManager?.destroy?.().catch(console.error);
59
+ }
60
+ async launch() {
61
+ console.log = (...args)=>{
62
+ console.error('[LOG]', ...args);
63
+ };
64
+ console.info = (...args)=>{
65
+ console.error('[INFO]', ...args);
66
+ };
67
+ console.debug = (...args)=>{
68
+ console.error('[DEBUG]', ...args);
69
+ };
70
+ await this.initializeToolsManager();
71
+ const transport = new StdioServerTransport();
72
+ try {
73
+ await this.mcpServer.connect(transport);
74
+ } catch (error) {
75
+ const message = getErrorMessage(error);
76
+ console.error(`Failed to connect MCP stdio transport: ${message}`);
77
+ throw new Error(`Failed to initialize MCP stdio transport: ${message}`);
78
+ }
79
+ let isShuttingDown = false;
80
+ const cleanup = ()=>{
81
+ if (isShuttingDown) return;
82
+ isShuttingDown = true;
83
+ console.error(`${this.config.name} shutting down...`);
84
+ this.performCleanup().finally(()=>process.exit(0));
85
+ };
86
+ process.on('uncaughtException', (error)=>{
87
+ if ('EPIPE' === error.code || 'ERR_STREAM_DESTROYED' === error.code) return void cleanup();
88
+ console.error(`[${this.config.name}] Uncaught Exception:`, error);
89
+ console.error('Stack:', error.stack);
90
+ });
91
+ process.on('unhandledRejection', (reason)=>{
92
+ console.error(`[${this.config.name}] Unhandled Rejection:`, reason);
93
+ if (reason instanceof Error) console.error('Stack:', reason.stack);
94
+ });
95
+ process.stdin.on('close', cleanup);
96
+ process.stdin.on('end', cleanup);
97
+ process.stdout.on('error', cleanup);
98
+ process.once('SIGINT', cleanup);
99
+ process.once('SIGTERM', cleanup);
100
+ process.once('SIGHUP', cleanup);
101
+ return {
102
+ close: async ()=>{
103
+ this.performCleanup();
104
+ }
105
+ };
106
+ }
107
+ async launchHttp(options) {
108
+ if (!Number.isInteger(options.port) || options.port < 1 || options.port > 65535) throw new Error(`Invalid port number: ${options.port}. Port must be between 1 and 65535.`);
109
+ await this.initializeToolsManager();
110
+ const app = express();
111
+ app.use(express.json({
112
+ limit: '10mb'
113
+ }));
114
+ const sessions = new Map();
115
+ app.all('/mcp', async (req, res)=>{
116
+ const startTime = Date.now();
117
+ const requestId = randomUUID().substring(0, 8);
118
+ try {
119
+ const rawSessionId = req.headers['mcp-session-id'];
120
+ const sessionId = Array.isArray(rawSessionId) ? rawSessionId[0] : rawSessionId;
121
+ let session = sessionId ? sessions.get(sessionId) : void 0;
122
+ if (!session && 'POST' === req.method) {
123
+ if (sessions.size >= MAX_SESSIONS) {
124
+ console.error(`[${new Date().toISOString()}] [${requestId}] Session limit reached: ${sessions.size}/${MAX_SESSIONS}`);
125
+ res.status(503).json({
126
+ error: 'Too many active sessions',
127
+ message: 'Server is at maximum capacity. Please try again later.'
128
+ });
129
+ return;
130
+ }
131
+ session = await this.createHttpSession(sessions);
132
+ console.log(`[${new Date().toISOString()}] [${requestId}] New session created: ${session.transport.sessionId}`);
133
+ }
134
+ if (session) {
135
+ session.lastAccessedAt = new Date();
136
+ await session.transport.handleRequest(req, res, req.body);
137
+ const duration = Date.now() - startTime;
138
+ console.log(`[${new Date().toISOString()}] [${requestId}] Request completed in ${duration}ms`);
139
+ } else {
140
+ console.error(`[${new Date().toISOString()}] [${requestId}] Invalid session or GET without session`);
141
+ res.status(400).json({
142
+ error: 'Invalid session or GET without session'
143
+ });
144
+ }
145
+ } catch (error) {
146
+ const message = getErrorMessage(error);
147
+ const duration = Date.now() - startTime;
148
+ console.error(`[${new Date().toISOString()}] [${requestId}] MCP request error after ${duration}ms: ${message}`);
149
+ if (!res.headersSent) res.status(500).json({
150
+ error: 'Internal server error',
151
+ message: 'Failed to process MCP request'
152
+ });
153
+ }
154
+ });
155
+ const host = options.host || 'localhost';
156
+ const server = app.listen(options.port, host, ()=>{
157
+ console.log(`${this.config.name} HTTP server listening on http://${host}:${options.port}/mcp`);
158
+ }).on('error', (error)=>{
159
+ if ('EADDRINUSE' === error.code) console.error(`ERROR: Port ${options.port} is already in use.\nPlease try a different port: --port=<number>\nExample: --mode=http --port=${options.port + 1}`);
160
+ else if ('EACCES' === error.code) console.error(`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.`);
161
+ else console.error(`ERROR: Failed to start HTTP server on ${host}:${options.port}\nReason: ${error.message}\nCode: ${error.code || 'unknown'}`);
162
+ process.exit(1);
163
+ });
164
+ const cleanupInterval = this.startSessionCleanup(sessions);
165
+ this.setupHttpShutdownHandlers(server, sessions, cleanupInterval);
166
+ return {
167
+ port: options.port,
168
+ host,
169
+ close: async ()=>{
170
+ clearInterval(cleanupInterval);
171
+ for (const session of sessions.values())try {
172
+ await session.transport.close();
173
+ } catch (error) {
174
+ const message = getErrorMessage(error);
175
+ console.error(`Failed to close session ${session.transport.sessionId}: ${message}`);
176
+ }
177
+ sessions.clear();
178
+ return new Promise((resolve)=>{
179
+ server.close(async (err)=>{
180
+ if (err) console.error('Error closing HTTP server:', err);
181
+ await this.performCleanup();
182
+ resolve();
183
+ });
184
+ });
185
+ }
186
+ };
187
+ }
188
+ async createHttpSession(sessions) {
189
+ const transport = new StreamableHTTPServerTransport({
190
+ sessionIdGenerator: ()=>randomUUID(),
191
+ onsessioninitialized: (sid)=>{
192
+ sessions.set(sid, {
193
+ transport,
194
+ createdAt: new Date(),
195
+ lastAccessedAt: new Date()
196
+ });
197
+ console.log(`[${new Date().toISOString()}] Session ${sid} initialized (total: ${sessions.size})`);
198
+ }
199
+ });
200
+ transport.onclose = ()=>{
201
+ if (transport.sessionId) {
202
+ sessions.delete(transport.sessionId);
203
+ console.log(`[${new Date().toISOString()}] Session ${transport.sessionId} closed (remaining: ${sessions.size})`);
204
+ }
205
+ };
206
+ try {
207
+ await this.mcpServer.connect(transport);
208
+ } catch (error) {
209
+ const message = getErrorMessage(error);
210
+ console.error(`[${new Date().toISOString()}] Failed to connect MCP transport: ${message}`);
211
+ if (transport.sessionId) sessions.delete(transport.sessionId);
212
+ throw new Error(`Failed to initialize MCP session: ${message}`);
213
+ }
214
+ return {
215
+ transport,
216
+ createdAt: new Date(),
217
+ lastAccessedAt: new Date()
218
+ };
219
+ }
220
+ startSessionCleanup(sessions) {
221
+ return setInterval(()=>{
222
+ const now = Date.now();
223
+ for (const [sid, session] of sessions)if (now - session.lastAccessedAt.getTime() > SESSION_TIMEOUT_MS) try {
224
+ session.transport.close();
225
+ sessions.delete(sid);
226
+ console.log(`[${new Date().toISOString()}] Session ${sid} cleaned up due to inactivity (remaining: ${sessions.size})`);
227
+ } catch (error) {
228
+ const message = getErrorMessage(error);
229
+ console.error(`[${new Date().toISOString()}] Failed to close session ${sid} during cleanup: ${message}`);
230
+ sessions.delete(sid);
231
+ }
232
+ }, CLEANUP_INTERVAL_MS);
233
+ }
234
+ setupHttpShutdownHandlers(server, sessions, cleanupInterval) {
235
+ const cleanup = ()=>{
236
+ console.error(`${this.config.name} shutting down...`);
237
+ clearInterval(cleanupInterval);
238
+ for (const session of sessions.values())try {
239
+ session.transport.close();
240
+ } catch (error) {
241
+ const message = getErrorMessage(error);
242
+ console.error(`Error closing session during shutdown: ${message}`);
243
+ }
244
+ sessions.clear();
245
+ try {
246
+ server.close(()=>{
247
+ this.performCleanup().finally(()=>process.exit(0));
248
+ });
249
+ setTimeout(()=>{
250
+ console.error('Forcefully shutting down after timeout');
251
+ this.performCleanup().finally(()=>process.exit(1));
252
+ }, 5000);
253
+ } catch (error) {
254
+ const message = getErrorMessage(error);
255
+ console.error(`Error closing HTTP server: ${message}`);
256
+ this.performCleanup().finally(()=>process.exit(1));
257
+ }
258
+ };
259
+ process.once('SIGINT', cleanup);
260
+ process.once('SIGTERM', cleanup);
261
+ }
262
+ getServer() {
263
+ return this.mcpServer;
264
+ }
265
+ getToolsManager() {
266
+ return this.toolsManager;
267
+ }
268
+ constructor(config, toolsManager){
269
+ _define_property(this, "mcpServer", void 0);
270
+ _define_property(this, "toolsManager", void 0);
271
+ _define_property(this, "config", void 0);
272
+ _define_property(this, "providedToolsManager", void 0);
273
+ this.config = config;
274
+ this.mcpServer = new McpServer({
275
+ name: config.name,
276
+ version: config.version,
277
+ description: config.description
278
+ });
279
+ this.providedToolsManager = toolsManager;
280
+ }
281
+ }
282
+ export { BaseMCPServer, CLI_ARGS_CONFIG, launchMCPServer };
@@ -0,0 +1,159 @@
1
+ import { parseBase64 } from "@godscene/shared/img";
2
+ import { getDebug } from "@godscene/shared/logger";
3
+ import { camelToKebab, getKeyAliases } from "../key-alias-utils.mjs";
4
+ import { generateCliReportSession, readCliReportSession, writeCliReportSession } from "./cli-report-session.mjs";
5
+ import { createNamespacedInitArgSchema, extractNamespacedArgs, sanitizeNamespacedArgs } from "./init-arg-utils.mjs";
6
+ import { generateCommonTools, generateToolsFromActionSpace } from "./tool-generator.mjs";
7
+ function _define_property(obj, key, value) {
8
+ if (key in obj) Object.defineProperty(obj, key, {
9
+ value: value,
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true
13
+ });
14
+ else obj[key] = value;
15
+ return obj;
16
+ }
17
+ const debug = getDebug('mcp:base-tools');
18
+ class BaseMidsceneTools {
19
+ getInitArgKeys() {
20
+ return this.initArgSpec ? Object.keys(this.initArgSpec.shape) : [];
21
+ }
22
+ extractAgentInitParam(args) {
23
+ if (!this.initArgSpec) return;
24
+ const extracted = extractNamespacedArgs(args, this.initArgSpec.namespace, this.getInitArgKeys());
25
+ if (this.initArgSpec.adapt) return this.initArgSpec.adapt(extracted);
26
+ return extracted;
27
+ }
28
+ sanitizeToolArgs(args) {
29
+ if (!this.initArgSpec) return args;
30
+ return sanitizeNamespacedArgs(args, this.initArgSpec.namespace, this.getInitArgKeys());
31
+ }
32
+ getAgentInitArgSchema() {
33
+ if (!this.initArgSpec) return {};
34
+ return createNamespacedInitArgSchema(this.initArgSpec.namespace, this.initArgSpec.shape);
35
+ }
36
+ getAgentInitArgCliMetadata() {
37
+ if (!this.initArgSpec?.cli) return;
38
+ const options = Object.fromEntries(this.getInitArgKeys().map((key)=>{
39
+ const canonicalKey = `${this.initArgSpec.namespace}.${key}`;
40
+ const preferredName = this.initArgSpec.cli?.preferredNames?.[key] ?? (this.initArgSpec.cli?.preferBareKeys ? camelToKebab(key) : canonicalKey);
41
+ const acceptedNames = new Set([
42
+ preferredName,
43
+ ...this.initArgSpec.cli?.preferBareKeys ? getKeyAliases(key) : getKeyAliases(canonicalKey)
44
+ ]);
45
+ acceptedNames.delete(preferredName);
46
+ return [
47
+ canonicalKey,
48
+ {
49
+ preferredName,
50
+ aliases: [
51
+ ...acceptedNames
52
+ ]
53
+ }
54
+ ];
55
+ }));
56
+ return {
57
+ options
58
+ };
59
+ }
60
+ preparePlatformTools() {
61
+ return [];
62
+ }
63
+ getCliReportSessionName() {}
64
+ createNewCliReportSession(targetIdentity) {
65
+ const sessionName = this.getCliReportSessionName();
66
+ if (!sessionName) return;
67
+ return generateCliReportSession(sessionName, targetIdentity);
68
+ }
69
+ commitCliReportSession(session) {
70
+ if (session) writeCliReportSession(session);
71
+ }
72
+ readCliReportFileName() {
73
+ const sessionName = this.getCliReportSessionName();
74
+ if (!sessionName) return;
75
+ return readCliReportSession(sessionName)?.reportFileName;
76
+ }
77
+ readCliReportAgentOptions() {
78
+ const reportFileName = this.readCliReportFileName();
79
+ if (!reportFileName) return;
80
+ return {
81
+ reportFileName,
82
+ reportAttributes: {
83
+ 'data-group-id': reportFileName
84
+ }
85
+ };
86
+ }
87
+ async initTools() {
88
+ this.toolDefinitions = [];
89
+ const platformTools = this.preparePlatformTools();
90
+ this.toolDefinitions.push(...platformTools);
91
+ let actionSpace;
92
+ if (this.agent) {
93
+ actionSpace = await this.agent.getActionSpace();
94
+ debug('Action space from agent:', actionSpace.map((a)=>a.name).join(', '));
95
+ } else {
96
+ const tempDevice = this.createTemporaryDevice();
97
+ actionSpace = tempDevice.actionSpace();
98
+ await tempDevice.destroy?.();
99
+ debug('Action space from temporary device:', actionSpace.map((a)=>a.name).join(', '));
100
+ }
101
+ const actionTools = generateToolsFromActionSpace(actionSpace, (args = {})=>this.ensureAgent(this.extractAgentInitParam(args)), (args = {})=>this.sanitizeToolArgs(args), this.getAgentInitArgSchema(), this.getAgentInitArgCliMetadata());
102
+ const commonTools = generateCommonTools((args = {})=>this.ensureAgent(this.extractAgentInitParam(args)), this.getAgentInitArgSchema(), this.getAgentInitArgCliMetadata());
103
+ this.toolDefinitions.push(...actionTools, ...commonTools);
104
+ debug('Total tools prepared:', this.toolDefinitions.length);
105
+ }
106
+ attachToServer(server) {
107
+ this.mcpServer = server;
108
+ if (0 === this.toolDefinitions.length) debug('Warning: No tools to register. Tools may be initialized lazily.');
109
+ for (const toolDef of this.toolDefinitions)this.mcpServer.tool(toolDef.name, toolDef.description, toolDef.schema, toolDef.handler);
110
+ debug('Registered', this.toolDefinitions.length, 'tools');
111
+ }
112
+ async destroy() {
113
+ await this.agent?.destroy?.();
114
+ }
115
+ getToolDefinitions() {
116
+ return this.toolDefinitions;
117
+ }
118
+ setAgent(agent) {
119
+ this.agent = agent;
120
+ }
121
+ buildScreenshotContent(screenshot) {
122
+ const { mimeType, body } = parseBase64(screenshot);
123
+ return [
124
+ {
125
+ type: 'image',
126
+ data: body,
127
+ mimeType
128
+ }
129
+ ];
130
+ }
131
+ buildTextResult(text) {
132
+ return {
133
+ content: [
134
+ {
135
+ type: 'text',
136
+ text
137
+ }
138
+ ]
139
+ };
140
+ }
141
+ createDisconnectHandler(platformName) {
142
+ return async ()=>{
143
+ if (!this.agent) return this.buildTextResult('No active connection to disconnect');
144
+ try {
145
+ await this.agent.destroy?.();
146
+ } catch (error) {
147
+ debug('Failed to destroy agent during disconnect:', error);
148
+ }
149
+ this.agent = void 0;
150
+ return this.buildTextResult(`Disconnected from ${platformName}`);
151
+ };
152
+ }
153
+ constructor(){
154
+ _define_property(this, "mcpServer", void 0);
155
+ _define_property(this, "agent", void 0);
156
+ _define_property(this, "toolDefinitions", []);
157
+ }
158
+ }
159
+ export { BaseMidsceneTools };
@@ -0,0 +1,35 @@
1
+ import { existsSync } from "node:fs";
2
+ import { MIDSCENE_MCP_CHROME_PATH, globalConfigManager } from "../env/index.mjs";
3
+ function getSystemChromePath() {
4
+ const platform = process.platform;
5
+ const chromePaths = {
6
+ darwin: [
7
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
8
+ '/Applications/Chromium.app/Contents/MacOS/Chromium'
9
+ ],
10
+ win32: [
11
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
12
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
13
+ `C:\\Users\\${process.env.USERNAME ?? process.env.USER}\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe`
14
+ ],
15
+ linux: [
16
+ '/opt/google/chrome/chrome',
17
+ '/opt/google/chrome/google-chrome',
18
+ '/usr/bin/google-chrome-stable',
19
+ '/usr/bin/google-chrome',
20
+ '/usr/bin/chromium-browser',
21
+ '/usr/bin/chromium',
22
+ '/snap/bin/chromium'
23
+ ]
24
+ };
25
+ const paths = chromePaths[platform] ?? [];
26
+ return paths.find((p)=>existsSync(p));
27
+ }
28
+ function resolveChromePath() {
29
+ const envPath = globalConfigManager.getEnvConfigValue(MIDSCENE_MCP_CHROME_PATH);
30
+ if (envPath && 'auto' !== envPath && existsSync(envPath)) return envPath;
31
+ const systemPath = getSystemChromePath();
32
+ if (systemPath) return systemPath;
33
+ throw new Error('Chrome not found. Install Google Chrome or set MIDSCENE_MCP_CHROME_PATH environment variable.');
34
+ }
35
+ export { getSystemChromePath, resolveChromePath };
@@ -0,0 +1,78 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getMidsceneRunBaseDir, getMidsceneRunSubDir } from "../common.mjs";
4
+ const sessionDirName = 'cli-report-session';
5
+ function sanitizeSessionName(sessionName) {
6
+ return sessionName.replace(/[^a-zA-Z0-9._-]/g, '_') || 'default';
7
+ }
8
+ function sanitizeFileSegment(segment) {
9
+ const sanitized = segment.replace(/[^a-zA-Z0-9._-]/g, '_') || 'unknown';
10
+ return sanitized.slice(0, 80);
11
+ }
12
+ function ensureHtmlFileName(reportFileName) {
13
+ return reportFileName.endsWith('.html') ? reportFileName : `${reportFileName}.html`;
14
+ }
15
+ function formatDateForFileName(date) {
16
+ const pad = (value)=>String(value).padStart(2, '0');
17
+ const day = [
18
+ date.getFullYear(),
19
+ pad(date.getMonth() + 1),
20
+ pad(date.getDate())
21
+ ].join('-');
22
+ const time = [
23
+ pad(date.getHours()),
24
+ pad(date.getMinutes()),
25
+ pad(date.getSeconds())
26
+ ].join('-');
27
+ return `${day}_${time}`;
28
+ }
29
+ function randomId() {
30
+ return Math.random().toString(36).slice(2, 10);
31
+ }
32
+ function getCliReportSessionDir() {
33
+ const dir = join(getMidsceneRunBaseDir(), sessionDirName);
34
+ if (!existsSync(dir)) mkdirSync(dir, {
35
+ recursive: true
36
+ });
37
+ return dir;
38
+ }
39
+ function getCliReportSessionPath(sessionName) {
40
+ return join(getCliReportSessionDir(), `${sanitizeSessionName(sessionName)}.json`);
41
+ }
42
+ function generateCliReportSession(sessionName, targetIdentity) {
43
+ const identitySegment = targetIdentity ? `-${sanitizeFileSegment(targetIdentity)}` : '';
44
+ const reportFileName = `${sanitizeSessionName(sessionName)}${identitySegment}-${formatDateForFileName(new Date())}-${randomId()}`;
45
+ const reportPath = join(getMidsceneRunSubDir('report'), ensureHtmlFileName(reportFileName));
46
+ const session = {
47
+ version: 1,
48
+ sessionName,
49
+ ...targetIdentity ? {
50
+ targetIdentity
51
+ } : {},
52
+ reportFileName,
53
+ reportPath,
54
+ createdAt: Date.now()
55
+ };
56
+ return session;
57
+ }
58
+ function writeCliReportSession(session) {
59
+ writeFileSync(getCliReportSessionPath(session.sessionName), JSON.stringify(session, null, 2), 'utf-8');
60
+ }
61
+ function createCliReportSession(sessionName, targetIdentity) {
62
+ const session = generateCliReportSession(sessionName, targetIdentity);
63
+ writeCliReportSession(session);
64
+ return session;
65
+ }
66
+ function readCliReportSession(sessionName) {
67
+ const sessionPath = getCliReportSessionPath(sessionName);
68
+ if (!existsSync(sessionPath)) return;
69
+ try {
70
+ const raw = readFileSync(sessionPath, 'utf-8');
71
+ const parsed = JSON.parse(raw);
72
+ if (1 !== parsed.version || parsed.sessionName !== sessionName || 'string' != typeof parsed.reportFileName || !parsed.reportFileName.trim() || /[\\/]/.test(parsed.reportFileName)) return;
73
+ return parsed;
74
+ } catch {
75
+ return;
76
+ }
77
+ }
78
+ export { createCliReportSession, generateCliReportSession, readCliReportSession, writeCliReportSession };
@@ -0,0 +1,19 @@
1
+ function getErrorMessage(error) {
2
+ if (error instanceof Error) return error.message;
3
+ if (null == error) return String(error);
4
+ if ('object' != typeof error) return String(error);
5
+ const candidate = extractStringMessage(error);
6
+ if (candidate) return candidate;
7
+ try {
8
+ return JSON.stringify(error);
9
+ } catch {
10
+ return Object.prototype.toString.call(error);
11
+ }
12
+ }
13
+ function extractStringMessage(error) {
14
+ const anyError = error;
15
+ if ('string' == typeof anyError.message && anyError.message) return anyError.message;
16
+ if (anyError.error && 'string' == typeof anyError.error.message && anyError.error.message) return anyError.error.message;
17
+ if (anyError.cause && 'string' == typeof anyError.cause.message && anyError.cause.message) return anyError.cause.message;
18
+ }
19
+ export { getErrorMessage };
@@ -0,0 +1,9 @@
1
+ export * from "./base-server.mjs";
2
+ export * from "./base-tools.mjs";
3
+ export * from "./init-arg-utils.mjs";
4
+ export * from "./error-formatter.mjs";
5
+ export * from "./tool-generator.mjs";
6
+ export * from "./types.mjs";
7
+ export * from "./inject-report-html-plugin.mjs";
8
+ export * from "./launcher-helper.mjs";
9
+ export * from "./chrome-path.mjs";