@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.
Files changed (47) hide show
  1. package/README.md +48 -0
  2. package/build/api.js +18 -0
  3. package/build/index.js +11 -0
  4. package/build/server.js +140 -0
  5. package/build/tools/media.js +33 -0
  6. package/build/tools/registry.js +8 -0
  7. package/build/tools/types.js +1 -0
  8. package/build/tools/v1/category.js +65 -0
  9. package/build/tools/v1/incendo.js +66 -0
  10. package/build/tools/v1/index.js +16 -0
  11. package/build/tools/v1/material.js +79 -0
  12. package/build/tools/v1/product.js +85 -0
  13. package/build/tools/v1/simplyprint.js +44 -0
  14. package/build/tools/v1/unit.js +67 -0
  15. package/build/tools/v1/user.js +46 -0
  16. package/build/tools/v2/auth.js +75 -0
  17. package/build/tools/v2/category.js +109 -0
  18. package/build/tools/v2/device.js +97 -0
  19. package/build/tools/v2/index.js +16 -0
  20. package/build/tools/v2/material.js +114 -0
  21. package/build/tools/v2/part.js +102 -0
  22. package/build/tools/v2/product.js +99 -0
  23. package/build/tools/v2/unit.js +86 -0
  24. package/package.json +26 -0
  25. package/src/api.ts +24 -0
  26. package/src/index.ts +13 -0
  27. package/src/server.ts +165 -0
  28. package/src/tools/media.ts +36 -0
  29. package/src/tools/registry.ts +10 -0
  30. package/src/tools/types.ts +12 -0
  31. package/src/tools/v1/category.ts +67 -0
  32. package/src/tools/v1/incendo.ts +68 -0
  33. package/src/tools/v1/index.ts +18 -0
  34. package/src/tools/v1/material.ts +81 -0
  35. package/src/tools/v1/product.ts +87 -0
  36. package/src/tools/v1/simplyprint.ts +46 -0
  37. package/src/tools/v1/unit.ts +69 -0
  38. package/src/tools/v1/user.ts +48 -0
  39. package/src/tools/v2/auth.ts +77 -0
  40. package/src/tools/v2/category.ts +111 -0
  41. package/src/tools/v2/device.ts +99 -0
  42. package/src/tools/v2/index.ts +18 -0
  43. package/src/tools/v2/material.ts +116 -0
  44. package/src/tools/v2/part.ts +104 -0
  45. package/src/tools/v2/product.ts +101 -0
  46. package/src/tools/v2/unit.ts +88 -0
  47. package/tsconfig.json +15 -0
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # whatt.io MCP Server
2
+
3
+ A Model Context Protocol (MCP) server for integrating Language Models with the whatt.io API.
4
+
5
+ ## Installation
6
+
7
+ 1. Install dependencies:
8
+ ```bash
9
+ npm install
10
+ ```
11
+
12
+ 2. Build the server:
13
+ ```bash
14
+ npm run build
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ Set the environment variable `WHATTIO_API_KEY` before running the server or connecting an MCP Client to it.
20
+
21
+ ```bash
22
+ export WHATTIO_API_KEY="your-whatt-io-api-key"
23
+ ```
24
+
25
+ You can also override the base URL (defaults to `https://api.whatt.io/v1`):
26
+ ```bash
27
+ export WHATTIO_API_BASE_URL="https://analytics.whattio.com/api"
28
+ ```
29
+
30
+ ## Running the Server
31
+
32
+ Run the server natively via the CLI interface (stdio):
33
+
34
+ ```bash
35
+ npm start
36
+ ```
37
+
38
+ ### Using Model Context Protocol Inspector
39
+
40
+ To debug and test tools interactively:
41
+
42
+ ```bash
43
+ npx @modelcontextprotocol/inspector node build/index.js
44
+ ```
45
+
46
+ ## Available Tools
47
+
48
+ - `get_whattio_data`: Send GET requests to any whatt.io API endpoint.
package/build/api.js ADDED
@@ -0,0 +1,18 @@
1
+ import axios from 'axios';
2
+ // The base URL for whatt.io API. Can be overridden via environment variables.
3
+ const API_BASE_URL = process.env.WHATTIO_API_BASE_URL || 'https://whatt.io';
4
+ export const createApiClient = () => {
5
+ const apiKey = process.env.WHATTIO_API_KEY;
6
+ const headers = {
7
+ 'Content-Type': 'application/json',
8
+ 'Accept': 'application/json',
9
+ };
10
+ if (apiKey) {
11
+ headers['Authorization'] = `Bearer ${apiKey}`;
12
+ }
13
+ const client = axios.create({
14
+ baseURL: API_BASE_URL,
15
+ headers,
16
+ });
17
+ return client;
18
+ };
package/build/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { server } from "./server.js";
3
+ async function main() {
4
+ const transport = new StdioServerTransport();
5
+ await server.connect(transport);
6
+ console.error("whatt.io MCP Server running on stdio.");
7
+ }
8
+ main().catch((error) => {
9
+ console.error("Fatal error running server:", error);
10
+ process.exit(1);
11
+ });
@@ -0,0 +1,140 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
3
+ import { createApiClient } from './api.js';
4
+ import { allTools } from './tools/registry.js';
5
+ import axios from 'axios';
6
+ // Initialize the API Client natively so it's ready. If the token is missing,
7
+ // endpoints that don't need it will work. Others will fail with 401 as expected.
8
+ const apiClient = createApiClient();
9
+ const server = new Server({
10
+ name: 'whattio-mcp-server',
11
+ version: '2.0.0', // We bumped the version to 2.0.0 to signify V2 tool support
12
+ }, {
13
+ capabilities: {
14
+ tools: {},
15
+ },
16
+ });
17
+ // Register all tools from the registry dynamically
18
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
19
+ return {
20
+ tools: allTools.map((tool) => ({
21
+ name: tool.name,
22
+ description: tool.description,
23
+ inputSchema: tool.inputSchema,
24
+ })),
25
+ };
26
+ });
27
+ // Handle tool execution requests
28
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29
+ const toolName = request.params.name;
30
+ const targetTool = allTools.find((t) => t.name === toolName);
31
+ if (!targetTool) {
32
+ throw new McpError(ErrorCode.MethodNotFound, `Tool not found: ${toolName}`);
33
+ }
34
+ try {
35
+ const args = request.params.arguments || {};
36
+ let result = await targetTool.handler(args, apiClient);
37
+ if (result && result._isNativeMedia) {
38
+ return {
39
+ content: [
40
+ {
41
+ type: result.type,
42
+ data: result.data,
43
+ mimeType: result.mimeType
44
+ }
45
+ ]
46
+ };
47
+ }
48
+ // Recursively normalize relative URLs or raw filenames so Claude's Artifact UIs don't get broken <img> paths
49
+ const cdnBase = 'https://whattio-files.fra1.digitaloceanspaces.com';
50
+ // Helper to format a string into a fully qualified DO Spaces URL
51
+ const tryToFormatUrl = (val, keyName = '') => {
52
+ if (!val)
53
+ return val;
54
+ if (val.startsWith('http'))
55
+ return val; // already absolute
56
+ const isLogo = keyName.toLowerCase().includes('logo');
57
+ const folder = isLogo ? 'team_images' : 'product_photos';
58
+ let cleanVal = val;
59
+ if (cleanVal.startsWith('/storage/'))
60
+ cleanVal = cleanVal.replace('/storage/', '');
61
+ else if (cleanVal.startsWith('storage/'))
62
+ cleanVal = cleanVal.replace('storage/', '');
63
+ else if (cleanVal.startsWith('/'))
64
+ cleanVal = cleanVal.slice(1);
65
+ return `${cdnBase}/${folder}/${cleanVal}`;
66
+ };
67
+ const normalizeUrls = (obj, parentKey = '') => {
68
+ if (typeof obj === 'string') {
69
+ // Broad check: if a loose string looks like an image/document file, format it
70
+ if (/\.(png|jpe?g|gif|webp|pdf)$/i.test(obj)) {
71
+ return tryToFormatUrl(obj, parentKey);
72
+ }
73
+ return obj;
74
+ }
75
+ if (Array.isArray(obj)) {
76
+ return obj.map(item => normalizeUrls(item, parentKey));
77
+ }
78
+ if (obj !== null && typeof obj === 'object') {
79
+ const newObj = {};
80
+ for (const key in obj) {
81
+ let val = obj[key];
82
+ if (['logo', 'image', 'video', 'video_url', 'pdf', 'document'].includes(key.toLowerCase())) {
83
+ if (typeof val === 'string') {
84
+ newObj[key] = tryToFormatUrl(val, key);
85
+ continue;
86
+ }
87
+ }
88
+ // The API returns 'images' as an array of strings. Handling that case explicitly:
89
+ if (key.toLowerCase() === 'images' && Array.isArray(val)) {
90
+ newObj[key] = val.map(item => (typeof item === 'string' ? tryToFormatUrl(item, key) : normalizeUrls(item, key)));
91
+ continue;
92
+ }
93
+ newObj[key] = normalizeUrls(val, key);
94
+ }
95
+ return newObj;
96
+ }
97
+ return obj;
98
+ };
99
+ result = normalizeUrls(result);
100
+ const stringifiedResult = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
101
+ 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 `![Alt](url)` 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.";
102
+ return {
103
+ content: [
104
+ {
105
+ type: 'text',
106
+ text: stringifiedResult + mediaHint,
107
+ },
108
+ ],
109
+ };
110
+ }
111
+ catch (error) {
112
+ if (axios.isAxiosError(error)) {
113
+ const statusCode = error.response?.status || 500;
114
+ const responseData = error.response?.data ? JSON.stringify(error.response.data) : error.message;
115
+ let extraHint = "";
116
+ if (statusCode === 404 || statusCode === 403) {
117
+ 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.";
118
+ }
119
+ return {
120
+ isError: true,
121
+ content: [
122
+ {
123
+ type: 'text',
124
+ text: `Whatt.io API Error (${statusCode}): ${responseData}${extraHint}`,
125
+ },
126
+ ],
127
+ };
128
+ }
129
+ return {
130
+ isError: true,
131
+ content: [
132
+ {
133
+ type: 'text',
134
+ text: `Unexpected Error: ${error.message || String(error)}`,
135
+ },
136
+ ],
137
+ };
138
+ }
139
+ });
140
+ export { server };
@@ -0,0 +1,33 @@
1
+ import axios from 'axios';
2
+ export const mediaTools = [
3
+ {
4
+ name: "fetch_media",
5
+ 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.",
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ url: { type: "string", description: "The absolute URL of the image to fetch" }
10
+ },
11
+ required: ["url"]
12
+ },
13
+ handler: async (args, apiClient) => {
14
+ // We don't necessarily use the apiClient since the URL could be absolute to S3 or elsewhere,
15
+ // but we will use axios natively to fetch the arraybuffer.
16
+ try {
17
+ const response = await axios.get(args.url, { responseType: 'arraybuffer' });
18
+ const mimeType = response.headers['content-type'] || 'image/jpeg';
19
+ const base64Data = Buffer.from(response.data, 'binary').toString('base64');
20
+ // Return in native MCP image format so Claude renders it immediately without a privacy button
21
+ return {
22
+ _isNativeMedia: true,
23
+ type: 'image',
24
+ data: base64Data,
25
+ mimeType: mimeType
26
+ };
27
+ }
28
+ catch (error) {
29
+ throw new Error(`Failed to fetch media from ${args.url}: ${error.message}`);
30
+ }
31
+ }
32
+ }
33
+ ];
@@ -0,0 +1,8 @@
1
+ import { v1Tools } from './v1/index.js';
2
+ import { v2Tools } from './v2/index.js';
3
+ import { mediaTools } from './media.js';
4
+ export const allTools = [
5
+ ...v1Tools,
6
+ ...v2Tools,
7
+ ...mediaTools
8
+ ];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,65 @@
1
+ export const categoryTools = [
2
+ {
3
+ name: "v1_list_categories",
4
+ description: "Show all categories within a team",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {}
8
+ },
9
+ handler: async (_args, apiClient) => {
10
+ const response = await apiClient.get('/api/categories');
11
+ return response.data;
12
+ }
13
+ },
14
+ {
15
+ name: "v1_create_category",
16
+ description: "Add a new category",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ name: { type: "string" },
21
+ country: { type: "string" },
22
+ city: { type: "string" },
23
+ email: { type: "string" },
24
+ phone: { type: "string" },
25
+ website: { type: "string" },
26
+ ai_description: { type: "string" }
27
+ },
28
+ required: ["name", "country", "city", "email"]
29
+ },
30
+ handler: async (args, apiClient) => {
31
+ const response = await apiClient.post('/api/category', null, { params: args });
32
+ return response.data;
33
+ }
34
+ },
35
+ {
36
+ name: "v1_get_category",
37
+ description: "Get information about a specific category",
38
+ inputSchema: {
39
+ type: "object",
40
+ properties: {
41
+ id: { type: "number" }
42
+ },
43
+ required: ["id"]
44
+ },
45
+ handler: async (args, apiClient) => {
46
+ const response = await apiClient.get(`/api/category/${args.id}`);
47
+ return response.data;
48
+ }
49
+ },
50
+ {
51
+ name: "v1_delete_category",
52
+ description: "Delete a specific category",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ id: { type: "number" }
57
+ },
58
+ required: ["id"]
59
+ },
60
+ handler: async (args, apiClient) => {
61
+ const response = await apiClient.delete(`/api/category/${args.id}`);
62
+ return response.data;
63
+ }
64
+ }
65
+ ];
@@ -0,0 +1,66 @@
1
+ export const incendoTools = [
2
+ {
3
+ name: "v1_incendo_ping",
4
+ description: "Check Incendo connectivity",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {}
8
+ },
9
+ handler: async (_args, apiClient) => {
10
+ const response = await apiClient.get('/api/incendo/ping');
11
+ return response.data;
12
+ }
13
+ },
14
+ {
15
+ name: "v1_incendo_setup",
16
+ description: "Setup Incendo integration",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ printer_id: { type: "string" },
21
+ status: { type: "number" }
22
+ },
23
+ required: ["printer_id"]
24
+ },
25
+ handler: async (args, apiClient) => {
26
+ const response = await apiClient.post('/api/incendo/setup', null, { params: args });
27
+ return response.data;
28
+ }
29
+ },
30
+ {
31
+ name: "v1_incendo_write",
32
+ description: "Write data to Incendo",
33
+ inputSchema: {
34
+ type: "object",
35
+ properties: {
36
+ id: { type: "number" },
37
+ serial: { type: "string" },
38
+ url: { type: "string" },
39
+ data: { type: "string" },
40
+ token: { type: "string" },
41
+ password: { type: "string" }
42
+ },
43
+ required: ["id", "serial", "url", "data", "token", "password"]
44
+ },
45
+ handler: async (args, apiClient) => {
46
+ const response = await apiClient.post('/api/incendo', null, { params: args });
47
+ return response.data;
48
+ }
49
+ },
50
+ {
51
+ name: "v1_incendo_read",
52
+ description: "Read data from Incendo using serial and token",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ serial: { type: "string" },
57
+ token: { type: "string" }
58
+ },
59
+ required: ["serial", "token"]
60
+ },
61
+ handler: async (args, apiClient) => {
62
+ const response = await apiClient.get(`/api/incendo/read/${args.serial}/${args.token}`);
63
+ return response.data;
64
+ }
65
+ }
66
+ ];
@@ -0,0 +1,16 @@
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
+ export const v1Tools = [
9
+ ...userTools,
10
+ ...categoryTools,
11
+ ...materialTools,
12
+ ...productTools,
13
+ ...unitTools,
14
+ ...incendoTools,
15
+ ...simplyprintTools
16
+ ];
@@ -0,0 +1,79 @@
1
+ export const materialTools = [
2
+ {
3
+ name: "v1_list_materials",
4
+ description: "Show all materials within a team",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {}
8
+ },
9
+ handler: async (_args, apiClient) => {
10
+ const response = await apiClient.get('/api/materials');
11
+ return response.data;
12
+ }
13
+ },
14
+ {
15
+ name: "v1_create_material",
16
+ description: "Add a new material",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ brand: { type: "string" },
21
+ name: { type: "string" },
22
+ code: { type: "string" },
23
+ color_code: { type: "number" },
24
+ data_sheet: { type: "string" },
25
+ safety_sheet: { type: "string" },
26
+ origin_type: { type: "string" },
27
+ origin_country: { type: "string" },
28
+ waste_category: { type: "string" },
29
+ resin_id: { type: "string" },
30
+ chem_name: { type: "string" },
31
+ recycled: { type: "boolean" },
32
+ bioderived: { type: "boolean" },
33
+ biodegradable: { type: "string" },
34
+ biodegrade_comment: { type: "string" },
35
+ recyclable: { type: "boolean" },
36
+ recycle_info: { type: "string" },
37
+ recycle_url: { type: "string" },
38
+ recycling_img: { type: "string" },
39
+ bio_derived: { type: "string" },
40
+ buy_url: { type: "string" }
41
+ },
42
+ required: ["brand", "name", "origin_country"]
43
+ },
44
+ handler: async (args, apiClient) => {
45
+ const response = await apiClient.post('/api/material', null, { params: args });
46
+ return response.data;
47
+ }
48
+ },
49
+ {
50
+ name: "v1_get_material",
51
+ description: "Get information about a specific material",
52
+ inputSchema: {
53
+ type: "object",
54
+ properties: {
55
+ id: { type: "number" }
56
+ },
57
+ required: ["id"]
58
+ },
59
+ handler: async (args, apiClient) => {
60
+ const response = await apiClient.get(`/api/material/${args.id}`);
61
+ return response.data;
62
+ }
63
+ },
64
+ {
65
+ name: "v1_delete_material",
66
+ description: "Delete a specific material",
67
+ inputSchema: {
68
+ type: "object",
69
+ properties: {
70
+ id: { type: "number" }
71
+ },
72
+ required: ["id"]
73
+ },
74
+ handler: async (args, apiClient) => {
75
+ const response = await apiClient.delete(`/api/material/${args.id}`);
76
+ return response.data;
77
+ }
78
+ }
79
+ ];
@@ -0,0 +1,85 @@
1
+ export const productTools = [
2
+ {
3
+ name: "v1_list_products",
4
+ description: "Show all products within a team. Also use this tool whenever the user mentions 'DPP', 'Digital Product Passports', 'digital twins', or wants to search their whatt.io product catalog.",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {}
8
+ },
9
+ handler: async (_args, apiClient) => {
10
+ const response = await apiClient.get('/api/products');
11
+ return response.data;
12
+ }
13
+ },
14
+ {
15
+ name: "v1_create_product",
16
+ description: "Add a new product",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ name: { type: "string" },
21
+ product_number: { type: "string" },
22
+ version: { type: "string" },
23
+ description: { type: "string" },
24
+ gtin: { type: "string" },
25
+ image: { type: "string" },
26
+ video: { type: "string" },
27
+ buy_url: { type: "string" },
28
+ category_id: { type: "number" },
29
+ ai_description: { type: "string" },
30
+ type: { type: "string" },
31
+ public: { type: "number" }
32
+ },
33
+ required: ["name", "product_number", "description"]
34
+ },
35
+ handler: async (args, apiClient) => {
36
+ const response = await apiClient.post('/api/product', null, { params: args });
37
+ return response.data;
38
+ }
39
+ },
40
+ {
41
+ name: "v1_get_product",
42
+ description: "Get information about a specific product",
43
+ inputSchema: {
44
+ type: "object",
45
+ properties: {
46
+ id: { type: "number" }
47
+ },
48
+ required: ["id"]
49
+ },
50
+ handler: async (args, apiClient) => {
51
+ const response = await apiClient.get(`/api/product/${args.id}`);
52
+ return response.data;
53
+ }
54
+ },
55
+ {
56
+ name: "v1_get_ext_product",
57
+ description: "Get information about a specific product with extended format",
58
+ inputSchema: {
59
+ type: "object",
60
+ properties: {
61
+ id: { type: "number" }
62
+ },
63
+ required: ["id"]
64
+ },
65
+ handler: async (args, apiClient) => {
66
+ const response = await apiClient.get(`/api/product/ext/${args.id}`);
67
+ return response.data;
68
+ }
69
+ },
70
+ {
71
+ name: "v1_delete_product",
72
+ description: "Delete a specific product",
73
+ inputSchema: {
74
+ type: "object",
75
+ properties: {
76
+ id: { type: "number" }
77
+ },
78
+ required: ["id"]
79
+ },
80
+ handler: async (args, apiClient) => {
81
+ const response = await apiClient.delete(`/api/product/${args.id}`);
82
+ return response.data;
83
+ }
84
+ }
85
+ ];
@@ -0,0 +1,44 @@
1
+ export const simplyprintTools = [
2
+ {
3
+ name: "v1_simplyprint_printers",
4
+ description: "Fetch SimplyPrint printers associated with user",
5
+ inputSchema: {
6
+ type: "object",
7
+ properties: {}
8
+ },
9
+ handler: async (_args, apiClient) => {
10
+ const response = await apiClient.get('/api/simplyprint/printers');
11
+ return response.data;
12
+ }
13
+ },
14
+ {
15
+ name: "v1_simplyprint_jobs",
16
+ description: "Fetch SimplyPrint print jobs for a specific printer",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ printer_id: { type: "string", description: "Printer ID" }
21
+ },
22
+ required: ["printer_id"]
23
+ },
24
+ handler: async (args, apiClient) => {
25
+ const response = await apiClient.get(`/api/simplyprint/jobs/${args.printer_id}`);
26
+ return response.data;
27
+ }
28
+ },
29
+ {
30
+ name: "v1_simplyprint_filament",
31
+ description: "Fetch remaining filament info for a specific printer",
32
+ inputSchema: {
33
+ type: "object",
34
+ properties: {
35
+ printer_id: { type: "string", description: "Printer ID" }
36
+ },
37
+ required: ["printer_id"]
38
+ },
39
+ handler: async (args, apiClient) => {
40
+ const response = await apiClient.get(`/api/getFilament/filament/${args.printer_id}`);
41
+ return response.data;
42
+ }
43
+ }
44
+ ];