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

```
├── .dockerignore
├── .gitignore
├── Dockerfile
├── docs
│   └── API_SETUP.md
├── LICENSE
├── package.json
├── README.md
├── src
│   ├── api
│   │   └── client.ts
│   ├── index.ts
│   └── types.ts
└── tsconfig.json
```

# Files

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

```
node_modules/
build/
dist/
.env
.env.local
.vscode/
.idea/
*.log
.DS_Store
```

--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------

```
# Node modules and package-lock.json (will be installed in container)
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build output (will be built in container)
build
dist

# Development and IDE files
.git
.gitignore
README.md
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Logs
logs
*.log

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# ESLint cache
.eslintcache

# Optional npm cache directory
.npm

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt

# Storybook build outputs
.out
.storybook-out

# Temporary folders
tmp/
temp/

# Test files
test.txt

```

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

```markdown
# Freepik MCP Server

An MCP server implementation for interacting with Freepik's API, providing access to stock photos and Mystic AI image generation capabilities.

## Features

- Search Freepik resources (photos, vectors, PSDs)
- Get detailed resource information
- Download resources
- Generate images using Mystic AI
- Check image generation status

## Prerequisites

- Node.js 18 or higher
- A Freepik API key (see [API Setup Guide](docs/API_SETUP.md))

## Installation

```bash
# Create a new directory for your MCP servers
mkdir mcp-servers
cd mcp-servers

# Clone the repository
git clone https://github.com/MCERQUA/freepik-mcp.git
cd freepik-mcp

# Install dependencies
npm install

# Build the server
npm run build
```

## Configuration

1. First, obtain your Freepik API key by following the instructions in [API_SETUP.md](docs/API_SETUP.md)

2. Add the server to your MCP settings file:

```json
{
  "mcpServers": {
    "freepik": {
      "command": "node",
      "args": ["path/to/freepik-mcp/build/index.js"],
      "env": {
        "FREEPIK_API_KEY": "your-api-key-here"
      },
      "disabled": false,
      "autoApprove": []
    }
  }
}
```

## Available Tools

### search_resources
Search for Freepik resources with various filters:
```typescript
{
  term?: string;          // Search term
  limit?: number;         // Results per page
  order?: 'relevance' | 'recent';
  filters?: {
    orientation?: {
      landscape?: boolean;
      portrait?: boolean;
      square?: boolean;
      panoramic?: boolean;
    };
    content_type?: {
      photo?: boolean;
      psd?: boolean;
      vector?: boolean;
    };
    license?: {
      freemium?: boolean;
      premium?: boolean;
    };
  };
}
```

### get_resource
Get detailed information about a specific resource:
```typescript
{
  id: number;  // Resource ID to get details for
}
```

### download_resource
Get download URL for a specific resource:
```typescript
{
  id: number;  // Resource ID to download
}
```

### generate_image
Generate an image using Freepik Mystic AI:
```typescript
{
  prompt: string;         // Text description of the image to generate
  resolution?: '2k' | '4k';
  aspect_ratio?: 'square_1_1' | 'classic_4_3' | 'traditional_3_4' | 
                 'widescreen_16_9' | 'social_story_9_16';
  realism?: boolean;      // Enable realistic style
  engine?: 'automatic' | 'magnific_illusio' | 'magnific_sharpy' | 'magnific_sparkle';
  creative_detailing?: number;  // 0-100
}
```

### check_status
Check the status of a Mystic image generation task:
```typescript
{
  task_id: string;  // ID of the generation task to check
}
```

## Development

```bash
# Install dependencies
npm install

# Build the server
npm run build

# Run in development mode
npm run dev
```

## Error Handling

The server implements comprehensive error handling:

- API errors are logged with detailed information
- Input validation using Zod schemas
- Proper error responses with context
- Rate limiting awareness

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

MIT

```

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

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

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

