makeit4me 1.0.0 → 1.0.1
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/.mcp.json +1 -1
- package/bin.js +2 -0
- package/dist/index.js +62 -0
- package/package.json +17 -8
- package/index.js +0 -223
package/package.json
CHANGED
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "makeit4me",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"makeit4me": "./
|
|
7
|
+
"makeit4me": "./bin.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"index.js",
|
|
10
|
+
"dist/index.js",
|
|
11
|
+
"bin.js",
|
|
11
12
|
".claude-plugin/",
|
|
12
13
|
".mcp.json",
|
|
13
14
|
"skills/"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
|
-
"
|
|
17
|
+
"build": "esbuild index.js --bundle --platform=node --format=esm --outfile=dist/index.js --minify",
|
|
18
|
+
"start": "node dist/index.js"
|
|
17
19
|
},
|
|
18
20
|
"author": "Lookahead Labs LLC",
|
|
19
21
|
"license": "SEE LICENSE IN LICENSE",
|
|
20
22
|
"description": "MCP server that bridges AI agents to SketchUp via the MakeIt4Me plugin",
|
|
21
|
-
"keywords": [
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"sketchup",
|
|
26
|
+
"3d-modeling",
|
|
27
|
+
"cad",
|
|
28
|
+
"architecture"
|
|
29
|
+
],
|
|
22
30
|
"repository": {
|
|
23
31
|
"type": "git",
|
|
24
32
|
"url": "https://www.makeit4me.net"
|
|
25
33
|
},
|
|
26
34
|
"homepage": "https://www.makeit4me.net",
|
|
27
|
-
"
|
|
28
|
-
"@modelcontextprotocol/sdk": "^1.27.1"
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
37
|
+
"esbuild": "^0.27.4"
|
|
29
38
|
}
|
|
30
39
|
}
|
package/index.js
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import http from "node:http";
|
|
7
|
-
import fs from "node:fs";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import os from "node:os";
|
|
10
|
-
|
|
11
|
-
const PORT = 9876;
|
|
12
|
-
const BASE = `http://127.0.0.1:${PORT}`;
|
|
13
|
-
|
|
14
|
-
// ── HTTP helper ──────────────────────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
function request(method, endpoint, body) {
|
|
17
|
-
return new Promise((resolve, reject) => {
|
|
18
|
-
const url = new URL(endpoint, BASE);
|
|
19
|
-
const payload = body ? JSON.stringify(body) : undefined;
|
|
20
|
-
|
|
21
|
-
const req = http.request(
|
|
22
|
-
{
|
|
23
|
-
hostname: url.hostname,
|
|
24
|
-
port: url.port,
|
|
25
|
-
path: url.pathname,
|
|
26
|
-
method,
|
|
27
|
-
headers: {
|
|
28
|
-
...(payload
|
|
29
|
-
? {
|
|
30
|
-
"Content-Type": "application/json",
|
|
31
|
-
"Content-Length": Buffer.byteLength(payload),
|
|
32
|
-
}
|
|
33
|
-
: {}),
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
(res) => {
|
|
37
|
-
const chunks = [];
|
|
38
|
-
res.on("data", (c) => chunks.push(c));
|
|
39
|
-
res.on("end", () => {
|
|
40
|
-
const text = Buffer.concat(chunks).toString();
|
|
41
|
-
try {
|
|
42
|
-
resolve(JSON.parse(text));
|
|
43
|
-
} catch {
|
|
44
|
-
resolve({ ok: false, error: `Non-JSON response: ${text.slice(0, 200)}` });
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
req.on("error", (err) => {
|
|
51
|
-
resolve({
|
|
52
|
-
ok: false,
|
|
53
|
-
error: `Connection failed: ${err.message}. Is SketchUp running with MakeIt4Me?`,
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
if (payload) req.write(payload);
|
|
58
|
-
req.end();
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ── MCP Server ───────────────────────────────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
const server = new McpServer({
|
|
65
|
-
name: "makeit4me",
|
|
66
|
-
version: "1.0.0",
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// ── Tools ────────────────────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
server.registerTool("sketchup_status", {
|
|
72
|
-
title: "SketchUp Status",
|
|
73
|
-
description:
|
|
74
|
-
"Check if SketchUp is running and the MakeIt4Me plugin is active. " +
|
|
75
|
-
"Returns version info and active model details. Always call this first.",
|
|
76
|
-
}, async () => {
|
|
77
|
-
const result = await request("GET", "/status");
|
|
78
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
server.registerTool("sketchup_models", {
|
|
82
|
-
title: "List Open Models",
|
|
83
|
-
description: "List all open SketchUp models with their titles and file paths.",
|
|
84
|
-
}, async () => {
|
|
85
|
-
const result = await request("GET", "/models");
|
|
86
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
server.registerTool("sketchup_inspect", {
|
|
90
|
-
title: "Inspect Model (Read-Only)",
|
|
91
|
-
description:
|
|
92
|
-
"Evaluate Ruby code in SketchUp for read-only queries. " +
|
|
93
|
-
"The variable `model` is pre-bound to Sketchup.active_model. " +
|
|
94
|
-
"Use this for reading model state — entity counts, bounds, materials, etc. " +
|
|
95
|
-
"For geometry changes, use sketchup_operation instead.",
|
|
96
|
-
inputSchema: {
|
|
97
|
-
code: z
|
|
98
|
-
.string()
|
|
99
|
-
.describe("Ruby code to evaluate in SketchUp. `model` is pre-bound to Sketchup.active_model."),
|
|
100
|
-
},
|
|
101
|
-
}, async ({ code }) => {
|
|
102
|
-
const result = await request("POST", "/inspect", { code });
|
|
103
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
server.registerTool("sketchup_operation", {
|
|
107
|
-
title: "Execute Operation (Undoable)",
|
|
108
|
-
description:
|
|
109
|
-
"Execute Ruby code wrapped in a named undo operation. " +
|
|
110
|
-
"Use for ALL geometry changes (add, move, delete) so the user can Ctrl+Z. " +
|
|
111
|
-
"The variable `model` is pre-bound to Sketchup.active_model.",
|
|
112
|
-
inputSchema: {
|
|
113
|
-
name: z
|
|
114
|
-
.string()
|
|
115
|
-
.describe("Undo label shown in Edit > Undo (e.g. 'Add front wall')"),
|
|
116
|
-
code: z
|
|
117
|
-
.string()
|
|
118
|
-
.describe("Ruby code to execute. `model` is pre-bound to Sketchup.active_model."),
|
|
119
|
-
},
|
|
120
|
-
}, async ({ name, code }) => {
|
|
121
|
-
const result = await request("POST", "/operation", { name, code });
|
|
122
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
server.registerTool("sketchup_selection", {
|
|
126
|
-
title: "Manage Selection",
|
|
127
|
-
description:
|
|
128
|
-
"Read or manipulate the active selection in SketchUp. " +
|
|
129
|
-
"Actions: 'get' (list selected), 'clear', 'add' (by entity GUIDs), 'remove' (by entity GUIDs).",
|
|
130
|
-
inputSchema: {
|
|
131
|
-
action: z.enum(["get", "clear", "add", "remove"]).describe("Selection action"),
|
|
132
|
-
entities: z
|
|
133
|
-
.array(z.string())
|
|
134
|
-
.optional()
|
|
135
|
-
.describe("Entity GUIDs for add/remove actions"),
|
|
136
|
-
},
|
|
137
|
-
}, async ({ action, entities }) => {
|
|
138
|
-
const body = { action };
|
|
139
|
-
if (entities) body.entities = entities;
|
|
140
|
-
const result = await request("POST", "/selection", body);
|
|
141
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
server.registerTool("sketchup_screenshot", {
|
|
145
|
-
title: "Take Viewport Screenshot",
|
|
146
|
-
description:
|
|
147
|
-
"Capture a screenshot of the current SketchUp viewport. " +
|
|
148
|
-
"Optionally set the camera position first. " +
|
|
149
|
-
"Returns the image so you can visually verify geometry.",
|
|
150
|
-
inputSchema: {
|
|
151
|
-
eye: z
|
|
152
|
-
.array(z.number())
|
|
153
|
-
.length(3)
|
|
154
|
-
.optional()
|
|
155
|
-
.describe("Camera eye position [x, y, z] in inches. If omitted, uses current camera."),
|
|
156
|
-
target: z
|
|
157
|
-
.array(z.number())
|
|
158
|
-
.length(3)
|
|
159
|
-
.optional()
|
|
160
|
-
.describe("Camera target/look-at point [x, y, z] in inches."),
|
|
161
|
-
orthographic: z
|
|
162
|
-
.boolean()
|
|
163
|
-
.optional()
|
|
164
|
-
.describe("Use parallel projection (orthographic) instead of perspective. Good for elevation views."),
|
|
165
|
-
width: z.number().optional().describe("Image width in pixels (default 1920)"),
|
|
166
|
-
height: z.number().optional().describe("Image height in pixels (default 1080)"),
|
|
167
|
-
},
|
|
168
|
-
}, async ({ eye, target, orthographic, width, height }) => {
|
|
169
|
-
const w = width || 1920;
|
|
170
|
-
const h = height || 1080;
|
|
171
|
-
const imgPath = path.join(os.tmpdir(), "sketchup_mcp_screenshot.png");
|
|
172
|
-
|
|
173
|
-
let code = "view = model.active_view\n";
|
|
174
|
-
|
|
175
|
-
if (eye && target) {
|
|
176
|
-
code += `eye = Geom::Point3d.new(${eye.join(", ")})\n`;
|
|
177
|
-
code += `target = Geom::Point3d.new(${target.join(", ")})\n`;
|
|
178
|
-
code += `up = Geom::Vector3d.new(0, 0, 1)\n`;
|
|
179
|
-
code += `cam = Sketchup::Camera.new(eye, target, up)\n`;
|
|
180
|
-
if (orthographic) {
|
|
181
|
-
code += `cam.perspective = false\n`;
|
|
182
|
-
}
|
|
183
|
-
code += `view.camera = cam\n`;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
code += `view.write_image("${imgPath}", ${w}, ${h}, true)\n`;
|
|
187
|
-
code += `"${imgPath}"`;
|
|
188
|
-
|
|
189
|
-
const result = await request("POST", "/inspect", { code });
|
|
190
|
-
|
|
191
|
-
if (!result.ok) {
|
|
192
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Read the image and return it
|
|
196
|
-
try {
|
|
197
|
-
const imgBuffer = fs.readFileSync(imgPath);
|
|
198
|
-
const base64 = imgBuffer.toString("base64");
|
|
199
|
-
return {
|
|
200
|
-
content: [
|
|
201
|
-
{
|
|
202
|
-
type: "image",
|
|
203
|
-
data: base64,
|
|
204
|
-
mimeType: "image/png",
|
|
205
|
-
},
|
|
206
|
-
],
|
|
207
|
-
};
|
|
208
|
-
} catch (err) {
|
|
209
|
-
return {
|
|
210
|
-
content: [
|
|
211
|
-
{
|
|
212
|
-
type: "text",
|
|
213
|
-
text: `Screenshot saved to ${imgPath} but failed to read: ${err.message}`,
|
|
214
|
-
},
|
|
215
|
-
],
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// ── Start ────────────────────────────────────────────────────────────────────
|
|
221
|
-
|
|
222
|
-
const transport = new StdioServerTransport();
|
|
223
|
-
await server.connect(transport);
|