@saroby/nanobanana-mcp 1.0.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/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @saroby/nanobanana-mcp
2
+
3
+ Google Gemini image generation MCP server (Nano Banana).
4
+
5
+ ## Features
6
+
7
+ - **generate_image** - Generate images using Google Gemini models
8
+ - `flash` mode: gemini-2.0-flash-exp (fast, ~2-3s)
9
+ - `pro` mode: imagen-3.0-generate-002 (high quality, ~5-8s)
10
+ - **list_images** - List generated images in a directory
11
+
12
+ ## Setup
13
+
14
+ ### Get API Key
15
+
16
+ 1. Go to [Google AI Studio](https://aistudio.google.com/apikey)
17
+ 2. Create an API key
18
+
19
+ ### Install in Claude Code
20
+
21
+ ```bash
22
+ claude mcp add nanobanana -e GEMINI_API_KEY=your-key-here -- npx -y @saroby/nanobanana-mcp
23
+ ```
24
+
25
+ ### Manual Usage
26
+
27
+ ```bash
28
+ GEMINI_API_KEY=your-key-here npx @saroby/nanobanana-mcp
29
+ ```
30
+
31
+ ## Tools
32
+
33
+ ### generate_image
34
+
35
+ | Parameter | Type | Required | Description |
36
+ |-----------|------|----------|-------------|
37
+ | `prompt` | string | Yes | Image description (1-8192 chars) |
38
+ | `model` | `"flash"` \| `"pro"` | No | Model selection (default: flash) |
39
+ | `aspect_ratio` | enum | No | `1:1`, `16:9`, `9:16`, `4:3`, `3:4` |
40
+ | `negative_prompt` | string | No | Elements to exclude |
41
+ | `output_dir` | string | No | Save directory (default: ./nanobanana-images) |
42
+ | `count` | number | No | Number of images 1-4 (default: 1) |
43
+
44
+ ### list_images
45
+
46
+ | Parameter | Type | Required | Description |
47
+ |-----------|------|----------|-------------|
48
+ | `directory` | string | No | Directory to search (default: ./nanobanana-images) |
49
+
50
+ ## Development
51
+
52
+ ```bash
53
+ npm install
54
+ npm run build
55
+ npm run dev # watch mode
56
+ ```
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,14 @@
1
+ import { type ModelType, type AspectRatio } from "./utils/config.js";
2
+ export interface GenerateImageOptions {
3
+ prompt: string;
4
+ model?: ModelType;
5
+ aspectRatio?: AspectRatio;
6
+ negativePrompt?: string;
7
+ count?: number;
8
+ }
9
+ export interface GeneratedImage {
10
+ base64Data: string;
11
+ mimeType: string;
12
+ }
13
+ export declare function generateImages(options: GenerateImageOptions): Promise<GeneratedImage[]>;
14
+ //# sourceMappingURL=gemini.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../src/gemini.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAExF,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,cAAc,EAAE,CAAC,CAkB3B"}
package/dist/gemini.js ADDED
@@ -0,0 +1,46 @@
1
+ import { GoogleGenAI } from "@google/genai";
2
+ import { getApiKey, MODELS } from "./utils/config.js";
3
+ export async function generateImages(options) {
4
+ const apiKey = getApiKey();
5
+ const genai = new GoogleGenAI({ apiKey });
6
+ const model = options.model || "flash";
7
+ const modelId = MODELS[model];
8
+ const count = options.count || 1;
9
+ const images = [];
10
+ for (let i = 0; i < count; i++) {
11
+ const image = await generateWithGemini(genai, modelId, options);
12
+ if (image) {
13
+ images.push(image);
14
+ }
15
+ }
16
+ return images;
17
+ }
18
+ async function generateWithGemini(genai, modelId, options) {
19
+ let promptText = options.prompt;
20
+ if (options.negativePrompt) {
21
+ promptText += `\n\nAvoid: ${options.negativePrompt}`;
22
+ }
23
+ if (options.aspectRatio && options.aspectRatio !== "1:1") {
24
+ promptText += `\n\nAspect ratio: ${options.aspectRatio}`;
25
+ }
26
+ const response = await genai.models.generateContent({
27
+ model: modelId,
28
+ contents: [{ role: "user", parts: [{ text: promptText }] }],
29
+ config: {
30
+ responseModalities: ["image", "text"],
31
+ },
32
+ });
33
+ const parts = response.candidates?.[0]?.content?.parts;
34
+ if (!parts)
35
+ return null;
36
+ for (const part of parts) {
37
+ if (part.inlineData?.data && part.inlineData.mimeType?.startsWith("image/")) {
38
+ return {
39
+ base64Data: part.inlineData.data,
40
+ mimeType: part.inlineData.mimeType,
41
+ };
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../src/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,EAAoC,MAAM,mBAAmB,CAAC;AAexF,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAA6B;IAE7B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAE1C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC;IACvC,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;IAEjC,MAAM,MAAM,GAAqB,EAAE,CAAC;IAEpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAChE,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,KAAkB,EAClB,OAAe,EACf,OAA6B;IAE7B,IAAI,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAChC,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3B,UAAU,IAAI,cAAc,OAAO,CAAC,cAAc,EAAE,CAAC;IACvD,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;QACzD,UAAU,IAAI,qBAAqB,OAAO,CAAC,WAAW,EAAE,CAAC;IAC3D,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC;QAClD,KAAK,EAAE,OAAO;QACd,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QAC3D,MAAM,EAAE;YACN,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC;SACtC;KACF,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC;IACvD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,UAAU,EAAE,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5E,OAAO;gBACL,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI;gBAChC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ;aACnC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerGenerateImageTool } from "./tools/generate-image.js";
5
+ import { registerListImagesTool } from "./tools/list-images.js";
6
+ const server = new McpServer({
7
+ name: "nanobanana-mcp",
8
+ version: "1.0.0",
9
+ });
10
+ registerGenerateImageTool(server);
11
+ registerListImagesTool(server);
12
+ async function main() {
13
+ const transport = new StdioServerTransport();
14
+ await server.connect(transport);
15
+ console.error("nanobanana-mcp server running on stdio");
16
+ }
17
+ main().catch((error) => {
18
+ console.error("Fatal error:", error);
19
+ process.exit(1);
20
+ });
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,gBAAgB;IACtB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,yBAAyB,CAAC,MAAM,CAAC,CAAC;AAClC,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAE/B,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;AAC1D,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerGenerateImageTool(server: McpServer): void;
3
+ //# sourceMappingURL=generate-image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-image.d.ts","sourceRoot":"","sources":["../../src/tools/generate-image.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAuCzE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6DjE"}
@@ -0,0 +1,85 @@
1
+ import { z } from "zod";
2
+ import { generateImages } from "../gemini.js";
3
+ import { resolveOutputDir, ASPECT_RATIOS } from "../utils/config.js";
4
+ import { saveBase64Image } from "../utils/file.js";
5
+ const GenerateImageSchema = {
6
+ prompt: z
7
+ .string()
8
+ .min(1)
9
+ .max(8192)
10
+ .describe("Image description prompt (1-8192 characters)"),
11
+ model: z
12
+ .enum(["flash", "pro"])
13
+ .optional()
14
+ .default("flash")
15
+ .describe("Model to use: 'flash' (fast, gemini-2.0-flash-exp) or 'pro' (high quality, imagen-3.0)"),
16
+ aspect_ratio: z
17
+ .enum(ASPECT_RATIOS)
18
+ .optional()
19
+ .default("1:1")
20
+ .describe("Image aspect ratio"),
21
+ negative_prompt: z
22
+ .string()
23
+ .optional()
24
+ .describe("Elements to exclude from the image"),
25
+ output_dir: z
26
+ .string()
27
+ .optional()
28
+ .describe("Output directory path (default: ./nanobanana-images)"),
29
+ count: z
30
+ .number()
31
+ .int()
32
+ .min(1)
33
+ .max(4)
34
+ .optional()
35
+ .default(1)
36
+ .describe("Number of images to generate (1-4)"),
37
+ };
38
+ export function registerGenerateImageTool(server) {
39
+ server.tool("generate_image", "Generate images using Google Gemini models. Supports 'flash' (fast, gemini-2.0-flash-exp) and 'pro' (high quality, imagen-3.0) models.", GenerateImageSchema, async (params) => {
40
+ try {
41
+ const outputDir = resolveOutputDir(params.output_dir);
42
+ const images = await generateImages({
43
+ prompt: params.prompt,
44
+ model: params.model,
45
+ aspectRatio: params.aspect_ratio,
46
+ negativePrompt: params.negative_prompt,
47
+ count: params.count,
48
+ });
49
+ if (images.length === 0) {
50
+ return {
51
+ content: [
52
+ {
53
+ type: "text",
54
+ text: "No images were generated. Try a different prompt or model.",
55
+ },
56
+ ],
57
+ };
58
+ }
59
+ const savedPaths = [];
60
+ const contentItems = [];
61
+ for (const image of images) {
62
+ const filePath = saveBase64Image(image.base64Data, outputDir);
63
+ savedPaths.push(filePath);
64
+ contentItems.push({
65
+ type: "image",
66
+ data: image.base64Data,
67
+ mimeType: image.mimeType,
68
+ });
69
+ }
70
+ contentItems.unshift({
71
+ type: "text",
72
+ text: `Generated ${images.length} image(s):\n${savedPaths.map((p, i) => `${i + 1}. ${p}`).join("\n")}\n\nModel: ${params.model || "flash"}\nPrompt: ${params.prompt}${params.negative_prompt ? `\nNegative: ${params.negative_prompt}` : ""}`,
73
+ });
74
+ return { content: contentItems };
75
+ }
76
+ catch (error) {
77
+ const message = error instanceof Error ? error.message : String(error);
78
+ return {
79
+ content: [{ type: "text", text: `Error generating image: ${message}` }],
80
+ isError: true,
81
+ };
82
+ }
83
+ });
84
+ }
85
+ //# sourceMappingURL=generate-image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-image.js","sourceRoot":"","sources":["../../src/tools/generate-image.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerListImagesTool(server: McpServer): void;
3
+ //# sourceMappingURL=list-images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-images.d.ts","sourceRoot":"","sources":["../../src/tools/list-images.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAWzE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8C9D"}
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { resolveOutputDir } from "../utils/config.js";
3
+ import { listImageFiles } from "../utils/file.js";
4
+ const ListImagesSchema = {
5
+ directory: z
6
+ .string()
7
+ .optional()
8
+ .describe("Directory to search for images (default: ./nanobanana-images)"),
9
+ };
10
+ export function registerListImagesTool(server) {
11
+ server.tool("list_images", "List generated images in the specified directory.", ListImagesSchema, async (params) => {
12
+ try {
13
+ const dir = resolveOutputDir(params.directory);
14
+ const files = listImageFiles(dir);
15
+ if (files.length === 0) {
16
+ return {
17
+ content: [
18
+ {
19
+ type: "text",
20
+ text: `No images found in: ${dir}`,
21
+ },
22
+ ],
23
+ };
24
+ }
25
+ const fileList = files
26
+ .map((f) => {
27
+ const sizeKB = (f.size / 1024).toFixed(1);
28
+ return `- ${f.name} (${sizeKB} KB, ${f.createdAt})`;
29
+ })
30
+ .join("\n");
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: `Found ${files.length} image(s) in ${dir}:\n\n${fileList}`,
36
+ },
37
+ ],
38
+ };
39
+ }
40
+ catch (error) {
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ return {
43
+ content: [{ type: "text", text: `Error listing images: ${message}` }],
44
+ isError: true,
45
+ };
46
+ }
47
+ });
48
+ }
49
+ //# sourceMappingURL=list-images.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-images.js","sourceRoot":"","sources":["../../src/tools/list-images.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,gBAAgB,GAAG;IACvB,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,+DAA+D,CAAC;CAC7E,CAAC;AAEF,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,MAAM,CAAC,IAAI,CACT,aAAa,EACb,mDAAmD,EACnD,gBAAgB,EAChB,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC/C,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;YAElC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvB,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,uBAAuB,GAAG,EAAE;yBACnC;qBACF;iBACF,CAAC;YACJ,CAAC;YAED,MAAM,QAAQ,GAAG,KAAK;iBACnB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACT,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBAC1C,OAAO,KAAK,CAAC,CAAC,IAAI,KAAK,MAAM,QAAQ,CAAC,CAAC,SAAS,GAAG,CAAC;YACtD,CAAC,CAAC;iBACD,IAAI,CAAC,IAAI,CAAC,CAAC;YAEd,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,SAAS,KAAK,CAAC,MAAM,gBAAgB,GAAG,QAAQ,QAAQ,EAAE;qBACjE;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GACX,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,OAAO,EAAE,EAAE,CAAC;gBACrE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ export declare function getApiKey(): string;
2
+ export declare const DEFAULT_OUTPUT_DIR: string;
3
+ export declare const MODELS: {
4
+ readonly flash: "gemini-3-flash-preview";
5
+ readonly pro: "gemini-3-pro-preview";
6
+ };
7
+ export type ModelType = keyof typeof MODELS;
8
+ export declare const ASPECT_RATIOS: readonly ["1:1", "16:9", "9:16", "4:3", "3:4"];
9
+ export type AspectRatio = (typeof ASPECT_RATIOS)[number];
10
+ export declare function resolveOutputDir(outputDir?: string): string;
11
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAEA,wBAAgB,SAAS,IAAI,MAAM,CASlC;AAED,eAAO,MAAM,kBAAkB,QAAwD,CAAC;AAExF,eAAO,MAAM,MAAM;;;CAGT,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,MAAM,CAAC;AAE5C,eAAO,MAAM,aAAa,gDAAiD,CAAC;AAC5E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3D"}
@@ -0,0 +1,20 @@
1
+ import path from "node:path";
2
+ export function getApiKey() {
3
+ const key = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
4
+ if (!key) {
5
+ throw new Error("API key not found. Set GEMINI_API_KEY or GOOGLE_API_KEY environment variable.\n" +
6
+ "Get your key at: https://aistudio.google.com/apikey");
7
+ }
8
+ return key;
9
+ }
10
+ export const DEFAULT_OUTPUT_DIR = process.env.IMAGE_OUTPUT_DIR || "./nanobanana-images";
11
+ export const MODELS = {
12
+ flash: "gemini-3-flash-preview",
13
+ pro: "gemini-3-pro-preview",
14
+ };
15
+ export const ASPECT_RATIOS = ["1:1", "16:9", "9:16", "4:3", "3:4"];
16
+ export function resolveOutputDir(outputDir) {
17
+ const dir = outputDir || DEFAULT_OUTPUT_DIR;
18
+ return path.isAbsolute(dir) ? dir : path.resolve(process.cwd(), dir);
19
+ }
20
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,UAAU,SAAS;IACvB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACrE,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,iFAAiF;YAC/E,qDAAqD,CACxD,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,qBAAqB,CAAC;AAExF,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,wBAAwB;IAC/B,GAAG,EAAE,sBAAsB;CACnB,CAAC;AAIX,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAU,CAAC;AAG5E,MAAM,UAAU,gBAAgB,CAAC,SAAkB;IACjD,MAAM,GAAG,GAAG,SAAS,IAAI,kBAAkB,CAAC;IAC5C,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;AACvE,CAAC"}
@@ -0,0 +1,11 @@
1
+ export declare function ensureDir(dirPath: string): void;
2
+ export declare function generateFileName(prefix?: string): string;
3
+ export declare function saveBase64Image(base64Data: string, outputDir: string, fileName?: string): string;
4
+ export interface ImageFileInfo {
5
+ name: string;
6
+ path: string;
7
+ size: number;
8
+ createdAt: string;
9
+ }
10
+ export declare function listImageFiles(dirPath: string): ImageFileInfo[];
11
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/utils/file.ts"],"names":[],"mappings":"AAGA,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI/C;AAED,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAOR;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAyB/D"}
@@ -0,0 +1,42 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export function ensureDir(dirPath) {
4
+ if (!fs.existsSync(dirPath)) {
5
+ fs.mkdirSync(dirPath, { recursive: true });
6
+ }
7
+ }
8
+ export function generateFileName(prefix) {
9
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
10
+ const rand = Math.random().toString(36).substring(2, 8);
11
+ return `${prefix ? prefix + "-" : ""}${timestamp}-${rand}.png`;
12
+ }
13
+ export function saveBase64Image(base64Data, outputDir, fileName) {
14
+ ensureDir(outputDir);
15
+ const name = fileName || generateFileName("nanobanana");
16
+ const filePath = path.join(outputDir, name);
17
+ const buffer = Buffer.from(base64Data, "base64");
18
+ fs.writeFileSync(filePath, buffer);
19
+ return filePath;
20
+ }
21
+ export function listImageFiles(dirPath) {
22
+ if (!fs.existsSync(dirPath)) {
23
+ return [];
24
+ }
25
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
26
+ const imageExtensions = new Set([".png", ".jpg", ".jpeg", ".webp", ".gif"]);
27
+ return entries
28
+ .filter((entry) => entry.isFile() &&
29
+ imageExtensions.has(path.extname(entry.name).toLowerCase()))
30
+ .map((entry) => {
31
+ const fullPath = path.join(dirPath, entry.name);
32
+ const stat = fs.statSync(fullPath);
33
+ return {
34
+ name: entry.name,
35
+ path: fullPath,
36
+ size: stat.size,
37
+ createdAt: stat.birthtime.toISOString(),
38
+ };
39
+ })
40
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt));
41
+ }
42
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/utils/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAe;IAC9C,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACxD,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,IAAI,IAAI,MAAM,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,UAAkB,EAClB,SAAiB,EACjB,QAAiB;IAEjB,SAAS,CAAC,SAAS,CAAC,CAAC;IACrB,MAAM,IAAI,GAAG,QAAQ,IAAI,gBAAgB,CAAC,YAAY,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACjD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnC,OAAO,QAAQ,CAAC;AAClB,CAAC;AASD,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACjE,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAE5E,OAAO,OAAO;SACX,MAAM,CACL,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,MAAM,EAAE;QACd,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC,CAC9D;SACA,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACb,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACnC,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE;SACxC,CAAC;IACJ,CAAC,CAAC;SACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAC5D,CAAC"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@saroby/nanobanana-mcp",
3
+ "version": "1.0.1",
4
+ "description": "MCP server for Google Gemini image generation (Nano Banana)",
5
+ "license": "MIT",
6
+ "author": "saroby",
7
+ "type": "module",
8
+ "bin": {
9
+ "nanobanana-mcp": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc && shx chmod +x dist/index.js",
16
+ "prepare": "npm run build",
17
+ "dev": "tsc --watch"
18
+ },
19
+ "dependencies": {
20
+ "@google/genai": "^1.0.0",
21
+ "@modelcontextprotocol/sdk": "^1.26.0",
22
+ "zod": "^3.23.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22",
26
+ "shx": "^0.3.4",
27
+ "typescript": "^5.8.2"
28
+ }
29
+ }