#
tokens: 5225/50000 10/10 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   └── workflows
│       └── npm-publish.yml
├── .gitignore
├── example-client
│   ├── package-lock.json
│   ├── package.json
│   ├── README.md
│   ├── src
│   │   └── index.ts
│   └── tsconfig.json
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules/
build/
*.log
.env*
.vscode/

```

--------------------------------------------------------------------------------
/example-client/README.md:
--------------------------------------------------------------------------------

```markdown
## Example client to call the `generate_image` tool

This example demonstrates how to call the `generate_image` tool from terminal.

### Prerequisites

- build the MCP server
```bash
cd mcp-server-amazon-bedrock
npm install
npm run build
```

### Usage

```bash
cd example-client
npm install
npm run build
node build/index.js
```
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/zxkane-mcp-server-amazon-bedrock-badge.png)](https://mseep.ai/app/zxkane-mcp-server-amazon-bedrock)

# Amazon Bedrock MCP Server

A Model Control Protocol (MCP) server that integrates with Amazon Bedrock's Nova Canvas model for AI image generation.

<a href="https://glama.ai/mcp/servers/9qw7dwpvj9"><img width="380" height="200" src="https://glama.ai/mcp/servers/9qw7dwpvj9/badge" alt="Amazon Bedrock Server MCP server" /></a>

## Features

- High-quality image generation from text descriptions using Amazon's Nova Canvas model
- Advanced control through negative prompts to refine image composition
- Flexible configuration options for image dimensions and quality
- Deterministic image generation with seed control
- Robust input validation and error handling

## Prerequisites

1. Active AWS account with Amazon Bedrock and Nova Canvas model access
2. Properly configured AWS credentials with required permissions
3. Node.js version 18 or later

## Installation

### AWS Credentials Configuration

The server requires AWS credentials with appropriate Amazon Bedrock permissions. Configure these using one of the following methods:

1. Environment variables:
   ```bash
   export AWS_ACCESS_KEY_ID=your_access_key
   export AWS_SECRET_ACCESS_KEY=your_secret_key
   export AWS_REGION=us-east-1  # or your preferred region
   ```

2. AWS credentials file (`~/.aws/credentials`):
   ```ini
   [the_profile_name]
   aws_access_key_id = your_access_key
   aws_secret_access_key = your_secret_key
   ```
   Environment variable for active profile:
   ```bash
   export AWS_PROFILE=the_profile_name
   ```

3. IAM role (when deployed on AWS infrastructure)

### Claude Desktop Integration

To integrate with Claude Desktop, add the following configuration to your settings file:

MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
Windows: `%APPDATA%/Claude/claude_desktop_config.json`

```json
{
  "mcpServers": {
    "amazon-bedrock": {
      "command": "npx",
      "args": [
        "-y",
        "@zxkane/mcp-server-amazon-bedrock"
      ],
      "env": {
        "AWS_PROFILE": "your_profile_name",         // Optional, only if you want to use a specific profile
        "AWS_ACCESS_KEY_ID": "your_access_key",     // Optional if using AWS credentials file or IAM role
        "AWS_SECRET_ACCESS_KEY": "your_secret_key", // Optional if using AWS credentials file or IAM role
        "AWS_REGION": "us-east-1"                   // Optional, defaults to 'us-east-1'
      }
    }
  }
}
```

## Available Tools

### generate_image

Creates images from text descriptions using Amazon Bedrock's Nova Canvas model.

#### Parameters

- `prompt` (required): Descriptive text for the desired image (1-1024 characters)
- `negativePrompt` (optional): Elements to exclude from the image (1-1024 characters)
- `width` (optional): Image width in pixels (default: 1024)
- `height` (optional): Image height in pixels (default: 1024)
- `quality` (optional): Image quality level - "standard" or "premium" (default: "standard")
- `cfg_scale` (optional): Prompt adherence strength (1.1-10, default: 6.5)
- `seed` (optional): Generation seed for reproducibility (0-858993459, default: 12)
- `numberOfImages` (optional): Batch size for generation (1-5, default: 1)

#### Example Implementation

```typescript
const result = await callTool('generate_image', {
  prompt: "A serene mountain landscape at sunset",
  negativePrompt: "people, buildings, vehicles",
  quality: "premium",
  cfg_scale: 8,
  numberOfImages: 2
});
```

#### Prompt Guidelines

For optimal results, avoid negative phrasing ("no", "not", "without") in the main prompt. Instead, move these elements to the `negativePrompt` parameter. For example, rather than using "a landscape without buildings" in the prompt, use "buildings" in the `negativePrompt`.

For detailed usage guidelines, refer to the [Nova Canvas documentation][nova-canvas-doc].

## Development

To set up and run the server in a local environment:

```bash
git clone https://github.com/zxkane/mcp-server-amazon-bedrock.git
cd mcp-server-amazon-bedrock
npm install
npm run build
```

### Performance Considerations

Generation time is influenced by resolution (`width` and `height`), `numberOfImages`, and `quality` settings. When using higher values, be mindful of potential timeout implications in your implementation.

## License

This project is licensed under the MIT License - see the LICENSE file for details.

[nova-canvas-doc]: https://docs.aws.amazon.com/nova/latest/userguide/image-gen-access.html

```

--------------------------------------------------------------------------------
/example-client/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "example-client",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node build/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "terminal-image": "^3.0.0",
    "typescript": "^5.0.0"
  }
}

```

--------------------------------------------------------------------------------
/example-client/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ES2020",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "build",
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------

```yaml
name: Publish to NPM

on:
  release:
    types: [created]

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://registry.npmjs.org'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
      
      - name: Publish to NPM
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.npm_token }} 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@zxkane/mcp-server-amazon-bedrock",
  "version": "0.1.1",
  "description": "Use Amazon Bedrock Nova models to generate image.",
  "author": "Kane Zhu",
  "type": "module",
  "bin": {
    "mcp-server-amazon-bedrock": "./build/index.js"
  },
  "files": [
    "build"
  ],
  "publishConfig": {
    "access": "public"
  },
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
    "prepare": "npm run build",
    "watch": "tsc --watch",
    "inspector": "npx @modelcontextprotocol/inspector build/index.js"
  },
  "dependencies": {
    "@aws-sdk/client-bedrock-runtime": "^3.716.0",
    "@aws-sdk/credential-providers": "^3.716.0",
    "@modelcontextprotocol/sdk": "0.6.0",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@types/node": "^20.11.24",
    "typescript": "^5.3.3"
  },
  "keywords": [
    "mcp",
    "amazon",
    "bedrock",
    "ai",
    "image-generation"
  ],
  "license": "MIT"
}

```

--------------------------------------------------------------------------------
/example-client/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import path from 'path';
import { fileURLToPath } from 'url';
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import {
  ListToolsResultSchema,
  CallToolResultSchema
} from "@modelcontextprotocol/sdk/types.js";
import terminalImage from "terminal-image";

async function main() {
  // Get the current file's directory
  const __filename = fileURLToPath(import.meta.url);
  const __dirname = path.dirname(__filename);

  // Initialize the client
  const client = new Client(
    {
      name: "example-bedrock-client",
      version: "1.0.0",
    },
    {
      capabilities: {},
    }
  );

  try {
    // Use path.resolve to get the correct path to the server script
    const transport = new StdioClientTransport({
      command: path.resolve(__dirname, "../../build/index.js"),
      env: {
        ...process.env, // Pass all environment variables
        NODE_ENV: process.env.NODE_ENV || 'development',
      }
    });
    await client.connect(transport);
    console.log("Connected to Amazon Bedrock MCP server");

    // List available tools
    const tools = await client.request(
      { method: "tools/list" },
      ListToolsResultSchema
    );
    console.log("Available tools:", tools);

    // Log start time
    const startTime = new Date();
    console.log(`Image Generation Request started at: ${startTime.toISOString()}`);

    // Example: Generate an image using the generate_image tool
    const imageResult = await client.request(
      {
        method: "tools/call",
        params: {
          name: "generate_image",
          arguments: {
            prompt: "A serene landscape with mountains and a lake at sunset",
            width: 1024,
            height: 1024,
            quality: "standard", 
            cfg_scale: 7,
            seed: 42,
            numberOfImages: 2,
          },
        },
      },
      CallToolResultSchema
    );
    // Log end time and duration
    const endTime = new Date();
    const duration = endTime.getTime() - startTime.getTime();

    if (imageResult.content && imageResult.content.length > 0) {
      for (const content of imageResult.content) {
        if (content.type === "image") {
          console.log("Image generated successfully!");
          try {
            const buffer = Buffer.from(content.data, 'base64');
            console.log(await terminalImage.buffer(buffer));
          } catch (err) {
            console.error('Error displaying image:', err);
          }
        } else if (content.type === "text") {
          console.log("Text generated:", content.text);
        }
      }
    }

    console.log(`Request completed at: ${endTime.toISOString()}`);
    console.log(`Total duration: ${duration}ms`);

    // Clean up
    await client.close();
  } catch (error) {
    console.error("Error:", error);
    process.exit(1);
  }
}

// Handle interrupts
process.on("SIGINT", () => {
  console.log("Interrupted, exiting...");
  process.exit(0);
});

main().catch(console.error);

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
} from "@modelcontextprotocol/sdk/types.js";
import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime";
import { NodeHttpHandler } from "@smithy/node-http-handler";
import { fromNodeProviderChain, fromIni } from "@aws-sdk/credential-providers";
import { z } from "zod";

