@sealab/mcp-server 1.0.3 → 1.0.4
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/dist/index.js +2 -1
- package/dist/tools/ai-drawing.js +563 -1
- package/dist/tools/cart-cross-check.js +182 -0
- package/dist/tools/elevation-run-segmentation.js +99 -0
- package/dist/tools/orders.js +2 -0
- package/dist/tools/placement-projection.js +119 -0
- package/dist/tools/projects.js +368 -0
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/tools/ai-drawing.test.ts +464 -44
- package/src/tools/ai-drawing.ts +750 -36
- package/src/tools/cart-cross-check.test.ts +84 -0
- package/src/tools/cart-cross-check.ts +267 -0
- package/src/tools/elevation-run-segmentation.test.ts +64 -0
- package/src/tools/elevation-run-segmentation.ts +175 -0
- package/src/tools/orders.ts +7 -3
- package/src/tools/placement-projection.test.ts +185 -0
- package/src/tools/placement-projection.ts +247 -0
- package/src/tools/projects.test.ts +135 -0
- package/src/tools/projects.ts +370 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { client, handleAxiosError } from '../client/api-client';
|
|
3
|
+
|
|
4
|
+
const ProjectStatusSchema = z.enum(['Active', 'On Hold', 'Complete']);
|
|
5
|
+
const PermissionTypeSchema = z.enum(['VIEW', 'EDIT', 'ADMIN']);
|
|
6
|
+
const MaterialFieldSchema = z.enum(['caseMaterial', 'frontMaterial', 'backPanelMaterial', 'innerCaseMaterial']);
|
|
7
|
+
const FinishingTypeSchema = z.enum(['Matte', 'Satin', 'Primed']);
|
|
8
|
+
|
|
9
|
+
const ListProjectsSchema = z.object({});
|
|
10
|
+
|
|
11
|
+
const GetProjectSchema = z.object({
|
|
12
|
+
projectId: z.number().int().positive().describe('Project ID. Use list_projects first if unknown.'),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const ProjectInputSchema = z.object({
|
|
16
|
+
name: z.string().min(1).describe('Project name.'),
|
|
17
|
+
address1: z.string().optional().describe('Project address line 1.'),
|
|
18
|
+
address2: z.string().optional().describe('Project address line 2.'),
|
|
19
|
+
city: z.string().optional().describe('Project city.'),
|
|
20
|
+
state: z.string().optional().describe('Project state.'),
|
|
21
|
+
zipcode: z.string().optional().describe('Project ZIP/postal code.'),
|
|
22
|
+
status: ProjectStatusSchema.default('Active').describe('Project status.'),
|
|
23
|
+
phase: z.string().optional().describe('Project phase, e.g. Design, Shop Drawings, Install.'),
|
|
24
|
+
clientName: z.string().optional().describe('Client or end-customer name.'),
|
|
25
|
+
budget: z.number().optional().describe('Optional project budget.'),
|
|
26
|
+
dueDate: z.string().optional().describe('Optional due date in YYYY-MM-DD format.'),
|
|
27
|
+
taxExempt: z.boolean().optional().describe('Whether project summary pricing should display without sales tax.'),
|
|
28
|
+
notes: z.string().optional().describe('Project notes.'),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const CreateProjectSchema = ProjectInputSchema;
|
|
32
|
+
|
|
33
|
+
const UpdateProjectSchema = ProjectInputSchema.partial().extend({
|
|
34
|
+
projectId: z.number().int().positive().describe('Project ID to update.'),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const DeleteProjectSchema = z.object({
|
|
38
|
+
projectId: z.number().int().positive().describe('Project ID to delete.'),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const ProjectOrderSchema = z.object({
|
|
42
|
+
projectId: z.number().int().positive().describe('Project ID.'),
|
|
43
|
+
orderId: z.string().describe('Order ID to attach or detach.'),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const UpdateProjectOrderQuantitySchema = ProjectOrderSchema.extend({
|
|
47
|
+
quantity: z.number().int().min(1).describe('Project quantity multiplier for this order. Does not change the order itself.'),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const UpdateProjectOrderOptionsSchema = ProjectOrderSchema.extend({
|
|
51
|
+
includeDrawerboxes: z.boolean().optional().describe('Include drawer boxes for this order. Omit to leave unchanged.'),
|
|
52
|
+
includeAssembly: z.boolean().optional().describe('Include assembly for this order. Omit to leave unchanged.'),
|
|
53
|
+
includeHardware: z.boolean().optional().describe('Include hardware for this order. Omit to leave unchanged.'),
|
|
54
|
+
includeFinishing: z.boolean().optional().describe('Include finishing for this order. Omit to leave unchanged.'),
|
|
55
|
+
finishingType: FinishingTypeSchema.optional().describe('Finishing type. Required when setting includeFinishing to true.'),
|
|
56
|
+
finishingColor: z.string().optional().describe('Finishing color. Required when setting includeFinishing to true.'),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const UpdateProjectArticleMaterialSchema = ProjectOrderSchema.extend({
|
|
60
|
+
positionName: z.string().describe('Article position name within the order, e.g. "Base_01".'),
|
|
61
|
+
field: MaterialFieldSchema.describe('Material field to update.'),
|
|
62
|
+
materialDescription: z.string().min(1).describe('Exact material description from the Sealab material list.'),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const UpdateProjectArticleMaterialsSchema = ProjectOrderSchema.extend({
|
|
66
|
+
positionNames: z.array(z.string()).min(1).describe('Article position names to update.'),
|
|
67
|
+
field: MaterialFieldSchema.describe('Material field to update.'),
|
|
68
|
+
materialDescription: z.string().min(1).describe('Exact material description from the Sealab material list.'),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const ProjectAccessSchema = z.object({
|
|
72
|
+
projectId: z.number().int().positive().describe('Project ID.'),
|
|
73
|
+
grantedToEmail: z.string().email().describe('Email address to grant or update access for.'),
|
|
74
|
+
permissionType: PermissionTypeSchema.default('VIEW').describe('VIEW = read-only, EDIT = can update project/order options, ADMIN = admin-level project permission.'),
|
|
75
|
+
hidePrice: z.boolean().default(false).describe('Whether to hide project pricing from this user.'),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const RevokeProjectAccessSchema = z.object({
|
|
79
|
+
projectId: z.number().int().positive().describe('Project ID.'),
|
|
80
|
+
email: z.string().email().describe('Email address whose project access should be revoked.'),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const ListProjectAccessSchema = z.object({
|
|
84
|
+
projectId: z.number().int().positive().describe('Project ID.'),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
function compactProject(project: any): any {
|
|
88
|
+
return {
|
|
89
|
+
projectId: project.projectId,
|
|
90
|
+
name: project.name,
|
|
91
|
+
status: project.status,
|
|
92
|
+
phase: project.phase,
|
|
93
|
+
clientName: project.clientName,
|
|
94
|
+
address: [project.address1, project.address2, project.city, project.state, project.zipcode].filter(Boolean).join(', '),
|
|
95
|
+
shared: project.shared,
|
|
96
|
+
permissionType: project.permissionType,
|
|
97
|
+
hidePrice: project.hidePrice,
|
|
98
|
+
updatedAt: project.updatedAt,
|
|
99
|
+
orderCount: project.orders?.length ?? project.summary?.orderSummaries?.length,
|
|
100
|
+
projectTotalValue: project.hidePrice ? undefined : project.summary?.projectTotalValue,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function formatProjectList(projects: any[]): string {
|
|
105
|
+
if (!projects || projects.length === 0) return 'No projects found.';
|
|
106
|
+
return JSON.stringify(projects.map(compactProject), null, 2);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function formatProject(project: any): string {
|
|
110
|
+
return JSON.stringify(project, null, 2);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function apiError(error: unknown, fallback: string): Promise<string> {
|
|
114
|
+
try { handleAxiosError(error); } catch (e: any) { return e.message; }
|
|
115
|
+
return fallback;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function listProjects(_: z.infer<typeof ListProjectsSchema>): Promise<string> {
|
|
119
|
+
try {
|
|
120
|
+
const { data } = await client.get('/projects');
|
|
121
|
+
return formatProjectList(data);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return apiError(error, 'Unexpected error listing projects.');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function getProject(input: z.infer<typeof GetProjectSchema>): Promise<string> {
|
|
128
|
+
try {
|
|
129
|
+
const { data } = await client.get(`/projects/${input.projectId}`);
|
|
130
|
+
return formatProject(data);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
return apiError(error, 'Unexpected error fetching project.');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function createProject(input: z.infer<typeof CreateProjectSchema>): Promise<string> {
|
|
137
|
+
try {
|
|
138
|
+
const { data } = await client.post('/projects', input);
|
|
139
|
+
return `Project created successfully. Project ID: ${data.projectId}. Name: ${data.name}.`;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return apiError(error, 'Unexpected error creating project.');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function updateProject(input: z.infer<typeof UpdateProjectSchema>): Promise<string> {
|
|
146
|
+
const { projectId, ...payload } = input;
|
|
147
|
+
try {
|
|
148
|
+
const { data: current } = await client.get(`/projects/${projectId}`);
|
|
149
|
+
const { data } = await client.put(`/projects/${projectId}`, { ...current, ...payload });
|
|
150
|
+
return `Project updated successfully.\n${formatProject(data)}`;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return apiError(error, 'Unexpected error updating project.');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function deleteProject(input: z.infer<typeof DeleteProjectSchema>): Promise<string> {
|
|
157
|
+
try {
|
|
158
|
+
await client.delete(`/projects/${input.projectId}`);
|
|
159
|
+
return `Project ${input.projectId} deleted successfully. Pending attached orders were detached, not deleted.`;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return apiError(error, 'Unexpected error deleting project.');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function attachOrderToProject(input: z.infer<typeof ProjectOrderSchema>): Promise<string> {
|
|
166
|
+
try {
|
|
167
|
+
await client.post(`/projects/${input.projectId}/orders/${input.orderId}`);
|
|
168
|
+
return `Order ${input.orderId} attached to project ${input.projectId}.`;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
return apiError(error, 'Unexpected error attaching order to project.');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function detachOrderFromProject(input: z.infer<typeof ProjectOrderSchema>): Promise<string> {
|
|
175
|
+
try {
|
|
176
|
+
await client.delete(`/projects/${input.projectId}/orders/${input.orderId}`);
|
|
177
|
+
return `Order ${input.orderId} detached from project ${input.projectId}.`;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
return apiError(error, 'Unexpected error detaching order from project.');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function updateProjectOrderQuantity(input: z.infer<typeof UpdateProjectOrderQuantitySchema>): Promise<string> {
|
|
184
|
+
try {
|
|
185
|
+
const { data } = await client.put(`/projects/${input.projectId}/orders/${input.orderId}/quantity`, {
|
|
186
|
+
quantity: input.quantity,
|
|
187
|
+
});
|
|
188
|
+
return `Project order quantity updated.\n${formatProject(data)}`;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
return apiError(error, 'Unexpected error updating project order quantity.');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function updateProjectOrderOptions(input: z.infer<typeof UpdateProjectOrderOptionsSchema>): Promise<string> {
|
|
195
|
+
const { projectId, orderId, ...payload } = input;
|
|
196
|
+
try {
|
|
197
|
+
const { data } = await client.patch(`/projects/${projectId}/orders/${orderId}/options`, payload);
|
|
198
|
+
return `Project order options updated.\n${JSON.stringify(data, null, 2)}`;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return apiError(error, 'Unexpected error updating project order options.');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function updateProjectArticleMaterial(input: z.infer<typeof UpdateProjectArticleMaterialSchema>): Promise<string> {
|
|
205
|
+
try {
|
|
206
|
+
const { data } = await client.patch(
|
|
207
|
+
`/projects/${input.projectId}/orders/${input.orderId}/articles/${encodeURIComponent(input.positionName)}/materials`,
|
|
208
|
+
{ field: input.field, materialDescription: input.materialDescription }
|
|
209
|
+
);
|
|
210
|
+
return `Project article material updated.\n${JSON.stringify(data, null, 2)}`;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return apiError(error, 'Unexpected error updating project article material.');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function updateProjectArticleMaterials(input: z.infer<typeof UpdateProjectArticleMaterialsSchema>): Promise<string> {
|
|
217
|
+
try {
|
|
218
|
+
const { data } = await client.patch(
|
|
219
|
+
`/projects/${input.projectId}/orders/${input.orderId}/articles/materials`,
|
|
220
|
+
{ positionNames: input.positionNames, field: input.field, materialDescription: input.materialDescription }
|
|
221
|
+
);
|
|
222
|
+
return `Project article materials updated.\n${JSON.stringify(data, null, 2)}`;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
return apiError(error, 'Unexpected error updating project article materials.');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export async function shareProjectAccess(input: z.infer<typeof ProjectAccessSchema>): Promise<string> {
|
|
229
|
+
try {
|
|
230
|
+
await client.post(`/projects/${input.projectId}/permissions/grant`, {
|
|
231
|
+
grantedToEmail: input.grantedToEmail,
|
|
232
|
+
permissionType: input.permissionType,
|
|
233
|
+
hidePrice: input.hidePrice,
|
|
234
|
+
});
|
|
235
|
+
const priceNote = input.hidePrice ? ' Pricing will be hidden from them.' : '';
|
|
236
|
+
return `Access granted. ${input.grantedToEmail} now has ${input.permissionType} access to project ${input.projectId}.${priceNote}`;
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return apiError(error, 'Unexpected error granting project access.');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export async function updateProjectAccess(input: z.infer<typeof ProjectAccessSchema>): Promise<string> {
|
|
243
|
+
try {
|
|
244
|
+
await client.put(`/projects/${input.projectId}/permissions/update`, {
|
|
245
|
+
grantedToEmail: input.grantedToEmail,
|
|
246
|
+
permissionType: input.permissionType,
|
|
247
|
+
hidePrice: input.hidePrice,
|
|
248
|
+
});
|
|
249
|
+
const priceNote = input.hidePrice ? ' Pricing is hidden from them.' : ' Pricing is visible to them.';
|
|
250
|
+
return `Permission updated. ${input.grantedToEmail} now has ${input.permissionType} access to project ${input.projectId}.${priceNote}`;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return apiError(error, 'Unexpected error updating project access.');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function revokeProjectAccess(input: z.infer<typeof RevokeProjectAccessSchema>): Promise<string> {
|
|
257
|
+
try {
|
|
258
|
+
await client.delete(`/projects/${input.projectId}/permissions/revoke`, {
|
|
259
|
+
params: { email: input.email },
|
|
260
|
+
});
|
|
261
|
+
return `Access revoked. ${input.email} can no longer access project ${input.projectId}.`;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
return apiError(error, 'Unexpected error revoking project access.');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export async function listProjectAccess(input: z.infer<typeof ListProjectAccessSchema>): Promise<string> {
|
|
268
|
+
try {
|
|
269
|
+
const { data } = await client.get(`/projects/${input.projectId}/permissions`);
|
|
270
|
+
if (!data || data.length === 0) {
|
|
271
|
+
return `No shared access on project ${input.projectId}. Only the project owner can view it.`;
|
|
272
|
+
}
|
|
273
|
+
return JSON.stringify(data, null, 2);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
return apiError(error, 'Unexpected error listing project access.');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export const projectTools = [
|
|
280
|
+
{
|
|
281
|
+
name: 'list_projects',
|
|
282
|
+
description: 'List projects available to the authenticated customer, including owned and shared projects. Use this before choosing a projectId.',
|
|
283
|
+
inputSchema: ListProjectsSchema,
|
|
284
|
+
handler: listProjects,
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'get_project',
|
|
288
|
+
description: 'Get full project details including attached orders, material/service/component summaries, custom line items, and permission metadata.',
|
|
289
|
+
inputSchema: GetProjectSchema,
|
|
290
|
+
handler: getProject,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: 'create_project',
|
|
294
|
+
description: 'Create a new customer project. Use this when the customer wants a new job/project container before attaching or placing orders.',
|
|
295
|
+
inputSchema: CreateProjectSchema,
|
|
296
|
+
handler: createProject,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: 'update_project',
|
|
300
|
+
description: 'Update project metadata such as name, address, status, phase, client, budget, due date, tax exemption, or notes. Omitted fields are left unchanged.',
|
|
301
|
+
inputSchema: UpdateProjectSchema,
|
|
302
|
+
handler: updateProject,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: 'delete_project',
|
|
306
|
+
description: 'Delete a project owned by the customer. Production or paid attached orders block deletion; pending orders are detached and remain in order history.',
|
|
307
|
+
inputSchema: DeleteProjectSchema,
|
|
308
|
+
handler: deleteProject,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: 'attach_order_to_project',
|
|
312
|
+
description: 'Attach one of the customer-owned orders to a project the customer can edit. This copies the project snapshot to the order.',
|
|
313
|
+
inputSchema: ProjectOrderSchema,
|
|
314
|
+
handler: attachOrderToProject,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: 'detach_order_from_project',
|
|
318
|
+
description: 'Remove an order from a project without deleting the order.',
|
|
319
|
+
inputSchema: ProjectOrderSchema,
|
|
320
|
+
handler: detachOrderFromProject,
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: 'update_project_order_quantity',
|
|
324
|
+
description: 'Set the project quantity multiplier for an attached order. This affects project summaries only; it does not alter the actual order.',
|
|
325
|
+
inputSchema: UpdateProjectOrderQuantitySchema,
|
|
326
|
+
handler: updateProjectOrderQuantity,
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: 'update_project_order_options',
|
|
330
|
+
description: 'Update service options for an order within a project: drawerboxes, assembly, hardware, finishing type/color. Omit unchanged fields.',
|
|
331
|
+
inputSchema: UpdateProjectOrderOptionsSchema,
|
|
332
|
+
handler: updateProjectOrderOptions,
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: 'update_project_article_material',
|
|
336
|
+
description: 'Update a material field for one cabinet/article in an attached project order. Use exact material descriptions.',
|
|
337
|
+
inputSchema: UpdateProjectArticleMaterialSchema,
|
|
338
|
+
handler: updateProjectArticleMaterial,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: 'update_project_article_materials',
|
|
342
|
+
description: 'Bulk update a material field for multiple cabinet/article positions in an attached project order. Use exact material descriptions.',
|
|
343
|
+
inputSchema: UpdateProjectArticleMaterialsSchema,
|
|
344
|
+
handler: updateProjectArticleMaterials,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'share_project_access',
|
|
348
|
+
description: 'Grant another user access to a project. Project owners only. Permission levels are VIEW, EDIT, and ADMIN; hidePrice hides project pricing.',
|
|
349
|
+
inputSchema: ProjectAccessSchema,
|
|
350
|
+
handler: shareProjectAccess,
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'update_project_access',
|
|
354
|
+
description: 'Update an existing project permission level or price visibility. Project owners only.',
|
|
355
|
+
inputSchema: ProjectAccessSchema,
|
|
356
|
+
handler: updateProjectAccess,
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'revoke_project_access',
|
|
360
|
+
description: 'Revoke a user from a project. Project owners only.',
|
|
361
|
+
inputSchema: RevokeProjectAccessSchema,
|
|
362
|
+
handler: revokeProjectAccess,
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: 'list_project_access',
|
|
366
|
+
description: 'List users who currently have shared access to a project. Project owners only.',
|
|
367
|
+
inputSchema: ListProjectAccessSchema,
|
|
368
|
+
handler: listProjectAccess,
|
|
369
|
+
},
|
|
370
|
+
];
|