freeform-modeling-mcp 1.0.29 → 1.0.31

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.
@@ -0,0 +1,25 @@
1
+ {
2
+ "mcpServers": {
3
+ "freeform-modeling-mcp": {
4
+ "command": "npx",
5
+ "args": [
6
+ "tsx",
7
+ "src/cli.ts",
8
+ "start",
9
+ "--stdio"
10
+ ]
11
+ },
12
+ "aholo-sdk-mcp": {
13
+ "type": "stdio",
14
+ "command": "npx",
15
+ "args": [
16
+ "-y",
17
+ "@manycore/aholo-sdk-mcp"
18
+ ],
19
+ "env": {
20
+ "AHOLO_API_KEY": "urSuPSeAcEnNEEn",
21
+ "AHOLO_REGION": "cn"
22
+ }
23
+ }
24
+ }
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freeform-modeling-mcp",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "description": "KooMaster MCP Server with WebSocket Bridge",
5
5
  "main": "index.cjs",
6
6
  "type": "module",
@@ -18,7 +18,7 @@ export async function startMCPServer(options: { dev?: boolean, stdio?: boolean }
18
18
  const wsBridge = new WebSocketBridge();
19
19
  await wsBridge.initialize();
20
20
  const wsPort = wsBridge.getPort();
21
- const wsUrl = `ws://localhost:${wsPort}`;
21
+ const wsUrl = `ws://127.0.0.1:${wsPort}`;
22
22
 
23
23
  await llmClient.connect(wsUrl);
24
24
 
@@ -6,6 +6,25 @@ import * as http from 'http';
6
6
  import { BridgeLockManager, getActiveBridges } from './lock';
7
7
  import logger from '../logger';
8
8
 
9
+ function isAllowedOrigin(origin: string): boolean {
10
+ try {
11
+ const { hostname } = new URL(origin);
12
+ return hostname === 'localhost' || hostname === '127.0.0.1'
13
+ || hostname === 'kujiale.com' || hostname.endsWith('.kujiale.com')
14
+ || hostname === 'coohom.com' || hostname.endsWith('.coohom.com');
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ function setCors(req: http.IncomingMessage, res: http.ServerResponse): void {
21
+ const origin = req.headers.origin;
22
+ if (origin && isAllowedOrigin(origin)) {
23
+ res.setHeader('Access-Control-Allow-Origin', origin);
24
+ res.setHeader('Vary', 'Origin');
25
+ }
26
+ }
27
+
9
28
  // 客户端类型
10
29
  export enum ClientType {
11
30
  MCP = 'mcp',
@@ -101,14 +120,21 @@ export class WebSocketBridge extends EventEmitter {
101
120
  // 使用锁管理器获取可用端口
102
121
  this.port = await this.lockManager.initialize();
103
122
 
104
- // 创建 HTTP 服务器
123
+ // 创建 HTTP 服务器(与 WebSocket 共用端口;/bridge-status 供浏览器发现)
105
124
  this.httpServer = http.createServer((req, res) => {
106
- // 处理HTTP请求
125
+ if (req.method === 'OPTIONS') {
126
+ setCors(req, res);
127
+ res.writeHead(204);
128
+ res.end();
129
+ return;
130
+ }
107
131
  if (req.url === '/bridge-status') {
132
+ setCors(req, res);
108
133
  res.writeHead(200, { 'Content-Type': 'application/json' });
109
134
  res.end(JSON.stringify({
110
135
  service: 'WebSocketBridge',
111
136
  version: '1.0.0',
137
+ port: this.port,
112
138
  status: this._status,
113
139
  clients: {
114
140
  total: this.clients.size,
@@ -122,8 +148,10 @@ export class WebSocketBridge extends EventEmitter {
122
148
  }
123
149
  });
124
150
 
125
- // 创建 WebSocket 服务器
126
- this.server = new WebSocketServer({ server: this.httpServer });
151
+ this.server = new WebSocketServer({
152
+ server: this.httpServer,
153
+ verifyClient: ({ origin }) => !origin || isAllowedOrigin(origin),
154
+ });
127
155
 
128
156
  // 设置事件处理程序
129
157
  this.setupEventHandlers();
@@ -184,8 +212,7 @@ export class WebSocketBridge extends EventEmitter {
184
212
  }
185
213
  });
186
214
 
187
- // 启动 HTTP 服务器
188
- this.httpServer!.listen(this.port);
215
+ this.httpServer!.listen(this.port, '127.0.0.1');
189
216
  } catch (error) {
190
217
  logger.error('Failed to start WebSocketBridge:', error);
191
218
  reject(error);
@@ -75,7 +75,7 @@ function isProcessRunning(pid: number): boolean {
75
75
  // 检查特定端口是否有WebSocketBridge服务响应
76
76
  async function checkBridgeAtPort(port: number): Promise<boolean> {
77
77
  return new Promise((resolve) => {
78
- const req = http.get(`http://localhost:${port}/bridge-status`, (res) => {
78
+ const req = http.get(`http://127.0.0.1:${port}/bridge-status`, (res) => {
79
79
  // 收到响应,检查是否是我们的服务
80
80
  let data = '';
81
81
  res.on('data', (chunk) => {
@@ -186,7 +186,7 @@ export class BridgeLockManager extends EventEmitter {
186
186
  if (!portAvailable) {
187
187
  logger.info(`Port ${this.port} is not available, searching for another port...`);
188
188
  try {
189
- this.port = await findAvailablePort(this.port + 1);
189
+ this.port = await findAvailablePort(this.port + 1, 10);
190
190
  logger.info(`Found available port: ${this.port}`);
191
191
  } catch (e) {
192
192
  logger.error(`Cannot find available port:`, e.message);
@@ -45,6 +45,7 @@ export const importGeneratedAssetTool = {
45
45
  name: z.string().describe('The name to give to the imported object in the scene'),
46
46
  model_url: z.string().describe('Public HTTP(S) URL of a completed GLB/GLTF model. Do not pass archive or other mesh formats such as .zip/.obj/.fbx.'),
47
47
  preview_image_url: z.string().optional().describe('Optional preview image URL to show alongside the import job.'),
48
+ brandgoodid: z.string().optional().describe('Optional inventoried product id (brandGoodId). When provided, Koomaster stores it as the xref resource (type=custom) for rendering, and keeps the uploaded OBJ id in customProperties so the editor still displays via the original OBJ path.'),
48
49
  location: z.array(z.number()).describe('[x, y, z] center position coordinates for the imported model'),
49
50
  dimensions: z.array(z.number()).describe('[width, height, depth] absolute dimensions for the imported model, in millimeters'),
50
51
  rotation: z.array(z.number()).optional().describe('Optional [x, y, z] rotation in degrees. Following the Z-up coordinate system: rotation around X-axis tilts the object left/right (like nodding sideways), rotation around Y-axis tilts the object forward/backward (like nodding), and rotation around Z-axis rotates the object in the horizontal plane (like spinning on the ground). Positive values follow the right-hand rule: point thumb in positive axis direction, fingers show positive rotation direction.'),
@@ -91,6 +92,7 @@ export const importGeneratedAssetTool = {
91
92
  rotation: args.rotation,
92
93
  zipBuffer: zipBuffer.toString("base64"),
93
94
  boundingBox,
95
+ brandgoodid: args.brandgoodid,
94
96
  });
95
97
  baseProgressObj.data = objectInfo;
96
98
 
@@ -41,7 +41,7 @@ export const getToolsInfoTool = {
41
41
  "sweep - Creates a 3D solid by sweeping a FACE along auxiliary curves. NOTE: This tool operates ONLY on faces, not on entire objects or groups. The face can be either selected by the user or created programmatically. IMPORTANT: The face being swept (profile) must NOT be parallel to the auxiliary curves (path) - the sweep direction must have some component perpendicular to the face normal for the operation to succeed. After the sweep operation completes, the original face (profile) will be automatically deleted from the model.",
42
42
  ],
43
43
  generated: [
44
- "import_generated_asset - Import a generated GLB/GLTF model from any source that provides a public model URL. Requires model_url plus name/location/dimensions.",
44
+ "import_generated_asset - Import a generated GLB/GLTF model from any source that provides a public model URL. Requires model_url plus name/location/dimensions. Optional brandgoodid stores the inventoried product id as the xref resource.",
45
45
  "poll_import_status - Query GLB-to-scene import progress using the import_uuid returned by import_generated_asset."
46
46
  ],
47
47
  assets: [