```json
{
  "name": "freepik-mcp",
  "version": "0.1.0",
  "description": "MCP server for interacting with Freepik's API",
  "type": "module",
  "main": "build/index.js",
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
    "dev": "tsc -w",
    "start": "node build/index.js",
    "lint": "eslint . --ext .ts",
    "format": "prettier --write 'src/**/*.ts'"
  },
  "keywords": [
    "mcp",
    "freepik",
    "api",
    "stock-photos",
    "ai-generation"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.15.0",
    "axios": "^1.6.0",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "eslint": "^8.0.0",
    "prettier": "^3.0.0",
    "typescript": "^5.0.0"
  }
}
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Multi-stage build for optimal image size
FROM node:20-alpine AS builder

# Set working directory
WORKDIR /app

# Copy package files
COPY package*.json ./
COPY tsconfig.json ./

# Install dependencies (including dev dependencies for build)
RUN npm ci

# Copy source code
COPY src/ ./src/

# Build the application
RUN npm run build

# Production stage
FROM node:20-alpine AS production

# Create app directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install only production dependencies
RUN npm ci --only=production && npm cache clean --force

# Copy built application from builder stage
COPY --from=builder /app/build ./build

# Create non-root user for security
RUN addgroup -g 1001 -S nodejs && \
    adduser -S mcp -u 1001

# Change ownership of the app directory
RUN chown -R mcp:nodejs /app
USER mcp

# Expose the port the app runs on (if applicable)
# Note: MCP servers typically use stdio, but this is here for potential HTTP usage
EXPOSE 3000

# Health check (optional, can be customized based on your server's health endpoint)
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "process.exit(0)" || exit 1

# Start the application
CMD ["node", "build/index.js"]

```

--------------------------------------------------------------------------------
/docs/API_SETUP.md:
--------------------------------------------------------------------------------

```markdown
# Freepik API Setup Guide

## Getting Your API Key

1. Create a Freepik Account
   - Visit [Freepik.com](https://www.freepik.com)
   - Click "Sign up" and create an account
   - Verify your email address

2. Access the Developer Portal
   - Log in to your Freepik account
   - Visit the [API Documentation](https://developer.freepik.com/)
   - Click "Get API Key"

3. Create an API Key
   - Fill out the required information about your intended API usage
   - Accept the terms of service
   - Your API key will be generated and displayed

## Configuring the MCP Server

1. Locate your MCP settings file:
   - VSCode extension: `~/.vscode/extensions/your-extension/settings/mcp_settings.json`
   - Claude desktop app: `~/Library/Application Support/Claude/claude_desktop_config.json`

2. Add the Freepik MCP server configuration:
   ```json
   {
     "mcpServers": {
       "freepik": {
         "command": "node",
         "args": ["path/to/freepik-mcp/build/index.js"],
         "env": {
           "FREEPIK_API_KEY": "your-api-key-here"
         },
         "disabled": false,
         "autoApprove": []
       }
     }
   }
   ```

3. Replace `your-api-key-here` with your actual Freepik API key

## Security Best Practices

1. Keep your API key secure:
   - Never commit your API key to version control
   - Use environment variables or secure configuration management
   - Rotate your API key periodically

2. Monitor API usage:
   - Keep track of your API consumption
   - Set up alerts for unusual activity
   - Review API logs regularly

3. Rate Limiting:
   - Be aware of Freepik's API rate limits
   - Implement appropriate error handling
   - Cache responses when possible

## Troubleshooting

If you encounter issues:

1. Verify your API key is correct
2. Check your network connection
3. Ensure your API key has the necessary permissions
4. Review Freepik's API documentation for any changes
5. Check the server logs for detailed error messages

For additional help, visit the [Freepik Developer Support](https://developer.freepik.com/support)
```

--------------------------------------------------------------------------------
/src/api/client.ts:
--------------------------------------------------------------------------------

