@whattio/mcp-server 1.0.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 +48 -0
- package/build/api.js +18 -0
- package/build/index.js +11 -0
- package/build/server.js +140 -0
- package/build/tools/media.js +33 -0
- package/build/tools/registry.js +8 -0
- package/build/tools/types.js +1 -0
- package/build/tools/v1/category.js +65 -0
- package/build/tools/v1/incendo.js +66 -0
- package/build/tools/v1/index.js +16 -0
- package/build/tools/v1/material.js +79 -0
- package/build/tools/v1/product.js +85 -0
- package/build/tools/v1/simplyprint.js +44 -0
- package/build/tools/v1/unit.js +67 -0
- package/build/tools/v1/user.js +46 -0
- package/build/tools/v2/auth.js +75 -0
- package/build/tools/v2/category.js +109 -0
- package/build/tools/v2/device.js +97 -0
- package/build/tools/v2/index.js +16 -0
- package/build/tools/v2/material.js +114 -0
- package/build/tools/v2/part.js +102 -0
- package/build/tools/v2/product.js +99 -0
- package/build/tools/v2/unit.js +86 -0
- package/package.json +26 -0
- package/src/api.ts +24 -0
- package/src/index.ts +13 -0
- package/src/server.ts +165 -0
- package/src/tools/media.ts +36 -0
- package/src/tools/registry.ts +10 -0
- package/src/tools/types.ts +12 -0
- package/src/tools/v1/category.ts +67 -0
- package/src/tools/v1/incendo.ts +68 -0
- package/src/tools/v1/index.ts +18 -0
- package/src/tools/v1/material.ts +81 -0
- package/src/tools/v1/product.ts +87 -0
- package/src/tools/v1/simplyprint.ts +46 -0
- package/src/tools/v1/unit.ts +69 -0
- package/src/tools/v1/user.ts +48 -0
- package/src/tools/v2/auth.ts +77 -0
- package/src/tools/v2/category.ts +111 -0
- package/src/tools/v2/device.ts +99 -0
- package/src/tools/v2/index.ts +18 -0
- package/src/tools/v2/material.ts +116 -0
- package/src/tools/v2/part.ts +104 -0
- package/src/tools/v2/product.ts +101 -0
- package/src/tools/v2/unit.ts +88 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export const productTools = [
|
|
2
|
+
{
|
|
3
|
+
name: "v2_list_products",
|
|
4
|
+
description: "Retrieve a paginated list of all products belonging to the authenticated user's current team. Also use this tool whenever the user mentions 'DPP', 'Digital Product Passports', 'digital twins', or wants to search their whatt.io catalog.",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
page: { type: "number", description: "The page number to retrieve" },
|
|
9
|
+
per_page: { type: "number", description: "Number of items per page" }
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
handler: async (args, apiClient) => {
|
|
13
|
+
const response = await apiClient.get('/api/v2/product', { params: args });
|
|
14
|
+
return response.data;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "v2_create_product",
|
|
19
|
+
description: "Create a new product",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
name: { type: "string" },
|
|
24
|
+
product_number: { type: "string" },
|
|
25
|
+
description: { type: "string" },
|
|
26
|
+
gtin_number: { type: "string" },
|
|
27
|
+
category_id: { type: "number" },
|
|
28
|
+
ai_description: { type: "string" },
|
|
29
|
+
buy_link: { type: "string" },
|
|
30
|
+
video_url: { type: "string" },
|
|
31
|
+
primaryMaterial: { type: "number" },
|
|
32
|
+
secondaryMaterial: { type: "number" },
|
|
33
|
+
public: { type: "boolean" }
|
|
34
|
+
},
|
|
35
|
+
required: ["name", "product_number", "description"]
|
|
36
|
+
},
|
|
37
|
+
handler: async (args, apiClient) => {
|
|
38
|
+
const response = await apiClient.post('/api/v2/product/create', args);
|
|
39
|
+
return response.data;
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "v2_show_product",
|
|
44
|
+
description: "Get detailed information about a specific product",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
id: { type: "number", description: "Product ID" }
|
|
49
|
+
},
|
|
50
|
+
required: ["id"]
|
|
51
|
+
},
|
|
52
|
+
handler: async (args, apiClient) => {
|
|
53
|
+
const response = await apiClient.get(`/api/v2/product/${args.id}/show`);
|
|
54
|
+
return response.data;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "v2_update_product",
|
|
59
|
+
description: "Update the details of an existing product",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: "object",
|
|
62
|
+
properties: {
|
|
63
|
+
id: { type: "number", description: "Product ID" },
|
|
64
|
+
name: { type: "string" },
|
|
65
|
+
product_number: { type: "string" },
|
|
66
|
+
description: { type: "string" },
|
|
67
|
+
gtin_number: { type: "string" },
|
|
68
|
+
category_id: { type: "number" },
|
|
69
|
+
ai_description: { type: "string" },
|
|
70
|
+
buy_link: { type: "string" },
|
|
71
|
+
video_url: { type: "string" },
|
|
72
|
+
primaryMaterial: { type: "number" },
|
|
73
|
+
secondaryMaterial: { type: "number" },
|
|
74
|
+
public: { type: "boolean" }
|
|
75
|
+
},
|
|
76
|
+
required: ["id"]
|
|
77
|
+
},
|
|
78
|
+
handler: async (args, apiClient) => {
|
|
79
|
+
const { id, ...data } = args;
|
|
80
|
+
const response = await apiClient.post(`/api/v2/product/${id}/update`, { ...data, _method: 'PUT' });
|
|
81
|
+
return response.data;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "v2_delete_product",
|
|
86
|
+
description: "Delete a product from the system",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
id: { type: "number", description: "Product ID" }
|
|
91
|
+
},
|
|
92
|
+
required: ["id"]
|
|
93
|
+
},
|
|
94
|
+
handler: async (args, apiClient) => {
|
|
95
|
+
const response = await apiClient.delete(`/api/v2/product/${args.id}/delete`);
|
|
96
|
+
return response.data;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const unitTools = [
|
|
2
|
+
{
|
|
3
|
+
name: "v2_list_units",
|
|
4
|
+
description: "Retrieve a paginated list of all product units",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
page: { type: "number", description: "The page number to retrieve" },
|
|
9
|
+
per_page: { type: "number", description: "Number of items per page" }
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
handler: async (args, apiClient) => {
|
|
13
|
+
const response = await apiClient.get('/api/v2/unit', { params: args });
|
|
14
|
+
return response.data;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "v2_create_unit",
|
|
19
|
+
description: "Create a new product unit",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
serial: { type: "string" },
|
|
24
|
+
product_id: { type: "number" },
|
|
25
|
+
parts: { type: "string", description: "JSON string of component parts used in this unit" },
|
|
26
|
+
public: { type: "boolean" }
|
|
27
|
+
},
|
|
28
|
+
required: ["serial", "product_id"]
|
|
29
|
+
},
|
|
30
|
+
handler: async (args, apiClient) => {
|
|
31
|
+
const response = await apiClient.post('/api/v2/unit/create', args);
|
|
32
|
+
return response.data;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "v2_show_unit",
|
|
37
|
+
description: "Get detailed information about a specific unit",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
id: { type: "number", description: "Unit ID" }
|
|
42
|
+
},
|
|
43
|
+
required: ["id"]
|
|
44
|
+
},
|
|
45
|
+
handler: async (args, apiClient) => {
|
|
46
|
+
const response = await apiClient.get(`/api/v2/unit/${args.id}/show`);
|
|
47
|
+
return response.data;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "v2_update_unit",
|
|
52
|
+
description: "Update the details of an existing product unit instance",
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
id: { type: "number", description: "Unit ID" },
|
|
57
|
+
serial: { type: "string" },
|
|
58
|
+
product_id: { type: "number" },
|
|
59
|
+
parts: { type: "string" },
|
|
60
|
+
public: { type: "boolean" }
|
|
61
|
+
},
|
|
62
|
+
required: ["id"]
|
|
63
|
+
},
|
|
64
|
+
handler: async (args, apiClient) => {
|
|
65
|
+
const { id, ...data } = args;
|
|
66
|
+
// The openapi explicitly shows PUT for this endpoint for v2
|
|
67
|
+
const response = await apiClient.put(`/api/v2/unit/${id}/update`, data);
|
|
68
|
+
return response.data;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "v2_delete_unit",
|
|
73
|
+
description: "Permanently delete a unit from the system",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
id: { type: "number", description: "Unit ID" }
|
|
78
|
+
},
|
|
79
|
+
required: ["id"]
|
|
80
|
+
},
|
|
81
|
+
handler: async (args, apiClient) => {
|
|
82
|
+
const response = await apiClient.delete(`/api/v2/unit/${args.id}/delete`);
|
|
83
|
+
return response.data;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@whattio/mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The official Model Context Protocol (MCP) server for whatt.io",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"whattio-mcp": "build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node build/index.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["mcp", "whattio", "dpp", "ai", "llm"],
|
|
14
|
+
"author": "whatt.io",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
19
|
+
"axios": "^1.13.6",
|
|
20
|
+
"zod": "^4.3.6"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^25.5.0",
|
|
24
|
+
"typescript": "^6.0.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
// The base URL for whatt.io API. Can be overridden via environment variables.
|
|
4
|
+
const API_BASE_URL = process.env.WHATTIO_API_BASE_URL || 'https://whatt.io';
|
|
5
|
+
|
|
6
|
+
export const createApiClient = () => {
|
|
7
|
+
const apiKey = process.env.WHATTIO_API_KEY;
|
|
8
|
+
|
|
9
|
+
const headers: Record<string, string> = {
|
|
10
|
+
'Content-Type': 'application/json',
|
|
11
|
+
'Accept': 'application/json',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (apiKey) {
|
|
15
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const client = axios.create({
|
|
19
|
+
baseURL: API_BASE_URL,
|
|
20
|
+
headers,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return client;
|
|
24
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
import { server } from "./server.js";
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
const transport = new StdioServerTransport();
|
|
6
|
+
await server.connect(transport);
|
|
7
|
+
console.error("whatt.io MCP Server running on stdio.");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
main().catch((error) => {
|
|
11
|
+
console.error("Fatal error running server:", error);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import {
|
|
3
|
+
CallToolRequestSchema,
|
|
4
|
+
ListToolsRequestSchema,
|
|
5
|
+
ErrorCode,
|
|
6
|
+
McpError,
|
|
7
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { createApiClient } from './api.js';
|
|
9
|
+
import { allTools } from './tools/registry.js';
|
|
10
|
+
import axios from 'axios';
|
|
11
|
+
|
|
12
|
+
// Initialize the API Client natively so it's ready. If the token is missing,
|
|
13
|
+
// endpoints that don't need it will work. Others will fail with 401 as expected.
|
|
14
|
+
const apiClient = createApiClient();
|
|
15
|
+
|
|
16
|
+
const server = new Server(
|
|
17
|
+
{
|
|
18
|
+
name: 'whattio-mcp-server',
|
|
19
|
+
version: '2.0.0', // We bumped the version to 2.0.0 to signify V2 tool support
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
capabilities: {
|
|
23
|
+
tools: {},
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Register all tools from the registry dynamically
|
|
29
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
30
|
+
return {
|
|
31
|
+
tools: allTools.map((tool) => ({
|
|
32
|
+
name: tool.name,
|
|
33
|
+
description: tool.description,
|
|
34
|
+
inputSchema: tool.inputSchema,
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Handle tool execution requests
|
|
40
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
41
|
+
const toolName = request.params.name;
|
|
42
|
+
const targetTool = allTools.find((t) => t.name === toolName);
|
|
43
|
+
|
|
44
|
+
if (!targetTool) {
|
|
45
|
+
throw new McpError(ErrorCode.MethodNotFound, `Tool not found: ${toolName}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const args = request.params.arguments || {};
|
|
50
|
+
let result = await targetTool.handler(args, apiClient);
|
|
51
|
+
|
|
52
|
+
if (result && result._isNativeMedia) {
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: result.type,
|
|
57
|
+
data: result.data,
|
|
58
|
+
mimeType: result.mimeType
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Recursively normalize relative URLs or raw filenames so Claude's Artifact UIs don't get broken <img> paths
|
|
65
|
+
const cdnBase = 'https://whattio-files.fra1.digitaloceanspaces.com';
|
|
66
|
+
|
|
67
|
+
// Helper to format a string into a fully qualified DO Spaces URL
|
|
68
|
+
const tryToFormatUrl = (val: string, keyName: string = ''): string => {
|
|
69
|
+
if (!val) return val;
|
|
70
|
+
if (val.startsWith('http')) return val; // already absolute
|
|
71
|
+
|
|
72
|
+
const isLogo = keyName.toLowerCase().includes('logo');
|
|
73
|
+
const folder = isLogo ? 'team_images' : 'product_photos';
|
|
74
|
+
|
|
75
|
+
let cleanVal = val;
|
|
76
|
+
if (cleanVal.startsWith('/storage/')) cleanVal = cleanVal.replace('/storage/', '');
|
|
77
|
+
else if (cleanVal.startsWith('storage/')) cleanVal = cleanVal.replace('storage/', '');
|
|
78
|
+
else if (cleanVal.startsWith('/')) cleanVal = cleanVal.slice(1);
|
|
79
|
+
|
|
80
|
+
return `${cdnBase}/${folder}/${cleanVal}`;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const normalizeUrls = (obj: any, parentKey: string = ''): any => {
|
|
84
|
+
if (typeof obj === 'string') {
|
|
85
|
+
// Broad check: if a loose string looks like an image/document file, format it
|
|
86
|
+
if (/\.(png|jpe?g|gif|webp|pdf)$/i.test(obj)) {
|
|
87
|
+
return tryToFormatUrl(obj, parentKey);
|
|
88
|
+
}
|
|
89
|
+
return obj;
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(obj)) {
|
|
92
|
+
return obj.map(item => normalizeUrls(item, parentKey));
|
|
93
|
+
}
|
|
94
|
+
if (obj !== null && typeof obj === 'object') {
|
|
95
|
+
const newObj: any = {};
|
|
96
|
+
for (const key in obj) {
|
|
97
|
+
let val = obj[key];
|
|
98
|
+
|
|
99
|
+
if (['logo', 'image', 'video', 'video_url', 'pdf', 'document'].includes(key.toLowerCase())) {
|
|
100
|
+
if (typeof val === 'string') {
|
|
101
|
+
newObj[key] = tryToFormatUrl(val, key);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// The API returns 'images' as an array of strings. Handling that case explicitly:
|
|
106
|
+
if (key.toLowerCase() === 'images' && Array.isArray(val)) {
|
|
107
|
+
newObj[key] = val.map(item => (typeof item === 'string' ? tryToFormatUrl(item, key) : normalizeUrls(item, key)));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
newObj[key] = normalizeUrls(val, key);
|
|
112
|
+
}
|
|
113
|
+
return newObj;
|
|
114
|
+
}
|
|
115
|
+
return obj;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
result = normalizeUrls(result);
|
|
119
|
+
|
|
120
|
+
const stringifiedResult = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
121
|
+
|
|
122
|
+
const mediaHint = "\n\n🤖 AI FORMATTING HINT: If this data contains images, logos, or PDFs, display them! CRITICAL: Due to strict Content Security Policy (CSP), images WILL BREAK if you try to render them inside a React Artifact. To show product or team images, ALWAYS use standard markdown `` in the regular chat text OUTSIDE and BEFORE you create any React Artifacts. Do not put `<img src>` tags in React code. ALSO: When you present a direct external link to a Digital Product Passport (DPP), do not constrain your React UI. Instead, provide a React component with a 'Launch DPP' button that executes `window.open(url, '_blank', 'width=450,height=850')` so the external whatt.io site opens in a mobile-proportioned popup window.";
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: 'text',
|
|
128
|
+
text: stringifiedResult + mediaHint,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
} catch (error: any) {
|
|
133
|
+
if (axios.isAxiosError(error)) {
|
|
134
|
+
const statusCode = error.response?.status || 500;
|
|
135
|
+
const responseData = error.response?.data ? JSON.stringify(error.response.data) : error.message;
|
|
136
|
+
|
|
137
|
+
let extraHint = "";
|
|
138
|
+
if (statusCode === 404 || statusCode === 403) {
|
|
139
|
+
extraHint = "\n\n🤖 AI HINT: You received a 404/403 error. This usually happens because the item belongs to a different Team context on whatt.io. You MUST use the `v2_switch_team` or `v1_switch_team` tool to change the Active Team, and then retry your request.";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
isError: true,
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: 'text',
|
|
147
|
+
text: `Whatt.io API Error (${statusCode}): ${responseData}${extraHint}`,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
isError: true,
|
|
155
|
+
content: [
|
|
156
|
+
{
|
|
157
|
+
type: 'text',
|
|
158
|
+
text: `Unexpected Error: ${error.message || String(error)}`,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
export { server };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { McpTool } from './types.js';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
export const mediaTools: McpTool[] = [
|
|
5
|
+
{
|
|
6
|
+
name: "fetch_media",
|
|
7
|
+
description: "Fetch an image from a whatt.io URL (or any URL) and render it directly in the chat. Use this whenever the user wants to visually see a product image, logo, or diagram from the JSON data you retrieved. Pass the absolute URL.",
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
url: { type: "string", description: "The absolute URL of the image to fetch" }
|
|
12
|
+
},
|
|
13
|
+
required: ["url"]
|
|
14
|
+
},
|
|
15
|
+
handler: async (args, apiClient) => {
|
|
16
|
+
// We don't necessarily use the apiClient since the URL could be absolute to S3 or elsewhere,
|
|
17
|
+
// but we will use axios natively to fetch the arraybuffer.
|
|
18
|
+
try {
|
|
19
|
+
const response = await axios.get(args.url, { responseType: 'arraybuffer' });
|
|
20
|
+
|
|
21
|
+
const mimeType = response.headers['content-type'] || 'image/jpeg';
|
|
22
|
+
const base64Data = Buffer.from(response.data, 'binary').toString('base64');
|
|
23
|
+
|
|
24
|
+
// Return in native MCP image format so Claude renders it immediately without a privacy button
|
|
25
|
+
return {
|
|
26
|
+
_isNativeMedia: true,
|
|
27
|
+
type: 'image',
|
|
28
|
+
data: base64Data,
|
|
29
|
+
mimeType: mimeType
|
|
30
|
+
};
|
|
31
|
+
} catch (error: any) {
|
|
32
|
+
throw new Error(`Failed to fetch media from ${args.url}: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
|
|
3
|
+
export interface McpTool {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object";
|
|
8
|
+
properties: Record<string, any>;
|
|
9
|
+
required?: string[];
|
|
10
|
+
};
|
|
11
|
+
handler: (args: any, apiClient: AxiosInstance) => Promise<any>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { McpTool } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export const categoryTools: McpTool[] = [
|
|
4
|
+
{
|
|
5
|
+
name: "v1_list_categories",
|
|
6
|
+
description: "Show all categories within a team",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {}
|
|
10
|
+
},
|
|
11
|
+
handler: async (_args, apiClient) => {
|
|
12
|
+
const response = await apiClient.get('/api/categories');
|
|
13
|
+
return response.data;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "v1_create_category",
|
|
18
|
+
description: "Add a new category",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
name: { type: "string" },
|
|
23
|
+
country: { type: "string" },
|
|
24
|
+
city: { type: "string" },
|
|
25
|
+
email: { type: "string" },
|
|
26
|
+
phone: { type: "string" },
|
|
27
|
+
website: { type: "string" },
|
|
28
|
+
ai_description: { type: "string" }
|
|
29
|
+
},
|
|
30
|
+
required: ["name", "country", "city", "email"]
|
|
31
|
+
},
|
|
32
|
+
handler: async (args, apiClient) => {
|
|
33
|
+
const response = await apiClient.post('/api/category', null, { params: args });
|
|
34
|
+
return response.data;
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "v1_get_category",
|
|
39
|
+
description: "Get information about a specific category",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
id: { type: "number" }
|
|
44
|
+
},
|
|
45
|
+
required: ["id"]
|
|
46
|
+
},
|
|
47
|
+
handler: async (args, apiClient) => {
|
|
48
|
+
const response = await apiClient.get(`/api/category/${args.id}`);
|
|
49
|
+
return response.data;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "v1_delete_category",
|
|
54
|
+
description: "Delete a specific category",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
id: { type: "number" }
|
|
59
|
+
},
|
|
60
|
+
required: ["id"]
|
|
61
|
+
},
|
|
62
|
+
handler: async (args, apiClient) => {
|
|
63
|
+
const response = await apiClient.delete(`/api/category/${args.id}`);
|
|
64
|
+
return response.data;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { McpTool } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export const incendoTools: McpTool[] = [
|
|
4
|
+
{
|
|
5
|
+
name: "v1_incendo_ping",
|
|
6
|
+
description: "Check Incendo connectivity",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {}
|
|
10
|
+
},
|
|
11
|
+
handler: async (_args, apiClient) => {
|
|
12
|
+
const response = await apiClient.get('/api/incendo/ping');
|
|
13
|
+
return response.data;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "v1_incendo_setup",
|
|
18
|
+
description: "Setup Incendo integration",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
printer_id: { type: "string" },
|
|
23
|
+
status: { type: "number" }
|
|
24
|
+
},
|
|
25
|
+
required: ["printer_id"]
|
|
26
|
+
},
|
|
27
|
+
handler: async (args, apiClient) => {
|
|
28
|
+
const response = await apiClient.post('/api/incendo/setup', null, { params: args });
|
|
29
|
+
return response.data;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "v1_incendo_write",
|
|
34
|
+
description: "Write data to Incendo",
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
id: { type: "number" },
|
|
39
|
+
serial: { type: "string" },
|
|
40
|
+
url: { type: "string" },
|
|
41
|
+
data: { type: "string" },
|
|
42
|
+
token: { type: "string" },
|
|
43
|
+
password: { type: "string" }
|
|
44
|
+
},
|
|
45
|
+
required: ["id", "serial", "url", "data", "token", "password"]
|
|
46
|
+
},
|
|
47
|
+
handler: async (args, apiClient) => {
|
|
48
|
+
const response = await apiClient.post('/api/incendo', null, { params: args });
|
|
49
|
+
return response.data;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "v1_incendo_read",
|
|
54
|
+
description: "Read data from Incendo using serial and token",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
serial: { type: "string" },
|
|
59
|
+
token: { type: "string" }
|
|
60
|
+
},
|
|
61
|
+
required: ["serial", "token"]
|
|
62
|
+
},
|
|
63
|
+
handler: async (args, apiClient) => {
|
|
64
|
+
const response = await apiClient.get(`/api/incendo/read/${args.serial}/${args.token}`);
|
|
65
|
+
return response.data;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { userTools } from './user.js';
|
|
2
|
+
import { categoryTools } from './category.js';
|
|
3
|
+
import { materialTools } from './material.js';
|
|
4
|
+
import { productTools } from './product.js';
|
|
5
|
+
import { unitTools } from './unit.js';
|
|
6
|
+
import { incendoTools } from './incendo.js';
|
|
7
|
+
import { simplyprintTools } from './simplyprint.js';
|
|
8
|
+
import { McpTool } from '../types.js';
|
|
9
|
+
|
|
10
|
+
export const v1Tools: McpTool[] = [
|
|
11
|
+
...userTools,
|
|
12
|
+
...categoryTools,
|
|
13
|
+
...materialTools,
|
|
14
|
+
...productTools,
|
|
15
|
+
...unitTools,
|
|
16
|
+
...incendoTools,
|
|
17
|
+
...simplyprintTools
|
|
18
|
+
];
|