viucraft-mcp 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BStorm IT
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # viucraft-mcp
2
+
3
+ VIUCraft MCP server — use VIUCraft image operations as AI tools via the [Model Context Protocol](https://modelcontextprotocol.io/).
4
+
5
+ This package is a **proxy**: it forwards tool calls to the VIUCraft HTTP API. No image processing happens locally.
6
+
7
+ ## Quick Start (Claude Desktop)
8
+
9
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "viucraft": {
15
+ "command": "npx",
16
+ "args": ["viucraft-mcp"],
17
+ "env": {
18
+ "VIUCRAFT_API_KEY": "vc_live_..."
19
+ }
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ ## Environment Variables
26
+
27
+ | Variable | Required | Default |
28
+ |----------|----------|---------|
29
+ | `VIUCRAFT_API_KEY` | yes | — |
30
+ | `VIUCRAFT_API_URL` | no | `https://api.viucraft.com` |
31
+
32
+ ## Available Tools
33
+
34
+ - `resize_image` — resize to width/height with optional fit mode
35
+ - `crop_image` — crop with optional gravity or offset
36
+ - `convert_format` — convert to jpg/png/webp/avif/gif with optional quality
37
+ - `apply_filter` — apply blur/grayscale/sepia/sharpen/invert/vignette/pixelate
38
+ - `transform_image` — arbitrary operation pipeline or named preset
39
+
40
+ See the [full MCP reference](https://github.com/BStorm-IT/viucraft/blob/main/docs/mcp.md) for complete tool documentation.
41
+
42
+ ## Build
43
+
44
+ ```bash
45
+ npm install
46
+ npm run build
47
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ import { handleToolCall, tools } from "./tools.js";
6
+ const apiKey = process.env.VIUCRAFT_API_KEY;
7
+ const apiUrl = process.env.VIUCRAFT_API_URL ?? "https://api.viucraft.com";
8
+ if (!apiKey) {
9
+ process.stderr.write("VIUCRAFT_API_KEY environment variable is required\n");
10
+ process.exit(1);
11
+ }
12
+ const server = new Server({ name: "viucraft", version: "1.0.0" }, { capabilities: { tools: {} } });
13
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
14
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15
+ const args = (request.params.arguments ?? {});
16
+ const result = await handleToolCall(request.params.name, args, apiKey, apiUrl);
17
+ // @modelcontextprotocol/sdk 1.29 tightened the CallTool handler's return type for its
18
+ // experimental "tasks" API. handleToolCall returns a valid { content, isError? } MCP
19
+ // result; cast to the base ServerResult to satisfy the new union (runtime unchanged).
20
+ return result;
21
+ });
22
+ const transport = new StdioServerTransport();
23
+ await server.connect(transport);
package/dist/proxy.js ADDED
@@ -0,0 +1,66 @@
1
+ // Session-aware JSON-RPC client for the VIUCraft `/mcp` endpoint.
2
+ //
3
+ // The endpoint is session-based: an `initialize` call (authenticated with
4
+ // `Authorization: Bearer <api key>`) returns an `X-MCP-Session-Id` response header that
5
+ // must accompany every subsequent `tools/call`. The proxy establishes that session lazily
6
+ // and re-establishes it if it expires (sessions have a ~30-minute TTL on the server).
7
+ const SESSION_EXPIRED = -32001;
8
+ let cachedSessionId = null;
9
+ async function rpc(apiUrl, apiKey, body, sessionId) {
10
+ const headers = {
11
+ // The /mcp endpoint authenticates via Authorization: Bearer (NOT X-API-Key).
12
+ "Authorization": `Bearer ${apiKey}`,
13
+ "Content-Type": "application/json",
14
+ };
15
+ if (sessionId) {
16
+ headers["X-MCP-Session-Id"] = sessionId;
17
+ }
18
+ const response = await fetch(`${apiUrl}/mcp`, {
19
+ body: JSON.stringify(body),
20
+ headers,
21
+ method: "POST",
22
+ });
23
+ const returnedSession = response.headers.get("x-mcp-session-id");
24
+ const json = (await response.json());
25
+ return { json, sessionId: returnedSession };
26
+ }
27
+ async function initializeSession(apiUrl, apiKey) {
28
+ const { json, sessionId } = await rpc(apiUrl, apiKey, {
29
+ id: 0,
30
+ jsonrpc: "2.0",
31
+ method: "initialize",
32
+ params: {
33
+ capabilities: {},
34
+ clientInfo: { name: "viucraft-mcp-proxy", version: "1.0.0" },
35
+ protocolVersion: "2024-11-05",
36
+ },
37
+ }, null);
38
+ if (json.error) {
39
+ throw new Error(`MCP initialize failed: ${json.error.message}`);
40
+ }
41
+ if (!sessionId) {
42
+ throw new Error("MCP initialize did not return a session id");
43
+ }
44
+ return sessionId;
45
+ }
46
+ /**
47
+ * Call a tool on the backend, transparently establishing (and refreshing) the MCP session.
48
+ */
49
+ export async function callTool(name, args, apiKey, apiUrl) {
50
+ if (!cachedSessionId) {
51
+ cachedSessionId = await initializeSession(apiUrl, apiKey);
52
+ }
53
+ const toolCall = {
54
+ id: 1,
55
+ jsonrpc: "2.0",
56
+ method: "tools/call",
57
+ params: { arguments: args, name },
58
+ };
59
+ let { json } = await rpc(apiUrl, apiKey, toolCall, cachedSessionId);
60
+ // The session may have expired between calls — re-establish it once and retry.
61
+ if (json.error?.code === SESSION_EXPIRED) {
62
+ cachedSessionId = await initializeSession(apiUrl, apiKey);
63
+ ({ json } = await rpc(apiUrl, apiKey, toolCall, cachedSessionId));
64
+ }
65
+ return json;
66
+ }
package/dist/tools.js ADDED
@@ -0,0 +1,239 @@
1
+ export const tools = [
2
+ {
3
+ description: "Returns a VIUCraft URL that resizes an image to the specified dimensions.",
4
+ inputSchema: {
5
+ properties: {
6
+ fit: {
7
+ description: "Fit mode for resizing.",
8
+ enum: ["contain", "cover", "fill", "inside", "outside"],
9
+ type: "string",
10
+ },
11
+ height: {
12
+ description: "Target height in pixels.",
13
+ minimum: 1,
14
+ type: "integer",
15
+ },
16
+ image_id: {
17
+ description: "The UUID of the image to resize.",
18
+ type: "string",
19
+ },
20
+ width: {
21
+ description: "Target width in pixels.",
22
+ minimum: 1,
23
+ type: "integer",
24
+ },
25
+ },
26
+ required: ["image_id"],
27
+ type: "object",
28
+ },
29
+ name: "resize_image",
30
+ },
31
+ {
32
+ description: "Returns a VIUCraft URL that crops an image to the specified dimensions and optional gravity or offset.",
33
+ inputSchema: {
34
+ properties: {
35
+ gravity: {
36
+ description: "Gravity / focal point for the crop.",
37
+ enum: ["center", "east", "north", "smart", "south", "west"],
38
+ type: "string",
39
+ },
40
+ height: {
41
+ description: "Crop height in pixels.",
42
+ minimum: 1,
43
+ type: "integer",
44
+ },
45
+ image_id: {
46
+ description: "The UUID of the image to crop.",
47
+ type: "string",
48
+ },
49
+ width: {
50
+ description: "Crop width in pixels.",
51
+ minimum: 1,
52
+ type: "integer",
53
+ },
54
+ x: {
55
+ description: "Horizontal offset in pixels (used with manual crop).",
56
+ minimum: 0,
57
+ type: "integer",
58
+ },
59
+ y: {
60
+ description: "Vertical offset in pixels (used with manual crop).",
61
+ minimum: 0,
62
+ type: "integer",
63
+ },
64
+ },
65
+ required: ["height", "image_id", "width"],
66
+ type: "object",
67
+ },
68
+ name: "crop_image",
69
+ },
70
+ {
71
+ description: "Returns a VIUCraft URL that converts an image to the specified format and optional quality.",
72
+ inputSchema: {
73
+ properties: {
74
+ format: {
75
+ description: "Target output format.",
76
+ enum: ["avif", "gif", "jpg", "png", "webp"],
77
+ type: "string",
78
+ },
79
+ image_id: {
80
+ description: "The UUID of the image to convert.",
81
+ type: "string",
82
+ },
83
+ quality: {
84
+ description: "Output quality (1–100). Applicable to jpg, webp, and avif.",
85
+ maximum: 100,
86
+ minimum: 1,
87
+ type: "integer",
88
+ },
89
+ },
90
+ required: ["format", "image_id"],
91
+ type: "object",
92
+ },
93
+ name: "convert_format",
94
+ },
95
+ {
96
+ description: "Returns a VIUCraft URL that applies a visual filter effect to an image.",
97
+ inputSchema: {
98
+ properties: {
99
+ filter: {
100
+ description: "The visual filter to apply.",
101
+ enum: ["blur", "grayscale", "invert", "pixelate", "sepia", "sharpen", "vignette"],
102
+ type: "string",
103
+ },
104
+ image_id: {
105
+ description: "The UUID of the image to filter.",
106
+ type: "string",
107
+ },
108
+ intensity: {
109
+ description: "Filter intensity between 0 (none) and 1 (full). Defaults to 1.",
110
+ maximum: 1,
111
+ minimum: 0,
112
+ type: "number",
113
+ },
114
+ },
115
+ required: ["filter", "image_id"],
116
+ type: "object",
117
+ },
118
+ name: "apply_filter",
119
+ },
120
+ {
121
+ description: "Builds a VIUCraft processing URL for an image by applying one or more operations. Supports arbitrary operation pipelines or a named preset.",
122
+ inputSchema: {
123
+ properties: {
124
+ image_id: {
125
+ description: "The UUID of the image to transform.",
126
+ type: "string",
127
+ },
128
+ operations: {
129
+ description: "Array of operations to apply, each with a name and params map.",
130
+ items: {
131
+ properties: {
132
+ name: {
133
+ description: "Operation name (e.g. resize, crop, blur).",
134
+ type: "string",
135
+ },
136
+ params: {
137
+ additionalProperties: true,
138
+ description: "Key-value parameters for the operation.",
139
+ type: "object",
140
+ },
141
+ },
142
+ required: ["name"],
143
+ type: "object",
144
+ },
145
+ type: "array",
146
+ },
147
+ output_format: {
148
+ description: "Desired output format (jpg, png, webp, avif, gif). Defaults to webp.",
149
+ enum: ["avif", "gif", "jpg", "png", "webp"],
150
+ type: "string",
151
+ },
152
+ preset: {
153
+ description: "Named preset to apply (e.g. thumbnail, social, banner).",
154
+ type: "string",
155
+ },
156
+ },
157
+ required: ["image_id"],
158
+ type: "object",
159
+ },
160
+ name: "transform_image",
161
+ },
162
+ {
163
+ description: "Returns the authenticated account: plan, subdomain, account id, the canonical image URL base " +
164
+ "to build delivery URLs against, current usage/quota, and the full capability set. Call this " +
165
+ "first to learn your subdomain so you can turn relative transform paths into absolute URLs.",
166
+ inputSchema: {
167
+ properties: {},
168
+ required: [],
169
+ type: "object",
170
+ },
171
+ name: "whoami",
172
+ },
173
+ {
174
+ description: "Uploads an image (passed as base64-encoded data) and returns its permanent image_id plus " +
175
+ "metadata. Use the returned image_id with the transform tools to build delivery URLs. " +
176
+ "Identical re-uploads return the existing image.",
177
+ inputSchema: {
178
+ properties: {
179
+ data: {
180
+ description: "The image file contents, base64-encoded.",
181
+ type: "string",
182
+ },
183
+ filename: {
184
+ description: 'Original filename including extension, e.g. "photo.png".',
185
+ type: "string",
186
+ },
187
+ folder: {
188
+ description: 'Optional virtual folder to store the image in (defaults to "/").',
189
+ type: "string",
190
+ },
191
+ },
192
+ required: ["data", "filename"],
193
+ type: "object",
194
+ },
195
+ name: "upload_image",
196
+ },
197
+ {
198
+ description: "Permanently deletes one of your images by its UUID. Frees the storage quota and fires the " +
199
+ "image.deleted webhook. This cannot be undone.",
200
+ inputSchema: {
201
+ properties: {
202
+ image_id: {
203
+ description: "The UUID of the image to delete.",
204
+ type: "string",
205
+ },
206
+ },
207
+ required: ["image_id"],
208
+ type: "object",
209
+ },
210
+ name: "delete_image",
211
+ },
212
+ ];
213
+ import { callTool } from "./proxy.js";
214
+ export async function handleToolCall(name, args, apiKey, apiUrl) {
215
+ try {
216
+ const result = await callTool(name, args, apiKey, apiUrl);
217
+ if (result.error) {
218
+ return {
219
+ content: [{ text: `VIUCraft error: ${result.error.message}`, type: "text" }],
220
+ isError: true,
221
+ };
222
+ }
223
+ if (result.result?.content) {
224
+ return {
225
+ content: result.result.content,
226
+ isError: result.result.isError,
227
+ };
228
+ }
229
+ return {
230
+ content: [{ text: JSON.stringify(result), type: "text" }],
231
+ };
232
+ }
233
+ catch (err) {
234
+ return {
235
+ content: [{ text: `Error calling VIUCraft API: ${String(err)}`, type: "text" }],
236
+ isError: true,
237
+ };
238
+ }
239
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "viucraft-mcp",
3
+ "version": "1.0.0",
4
+ "description": "VIUCraft MCP server — use VIUCraft image operations as AI tools",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "BStorm IT",
8
+ "homepage": "https://github.com/BStorm-IT/viucraft-mcp#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/BStorm-IT/viucraft-mcp.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/BStorm-IT/viucraft-mcp/issues"
15
+ },
16
+ "keywords": [
17
+ "viucraft",
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "image-processing",
21
+ "ai-tools"
22
+ ],
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "bin": {"viucraft-mcp": "./dist/index.js"},
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc",
32
+ "typecheck": "tsc --noEmit",
33
+ "start": "node dist/index.js",
34
+ "prepublishOnly": "npm run build"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.0.0",
44
+ "typescript": "^5.3.0"
45
+ }
46
+ }