@smallpict/mcp 0.0.2
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 +72 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @smallpict/mcp
|
|
2
|
+
|
|
3
|
+
Official **Model Context Protocol (MCP)** server for [SmallPict](https://smallpict.app), the high-performance image optimization and AVIF/WebP transcoding platform.
|
|
4
|
+
|
|
5
|
+
This MCP server equips AI coding assistants (such as **Claude Desktop**, **Cursor**, **Windsurf**, and **Antigravity**) with direct capabilities to compress, transcode, and maintain web image assets.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Capabilities / Tools
|
|
10
|
+
|
|
11
|
+
1. **`smallpict_optimize_image`**:
|
|
12
|
+
- Compresses local image files or remote URLs to **WebP** or **AVIF**.
|
|
13
|
+
- Supports **Lossy** (`quality: 1-100`) and **Lossless** (`lossless: true`) modes.
|
|
14
|
+
- Downscaling constraints: `maxDimension` (longest edge), `maxWidth`, and `maxHeight`.
|
|
15
|
+
- Automatically writes the optimized image back to the workspace if `outputPath` is provided.
|
|
16
|
+
- Outputs full telemetry: original size, compressed size, and percentage saved (up to 85%).
|
|
17
|
+
2. **`smallpict_get_quota`**:
|
|
18
|
+
- Inspects the account's active plan, processed bytes, and remaining monthly quota.
|
|
19
|
+
3. **`smallpict_purge_cdn_cache`**:
|
|
20
|
+
- Invalidates cached asset URLs on Cloudflare Edge CDN PoPs globally.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation & Setup
|
|
25
|
+
|
|
26
|
+
### 1. Build from Source
|
|
27
|
+
```bash
|
|
28
|
+
cd /Users/ariefjr/Documents/hermoves-id/wordpres-plugins/smallpict-mcp
|
|
29
|
+
npm install
|
|
30
|
+
npm run build
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Configure in Claude Desktop
|
|
34
|
+
Add the following to your `claude_desktop_config.json`:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"smallpict": {
|
|
40
|
+
"command": "node",
|
|
41
|
+
"args": ["/Users/ariefjr/Documents/hermoves-id/wordpres-plugins/smallpict-mcp/dist/index.js"],
|
|
42
|
+
"env": {
|
|
43
|
+
"SMALLPICT_API_KEY": "sp_sdk_live_...",
|
|
44
|
+
"SMALLPICT_SECRET_KEY": "sp_sec_..."
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Configure in Cursor (`~/.cursor/mcp.json`)
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"smallpict": {
|
|
56
|
+
"command": "node",
|
|
57
|
+
"args": ["/Users/ariefjr/Documents/hermoves-id/wordpres-plugins/smallpict-mcp/dist/index.js"],
|
|
58
|
+
"env": {
|
|
59
|
+
"SMALLPICT_API_KEY": "sp_sdk_live_...",
|
|
60
|
+
"SMALLPICT_SECRET_KEY": "sp_sec_..."
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Environment Variables
|
|
70
|
+
- `SMALLPICT_API_KEY` (Required): Your API key (`sp_sdk_...` or sandbox evaluation key `sp_test_...`).
|
|
71
|
+
- `SMALLPICT_SECRET_KEY` (Required for HMAC): Your API secret key.
|
|
72
|
+
- `SMALLPICT_BASE_URL` (Optional): API endpoint URL (default: `https://api.smallpict.app`).
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
|
|
7
|
+
// src/tools/optimize.ts
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import fs from "fs/promises";
|
|
10
|
+
import path from "path";
|
|
11
|
+
|
|
12
|
+
// src/client.ts
|
|
13
|
+
import { SmallPictClient } from "@smallpict/sdk";
|
|
14
|
+
var clientInstance = null;
|
|
15
|
+
function getSmallPictClient() {
|
|
16
|
+
if (clientInstance) {
|
|
17
|
+
return clientInstance;
|
|
18
|
+
}
|
|
19
|
+
const apiKey = process.env.SMALLPICT_API_KEY;
|
|
20
|
+
const secretKey = process.env.SMALLPICT_SECRET_KEY;
|
|
21
|
+
const baseUrl = process.env.SMALLPICT_BASE_URL || "https://api.smallpict.app";
|
|
22
|
+
if (!apiKey) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"Missing required environment variable SMALLPICT_API_KEY. Please provide SMALLPICT_API_KEY in MCP server configuration."
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
clientInstance = new SmallPictClient({
|
|
28
|
+
apiKey,
|
|
29
|
+
secretKey,
|
|
30
|
+
baseUrl
|
|
31
|
+
});
|
|
32
|
+
return clientInstance;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/tools/optimize.ts
|
|
36
|
+
var optimizeSchema = {
|
|
37
|
+
filePath: z.string().describe("Local file path or remote image URL (http/https) to optimize"),
|
|
38
|
+
format: z.enum(["webp", "avif", "auto"]).default("webp").describe("Target image format (webp, avif, or auto)"),
|
|
39
|
+
quality: z.number().min(1).max(100).default(80).optional().describe("Compression quality (1-100, default: 80)"),
|
|
40
|
+
maxDimension: z.number().positive().optional().describe("Optional maximum bounding dimension (longest edge) in pixels for image downscaling"),
|
|
41
|
+
maxWidth: z.number().positive().optional().describe("Optional maximum width in pixels for image resizing"),
|
|
42
|
+
maxHeight: z.number().positive().optional().describe("Optional maximum height in pixels for image resizing"),
|
|
43
|
+
lossless: z.boolean().default(false).optional().describe("Enable lossless compression mode (pixel-perfect fidelity, ignores quality)"),
|
|
44
|
+
outputPath: z.string().optional().describe("Optional local path where the optimized image should be saved")
|
|
45
|
+
};
|
|
46
|
+
function detectMimeType(filePath) {
|
|
47
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
48
|
+
switch (ext) {
|
|
49
|
+
case ".png":
|
|
50
|
+
return "image/png";
|
|
51
|
+
case ".webp":
|
|
52
|
+
return "image/webp";
|
|
53
|
+
case ".avif":
|
|
54
|
+
return "image/avif";
|
|
55
|
+
case ".gif":
|
|
56
|
+
return "image/gif";
|
|
57
|
+
case ".jpg":
|
|
58
|
+
case ".jpeg":
|
|
59
|
+
default:
|
|
60
|
+
return "image/jpeg";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function handleOptimize(args) {
|
|
64
|
+
const client = getSmallPictClient();
|
|
65
|
+
const format = args.format || "webp";
|
|
66
|
+
const quality = args.quality ?? 80;
|
|
67
|
+
const lossless = args.lossless ?? false;
|
|
68
|
+
console.error(
|
|
69
|
+
`[smallpict-mcp] Optimizing image: ${args.filePath} -> ${format} (q=${quality}, lossless=${lossless}${args.maxDimension ? `, maxDim=${args.maxDimension}` : ""})`
|
|
70
|
+
);
|
|
71
|
+
let isRemote = args.filePath.startsWith("http://") || args.filePath.startsWith("https://");
|
|
72
|
+
let source;
|
|
73
|
+
let filename = path.basename(args.filePath);
|
|
74
|
+
let mimeType = detectMimeType(args.filePath);
|
|
75
|
+
if (isRemote) {
|
|
76
|
+
source = args.filePath;
|
|
77
|
+
} else {
|
|
78
|
+
const resolvedPath = path.resolve(process.cwd(), args.filePath);
|
|
79
|
+
const fileBuffer = await fs.readFile(resolvedPath);
|
|
80
|
+
source = new Uint8Array(fileBuffer);
|
|
81
|
+
filename = path.basename(resolvedPath);
|
|
82
|
+
mimeType = detectMimeType(resolvedPath);
|
|
83
|
+
}
|
|
84
|
+
const result = await client.optimize(source, {
|
|
85
|
+
filename,
|
|
86
|
+
mimeType,
|
|
87
|
+
format,
|
|
88
|
+
quality,
|
|
89
|
+
maxWidth: args.maxWidth,
|
|
90
|
+
maxHeight: args.maxHeight,
|
|
91
|
+
lossless,
|
|
92
|
+
...args.maxDimension ? { maxDimension: args.maxDimension } : {}
|
|
93
|
+
});
|
|
94
|
+
let savedMessage = "";
|
|
95
|
+
if (args.outputPath && result.url) {
|
|
96
|
+
const targetPath = path.resolve(process.cwd(), args.outputPath);
|
|
97
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
98
|
+
console.error(`[smallpict-mcp] Downloading optimized image from ${result.url} to ${targetPath}`);
|
|
99
|
+
const res = await fetch(result.url);
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
throw new Error(`Failed to download optimized asset from ${result.url}: ${res.statusText}`);
|
|
102
|
+
}
|
|
103
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
104
|
+
await fs.writeFile(targetPath, Buffer.from(arrayBuffer));
|
|
105
|
+
savedMessage = `
|
|
106
|
+
- **Saved to**: \`${targetPath}\``;
|
|
107
|
+
}
|
|
108
|
+
const formattedOriginal = (result.originalSize / 1024).toFixed(1) + " KB";
|
|
109
|
+
const formattedCompressed = (result.compressedSize / 1024).toFixed(1) + " KB";
|
|
110
|
+
return {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: `### \u2705 SmallPict Image Optimization Succeeded
|
|
115
|
+
- **File**: \`${filename}\`
|
|
116
|
+
- **Output Format**: \`${result.format}\`
|
|
117
|
+
- **Compression Mode**: \`${lossless ? "Lossless (Pixel-Perfect)" : `Lossy (q=${quality})`}\`${args.maxDimension ? `
|
|
118
|
+
- **Max Dimension Constraint**: \`${args.maxDimension}px\`` : ""}
|
|
119
|
+
- **Original Size**: ${formattedOriginal}
|
|
120
|
+
- **Optimized Size**: ${formattedCompressed}
|
|
121
|
+
- **Bandwidth Savings**: **-${result.savingsPercentage}%** (${(result.bytesSaved / 1024).toFixed(1)} KB saved)
|
|
122
|
+
- **Job ID**: \`${result.jobId}\`
|
|
123
|
+
- **CDN Delivery URL**: ${result.url || "N/A"}${savedMessage}
|
|
124
|
+
`
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/tools/quota.ts
|
|
131
|
+
var quotaSchema = {};
|
|
132
|
+
async function handleGetQuota() {
|
|
133
|
+
const client = getSmallPictClient();
|
|
134
|
+
console.error("[smallpict-mcp] Fetching SmallPict account quota...");
|
|
135
|
+
const quota = await client.getQuota();
|
|
136
|
+
const formatBytes = (bytes) => {
|
|
137
|
+
if (bytes >= 1024 * 1024 * 1024) return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB";
|
|
138
|
+
if (bytes >= 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(2) + " MB";
|
|
139
|
+
return (bytes / 1024).toFixed(1) + " KB";
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: "text",
|
|
145
|
+
text: `### \u{1F4CA} SmallPict Quota & Plan Status
|
|
146
|
+
- **Current Plan**: \`${quota.plan.toUpperCase()}\`
|
|
147
|
+
- **Monthly Usage**: ${formatBytes(quota.bytesUsed)} / ${formatBytes(quota.quotaLimit)} (**${quota.quotaPercentage}%**)
|
|
148
|
+
- **CDN Egress Used**: ${formatBytes(quota.cdnEgressUsedBytes || 0)}
|
|
149
|
+
- **Active API Keys**: ${quota.activeKeysCount ?? "N/A"}
|
|
150
|
+
- **Registered Sites**: ${quota.activeSitesCount ?? "N/A"}
|
|
151
|
+
`
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/tools/purge.ts
|
|
158
|
+
import { z as z2 } from "zod";
|
|
159
|
+
var purgeSchema = {
|
|
160
|
+
urls: z2.array(z2.string()).describe("List of asset URLs to invalidate on Cloudflare Edge CDN"),
|
|
161
|
+
purgeType: z2.enum(["url", "all"]).default("url").optional().describe("Purge scope: url or all (default: url)")
|
|
162
|
+
};
|
|
163
|
+
async function handlePurge(args) {
|
|
164
|
+
const client = getSmallPictClient();
|
|
165
|
+
console.error(`[smallpict-mcp] Purging ${args.urls.length} asset(s) from CDN edge cache...`);
|
|
166
|
+
const response = await client.purgeCdn({
|
|
167
|
+
urls: args.urls,
|
|
168
|
+
purgeType: args.purgeType || "url"
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
content: [
|
|
172
|
+
{
|
|
173
|
+
type: "text",
|
|
174
|
+
text: `### \u{1F680} SmallPict CDN Cache Purged
|
|
175
|
+
- **Status**: \`success\`
|
|
176
|
+
- **Purged URLs**:
|
|
177
|
+
${args.urls.map((u) => ` - \`${u}\``).join("\n")}
|
|
178
|
+
- **Message**: ${response.message || "Assets successfully purged from Cloudflare edge locations"}
|
|
179
|
+
`
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/index.ts
|
|
186
|
+
var server = new McpServer({
|
|
187
|
+
name: "smallpict-mcp",
|
|
188
|
+
version: "0.0.2"
|
|
189
|
+
});
|
|
190
|
+
server.tool(
|
|
191
|
+
"smallpict_optimize_image",
|
|
192
|
+
"Compress and convert local or remote images (JPEG, PNG, GIF) into modern WebP/AVIF using SmallPict API with up to 85% bandwidth reduction",
|
|
193
|
+
optimizeSchema,
|
|
194
|
+
handleOptimize
|
|
195
|
+
);
|
|
196
|
+
server.tool(
|
|
197
|
+
"smallpict_get_quota",
|
|
198
|
+
"Inspect active SmallPict subscription plan, monthly bytes consumed, and remaining bandwidth quota",
|
|
199
|
+
quotaSchema,
|
|
200
|
+
handleGetQuota
|
|
201
|
+
);
|
|
202
|
+
server.tool(
|
|
203
|
+
"smallpict_purge_cdn_cache",
|
|
204
|
+
"Invalidate cached image URLs across Cloudflare Edge CDN PoPs",
|
|
205
|
+
purgeSchema,
|
|
206
|
+
handlePurge
|
|
207
|
+
);
|
|
208
|
+
async function main() {
|
|
209
|
+
const transport = new StdioServerTransport();
|
|
210
|
+
await server.connect(transport);
|
|
211
|
+
console.error("\u2705 [smallpict-mcp] Server running on stdio transport");
|
|
212
|
+
}
|
|
213
|
+
main().catch((error) => {
|
|
214
|
+
console.error("\u274C [smallpict-mcp] Fatal error:", error);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
});
|
|
217
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/tools/optimize.ts","../src/client.ts","../src/tools/quota.ts","../src/tools/purge.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { optimizeSchema, handleOptimize } from './tools/optimize.js';\nimport { quotaSchema, handleGetQuota } from './tools/quota.js';\nimport { purgeSchema, handlePurge } from './tools/purge.js';\n\nconst server = new McpServer({\n name: 'smallpict-mcp',\n version: '0.0.2',\n});\n\n// 1. Tool: Optimize Image\nserver.tool(\n 'smallpict_optimize_image',\n 'Compress and convert local or remote images (JPEG, PNG, GIF) into modern WebP/AVIF using SmallPict API with up to 85% bandwidth reduction',\n optimizeSchema,\n handleOptimize\n);\n\n// 2. Tool: Get Quota\nserver.tool(\n 'smallpict_get_quota',\n 'Inspect active SmallPict subscription plan, monthly bytes consumed, and remaining bandwidth quota',\n quotaSchema,\n handleGetQuota\n);\n\n// 3. Tool: Purge CDN Cache\nserver.tool(\n 'smallpict_purge_cdn_cache',\n 'Invalidate cached image URLs across Cloudflare Edge CDN PoPs',\n purgeSchema,\n handlePurge\n);\n\nasync function main() {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n console.error('✅ [smallpict-mcp] Server running on stdio transport');\n}\n\nmain().catch((error) => {\n console.error('❌ [smallpict-mcp] Fatal error:', error);\n process.exit(1);\n});\n","import { z } from 'zod';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { getSmallPictClient } from '../client.js';\n\nexport const optimizeSchema = {\n filePath: z.string().describe('Local file path or remote image URL (http/https) to optimize'),\n format: z.enum(['webp', 'avif', 'auto']).default('webp').describe('Target image format (webp, avif, or auto)'),\n quality: z.number().min(1).max(100).default(80).optional().describe('Compression quality (1-100, default: 80)'),\n maxDimension: z.number().positive().optional().describe('Optional maximum bounding dimension (longest edge) in pixels for image downscaling'),\n maxWidth: z.number().positive().optional().describe('Optional maximum width in pixels for image resizing'),\n maxHeight: z.number().positive().optional().describe('Optional maximum height in pixels for image resizing'),\n lossless: z.boolean().default(false).optional().describe('Enable lossless compression mode (pixel-perfect fidelity, ignores quality)'),\n outputPath: z.string().optional().describe('Optional local path where the optimized image should be saved'),\n};\n\nfunction detectMimeType(filePath: string): string {\n const ext = path.extname(filePath).toLowerCase();\n switch (ext) {\n case '.png':\n return 'image/png';\n case '.webp':\n return 'image/webp';\n case '.avif':\n return 'image/avif';\n case '.gif':\n return 'image/gif';\n case '.jpg':\n case '.jpeg':\n default:\n return 'image/jpeg';\n }\n}\n\nexport async function handleOptimize(args: {\n filePath: string;\n format?: 'webp' | 'avif' | 'auto';\n quality?: number;\n maxDimension?: number;\n maxWidth?: number;\n maxHeight?: number;\n lossless?: boolean;\n outputPath?: string;\n}) {\n const client = getSmallPictClient();\n const format = args.format || 'webp';\n const quality = args.quality ?? 80;\n const lossless = args.lossless ?? false;\n\n console.error(\n `[smallpict-mcp] Optimizing image: ${args.filePath} -> ${format} (q=${quality}, lossless=${lossless}${args.maxDimension ? `, maxDim=${args.maxDimension}` : ''})`\n );\n\n let isRemote = args.filePath.startsWith('http://') || args.filePath.startsWith('https://');\n let source: string | Uint8Array;\n let filename = path.basename(args.filePath);\n let mimeType = detectMimeType(args.filePath);\n\n if (isRemote) {\n source = args.filePath;\n } else {\n const resolvedPath = path.resolve(process.cwd(), args.filePath);\n const fileBuffer = await fs.readFile(resolvedPath);\n source = new Uint8Array(fileBuffer);\n filename = path.basename(resolvedPath);\n mimeType = detectMimeType(resolvedPath);\n }\n\n const result = await client.optimize(source, {\n filename,\n mimeType,\n format,\n quality,\n maxWidth: args.maxWidth,\n maxHeight: args.maxHeight,\n lossless,\n ...(args.maxDimension ? { maxDimension: args.maxDimension } : {}),\n } as any);\n\n let savedMessage = '';\n if (args.outputPath && result.url) {\n const targetPath = path.resolve(process.cwd(), args.outputPath);\n await fs.mkdir(path.dirname(targetPath), { recursive: true });\n \n console.error(`[smallpict-mcp] Downloading optimized image from ${result.url} to ${targetPath}`);\n const res = await fetch(result.url);\n if (!res.ok) {\n throw new Error(`Failed to download optimized asset from ${result.url}: ${res.statusText}`);\n }\n const arrayBuffer = await res.arrayBuffer();\n await fs.writeFile(targetPath, Buffer.from(arrayBuffer));\n savedMessage = `\\n- **Saved to**: \\`${targetPath}\\``;\n }\n\n const formattedOriginal = (result.originalSize / 1024).toFixed(1) + ' KB';\n const formattedCompressed = (result.compressedSize / 1024).toFixed(1) + ' KB';\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `### ✅ SmallPict Image Optimization Succeeded\n- **File**: \\`${filename}\\`\n- **Output Format**: \\`${result.format}\\`\n- **Compression Mode**: \\`${lossless ? 'Lossless (Pixel-Perfect)' : `Lossy (q=${quality})`}\\`${args.maxDimension ? `\\n- **Max Dimension Constraint**: \\`${args.maxDimension}px\\`` : ''}\n- **Original Size**: ${formattedOriginal}\n- **Optimized Size**: ${formattedCompressed}\n- **Bandwidth Savings**: **-${result.savingsPercentage}%** (${((result.bytesSaved) / 1024).toFixed(1)} KB saved)\n- **Job ID**: \\`${result.jobId}\\`\n- **CDN Delivery URL**: ${result.url || 'N/A'}${savedMessage}\n`,\n },\n ],\n };\n}\n","import { SmallPictClient } from '@smallpict/sdk';\n\nlet clientInstance: SmallPictClient | null = null;\n\nexport function getSmallPictClient(): SmallPictClient {\n if (clientInstance) {\n return clientInstance;\n }\n\n const apiKey = process.env.SMALLPICT_API_KEY;\n const secretKey = process.env.SMALLPICT_SECRET_KEY;\n const baseUrl = process.env.SMALLPICT_BASE_URL || 'https://api.smallpict.app';\n\n if (!apiKey) {\n throw new Error(\n 'Missing required environment variable SMALLPICT_API_KEY. Please provide SMALLPICT_API_KEY in MCP server configuration.'\n );\n }\n\n clientInstance = new SmallPictClient({\n apiKey,\n secretKey,\n baseUrl,\n });\n\n return clientInstance;\n}\n","import { getSmallPictClient } from '../client.js';\n\nexport const quotaSchema = {};\n\nexport async function handleGetQuota() {\n const client = getSmallPictClient();\n\n console.error('[smallpict-mcp] Fetching SmallPict account quota...');\n const quota = await client.getQuota();\n\n const formatBytes = (bytes: number) => {\n if (bytes >= 1024 * 1024 * 1024) return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';\n if (bytes >= 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(2) + ' MB';\n return (bytes / 1024).toFixed(1) + ' KB';\n };\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `### 📊 SmallPict Quota & Plan Status\n- **Current Plan**: \\`${quota.plan.toUpperCase()}\\`\n- **Monthly Usage**: ${formatBytes(quota.bytesUsed)} / ${formatBytes(quota.quotaLimit)} (**${quota.quotaPercentage}%**)\n- **CDN Egress Used**: ${formatBytes(quota.cdnEgressUsedBytes || 0)}\n- **Active API Keys**: ${quota.activeKeysCount ?? 'N/A'}\n- **Registered Sites**: ${quota.activeSitesCount ?? 'N/A'}\n`,\n },\n ],\n };\n}\n","import { z } from 'zod';\nimport { getSmallPictClient } from '../client.js';\n\nexport const purgeSchema = {\n urls: z.array(z.string()).describe('List of asset URLs to invalidate on Cloudflare Edge CDN'),\n purgeType: z.enum(['url', 'all']).default('url').optional().describe('Purge scope: url or all (default: url)'),\n};\n\nexport async function handlePurge(args: { urls: string[]; purgeType?: 'url' | 'all' }) {\n const client = getSmallPictClient();\n\n console.error(`[smallpict-mcp] Purging ${args.urls.length} asset(s) from CDN edge cache...`);\n const response = await client.purgeCdn({\n urls: args.urls,\n purgeType: args.purgeType || 'url',\n });\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `### 🚀 SmallPict CDN Cache Purged\n- **Status**: \\`success\\`\n- **Purged URLs**:\n${args.urls.map((u) => ` - \\`${u}\\``).join('\\n')}\n- **Message**: ${response.message || 'Assets successfully purged from Cloudflare edge locations'}\n`,\n },\n ],\n };\n}\n"],"mappings":"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","names":["z","z"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@smallpict/mcp",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Official Model Context Protocol (MCP) server for SmallPict Image Optimization",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"smallpict-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"prepublishOnly": "npm run typecheck && npm run build",
|
|
20
|
+
"start": "node dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"model-context-protocol",
|
|
25
|
+
"smallpict",
|
|
26
|
+
"image-optimization",
|
|
27
|
+
"webp",
|
|
28
|
+
"avif",
|
|
29
|
+
"ai-agent"
|
|
30
|
+
],
|
|
31
|
+
"author": "SmallPict Engineering <support@smallpict.app>",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"homepage": "https://smallpict.app",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/tuxnoob/smallpict-mcp.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/tuxnoob/smallpict-mcp/issues"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"provenance": true
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
47
|
+
"@smallpict/sdk": "^0.0.2",
|
|
48
|
+
"zod": "^3.24.2"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^20.17.0",
|
|
52
|
+
"tsup": "^8.3.6",
|
|
53
|
+
"typescript": "^5.7.0"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|