const AWS_REGION = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1';
const AWS_PROFILE = process.env.AWS_PROFILE || 'default';

// Log AWS configuration for debugging
console.error('AWS Configuration:', {
  region: AWS_REGION,
  profile: AWS_PROFILE,
  hasAccessKeyId: !!process.env.AWS_ACCESS_KEY_ID,
  hasSecretKey: !!process.env.AWS_SECRET_ACCESS_KEY,
  hasSessionToken: !!process.env.AWS_SESSION_TOKEN
});

// Initialize Bedrock client with comprehensive configuration including credentials and timeouts
const bedrock = new BedrockRuntimeClient({
  region: AWS_REGION,
  maxAttempts: 3,
  // Try explicit profile credentials first, then fall back to provider chain
  credentials: async () => {
    try {
      // First try loading from profile
      const profileCreds = await fromIni({ profile: AWS_PROFILE })();
      console.error('Successfully loaded credentials from profile');
      return profileCreds;
    } catch (error) {
      console.error('Failed to load profile credentials, falling back to provider chain:', error);
      try {
        // Fall back to provider chain
        const chainCreds = await fromNodeProviderChain()();
        console.error('Successfully loaded credentials from provider chain');
        return chainCreds;
      } catch (error) {
        console.error('Failed to load credentials from provider chain:', error);
        throw error;
      }
    }
  },
  requestHandler: new NodeHttpHandler({
    connectionTimeout: 10000, // 10 seconds connection timeout
    requestTimeout: 300000, // 5 minutes request timeout
  })
});

