@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 +60 -0
- package/dist/gemini.d.ts +14 -0
- package/dist/gemini.d.ts.map +1 -0
- package/dist/gemini.js +46 -0
- package/dist/gemini.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/generate-image.d.ts +3 -0
- package/dist/tools/generate-image.d.ts.map +1 -0
- package/dist/tools/generate-image.js +85 -0
- package/dist/tools/generate-image.js.map +1 -0
- package/dist/tools/list-images.d.ts +3 -0
- package/dist/tools/list-images.d.ts.map +1 -0
- package/dist/tools/list-images.js +49 -0
- package/dist/tools/list-images.js.map +1 -0
- package/dist/utils/config.d.ts +11 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +20 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/file.d.ts +11 -0
- package/dist/utils/file.d.ts.map +1 -0
- package/dist/utils/file.js +42 -0
- package/dist/utils/file.js.map +1 -0
- package/package.json +29 -0
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
|
package/dist/gemini.d.ts
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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
|
+
}
|