#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
// API configuration
const API_BASE_URL = process.env.NANANA_API_URL || "https://nanana.app";
interface TextToImageParams {
prompt: string;
}
interface ImageToImageParams {
imageUrls: string[];
prompt: string;
}
// Tool definitions
const TEXT_TO_IMAGE_TOOL: Tool = {
name: "text_to_image",
description:
"Generate an image from a text prompt using Nanana AI. This operation typically takes 15-30 seconds to complete. The tool will wait for generation to finish and return the final image URL.",
inputSchema: {
type: "object",
properties: {
prompt: {
type: "string",
description: "The text prompt describing the image to generate",
},
},
required: ["prompt"],
},
};
const IMAGE_TO_IMAGE_TOOL: Tool = {
name: "image_to_image",
description:
"Transform existing images based on a text prompt using Nanana AI. This operation typically takes 15-30 seconds to complete. The tool will wait for transformation to finish and return the final image URL.",
inputSchema: {
type: "object",
properties: {
imageUrls: {
type: "array",
items: { type: "string" },
description: "Array of image URLs to transform (1-9 images)",
minItems: 1,
maxItems: 9,
},
prompt: {
type: "string",
description: "The text prompt describing how to transform the images",
},
},
required: ["imageUrls", "prompt"],
},
};
// Helper function to download image and convert to base64
async function imageUrlToBase64(url: string): Promise<string> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to download image: ${response.status}`);
}
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
return buffer.toString("base64");
}
// API call functions
async function callTextToImage(
apiToken: string,
params: TextToImageParams
): Promise<{ imageUrl: string; imageBase64: string; prompt: string }> {
const response = await fetch(`${API_BASE_URL}/api/mcp/v1/text-to-image`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt: params.prompt }),
});
if (!response.ok) {
const error = (await response.json().catch(() => ({}))) as {
error?: string;
};
throw new Error(
error.error || `API request failed with status ${response.status}`
);
}
const data = (await response.json()) as { imageUrl: string; prompt: string };
// Download image and convert to base64
// const imageBase64 = await imageUrlToBase64(data.imageUrl);
const imageBase64 = "";
return { imageUrl: data.imageUrl, imageBase64, prompt: data.prompt };
}
async function callImageToImage(
apiToken: string,
params: ImageToImageParams
): Promise<{ imageUrl: string; imageBase64: string; prompt: string }> {
const response = await fetch(`${API_BASE_URL}/api/mcp/v1/image-to-image`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
imageUrls: params.imageUrls,
prompt: params.prompt,
}),
});
if (!response.ok) {
const error = (await response.json().catch(() => ({}))) as {
error?: string;
};
throw new Error(
error.error || `API request failed with status ${response.status}`
);
}
const data = (await response.json()) as { imageUrl: string; prompt: string };
// Download image and convert to base64
const imageBase64 = await imageUrlToBase64(data.imageUrl);
return { imageUrl: data.imageUrl, imageBase64, prompt: data.prompt };
}
// Server setup
async function main() {
const apiToken = process.env.NANANA_API_TOKEN;
if (!apiToken) {
console.error("Error: NANANA_API_TOKEN environment variable is required");
process.exit(1);
}
const server = new Server(
{
name: "mcp-server-nano-banana",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
// Handle tools/list
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [TEXT_TO_IMAGE_TOOL, IMAGE_TO_IMAGE_TOOL],
};
});
// Handle tools/call
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === "text_to_image") {
const params = args as unknown as TextToImageParams;
console.error(
`[MCP] Starting text-to-image generation: "${params.prompt}"`
);
const result = await callTextToImage(apiToken, params);
console.error(`[MCP] Generation completed: ${result.imageUrl}`);
return {
content: [
{
type: "text",
text: `Successfully generated image!\n\nPrompt: ${result.prompt}\n\nImage URL: ${result.imageUrl}`,
},
],
};
} else if (name === "image_to_image") {
const params = args as unknown as ImageToImageParams;
console.error(
`[MCP] Starting image-to-image transformation: "${params.prompt}"`
);
const result = await callImageToImage(apiToken, params);
console.error(`[MCP] Transformation completed: ${result.imageUrl}`);
return {
content: [
{
type: "text",
text: `Successfully transformed image!\n\nPrompt: ${result.prompt}\n\nImage URL: ${result.imageUrl}`,
},
],
};
} else {
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
return {
content: [
{
type: "text",
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
});
// Start server with stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Nanana AI MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});