```typescript
import axios, { AxiosInstance } from 'axios';
import { 
  FreepikConfig, 
  GenerateImageParams, 
  GenerateImageResponse, 
  CheckStatusResponse,
  SearchResourcesParams,
  SearchResourcesResponse,
  ResourceResponse
} from '../types.js';

export class FreepikClient {
  private client: AxiosInstance;
  private baseUrl = 'https://api.freepik.com';

  constructor(config: FreepikConfig) {
    this.client = axios.create({
      baseURL: this.baseUrl,
      headers: {
        'x-freepik-api-key': config.apiKey,
        'Content-Type': 'application/json'
      }
    });

    // Add response interceptor for error handling
    this.client.interceptors.response.use(
      response => response,
      error => {
        if (error.response) {
          const { status, data } = error.response;
          console.error(`[Freepik API Error] Status: ${status}, Message:`, data);
        }
        throw error;
      }
    );
  }

  // Stock Photo API methods
  async searchResources(params: SearchResourcesParams): Promise<SearchResourcesResponse> {
    console.error('[Freepik] Searching resources with params:', params);
    const response = await this.client.get<SearchResourcesResponse>('/v1/resources', { params });
    return response.data;
  }

  async getResourceDetails(id: number): Promise<ResourceResponse> {
    console.error(`[Freepik] Getting resource details for id: ${id}`);
    const response = await this.client.get<ResourceResponse>(`/v1/resources/${id}`);
    return response.data;
  }

  async downloadResource(id: number): Promise<{ url: string }> {
    console.error(`[Freepik] Downloading resource id: ${id}`);
    const response = await this.client.get<{ url: string }>(`/v1/resources/${id}/download`);
    return response.data;
  }

  // Mystic API methods
  async generateImage(params: GenerateImageParams): Promise<GenerateImageResponse> {
    console.error('[Freepik] Generating image with params:', params);
    const response = await this.client.post<GenerateImageResponse>('/v1/ai/mystic', params);
    return response.data;
  }

  async checkStatus(taskId: string): Promise<CheckStatusResponse> {
    console.error(`[Freepik] Checking status for task: ${taskId}`);
    const response = await this.client.get<CheckStatusResponse>(`/v1/ai/mystic/${taskId}`);
    return response.data;
  }
}
```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

// Common types
export interface FreepikConfig {
  apiKey: string;
}

// Stock Photo types
export interface SearchResourcesParams {
  term?: string;
  page?: number;
  limit?: number;
  order?: 'relevance' | 'recent';
  filters?: {
    orientation?: {
      landscape?: boolean;
      portrait?: boolean;
      square?: boolean;
      panoramic?: boolean;
    };
    content_type?: {
      photo?: boolean;
      psd?: boolean;
      vector?: boolean;
    };
    license?: {
      freemium?: boolean;
      premium?: boolean;
    };
    people?: {
      include?: boolean;
      exclude?: boolean;
      number?: '1' | '2' | '3' | 'more_than_three';
      age?: 'infant' | 'child' | 'teen' | 'young-adult' | 'adult' | 'senior' | 'elder';
      gender?: 'male' | 'female';
      ethnicity?: 'south-asian' | 'middle-eastern' | 'east-asian' | 'black' | 'hispanic' | 'indian' | 'white' | 'multiracial';
    };
    color?: 'black' | 'blue' | 'gray' | 'green' | 'orange' | 'red' | 'white' | 'yellow' | 'purple' | 'cyan' | 'pink';
  };
}

export interface ResourceResponse {
  id: number;
  title: string;
  url: string;
  filename: string;
  licenses: Array<{
    type: 'freemium' | 'premium';
    url: string;
  }>;
  image: {
    type: 'photo' | 'vector' | 'psd';
    orientation: 'horizontal' | 'vertical' | 'square' | 'panoramic' | 'unknown';
    source: {
      url: string;
      key: string;
      size: string;
    };
  };
  author: {
    id: number;
    name: string;
    avatar: string;
    assets: number;
    slug: string;
  };
  stats: {
    downloads: number;
    likes: number;
  };
}

export interface SearchResourcesResponse {
  data: ResourceResponse[];
  meta: {
    current_page: number;
    last_page: number;
    per_page: number;
    total: number;
  };
}