// Log Bedrock client initialization
console.error('Bedrock client initialized');

// Constants
const NOVA_MODEL_ID = 'amazon.nova-canvas-v1:0';

// Input validation schemas based on AWS Nova documentation
const GenerateImageSchema = z.object({
  prompt: z.string().min(1).max(1024, "Prompt must be 1-1024 characters"),
  negativePrompt: z.string().min(1).max(1024, "Negative prompt must be 1-1024 characters").optional(),
  width: z.number().int()
    .min(320, "Width must be at least 320 pixels")
    .max(4096, "Width must be at most 4096 pixels")
    .refine(val => val % 16 === 0, "Width must be divisible by 16")
    .default(1024),
  height: z.number().int()
    .min(320, "Height must be at least 320 pixels")
    .max(4096, "Height must be at most 4096 pixels")
    .refine(val => val % 16 === 0, "Height must be divisible by 16")
    .default(1024),
  quality: z.enum(["standard", "premium"]).default("standard"),
  cfg_scale: z.number().min(1.1).max(10).default(6.5),
  seed: z.number().int().min(0).max(858993459).default(12),
  numberOfImages: z.number().int().min(1).max(5).default(1)
}).refine(
  (data) => {
    // Check aspect ratio between 1:4 and 4:1
    const ratio = data.width / data.height;
    return ratio >= 0.25 && ratio <= 4;
  },
  "Aspect ratio must be between 1:4 and 4:1"
).refine(
  (data) => {
    // Check total pixel count
    return data.width * data.height < 4194304;
  },
  "Total pixel count must be less than 4,194,304"
);

type GenerateImageInput = z.infer<typeof GenerateImageSchema>;

// Constants for image generation
const GENERATION_TYPES = {
  TEXT_TO_IMAGE: "TEXT_IMAGE",
};

/**
 * Create an MCP server for Amazon Bedrock image generation
 */
const server = new Server(
  {
    name: "mcp-server-amazon-bedrock",
    version: "0.1.0",
  },
  {
    capabilities: {
      tools: {},
      logging: {},
    },
  }
);

