@icogenie/mcp 0.1.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.1.0] - 2025-01-28
6
+
7
+ ### Added
8
+ - Initial release
9
+ - `generate_icon` tool - Generate single icon preview (1 credit)
10
+ - `regenerate_icon` tool - Regenerate variation with custom prompt (1 credit)
11
+ - `check_credits` tool - Check credit balance (free)
12
+ - `download_icon` tool - Download SVG/PNG package (5 credits single, 4/icon bundle)
13
+ - `normalize_bundle` tool - Plan bundle icon list (free, rate-limited)
14
+ - `generate_bundle` tool - Generate bundle from icon list (1 credit/icon)
15
+ - Browser-based OAuth authentication (shared with @icogenie/cli)
16
+ - Reference image support for style extraction
17
+ - CI/CD support via `ICOGENIE_SESSION_TOKEN` environment variable
18
+ - Stdio transport for Claude Desktop and Cursor integration
package/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # @icogenie/mcp
2
+
3
+ MCP (Model Context Protocol) server for IcoGenie. Enables AI agents like Claude to generate production-ready SVG icons programmatically.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @icogenie/mcp
9
+ # or use directly with npx
10
+ npx @icogenie/mcp
11
+ ```
12
+
13
+ ## Configuration
14
+
15
+ ### Claude Desktop
16
+
17
+ Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
18
+
19
+ ```json
20
+ {
21
+ "mcpServers": {
22
+ "icogenie": {
23
+ "command": "npx",
24
+ "args": ["-y", "@icogenie/mcp"]
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ ### Claude Code
31
+
32
+ The server works automatically when installed globally or via npx.
33
+
34
+ ## Authentication
35
+
36
+ On first use, the MCP server will:
37
+ 1. Open your browser for authentication
38
+ 2. Ask you to approve access
39
+ 3. Save the session token to `~/.icogenie/config.json`
40
+
41
+ Subsequent uses are automatic - the token is shared with the [IcoGenie CLI](https://www.npmjs.com/package/@icogenie/cli).
42
+
43
+ ### CI/CD Environments
44
+
45
+ Set `ICOGENIE_SESSION_TOKEN` environment variable to skip browser authentication:
46
+
47
+ ```bash
48
+ export ICOGENIE_SESSION_TOKEN="your-session-token"
49
+ ```
50
+
51
+ ## Available Tools
52
+
53
+ ### generate_icon
54
+ Generate a single icon preview from a text description.
55
+
56
+ **Cost:** 1 credit
57
+
58
+ ```
59
+ generate_icon({
60
+ prompt: "home icon",
61
+ style: "solid", // or "outline"
62
+ variations: 1, // 1, 2, or 4
63
+ referenceImagePath: "/path/to/reference.png" // optional
64
+ })
65
+ ```
66
+
67
+ **Returns:** `{ generationId, preview, creditsUsed, creditsRemaining, metadata }`
68
+
69
+ ### regenerate_icon
70
+ Regenerate a specific icon variation with a custom refinement prompt.
71
+
72
+ **Cost:** 1 credit
73
+
74
+ ```
75
+ regenerate_icon({
76
+ generationId: "abc123", // or bundleId for bundles
77
+ index: 0, // which variation (0-based)
78
+ prompt: "Make it more 3D" // optional refinement
79
+ })
80
+ ```
81
+
82
+ ### check_credits
83
+ Check your current credit balance.
84
+
85
+ **Cost:** Free
86
+
87
+ ```
88
+ check_credits()
89
+ ```
90
+
91
+ **Returns:** `{ credits, team, user }`
92
+
93
+ ### download_icon
94
+ Download the final SVG + PNG package for an icon or bundle.
95
+
96
+ **Cost:** 5 credits (single) or 4 credits/icon (bundle)
97
+
98
+ ```
99
+ download_icon({
100
+ generationId: "abc123", // or bundleId
101
+ outputPath: "./icons.zip" // optional, returns base64 if omitted
102
+ })
103
+ ```
104
+
105
+ ### normalize_bundle
106
+ Plan an icon bundle by generating an AI-enhanced icon list from a description.
107
+
108
+ **Cost:** Free (rate-limited)
109
+
110
+ ```
111
+ normalize_bundle({
112
+ description: "food delivery app",
113
+ targetCount: 10,
114
+ style: "solid"
115
+ })
116
+ ```
117
+
118
+ **Returns:** `{ icons, bundleType, styleRecommendation, reasoning }`
119
+
120
+ ### generate_bundle
121
+ Generate a bundle of icons from an icon list.
122
+
123
+ **Cost:** 1 credit per icon
124
+
125
+ ```
126
+ generate_bundle({
127
+ icons: [
128
+ { name: "home", description: "Home navigation icon" },
129
+ { name: "cart", description: "Shopping cart icon" }
130
+ ],
131
+ style: "solid"
132
+ })
133
+ ```
134
+
135
+ **Returns:** `{ bundleId, icons: [{ name, preview }], credits }`
136
+
137
+ ## Example Workflow
138
+
139
+ 1. **Check credits:**
140
+ ```
141
+ check_credits()
142
+ → { credits: 50, team: { name: "Personal" } }
143
+ ```
144
+
145
+ 2. **Generate a single icon:**
146
+ ```
147
+ generate_icon({ prompt: "notification bell icon", style: "outline" })
148
+ → { generationId: "abc123", preview: "...", creditsRemaining: 49 }
149
+ ```
150
+
151
+ 3. **Refine if needed:**
152
+ ```
153
+ regenerate_icon({ generationId: "abc123", index: 0, prompt: "Add a dot indicator" })
154
+ → { preview: "...", creditsRemaining: 48 }
155
+ ```
156
+
157
+ 4. **Download final package:**
158
+ ```
159
+ download_icon({ generationId: "abc123", outputPath: "./bell-icon.zip" })
160
+ → { savedTo: "./bell-icon.zip", creditsRemaining: 43 }
161
+ ```
162
+
163
+ ## Bundle Workflow
164
+
165
+ 1. **Plan the bundle (free):**
166
+ ```
167
+ normalize_bundle({ description: "e-commerce app icons", targetCount: 8 })
168
+ → { icons: [{ name: "cart", ... }, ...], styleRecommendation: "outline" }
169
+ ```
170
+
171
+ 2. **Review and generate:**
172
+ ```
173
+ generate_bundle({ icons: [...], style: "outline" })
174
+ → { bundleId: "xyz789", icons: [...], credits: { previewUsed: 8 } }
175
+ ```
176
+
177
+ 3. **Download bundle:**
178
+ ```
179
+ download_icon({ bundleId: "xyz789", outputPath: "./ecommerce-icons.zip" })
180
+ ```
181
+
182
+ ## Environment Variables
183
+
184
+ | Variable | Description | Default |
185
+ |----------|-------------|---------|
186
+ | `ICOGENIE_API_URL` | API endpoint | `https://icogenie.com` |
187
+ | `ICOGENIE_SESSION_TOKEN` | Session token (for CI/CD) | - |
188
+ | `ICOGENIE_CONFIG_DIR` | Config directory | `~/.icogenie` |
189
+
190
+ ## Credit Pricing
191
+
192
+ | Action | Cost |
193
+ |--------|------|
194
+ | Preview (single) | 1 credit |
195
+ | Preview (bundle) | 1 credit/icon |
196
+ | Download (single) | 5 credits |
197
+ | Download (bundle) | 4 credits/icon |
198
+ | Regenerate | 1 credit |
199
+
200
+ Purchase credits at [icogenie.com](https://icogenie.com).
201
+
202
+ ## Related
203
+
204
+ - [@icogenie/cli](https://www.npmjs.com/package/@icogenie/cli) - Command-line interface
205
+ - [IcoGenie Web](https://icogenie.com) - Web application
206
+
207
+ ## License
208
+
209
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,519 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+
6
+ // src/server.ts
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+
9
+ // src/tools/generate.ts
10
+ import { z } from "zod";
11
+
12
+ // src/api/client.ts
13
+ import { readFileSync, statSync } from "fs";
14
+ import { extname } from "path";
15
+
16
+ // src/auth/config.ts
17
+ import Conf from "conf";
18
+ import { homedir } from "os";
19
+ import { join } from "path";
20
+ import { mkdirSync, existsSync } from "fs";
21
+ var configDir = process.env.ICOGENIE_CONFIG_DIR || join(homedir(), ".icogenie");
22
+ if (!existsSync(configDir)) {
23
+ mkdirSync(configDir, { recursive: true });
24
+ }
25
+ var config = new Conf({
26
+ projectName: "icogenie",
27
+ cwd: configDir,
28
+ defaults: {
29
+ apiUrl: "https://icogenie.com"
30
+ }
31
+ });
32
+ function getApiUrl() {
33
+ return process.env.ICOGENIE_API_URL || config.get("apiUrl");
34
+ }
35
+ function getCredentials() {
36
+ const envToken = process.env.ICOGENIE_SESSION_TOKEN;
37
+ if (envToken) {
38
+ return {
39
+ sessionToken: envToken,
40
+ userId: "env-user",
41
+ userEmail: "env@icogenie.com",
42
+ teamId: "env-team",
43
+ teamName: "Environment",
44
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
45
+ };
46
+ }
47
+ return config.get("credentials");
48
+ }
49
+ function setCredentials(credentials) {
50
+ config.set("credentials", credentials);
51
+ }
52
+ function clearCredentials() {
53
+ config.delete("credentials");
54
+ }
55
+
56
+ // src/auth/flow.ts
57
+ import open from "open";
58
+ async function request(endpoint, body) {
59
+ const url = `${getApiUrl()}/api/cli${endpoint}`;
60
+ const response = await fetch(url, {
61
+ method: "POST",
62
+ headers: { "Content-Type": "application/json" },
63
+ body: JSON.stringify(body)
64
+ });
65
+ const data = await response.json();
66
+ if (!response.ok) {
67
+ throw new Error(data.error || data.message || "Request failed");
68
+ }
69
+ return data;
70
+ }
71
+ async function startAuth() {
72
+ return request("/auth/start", {});
73
+ }
74
+ async function pollAuth(pollToken) {
75
+ return request("/auth/poll", { pollToken });
76
+ }
77
+ async function authenticate() {
78
+ const { pollToken, loginUrl, expiresInSeconds } = await startAuth();
79
+ await open(loginUrl);
80
+ const pollInterval = 2e3;
81
+ const maxAttempts = Math.floor(expiresInSeconds * 1e3 / pollInterval);
82
+ let attempts = 0;
83
+ while (attempts < maxAttempts) {
84
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
85
+ attempts++;
86
+ const result = await pollAuth(pollToken);
87
+ if (result.status === "approved" && result.sessionToken && result.user && result.team) {
88
+ const credentials = {
89
+ sessionToken: result.sessionToken,
90
+ userId: result.user.id,
91
+ userEmail: result.user.email,
92
+ userName: result.user.name,
93
+ teamId: result.team.id,
94
+ teamName: result.team.name,
95
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
96
+ };
97
+ setCredentials(credentials);
98
+ return credentials;
99
+ }
100
+ if (result.status === "expired") {
101
+ throw new Error("Authentication request expired. Please try again.");
102
+ }
103
+ }
104
+ throw new Error("Authentication timed out. Please try again.");
105
+ }
106
+
107
+ // src/auth/middleware.ts
108
+ var AuthError = class extends Error {
109
+ constructor(message, code) {
110
+ super(message);
111
+ this.code = code;
112
+ this.name = "AuthError";
113
+ }
114
+ };
115
+ async function ensureAuth() {
116
+ let credentials = getCredentials();
117
+ if (!credentials) {
118
+ try {
119
+ credentials = await authenticate();
120
+ } catch (error) {
121
+ throw new AuthError(
122
+ `Authentication required. Please approve access in your browser. Error: ${error instanceof Error ? error.message : "Unknown error"}`,
123
+ "AUTH_FAILED"
124
+ );
125
+ }
126
+ }
127
+ return credentials;
128
+ }
129
+ async function handleAuthError() {
130
+ clearCredentials();
131
+ try {
132
+ return await authenticate();
133
+ } catch (error) {
134
+ throw new AuthError(
135
+ "Session expired. Please approve access in your browser.",
136
+ "SESSION_EXPIRED"
137
+ );
138
+ }
139
+ }
140
+
141
+ // src/api/client.ts
142
+ var ApiError = class extends Error {
143
+ constructor(message, statusCode, details) {
144
+ super(message);
145
+ this.statusCode = statusCode;
146
+ this.details = details;
147
+ this.name = "ApiError";
148
+ }
149
+ };
150
+ var SUPPORTED_IMAGE_FORMATS = [".png", ".jpg", ".jpeg", ".webp"];
151
+ var MAX_IMAGE_SIZE = 5 * 1024 * 1024;
152
+ function readReferenceImage(filePath) {
153
+ const ext = extname(filePath).toLowerCase();
154
+ if (!SUPPORTED_IMAGE_FORMATS.includes(ext)) {
155
+ throw new Error(`Unsupported image format: ${ext}. Supported: ${SUPPORTED_IMAGE_FORMATS.join(", ")}`);
156
+ }
157
+ const stats = statSync(filePath);
158
+ if (stats.size > MAX_IMAGE_SIZE) {
159
+ throw new Error(`Image too large: ${(stats.size / 1024 / 1024).toFixed(1)}MB. Max: 5MB`);
160
+ }
161
+ const buffer = readFileSync(filePath);
162
+ const mimeType = ext === ".png" ? "image/png" : ext === ".webp" ? "image/webp" : "image/jpeg";
163
+ return { data: buffer.toString("base64"), mimeType };
164
+ }
165
+ async function request2(endpoint, body, requireAuth = true) {
166
+ const url = `${getApiUrl()}/api/cli${endpoint}`;
167
+ const makeRequest = async (sessionToken) => {
168
+ const requestBody = sessionToken ? { ...body, sessionToken } : body;
169
+ const response = await fetch(url, {
170
+ method: "POST",
171
+ headers: { "Content-Type": "application/json" },
172
+ body: JSON.stringify(requestBody)
173
+ });
174
+ const data = await response.json();
175
+ if (response.status === 401 && requireAuth) {
176
+ const newCreds = await handleAuthError();
177
+ return makeRequest(newCreds.sessionToken);
178
+ }
179
+ if (!response.ok) {
180
+ throw new ApiError(
181
+ data.error || data.message || "Request failed",
182
+ response.status,
183
+ data
184
+ );
185
+ }
186
+ return data;
187
+ };
188
+ if (requireAuth) {
189
+ const credentials = await ensureAuth();
190
+ return makeRequest(credentials.sessionToken);
191
+ }
192
+ return makeRequest();
193
+ }
194
+ async function generate(options) {
195
+ return request2("/generate", {
196
+ prompt: options.prompt,
197
+ variations: options.variations || 1,
198
+ style: options.style || "solid",
199
+ ...options.referenceImage && { referenceImage: options.referenceImage }
200
+ });
201
+ }
202
+ async function regenerate(options) {
203
+ return request2("/regenerate", {
204
+ generationId: options.generationId,
205
+ bundleId: options.bundleId,
206
+ index: options.index,
207
+ prompt: options.prompt
208
+ });
209
+ }
210
+ async function getCredits() {
211
+ return request2("/credits", {});
212
+ }
213
+ async function download(options) {
214
+ return request2("/download", {
215
+ generationId: options.generationId,
216
+ bundleId: options.bundleId
217
+ });
218
+ }
219
+ async function normalizeBundle(options) {
220
+ return request2("/normalize", {
221
+ description: options.description,
222
+ targetCount: options.targetCount,
223
+ style: options.style
224
+ });
225
+ }
226
+ async function generateBundle(options) {
227
+ return request2("/bundle", {
228
+ description: options.description,
229
+ targetCount: options.targetCount,
230
+ icons: options.icons,
231
+ style: options.style || "solid",
232
+ ...options.referenceImage && { referenceImage: options.referenceImage }
233
+ });
234
+ }
235
+
236
+ // src/tools/generate.ts
237
+ var generateIconSchema = {
238
+ prompt: z.string().describe("Description of the icon to generate"),
239
+ style: z.enum(["solid", "outline"]).default("solid").describe("Icon style"),
240
+ variations: z.union([z.literal(1), z.literal(2), z.literal(4)]).default(1).describe("Number of variations (1, 2, or 4)"),
241
+ referenceImagePath: z.string().optional().describe("Local file path to reference image for style extraction"),
242
+ referenceImage: z.object({
243
+ data: z.string().describe("Base64-encoded image data"),
244
+ mimeType: z.enum(["image/png", "image/jpeg", "image/webp"])
245
+ }).optional().describe("Base64-encoded reference image")
246
+ };
247
+ async function generateIcon(args) {
248
+ let refImage;
249
+ if (args.referenceImagePath) {
250
+ refImage = readReferenceImage(args.referenceImagePath);
251
+ } else if (args.referenceImage) {
252
+ refImage = args.referenceImage;
253
+ }
254
+ const result = await generate({
255
+ prompt: args.prompt,
256
+ style: args.style || "solid",
257
+ variations: args.variations || 1,
258
+ referenceImage: refImage
259
+ });
260
+ return {
261
+ generationId: result.generationId,
262
+ preview: result.preview,
263
+ creditsUsed: result.creditsUsed,
264
+ creditsRemaining: result.creditsRemaining,
265
+ metadata: result.metadata
266
+ };
267
+ }
268
+
269
+ // src/tools/regenerate.ts
270
+ import { z as z2 } from "zod";
271
+ var regenerateIconSchema = {
272
+ generationId: z2.string().optional().describe("For single icon variations"),
273
+ bundleId: z2.string().optional().describe("For bundle icons"),
274
+ index: z2.number().describe("Which variation/icon to regenerate (0-based)"),
275
+ prompt: z2.string().optional().describe("Custom refinement prompt")
276
+ };
277
+ async function regenerateIcon(args) {
278
+ if (!args.generationId && !args.bundleId) {
279
+ throw new Error("Must provide either generationId or bundleId");
280
+ }
281
+ const result = await regenerate({
282
+ generationId: args.generationId,
283
+ bundleId: args.bundleId,
284
+ index: args.index,
285
+ prompt: args.prompt
286
+ });
287
+ return {
288
+ success: result.success,
289
+ preview: result.preview,
290
+ creditsUsed: result.creditsUsed,
291
+ creditsRemaining: result.creditsRemaining
292
+ };
293
+ }
294
+
295
+ // src/tools/credits.ts
296
+ var checkCreditsSchema = {};
297
+ async function checkCredits() {
298
+ const result = await getCredits();
299
+ return {
300
+ credits: result.credits,
301
+ team: result.team,
302
+ user: result.user
303
+ };
304
+ }
305
+
306
+ // src/tools/download.ts
307
+ import { z as z3 } from "zod";
308
+ import { writeFileSync } from "fs";
309
+ var downloadIconSchema = {
310
+ generationId: z3.string().optional().describe("ID from generate_icon result (for single icons)"),
311
+ bundleId: z3.string().optional().describe("ID from generate_bundle result (for bundles)"),
312
+ outputPath: z3.string().optional().describe("Where to save the ZIP file")
313
+ };
314
+ async function downloadIcon(args) {
315
+ if (!args.generationId && !args.bundleId) {
316
+ throw new Error("Must provide either generationId or bundleId");
317
+ }
318
+ const result = await download({
319
+ generationId: args.generationId,
320
+ bundleId: args.bundleId
321
+ });
322
+ const response = {
323
+ success: result.success,
324
+ creditsUsed: result.creditsUsed,
325
+ creditsRemaining: result.creditsRemaining,
326
+ iconCount: result.iconCount
327
+ };
328
+ if (args.outputPath) {
329
+ const buffer = Buffer.from(result.bundle.data, "base64");
330
+ writeFileSync(args.outputPath, buffer);
331
+ response.savedTo = args.outputPath;
332
+ } else {
333
+ response.bundle = {
334
+ data: result.bundle.data,
335
+ filename: result.bundle.filename
336
+ };
337
+ }
338
+ return response;
339
+ }
340
+
341
+ // src/tools/normalize-bundle.ts
342
+ import { z as z4 } from "zod";
343
+ var normalizeBundleSchema = {
344
+ description: z4.string().describe("Description of the icon bundle to create"),
345
+ targetCount: z4.number().optional().describe("Target number of icons (2-20)"),
346
+ style: z4.enum(["solid", "outline"]).optional().describe("Preferred icon style")
347
+ };
348
+ async function normalizeBundleTool(args) {
349
+ const result = await normalizeBundle({
350
+ description: args.description,
351
+ targetCount: args.targetCount,
352
+ style: args.style
353
+ });
354
+ return {
355
+ icons: result.icons,
356
+ bundleType: result.bundleType,
357
+ suggestedCount: result.suggestedCount,
358
+ styleRecommendation: result.styleRecommendation,
359
+ reasoning: result.reasoning
360
+ };
361
+ }
362
+
363
+ // src/tools/generate-bundle.ts
364
+ import { z as z5 } from "zod";
365
+ var generateBundleSchema = {
366
+ icons: z5.array(
367
+ z5.object({
368
+ name: z5.string().regex(/^[a-z0-9-]+$/).describe("Icon name in kebab-case"),
369
+ description: z5.string().describe("Icon description")
370
+ })
371
+ ).optional().describe("Icon list from normalize_bundle or custom"),
372
+ description: z5.string().optional().describe("Alternative: describe the bundle to auto-generate icon list"),
373
+ targetCount: z5.number().optional().describe("Target icon count when using description"),
374
+ style: z5.enum(["solid", "outline"]).default("solid").describe("Icon style"),
375
+ referenceImagePath: z5.string().optional().describe("Local file path to reference image"),
376
+ referenceImage: z5.object({
377
+ data: z5.string(),
378
+ mimeType: z5.enum(["image/png", "image/jpeg", "image/webp"])
379
+ }).optional().describe("Base64-encoded reference image")
380
+ };
381
+ async function generateBundleTool(args) {
382
+ if (!args.icons && !args.description) {
383
+ throw new Error("Must provide either icons array or description");
384
+ }
385
+ let refImage;
386
+ if (args.referenceImagePath) {
387
+ refImage = readReferenceImage(args.referenceImagePath);
388
+ } else if (args.referenceImage) {
389
+ refImage = args.referenceImage;
390
+ }
391
+ const result = await generateBundle({
392
+ icons: args.icons,
393
+ description: args.description,
394
+ targetCount: args.targetCount,
395
+ style: args.style || "solid",
396
+ referenceImage: refImage
397
+ });
398
+ return {
399
+ bundleId: result.bundleId,
400
+ iconCount: result.iconCount,
401
+ icons: result.icons.map((icon) => ({
402
+ name: icon.name,
403
+ description: icon.description,
404
+ preview: icon.preview
405
+ })),
406
+ credits: result.credits,
407
+ nextStep: result.nextStep
408
+ };
409
+ }
410
+
411
+ // src/server.ts
412
+ function createServer() {
413
+ const server = new McpServer({
414
+ name: "icogenie",
415
+ version: "0.1.0"
416
+ });
417
+ server.registerTool(
418
+ "generate_icon",
419
+ {
420
+ title: "Generate Icon",
421
+ description: "Generate an AI-powered icon preview from a text description. Costs 1 credit. Returns a generationId for download.",
422
+ inputSchema: generateIconSchema
423
+ },
424
+ async (args) => {
425
+ const result = await generateIcon(args);
426
+ return {
427
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
428
+ structuredContent: result
429
+ };
430
+ }
431
+ );
432
+ server.registerTool(
433
+ "regenerate_icon",
434
+ {
435
+ title: "Regenerate Icon",
436
+ description: "Regenerate a specific icon variation with an optional custom prompt. Costs 1 credit. Use with generationId (single) or bundleId (bundle).",
437
+ inputSchema: regenerateIconSchema
438
+ },
439
+ async (args) => {
440
+ const result = await regenerateIcon(args);
441
+ return {
442
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
443
+ structuredContent: result
444
+ };
445
+ }
446
+ );
447
+ server.registerTool(
448
+ "check_credits",
449
+ {
450
+ title: "Check Credits",
451
+ description: "Check the current credit balance and account information. Free to use.",
452
+ inputSchema: checkCreditsSchema
453
+ },
454
+ async () => {
455
+ const result = await checkCredits();
456
+ return {
457
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
458
+ structuredContent: result
459
+ };
460
+ }
461
+ );
462
+ server.registerTool(
463
+ "download_icon",
464
+ {
465
+ title: "Download Icon",
466
+ description: "Download the final SVG + PNG package for an icon. Costs 5 credits (single) or 4 credits/icon (bundle). Provide outputPath to save to file.",
467
+ inputSchema: downloadIconSchema
468
+ },
469
+ async (args) => {
470
+ const result = await downloadIcon(args);
471
+ return {
472
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
473
+ structuredContent: result
474
+ };
475
+ }
476
+ );
477
+ server.registerTool(
478
+ "normalize_bundle",
479
+ {
480
+ title: "Normalize Bundle",
481
+ description: "Plan an icon bundle by generating an AI-enhanced icon list from a description. Free but rate-limited. Review the list before generating.",
482
+ inputSchema: normalizeBundleSchema
483
+ },
484
+ async (args) => {
485
+ const result = await normalizeBundleTool(args);
486
+ return {
487
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
488
+ structuredContent: result
489
+ };
490
+ }
491
+ );
492
+ server.registerTool(
493
+ "generate_bundle",
494
+ {
495
+ title: "Generate Bundle",
496
+ description: "Generate a bundle of icons from an icon list. Costs 1 credit per icon. Use normalize_bundle first to plan, or provide icons directly.",
497
+ inputSchema: generateBundleSchema
498
+ },
499
+ async (args) => {
500
+ const result = await generateBundleTool(args);
501
+ return {
502
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
503
+ structuredContent: result
504
+ };
505
+ }
506
+ );
507
+ return server;
508
+ }
509
+
510
+ // src/index.ts
511
+ async function main() {
512
+ const server = createServer();
513
+ const transport = new StdioServerTransport();
514
+ await server.connect(transport);
515
+ }
516
+ main().catch((error) => {
517
+ console.error("Fatal error:", error);
518
+ process.exit(1);
519
+ });
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@icogenie/mcp",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for IcoGenie - Enable AI agents to generate icons programmatically",
5
+ "type": "module",
6
+ "bin": {
7
+ "icogenie-mcp": "dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "CHANGELOG.md",
15
+ "LICENSE"
16
+ ],
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.12.0",
19
+ "conf": "^12.0.0",
20
+ "open": "^10.0.0",
21
+ "zod": "^3.23.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^20.0.0",
25
+ "tsup": "^8.0.0",
26
+ "typescript": "^5.0.0",
27
+ "@repo/typescript-config": "0.0.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "keywords": [
33
+ "mcp",
34
+ "model-context-protocol",
35
+ "icon",
36
+ "svg",
37
+ "generator",
38
+ "ai",
39
+ "icogenie",
40
+ "claude",
41
+ "anthropic"
42
+ ],
43
+ "author": {
44
+ "name": "ICOGenie",
45
+ "url": "https://icogenie.com"
46
+ },
47
+ "license": "MIT",
48
+ "homepage": "https://icogenie.com",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/icogenie/icogenie-mcp.git"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public",
55
+ "registry": "https://registry.npmjs.org/"
56
+ },
57
+ "scripts": {
58
+ "build": "tsup src/index.ts --format esm --target node18 --dts --clean",
59
+ "dev": "tsup src/index.ts --format esm --target node18 --watch",
60
+ "type-check": "tsc --noEmit",
61
+ "start": "node dist/index.js",
62
+ "version": "pnpm run build",
63
+ "postversion": "git push && git push --tags",
64
+ "release:patch": "npm version patch -m 'chore(icogenie-mcp): release v%s' --tag-version-prefix='mcp-v'",
65
+ "release:minor": "npm version minor -m 'chore(icogenie-mcp): release v%s' --tag-version-prefix='mcp-v'",
66
+ "release:major": "npm version major -m 'chore(icogenie-mcp): release v%s' --tag-version-prefix='mcp-v'"
67
+ }
68
+ }