// Zod schemas for validation
export const SearchResourcesSchema = z.object({
  term: z.string().optional(),
  page: z.number().min(1).optional(),
  limit: z.number().min(1).optional(),
  order: z.enum(['relevance', 'recent']).optional(),
  filters: z.object({
    orientation: z.object({
      landscape: z.boolean().optional(),
      portrait: z.boolean().optional(),
      square: z.boolean().optional(),
      panoramic: z.boolean().optional()
    }).optional(),
    content_type: z.object({
      photo: z.boolean().optional(),
      psd: z.boolean().optional(),
      vector: z.boolean().optional()
    }).optional(),
    license: z.object({
      freemium: z.boolean().optional(),
      premium: z.boolean().optional()
    }).optional()
  }).optional()
});

export const GetResourceSchema = z.object({
  id: z.number().min(1)
});

export const DownloadResourceSchema = z.object({
  id: z.number().min(1)
});

// Mystic types
export interface GenerateImageParams {
  prompt: string;
  resolution?: '2k' | '4k';
  aspect_ratio?: 'square_1_1' | 'classic_4_3' | 'traditional_3_4' | 'widescreen_16_9' | 'social_story_9_16';
  structure_reference?: string;
  style_reference?: string;
  realism?: boolean;
  engine?: 'automatic' | 'magnific_illusio' | 'magnific_sharpy' | 'magnific_sparkle';
  creative_detailing?: number;
  filter_nsfw?: boolean;
}

export interface GenerateImageResponse {
  task_id: string;
  status: string;
}

export interface CheckStatusResponse {
  status: string;
  generated?: string[];
}

// Mystic Zod schemas
export const GenerateImageSchema = z.object({
  prompt: z.string().min(1),
  resolution: z.enum(['2k', '4k']).optional(),
  aspect_ratio: z.enum([
    'square_1_1',
    'classic_4_3',
    'traditional_3_4',
    'widescreen_16_9',
    'social_story_9_16'
  ]).optional(),
  structure_reference: z.string().optional(),
  style_reference: z.string().optional(),
  realism: z.boolean().optional(),
  engine: z.enum(['automatic', 'magnific_illusio', 'magnific_sharpy', 'magnific_sparkle']).optional(),
  creative_detailing: z.number().min(0).max(100).optional(),
  filter_nsfw: z.boolean().optional()
});