/**
 * Handler that lists available tools.
 * Exposes a single "generate_image" tool that generates images using Amazon Bedrock.
 */
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "generate_image",
        description: "Generate image(s) using Amazon Nova Canvas model. The returned data is Base64-encoded string that represent each image that was generated.",
        inputSchema: {
          type: "object",
          properties: {
            prompt: {
              type: "string",
              description: "Text description of the image to generate (1-1024 characters)",
            },
            negativePrompt: {
              type: "string",
              description: "Optional text description of what to avoid in the image (1-1024 characters)",
            },
            width: {
              type: "number",
              description: "Width of the generated image (default: 1024)",
            },
            height: {
              type: "number",
              description: "Height of the generated image (default: 1024)",
            },
            quality: {
              type: "string",
              enum: ["standard", "premium"],
              description: "Quality of the generated image (default: standard)",
            },
            cfg_scale: {
              type: "number",
              description: "How closely to follow the prompt (1.1-10, default: 6.5)",
            },
            seed: {
              type: "number",
              description: "Seed for reproducible generation (0-858993459, default: 12)",
            },
            numberOfImages: {
              type: "number",
              description: "Number of images to generate (1-5, default: 1)",
            },
          },
          required: ["prompt"],
        },
      },
    ],
  };
});

interface BedrockResponse {
  images: string[];
  error?: string;
}
/**
 * Handler for the generate_image tool.
 * Uses Amazon Bedrock to generate an image based on the provided parameters.
 */
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name !== "generate_image") {
    throw new McpError(
      ErrorCode.MethodNotFound,
      `Unknown tool: ${request.params.name}`
    );
  }

  try {
    // Validate and parse input
    const args = GenerateImageSchema.parse(request.params.arguments);
    
    server.sendLoggingMessage({
      level: "info",
      data: `Configuration: ${JSON.stringify({
        width: args.width,
        height: args.height,
        quality: args.quality,
        numberOfImages: args.numberOfImages,
        cfgScale: args.cfg_scale,
        seed: args.seed
      })}`,
    });

    const progressToken = request.params._meta?.progressToken;

    server.sendLoggingMessage({
      level: "info",
      data: "Sending request to Bedrock API...",
    });

    const response = await bedrock.send(new InvokeModelCommand({
      modelId: NOVA_MODEL_ID,
      contentType: "application/json",
      accept: "application/json",
      body: JSON.stringify({
        taskType: GENERATION_TYPES.TEXT_TO_IMAGE,
        textToImageParams: {
          text: args.prompt,
          negativeText: args.negativePrompt || undefined,
        },
        imageGenerationConfig: {
          numberOfImages: args.numberOfImages,
          height: args.height,
          width: args.width,
          quality: args.quality,
          cfgScale: args.cfg_scale,
          seed: args.seed
        },
      }),
    }));

    server.sendLoggingMessage({
      level: "info",
      data: "Received response from Bedrock API",
    });

    if (!response.body) {
      server.sendLoggingMessage({
        level: "error",
        data: "No response body received from Bedrock",
      });
      throw new McpError(
        ErrorCode.InternalError,
        "No response body received from Bedrock"
      );
    }

    const responseBody = JSON.parse(new TextDecoder().decode(response.body)) as BedrockResponse;

    if (!responseBody.images || responseBody.images.length === 0) {
      server.sendLoggingMessage({
        level: "error",
        data: "No image data in response",
      });
      throw new McpError(
        ErrorCode.InternalError,
        `No image data in response due to ${responseBody.error}.`
      );
    }

    server.sendLoggingMessage({
      level: "info",
      data: "Successfully generated image",
    });

    // Return the response in the correct MCP format
    return {
      content: [
        {
          type: "text",
          text: `This is the image generated for your request '${args.prompt}'.`,
        },
        ...responseBody.images.map(image => ({
          type: "image",
          data: image as string,
          mimeType: "image/png",
        })),
        {
          type: "text",
          text: "This is the end of the image generation.",
        }
      ],
    };
  } catch (error) {
    console.error('Error:', error);

    // Handle Zod validation errors
    if (error instanceof z.ZodError) {
      throw new McpError(
        ErrorCode.InvalidParams,
        `Invalid parameters: ${error.errors.map(e => e.message).join(", ")}`
      );
    }

    // Handle AWS Bedrock errors
    if (error instanceof Error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to generate image: ${error.message}`
      );
    }

    // Handle unknown errors
    throw new McpError(
      ErrorCode.InternalError,
      "An unexpected error occurred"
    );
  }
});

/**
 * Start the server using stdio transport.
 */
async function main() {
  try {
    const transport = new StdioServerTransport();
    await server.connect(transport);
    console.error('Amazon Bedrock MCP server running on stdio');
  } catch (error) {
    console.error('Failed to start MCP server:', error);
    throw error;
  }
}

// Error handling
server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
  await server.close();
  process.exit(0);
});

main().catch((error) => {
  console.error("Server error:", error);
  process.exit(1);
});

```