opentakeoff-mcp 0.2.0 → 0.4.0
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 +16 -2
- package/dist/server-core.js +178 -38
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -20,6 +20,17 @@ No clone, no build — point your MCP client at the published package:
|
|
|
20
20
|
|
|
21
21
|
Works with Claude Code (`claude mcp add opentakeoff -- npx -y opentakeoff-mcp`), Claude Desktop, Cursor, or any stdio MCP client. Node 20+.
|
|
22
22
|
|
|
23
|
+
## One-click install (Claude Desktop)
|
|
24
|
+
|
|
25
|
+
No Node, no npm: download **`opentakeoff-mcp.mcpb`** from the
|
|
26
|
+
[latest release](https://github.com/Kentucky-ai/opentakeoff/releases) and
|
|
27
|
+
double-click it — Claude Desktop installs the server with its dependencies
|
|
28
|
+
bundled. Built by `npm run mcpb` and attached automatically to every `mcp-v*`
|
|
29
|
+
release. The bundle is platform-neutral on purpose: it excludes the optional
|
|
30
|
+
native canvas, so every tool and the text/metadata resources work everywhere,
|
|
31
|
+
and the sheet-image resource says exactly what's missing where rendering isn't
|
|
32
|
+
available.
|
|
33
|
+
|
|
23
34
|
|
|
24
35
|
The takeoff engine — One-Click Area, the scale model, conditions, totals — on
|
|
25
36
|
**stdio for your MCP client**. An agent can open a plan, read the title block,
|
|
@@ -104,8 +115,11 @@ includes document text, shape vertices, or result payload content.
|
|
|
104
115
|
| `delete_shape` | Remove a committed shape by id. |
|
|
105
116
|
| `read_sheet_text` | Positioned page text (image px), optionally restricted to a region — title blocks, room labels, finish schedules. |
|
|
106
117
|
|
|
107
|
-
Every
|
|
108
|
-
|
|
118
|
+
Every tool declares an **`outputSchema`**, and every reply carries the payload
|
|
119
|
+
as **`structuredContent`** — typed, machine-validated on every call — alongside
|
|
120
|
+
the same compact JSON in a single text item for clients that predate structured
|
|
121
|
+
output. Failures come back as `isError: true` with `{"error": "..."}` — never a
|
|
122
|
+
dropped connection.
|
|
109
123
|
|
|
110
124
|
## Resources — browse before you measure
|
|
111
125
|
|
package/dist/server-core.js
CHANGED
|
@@ -199,6 +199,7 @@ function positionedText(ph) {
|
|
|
199
199
|
var UserError = class extends Error {
|
|
200
200
|
};
|
|
201
201
|
var ok = (payload) => ({
|
|
202
|
+
structuredContent: payload,
|
|
202
203
|
content: [{ type: "text", text: JSON.stringify(payload) }]
|
|
203
204
|
});
|
|
204
205
|
var fail = (err) => ({
|
|
@@ -883,8 +884,8 @@ var SNAP_CELL = 24;
|
|
|
883
884
|
var SNAP_TOL = 7;
|
|
884
885
|
var PALETTE = ["#c96442", "#2f7d54", "#2563eb", "#9333ea", "#b8860b", "#0d9488", "#be185d", "#1f2937", "#dc2626", "#0891b2"];
|
|
885
886
|
var HATCH_IDS = ["solid", "diag", "diag2", "cross", "diagdense", "horiz", "vert", "grid", "brick", "plank", "herring", "basket", "checker", "wave", "fleur", "speckle"];
|
|
886
|
-
var
|
|
887
|
-
var uid = (p) => `${p}-${
|
|
887
|
+
var mintUuid = () => globalThis.crypto && typeof globalThis.crypto.randomUUID === "function" ? globalThis.crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
888
|
+
var uid = (p) => `${p}-${mintUuid()}`;
|
|
888
889
|
var ANN_SCHEMA = "opentakeoff.takeoff_canvas.v1";
|
|
889
890
|
var IMAGE_MAX_EDGE = 1568;
|
|
890
891
|
var sheetSummary = (s) => ({
|
|
@@ -1114,8 +1115,9 @@ var Session = class {
|
|
|
1114
1115
|
if (opts.condition) {
|
|
1115
1116
|
shape_id = this.commit(s, opts.condition, opts.role, ring, { area_sf, perimeter_lf }, {
|
|
1116
1117
|
method: "one_click_v1",
|
|
1118
|
+
actor: "agent",
|
|
1117
1119
|
seed_norm: [x / s.widthPx, y / s.heightPx],
|
|
1118
|
-
reviewed:
|
|
1120
|
+
reviewed: false,
|
|
1119
1121
|
...f.hatchFiltered ? { hatch_filtered: true } : {}
|
|
1120
1122
|
}).id;
|
|
1121
1123
|
}
|
|
@@ -1128,7 +1130,7 @@ var Session = class {
|
|
|
1128
1130
|
const area_sf = round2(met.area * s.upp * s.upp);
|
|
1129
1131
|
const perimeter_lf = round2(met.perim * s.upp);
|
|
1130
1132
|
let shape_id;
|
|
1131
|
-
if (opts.condition) shape_id = this.commit(s, opts.condition, opts.role, verts, { area_sf, perimeter_lf }).id;
|
|
1133
|
+
if (opts.condition) shape_id = this.commit(s, opts.condition, opts.role, verts, { area_sf, perimeter_lf }, { method: "manual", actor: "agent" }).id;
|
|
1132
1134
|
return { area_sf, perimeter_lf, nverts: verts.length, ...shape_id ? { shape_id } : {} };
|
|
1133
1135
|
}
|
|
1134
1136
|
measureLine(name, pts, opts) {
|
|
@@ -1136,7 +1138,7 @@ var Session = class {
|
|
|
1136
1138
|
if (s.upp == null) throw new UserError(this.scaleGate(s));
|
|
1137
1139
|
const length_lf = round2(openLen(pts) * s.upp);
|
|
1138
1140
|
let shape_id;
|
|
1139
|
-
if (opts.condition) shape_id = this.commit(s, opts.condition, "linear", pts, { area_sf: 0, perimeter_lf: length_lf }).id;
|
|
1141
|
+
if (opts.condition) shape_id = this.commit(s, opts.condition, "linear", pts, { area_sf: 0, perimeter_lf: length_lf }, { method: "manual", actor: "agent" }).id;
|
|
1140
1142
|
return { length_lf, npts: pts.length, ...shape_id ? { shape_id } : {} };
|
|
1141
1143
|
}
|
|
1142
1144
|
summary() {
|
|
@@ -1176,7 +1178,7 @@ var Session = class {
|
|
|
1176
1178
|
};
|
|
1177
1179
|
|
|
1178
1180
|
// src/tools.ts
|
|
1179
|
-
import { z } from "zod";
|
|
1181
|
+
import { z as z2 } from "zod";
|
|
1180
1182
|
|
|
1181
1183
|
// src/trace.ts
|
|
1182
1184
|
var TRACE_ENV = "OPENTAKEOFF_MCP_TRACE";
|
|
@@ -1197,10 +1199,136 @@ function traceToolCall(tool, args, startedAt, reply) {
|
|
|
1197
1199
|
`);
|
|
1198
1200
|
}
|
|
1199
1201
|
|
|
1202
|
+
// src/outputs.ts
|
|
1203
|
+
import { z } from "zod";
|
|
1204
|
+
var point = z.tuple([z.number(), z.number()]);
|
|
1205
|
+
var sheetSummary2 = {
|
|
1206
|
+
sheet: z.string().describe('Sheet key: page 1 is the bare file name ("plan.pdf"), pages 2+ are "plan.pdf#2"'),
|
|
1207
|
+
page: z.number().int().describe("1-based page number"),
|
|
1208
|
+
width_pt: z.number(),
|
|
1209
|
+
height_pt: z.number(),
|
|
1210
|
+
width_px: z.number().describe("Image px at render scale 2.0 \u2014 the coordinate space every tool speaks"),
|
|
1211
|
+
height_px: z.number(),
|
|
1212
|
+
sheet_number: z.string().optional().describe('Title-block sheet number ("A-101") where detected'),
|
|
1213
|
+
detected_scale: z.string().optional().describe("Drawn scale note read off the sheet \u2014 a suggestion, never auto-applied")
|
|
1214
|
+
};
|
|
1215
|
+
var loadPlanOutput = {
|
|
1216
|
+
file: z.string(),
|
|
1217
|
+
page_count: z.number().int(),
|
|
1218
|
+
sheets: z.array(z.object(sheetSummary2)),
|
|
1219
|
+
note: z.string()
|
|
1220
|
+
};
|
|
1221
|
+
var sheetInfoOutput = {
|
|
1222
|
+
...sheetSummary2,
|
|
1223
|
+
seg_count: z.number().int().describe("Vector segment count"),
|
|
1224
|
+
has_vector_linework: z.boolean().describe("one_click needs vector linework"),
|
|
1225
|
+
scale_set: z.boolean(),
|
|
1226
|
+
upp: z.number().optional().describe("Real feet per image px at render scale 2.0 \u2014 present once the scale is set"),
|
|
1227
|
+
shape_count: z.number().int().describe("Committed shapes on this sheet")
|
|
1228
|
+
};
|
|
1229
|
+
var setScaleOutput = {
|
|
1230
|
+
sheet: z.string(),
|
|
1231
|
+
upp: z.number().describe("Real feet per image px at render scale 2.0"),
|
|
1232
|
+
label: z.string().optional().describe("The standard scale label, when set by label or detected note"),
|
|
1233
|
+
source: z.enum(["label", "upp", "calibrate", "detected"])
|
|
1234
|
+
};
|
|
1235
|
+
var oneClickOutput = {
|
|
1236
|
+
status: z.literal("ok"),
|
|
1237
|
+
nverts: z.number().int().describe("Vertex count of the traced polygon"),
|
|
1238
|
+
hatch_filtered: z.literal(true).optional().describe("Present when hatch/pattern linework was classified out of the boundary"),
|
|
1239
|
+
verts: z.array(point).optional().describe("Traced polygon vertices (image px), when return_verts was set"),
|
|
1240
|
+
area_sf: z.number().optional().describe("Scaled mode: traced area in SF"),
|
|
1241
|
+
perimeter_lf: z.number().optional().describe("Scaled mode: traced perimeter in LF"),
|
|
1242
|
+
shape_id: z.string().optional().describe("Scaled mode: id of the committed shape, when condition was passed"),
|
|
1243
|
+
area_px2: z.number().optional().describe("Preview mode (no scale): raw area in px\xB2"),
|
|
1244
|
+
perimeter_px: z.number().optional().describe("Preview mode (no scale): raw perimeter in px"),
|
|
1245
|
+
warning: z.string().optional().describe("Preview mode (no scale): why quantities are unavailable and what to do")
|
|
1246
|
+
};
|
|
1247
|
+
var measurePolygonOutput = {
|
|
1248
|
+
area_sf: z.number(),
|
|
1249
|
+
perimeter_lf: z.number(),
|
|
1250
|
+
nverts: z.number().int(),
|
|
1251
|
+
shape_id: z.string().optional().describe("Present when condition was passed and the shape committed")
|
|
1252
|
+
};
|
|
1253
|
+
var measureLineOutput = {
|
|
1254
|
+
length_lf: z.number(),
|
|
1255
|
+
npts: z.number().int(),
|
|
1256
|
+
shape_id: z.string().optional().describe("Present when condition was passed and the shape committed")
|
|
1257
|
+
};
|
|
1258
|
+
var summaryRow = z.object({
|
|
1259
|
+
id: z.string(),
|
|
1260
|
+
finish_tag: z.string(),
|
|
1261
|
+
multiplier: z.number(),
|
|
1262
|
+
waste_pct: z.number(),
|
|
1263
|
+
shape_count: z.number().int(),
|
|
1264
|
+
floor_sf: z.number(),
|
|
1265
|
+
wall_sf: z.number(),
|
|
1266
|
+
border_sf: z.number(),
|
|
1267
|
+
lf: z.number(),
|
|
1268
|
+
ea: z.number(),
|
|
1269
|
+
total_sf: z.number(),
|
|
1270
|
+
floor_sf_net: z.number(),
|
|
1271
|
+
wall_sf_net: z.number(),
|
|
1272
|
+
border_sf_net: z.number(),
|
|
1273
|
+
lf_net: z.number(),
|
|
1274
|
+
total_sf_net: z.number(),
|
|
1275
|
+
sy_net: z.number()
|
|
1276
|
+
}).passthrough();
|
|
1277
|
+
var takeoffSummaryOutput = {
|
|
1278
|
+
conditions: z.array(summaryRow),
|
|
1279
|
+
totals: z.object({
|
|
1280
|
+
total_sf: z.number(),
|
|
1281
|
+
total_sf_net: z.number(),
|
|
1282
|
+
lf: z.number(),
|
|
1283
|
+
lf_net: z.number(),
|
|
1284
|
+
ea: z.number(),
|
|
1285
|
+
sy_net: z.number()
|
|
1286
|
+
}).passthrough()
|
|
1287
|
+
};
|
|
1288
|
+
var exportTakeoffOutput = {
|
|
1289
|
+
schema: z.string(),
|
|
1290
|
+
project_name: z.string(),
|
|
1291
|
+
units: z.string(),
|
|
1292
|
+
sheets: z.array(z.object({ sheet_id: z.string(), units_per_px: z.number() })),
|
|
1293
|
+
conditions: z.array(z.object({
|
|
1294
|
+
id: z.string(),
|
|
1295
|
+
finish_tag: z.string(),
|
|
1296
|
+
color: z.string(),
|
|
1297
|
+
fill: z.string(),
|
|
1298
|
+
hatch: z.string(),
|
|
1299
|
+
multiplier: z.number(),
|
|
1300
|
+
waste_pct: z.number(),
|
|
1301
|
+
materials: z.array(z.unknown())
|
|
1302
|
+
}).passthrough()),
|
|
1303
|
+
shapes: z.array(z.object({
|
|
1304
|
+
id: z.string(),
|
|
1305
|
+
sheet_id: z.string(),
|
|
1306
|
+
condition_id: z.string(),
|
|
1307
|
+
measure_role: z.enum(["floor_area", "deduct", "linear"]),
|
|
1308
|
+
verts_norm: z.array(point).describe("Vertices normalized to sheet dims (0\u20131)"),
|
|
1309
|
+
computed: z.object({ area_sf: z.number(), perimeter_lf: z.number() }).passthrough(),
|
|
1310
|
+
origin: z.object({}).passthrough().optional().describe("Provenance: method (manual|one_click_v1), actor (omitted=human, 'agent'=MCP/automation), reviewed (human affirmed at an explicit gate), and correction fields (edited, edited_before_create, copied, proposed_verts_norm, edits)")
|
|
1311
|
+
}).passthrough()),
|
|
1312
|
+
markups: z.array(z.unknown()),
|
|
1313
|
+
sheet_group: z.array(z.unknown()),
|
|
1314
|
+
last_group: z.array(z.unknown()),
|
|
1315
|
+
sheet_tabs: z.array(z.unknown()),
|
|
1316
|
+
sheet_levels: z.object({}).passthrough()
|
|
1317
|
+
};
|
|
1318
|
+
var deleteShapeOutput = {
|
|
1319
|
+
deleted: z.string().describe("The removed shape's id"),
|
|
1320
|
+
shape_count: z.number().int().describe("Committed shapes remaining")
|
|
1321
|
+
};
|
|
1322
|
+
var readSheetTextOutput = {
|
|
1323
|
+
sheet: z.string(),
|
|
1324
|
+
items: z.array(z.object({ str: z.string(), x: z.number(), y: z.number() })).describe("Positioned text items (image px)"),
|
|
1325
|
+
text: z.string().describe("The items joined with spaces")
|
|
1326
|
+
};
|
|
1327
|
+
|
|
1200
1328
|
// src/tools.ts
|
|
1201
1329
|
var COORDS = "Coordinates are image px at render scale 2.0: PDF pt \xD7 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.";
|
|
1202
|
-
var pointSchema =
|
|
1203
|
-
var roleSchema =
|
|
1330
|
+
var pointSchema = z2.tuple([z2.number(), z2.number()]);
|
|
1331
|
+
var roleSchema = z2.enum(["floor_area", "deduct"]).default("floor_area");
|
|
1204
1332
|
var run = (tool, fn) => async (args) => {
|
|
1205
1333
|
const startedAt = process.hrtime.bigint();
|
|
1206
1334
|
let reply;
|
|
@@ -1215,7 +1343,8 @@ var run = (tool, fn) => async (args) => {
|
|
|
1215
1343
|
function registerTools(server, session) {
|
|
1216
1344
|
server.registerTool("load_plan", {
|
|
1217
1345
|
description: `Open a plan PDF from disk and replace the whole session (previous document, scales, conditions, and shapes are cleared). Returns file, page_count, and one entry per sheet: dims, title-block sheet_number, and the detected drawn scale where present. The loaded sheets also become browsable resources (takeoff://sheets). ${COORDS}`,
|
|
1218
|
-
inputSchema: { path:
|
|
1346
|
+
inputSchema: { path: z2.string().describe("Path to a plan PDF on disk") },
|
|
1347
|
+
outputSchema: loadPlanOutput
|
|
1219
1348
|
}, run("load_plan", async ({ path: path3 }) => {
|
|
1220
1349
|
const loaded = await session.loadPlan(path3);
|
|
1221
1350
|
server.sendResourceListChanged();
|
|
@@ -1223,17 +1352,19 @@ function registerTools(server, session) {
|
|
|
1223
1352
|
}));
|
|
1224
1353
|
server.registerTool("sheet_info", {
|
|
1225
1354
|
description: `Sheet detail: dims (px and pt), vector segment count, whether the sheet has vector linework (one_click needs it), scale status, the detected scale suggestion, and this sheet's committed shape count. ${COORDS}`,
|
|
1226
|
-
inputSchema: { sheet:
|
|
1355
|
+
inputSchema: { sheet: z2.string().describe('Sheet key ("plan.pdf", "plan.pdf#2") or title-block number ("A-101")') },
|
|
1356
|
+
outputSchema: sheetInfoOutput
|
|
1227
1357
|
}, run("sheet_info", ({ sheet }) => session.sheetInfo(sheet)));
|
|
1228
1358
|
server.registerTool("set_scale", {
|
|
1229
1359
|
description: `Set a sheet's scale \u2014 exactly ONE of: label (a standard scale, e.g. '1/4" = 1'-0"'), upp (real feet per image px), calibrate (two points along a known dimension plus its real feet), or use_detected (adopt the drawn scale note read off the sheet). The detected scale is never applied automatically \u2014 setting it is always this explicit call. ${COORDS}`,
|
|
1230
1360
|
inputSchema: {
|
|
1231
|
-
sheet:
|
|
1232
|
-
label:
|
|
1233
|
-
upp:
|
|
1234
|
-
calibrate:
|
|
1235
|
-
use_detected:
|
|
1236
|
-
}
|
|
1361
|
+
sheet: z2.string(),
|
|
1362
|
+
label: z2.string().optional().describe("A standard scale label, exactly as listed in the error on a miss"),
|
|
1363
|
+
upp: z2.number().optional().describe("Real feet per image px at render scale 2.0"),
|
|
1364
|
+
calibrate: z2.object({ p1: pointSchema, p2: pointSchema, feet: z2.number() }).optional().describe("Two points (image px) a known real distance apart, and that distance in feet"),
|
|
1365
|
+
use_detected: z2.boolean().optional().describe("true = adopt the sheet's detected scale")
|
|
1366
|
+
},
|
|
1367
|
+
outputSchema: setScaleOutput
|
|
1237
1368
|
}, run("set_scale", (a) => {
|
|
1238
1369
|
const given = [a.label !== void 0, a.upp !== void 0, a.calibrate !== void 0, a.use_detected !== void 0].filter(Boolean).length;
|
|
1239
1370
|
if (given !== 1) throw new UserError("Provide exactly one of: label, upp, calibrate, use_detected.");
|
|
@@ -1242,38 +1373,43 @@ function registerTools(server, session) {
|
|
|
1242
1373
|
server.registerTool("one_click", {
|
|
1243
1374
|
description: `One-Click Area: click inside a room (image px) and the plan's vector linework bounds it \u2014 flood fill, contour trace, vertices snapped to true PDF endpoints. With the sheet's scale set, returns area_sf / perimeter_lf; pass condition (a finish tag, e.g. "CPT-1") to commit the traced shape to the takeoff. Without a scale it returns px-only quantities with a warning and commits nothing. role "deduct" makes the committed shape subtract. ${COORDS}`,
|
|
1244
1375
|
inputSchema: {
|
|
1245
|
-
sheet:
|
|
1246
|
-
x:
|
|
1247
|
-
y:
|
|
1248
|
-
condition:
|
|
1376
|
+
sheet: z2.string(),
|
|
1377
|
+
x: z2.number(),
|
|
1378
|
+
y: z2.number(),
|
|
1379
|
+
condition: z2.string().optional().describe("Finish tag to commit under (minted on first use)"),
|
|
1249
1380
|
role: roleSchema,
|
|
1250
|
-
return_verts:
|
|
1251
|
-
}
|
|
1381
|
+
return_verts: z2.boolean().default(false).describe("Include the traced polygon's vertices (image px)")
|
|
1382
|
+
},
|
|
1383
|
+
outputSchema: oneClickOutput
|
|
1252
1384
|
}, run("one_click", (a) => session.oneClick(a.sheet, a.x, a.y, { condition: a.condition, role: a.role, returnVerts: a.return_verts })));
|
|
1253
1385
|
server.registerTool("measure_polygon", {
|
|
1254
1386
|
description: `Measure a closed polygon you supply (min 3 vertices, image px): area_sf and perimeter_lf at the sheet's scale. Requires the scale to be set. Pass condition to commit it; role "deduct" subtracts. ${COORDS}`,
|
|
1255
1387
|
inputSchema: {
|
|
1256
|
-
sheet:
|
|
1257
|
-
verts:
|
|
1258
|
-
condition:
|
|
1388
|
+
sheet: z2.string(),
|
|
1389
|
+
verts: z2.array(pointSchema).min(3),
|
|
1390
|
+
condition: z2.string().optional(),
|
|
1259
1391
|
role: roleSchema
|
|
1260
|
-
}
|
|
1392
|
+
},
|
|
1393
|
+
outputSchema: measurePolygonOutput
|
|
1261
1394
|
}, run("measure_polygon", (a) => session.measurePolygon(a.sheet, a.verts, { condition: a.condition, role: a.role })));
|
|
1262
1395
|
server.registerTool("measure_line", {
|
|
1263
1396
|
description: `Measure an open polyline (min 2 points, image px): length_lf at the sheet's scale. Requires the scale to be set. Pass condition to commit it as a linear shape (base, transitions, feature strips). ${COORDS}`,
|
|
1264
1397
|
inputSchema: {
|
|
1265
|
-
sheet:
|
|
1266
|
-
pts:
|
|
1267
|
-
condition:
|
|
1268
|
-
}
|
|
1398
|
+
sheet: z2.string(),
|
|
1399
|
+
pts: z2.array(pointSchema).min(2),
|
|
1400
|
+
condition: z2.string().optional()
|
|
1401
|
+
},
|
|
1402
|
+
outputSchema: measureLineOutput
|
|
1269
1403
|
}, run("measure_line", (a) => session.measureLine(a.sheet, a.pts, { condition: a.condition })));
|
|
1270
1404
|
server.registerTool("takeoff_summary", {
|
|
1271
1405
|
description: `Per-condition totals (floor/wall/border SF, LF, EA, SY, with and without waste) plus grand totals \u2014 the Report's numbers, computed by the same rules. ${COORDS}`,
|
|
1272
|
-
inputSchema: {}
|
|
1406
|
+
inputSchema: {},
|
|
1407
|
+
outputSchema: takeoffSummaryOutput
|
|
1273
1408
|
}, run("takeoff_summary", () => session.summary()));
|
|
1274
1409
|
server.registerTool("export_takeoff", {
|
|
1275
1410
|
description: `The full "opentakeoff.takeoff_canvas.v1" annotations payload \u2014 exactly what the app autosaves, importable by it. Returned inline; pass path to also write it to disk as JSON. ${COORDS}`,
|
|
1276
|
-
inputSchema: { path:
|
|
1411
|
+
inputSchema: { path: z2.string().optional().describe("File path to write the payload to") },
|
|
1412
|
+
outputSchema: exportTakeoffOutput
|
|
1277
1413
|
}, run("export_takeoff", async ({ path: outPath }) => {
|
|
1278
1414
|
const payload = session.exportPayload();
|
|
1279
1415
|
if (outPath) {
|
|
@@ -1284,14 +1420,16 @@ function registerTools(server, session) {
|
|
|
1284
1420
|
}));
|
|
1285
1421
|
server.registerTool("delete_shape", {
|
|
1286
1422
|
description: `Remove a committed shape by the id returned when it was committed. ${COORDS}`,
|
|
1287
|
-
inputSchema: { shape_id:
|
|
1423
|
+
inputSchema: { shape_id: z2.string() },
|
|
1424
|
+
outputSchema: deleteShapeOutput
|
|
1288
1425
|
}, run("delete_shape", ({ shape_id }) => session.deleteShape(shape_id)));
|
|
1289
1426
|
server.registerTool("read_sheet_text", {
|
|
1290
1427
|
description: `The sheet's text with positions \u2014 items [{str, x, y}] in image px plus the joined text. Optionally restrict to a region {x0, y0, x1, y1}. Use it to read title blocks, room labels, finish schedules, and scale notes. ${COORDS}`,
|
|
1291
1428
|
inputSchema: {
|
|
1292
|
-
sheet:
|
|
1293
|
-
region:
|
|
1294
|
-
}
|
|
1429
|
+
sheet: z2.string(),
|
|
1430
|
+
region: z2.object({ x0: z2.number(), y0: z2.number(), x1: z2.number(), y1: z2.number() }).optional()
|
|
1431
|
+
},
|
|
1432
|
+
outputSchema: readSheetTextOutput
|
|
1295
1433
|
}, run("read_sheet_text", (a) => session.readSheetText(a.sheet, a.region)));
|
|
1296
1434
|
}
|
|
1297
1435
|
|
|
@@ -1374,7 +1512,7 @@ function registerResources(server, session) {
|
|
|
1374
1512
|
// package.json
|
|
1375
1513
|
var package_default = {
|
|
1376
1514
|
name: "opentakeoff-mcp",
|
|
1377
|
-
version: "0.
|
|
1515
|
+
version: "0.4.0",
|
|
1378
1516
|
mcpName: "io.github.Kentucky-ai/opentakeoff",
|
|
1379
1517
|
type: "module",
|
|
1380
1518
|
description: "OpenTakeoff MCP server \u2014 drive the takeoff engine from your MCP client over stdio.",
|
|
@@ -1386,9 +1524,11 @@ var package_default = {
|
|
|
1386
1524
|
dev: "node --import tsx server.ts",
|
|
1387
1525
|
start: "node dist/server.js",
|
|
1388
1526
|
build: "esbuild server.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/server-core.js && node scripts/finish-build.mjs",
|
|
1527
|
+
"smoke:dist": "node scripts/smoke-dist.mjs",
|
|
1528
|
+
mcpb: "npm run build && node scripts/build-mcpb.mjs",
|
|
1389
1529
|
prepublishOnly: "npm run typecheck && npm test && npm run build",
|
|
1390
1530
|
typecheck: "tsc --noEmit",
|
|
1391
|
-
test: "node --import tsx --test test
|
|
1531
|
+
test: "node --import tsx --test test/e2e.test.ts test/resources.test.ts test/session.test.ts test/tools.test.ts"
|
|
1392
1532
|
},
|
|
1393
1533
|
dependencies: {
|
|
1394
1534
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opentakeoff-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"mcpName": "io.github.Kentucky-ai/opentakeoff",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "OpenTakeoff MCP server
|
|
6
|
+
"description": "OpenTakeoff MCP server — drive the takeoff engine from your MCP client over stdio.",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=20"
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
"dev": "node --import tsx server.ts",
|
|
13
13
|
"start": "node dist/server.js",
|
|
14
14
|
"build": "esbuild server.ts --bundle --platform=node --format=esm --packages=external --outfile=dist/server-core.js && node scripts/finish-build.mjs",
|
|
15
|
+
"smoke:dist": "node scripts/smoke-dist.mjs",
|
|
16
|
+
"mcpb": "npm run build && node scripts/build-mcpb.mjs",
|
|
15
17
|
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
16
18
|
"typecheck": "tsc --noEmit",
|
|
17
|
-
"test": "node --import tsx --test test
|
|
19
|
+
"test": "node --import tsx --test test/e2e.test.ts test/resources.test.ts test/session.test.ts test/tools.test.ts"
|
|
18
20
|
},
|
|
19
21
|
"dependencies": {
|
|
20
22
|
"@modelcontextprotocol/sdk": "^1.12.0",
|