export const CheckStatusSchema = z.object({
  task_id: z.string()
});
```

--------------------------------------------------------------------------------
/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 { FreepikClient } from './api/client.js';
import { 
  GenerateImageSchema, 
  CheckStatusSchema,
  SearchResourcesSchema,
  GetResourceSchema,
  DownloadResourceSchema
} from './types.js';

const API_KEY = process.env.FREEPIK_API_KEY;
if (!API_KEY) {
  throw new Error('FREEPIK_API_KEY environment variable is required');
}

class FreepikServer {
  private server: Server;
  private client: FreepikClient;

  constructor() {
    this.server = new Server(
      {
        name: 'freepik-mcp',
        version: '0.1.0',
      },
      {
        capabilities: {
          tools: {},
          resources: {},
        },
      }
    );

    this.client = new FreepikClient({ apiKey: API_KEY });

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

  private setupToolHandlers() {
    // List available tools - This is the key method for inspection
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'search_resources',
          description: 'Search for Freepik resources (photos, vectors, PSDs) with filters',
          inputSchema: {
            type: 'object',
            properties: {
              term: {
                type: 'string',
                description: 'Search term'
              },
              limit: {
                type: 'number',
                description: 'Limit results per page (default: 20, max: 200)',
                default: 20,
                maximum: 200
              },
              order: {
                type: 'string',
                enum: ['relevance', 'recent'],
                description: 'Sort order',
                default: 'relevance'
              },
              filters: {
                type: 'object',
                description: 'Optional filters to apply to search results',
                properties: {
                  orientation: {
                    type: 'object',
                    description: 'Filter by image orientation',
                    properties: {
                      landscape: { type: 'boolean', description: 'Include landscape orientation' },
                      portrait: { type: 'boolean', description: 'Include portrait orientation' },
                      square: { type: 'boolean', description: 'Include square orientation' },
                      panoramic: { type: 'boolean', description: 'Include panoramic orientation' }
                    }
                  },
                  content_type: {
                    type: 'object',
                    description: 'Filter by content type',
                    properties: {
                      photo: { type: 'boolean', description: 'Include photos' },
                      psd: { type: 'boolean', description: 'Include PSDs' },
                      vector: { type: 'boolean', description: 'Include vectors' }
                    }
                  },
                  license: {
                    type: 'object',
                    description: 'Filter by license type',
                    properties: {
                      freemium: { type: 'boolean', description: 'Include freemium resources' },
                      premium: { type: 'boolean', description: 'Include premium resources' }
                    }
                  }
                }
              }
            },
            required: ['term']
          }
        },
        {
          name: 'get_resource',
          description: 'Get detailed information about a specific Freepik resource',
          inputSchema: {
            type: 'object',
            properties: {
              id: {
                type: 'number',
                description: 'Resource ID to get details for'
              }
            },
            required: ['id']
          }
        },
        {
          name: 'download_resource',
          description: 'Get download URL for a specific Freepik resource',
          inputSchema: {
            type: 'object',
            properties: {
              id: {
                type: 'number',
                description: 'Resource ID to download'
              }
            },
            required: ['id']
          }
        },
        {
          name: 'generate_image',
          description: 'Generate an AI image using Freepik Mystic (requires Freepik Premium)',
          inputSchema: {
            type: 'object',
            properties: {
              prompt: {
                type: 'string',
                description: 'Text description of the image to generate'
              },
              resolution: {
                type: 'string',
                enum: ['2k', '4k'],
                description: 'Image resolution',
                default: '2k'
              },
              aspect_ratio: {
                type: 'string',
                enum: ['square_1_1', 'classic_4_3', 'traditional_3_4', 'widescreen_16_9', 'social_story_9_16'],
                description: 'Image aspect ratio',
                default: 'square_1_1'
              },
              realism: {
                type: 'boolean',
                description: 'Enable realistic style',
                default: false
              },
              engine: {
                type: 'string',
                enum: ['automatic', 'magnific_illusio', 'magnific_sharpy', 'magnific_sparkle'],
                description: 'AI engine to use',
                default: 'automatic'
              },
              creative_detailing: {
                type: 'number',
                minimum: 0,
                maximum: 100,
                description: 'Level of creative detail (0-100)',
                default: 50
              }
            },
            required: ['prompt']
          }
        },
        {
          name: 'check_status',
          description: 'Check the status of a Freepik Mystic image generation task',
          inputSchema: {
            type: 'object',
            properties: {
              task_id: {
                type: 'string',
                description: 'ID of the generation task to check'
              }
            },
            required: ['task_id']
          }
        },
      ],
    }));

    // Handle tool calls
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      try {
        switch (request.params.name) {
          case 'search_resources': {
            const validatedParams = SearchResourcesSchema.parse(request.params.arguments);
            const result = await this.client.searchResources(validatedParams);
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify(result, null, 2),
                },
              ],
            };
          }

          case 'get_resource': {
            const { id } = GetResourceSchema.parse(request.params.arguments);
            const result = await this.client.getResourceDetails(id);
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify(result, null, 2),
                },
              ],
            };
          }

          case 'download_resource': {
            const { id } = DownloadResourceSchema.parse(request.params.arguments);
            const result = await this.client.downloadResource(id);
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify(result, null, 2),
                },
              ],
            };
          }

          case 'generate_image': {
            const validatedParams = GenerateImageSchema.parse(request.params.arguments);
            const result = await this.client.generateImage(validatedParams);
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify(result, null, 2),
                },
              ],
            };
          }

          case 'check_status': {
            const { task_id } = CheckStatusSchema.parse(request.params.arguments);
            const result = await this.client.checkStatus(task_id);
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify(result, null, 2),
                },
              ],
            };
          }

          default:
            throw new McpError(
              ErrorCode.MethodNotFound,
              `Unknown tool: ${request.params.name}`
            );
        }
      } catch (error) {
        console.error('[Tool Error]', error);
        return {
          content: [
            {
              type: 'text',
              text: error instanceof Error ? error.message : String(error),
            },
          ],
          isError: true,
        };
      }
    });
  }

  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Freepik MCP server running on stdio');
  }
}

const server = new FreepikServer();
server.run().catch(console.error);
```