@assetlab/mcp-server 1.16.0 → 1.17.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 +1 -1
- package/dist/tools-write.js +85 -8
- package/dist/tools-write.js.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.js +15 -0
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Works with **Claude**, **ChatGPT**, **Microsoft Copilot**, and any MCP-compatibl
|
|
|
13
13
|
### Claude.ai
|
|
14
14
|
|
|
15
15
|
1. Create an API key in **AssetLab → Settings → API Keys**
|
|
16
|
-
2. In Claude.ai, go to **Settings →
|
|
16
|
+
2. In Claude.ai, go to **Settings → Connectors → Add connector**
|
|
17
17
|
3. Paste the connector URL: `https://mcp.assetlab.ca`
|
|
18
18
|
4. When prompted for auth, paste your API key (`al_live_...`)
|
|
19
19
|
|
package/dist/tools-write.js
CHANGED
|
@@ -25,6 +25,59 @@ function buildBody(params) {
|
|
|
25
25
|
}
|
|
26
26
|
return body;
|
|
27
27
|
}
|
|
28
|
+
const PM_RESOURCE_TYPES = ['TOOL', 'PART', 'MATERIAL', 'EQUIPMENT'];
|
|
29
|
+
function randomTaskId() {
|
|
30
|
+
const g = globalThis;
|
|
31
|
+
return g.crypto?.randomUUID?.() ?? `task_${Math.random().toString(36).slice(2, 12)}`;
|
|
32
|
+
}
|
|
33
|
+
function coerceNumber(value) {
|
|
34
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
35
|
+
return value;
|
|
36
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
37
|
+
const n = Number(value);
|
|
38
|
+
if (Number.isFinite(n))
|
|
39
|
+
return n;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Shape pm_template tasks/resources to match the UI form's zod schema so
|
|
45
|
+
* records written via MCP remain editable in the app. Unknown keys are dropped,
|
|
46
|
+
* resource types are uppercased to the allowed enum, and task ids are filled in.
|
|
47
|
+
*/
|
|
48
|
+
function normalizePmTemplateBody(params) {
|
|
49
|
+
const out = { ...params };
|
|
50
|
+
if (Array.isArray(out.tasks)) {
|
|
51
|
+
out.tasks = out.tasks.map((raw) => {
|
|
52
|
+
const t = (raw && typeof raw === 'object') ? raw : {};
|
|
53
|
+
const id = typeof t.id === 'string' && t.id.trim() !== '' ? t.id : randomTaskId();
|
|
54
|
+
const description = typeof t.description === 'string' ? t.description : '';
|
|
55
|
+
const completed = typeof t.completed === 'boolean' ? t.completed : false;
|
|
56
|
+
return { id, description, completed };
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(out.resources)) {
|
|
60
|
+
out.resources = out.resources.map((raw) => {
|
|
61
|
+
const r = (raw && typeof raw === 'object') ? raw : {};
|
|
62
|
+
const res = {};
|
|
63
|
+
if (typeof r.name === 'string')
|
|
64
|
+
res.name = r.name;
|
|
65
|
+
if (typeof r.type === 'string') {
|
|
66
|
+
const upper = r.type.toUpperCase();
|
|
67
|
+
if (PM_RESOURCE_TYPES.includes(upper))
|
|
68
|
+
res.type = upper;
|
|
69
|
+
}
|
|
70
|
+
const cost = coerceNumber(r.cost);
|
|
71
|
+
if (cost !== undefined)
|
|
72
|
+
res.cost = cost;
|
|
73
|
+
const quantity = coerceNumber(r.quantity);
|
|
74
|
+
if (quantity !== undefined)
|
|
75
|
+
res.quantity = quantity;
|
|
76
|
+
return res;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
28
81
|
// ---------------------------------------------------------------------------
|
|
29
82
|
// Registration
|
|
30
83
|
// ---------------------------------------------------------------------------
|
|
@@ -579,17 +632,22 @@ export function registerWriteTools(server, client) {
|
|
|
579
632
|
estimated_cost: z.number().min(0).optional().describe('Estimated cost'),
|
|
580
633
|
safety_requirements: z.string().optional().describe('Safety requirements'),
|
|
581
634
|
tasks: z.array(z.object({
|
|
582
|
-
id: z.string().describe('Unique task ID (
|
|
635
|
+
id: z.string().optional().describe('Unique task ID (auto-generated if omitted)'),
|
|
583
636
|
description: z.string().describe('Task description'),
|
|
584
|
-
completed: z.boolean().describe('Whether the task is completed'),
|
|
637
|
+
completed: z.boolean().optional().describe('Whether the task is completed (defaults to false)'),
|
|
585
638
|
})).optional().describe('Checklist of tasks baked into this template'),
|
|
586
|
-
resources: z.array(z.
|
|
639
|
+
resources: z.array(z.object({
|
|
640
|
+
name: z.string().optional().describe('Resource name'),
|
|
641
|
+
type: z.enum(PM_RESOURCE_TYPES).optional().describe('Resource type (TOOL, PART, MATERIAL, or EQUIPMENT)'),
|
|
642
|
+
quantity: z.number().optional().describe('Quantity required'),
|
|
643
|
+
cost: z.number().optional().describe('Unit cost'),
|
|
644
|
+
})).optional().describe('Resource references (parts, tools, materials, equipment)'),
|
|
587
645
|
documents: z.array(z.record(z.unknown())).optional().describe('Document references'),
|
|
588
646
|
asset_ids: z.array(z.string().uuid()).optional().describe('Default asset IDs to seed on derived schedules'),
|
|
589
647
|
location_ids: z.array(z.string().uuid()).optional().describe('Default location IDs to seed on derived schedules'),
|
|
590
648
|
}, async (params) => {
|
|
591
649
|
try {
|
|
592
|
-
const result = await client.create('pm-templates', buildBody(params));
|
|
650
|
+
const result = await client.create('pm-templates', buildBody(normalizePmTemplateBody(params)));
|
|
593
651
|
return formatResult(result);
|
|
594
652
|
}
|
|
595
653
|
catch (err) {
|
|
@@ -608,17 +666,22 @@ export function registerWriteTools(server, client) {
|
|
|
608
666
|
estimated_cost: z.number().min(0).optional().describe('Estimated cost'),
|
|
609
667
|
safety_requirements: z.string().optional().describe('Safety requirements'),
|
|
610
668
|
tasks: z.array(z.object({
|
|
611
|
-
id: z.string().describe('Unique task ID'),
|
|
669
|
+
id: z.string().optional().describe('Unique task ID (auto-generated if omitted)'),
|
|
612
670
|
description: z.string().describe('Task description'),
|
|
613
|
-
completed: z.boolean().describe('Whether the task is completed'),
|
|
671
|
+
completed: z.boolean().optional().describe('Whether the task is completed (defaults to false)'),
|
|
614
672
|
})).optional().describe('Checklist of tasks baked into this template'),
|
|
615
|
-
resources: z.array(z.
|
|
673
|
+
resources: z.array(z.object({
|
|
674
|
+
name: z.string().optional().describe('Resource name'),
|
|
675
|
+
type: z.enum(PM_RESOURCE_TYPES).optional().describe('Resource type (TOOL, PART, MATERIAL, or EQUIPMENT)'),
|
|
676
|
+
quantity: z.number().optional().describe('Quantity required'),
|
|
677
|
+
cost: z.number().optional().describe('Unit cost'),
|
|
678
|
+
})).optional().describe('Resource references (parts, tools, materials, equipment)'),
|
|
616
679
|
documents: z.array(z.record(z.unknown())).optional().describe('Document references'),
|
|
617
680
|
asset_ids: z.array(z.string().uuid()).optional().describe('Default asset IDs'),
|
|
618
681
|
location_ids: z.array(z.string().uuid()).optional().describe('Default location IDs'),
|
|
619
682
|
}, async ({ id, ...rest }) => {
|
|
620
683
|
try {
|
|
621
|
-
const result = await client.update('pm-templates', id, buildBody(rest));
|
|
684
|
+
const result = await client.update('pm-templates', id, buildBody(normalizePmTemplateBody(rest)));
|
|
622
685
|
return formatResult(result);
|
|
623
686
|
}
|
|
624
687
|
catch (err) {
|
|
@@ -2189,6 +2252,20 @@ export function registerWriteTools(server, client) {
|
|
|
2189
2252
|
return formatError(err);
|
|
2190
2253
|
}
|
|
2191
2254
|
});
|
|
2255
|
+
server.tool('upload_file', 'Upload a file to AssetLab storage by sending its bytes inline (base64). The AssetLab backend performs the storage upload server-side — use this tool when the client cannot PUT directly to Supabase Storage (e.g. Claude integrations whose outbound network blocks arbitrary supabase.co hosts). Returns { path, public_url, bucket, file_size, content_type }. After uploading, pass path or public_url to the appropriate record tool (update_asset image_url, create_asset_document file_path, update_work_order image_url, create_attachment file_path, create_project_document file_path, create_contract_document file_path). Server limit is ~10 MB decoded; MCP arg ceiling effectively caps file size around 700 KB–1 MB. For larger files, use create_upload_url instead. Requires upload_urls:write scope.', {
|
|
2256
|
+
bucket: z.enum(['documents', 'attachments', 'project-documents', 'contract-documents', 'asset-images']).describe('Storage bucket (required). Use "asset-images" for asset photos, "attachments" for work-order/PM attachments.'),
|
|
2257
|
+
file_name: z.string().min(1).max(500).describe('File name including extension (required)'),
|
|
2258
|
+
content_base64: z.string().min(1).describe('File contents base64-encoded (required). Data URI prefixes like "data:image/png;base64," are stripped automatically.'),
|
|
2259
|
+
content_type: z.string().max(200).optional().describe('MIME type (e.g. image/jpeg, application/pdf). Defaults to application/octet-stream.'),
|
|
2260
|
+
}, async (params) => {
|
|
2261
|
+
try {
|
|
2262
|
+
const result = await client.create('upload-files', buildBody(params));
|
|
2263
|
+
return formatResult(result);
|
|
2264
|
+
}
|
|
2265
|
+
catch (err) {
|
|
2266
|
+
return formatError(err);
|
|
2267
|
+
}
|
|
2268
|
+
});
|
|
2192
2269
|
// ============================================================
|
|
2193
2270
|
// Asset Documents (scope: asset_documents)
|
|
2194
2271
|
// ============================================================
|