npm package: @pulses/stocky-mcp
Description: A friendly MCP server for searching royalty-free stock images from Pexels and Unsplash
Author: Pulses.ai
Latest version: 1.0.1

# Stocky MCP Server - TypeScript Edition

A friendly MCP (Model Context Protocol) server for searching royalty-free stock images across Pexels and Unsplash APIs.

## Features

- **Multi-provider search**: Search across Pexels and Unsplash simultaneously
- **Detailed image information**: Retrieve comprehensive metadata for any image
- **Flexible downloads**: Download images in multiple sizes or as base64 data
- **Attribution control**: Toggle attribution links via environment variable
- **Proper TypeScript typing**: Full type safety with Zod validation
- **Native fetch**: Uses Node.js built-in fetch for HTTP requests

## Installation

```bash
npm install
```

## Building

```bash
npm run build
```

## Development

```bash
npm run dev
```

This will compile TypeScript and run the server.

## Configuration

### Environment Variables

Set the following environment variables to enable the respective providers:

```bash
# Pexels API Key (get one at https://www.pexels.com/api/)
export PEXELS_API_KEY="your_pexels_api_key"

# Unsplash API Key (get one at https://unsplash.com/developers)
export UNSPLASH_ACCESS_KEY="your_unsplash_api_key"

# Optional: Enable attribution links in responses
export ENABLE_ATTRIBUTION_LINKS="true"
```

At least one API key must be set to use the server.

## Project Structure

```
src/
├── index.ts              # Main entry point, server initialization
├── tools.ts              # Tool registrations and StockImageManager
└── providers/
    ├── types.ts          # TypeScript type definitions
    ├── pexels.ts         # Pexels API provider implementation
    └── unsplash.ts       # Unsplash API provider implementation
```

## Available Tools

### search_stock_images

Search for stock images across specified providers.

**Parameters:**
- `query` (string, required): Search query string
- `providers` (array, optional): List of specific providers to search (["pexels", "unsplash"])
- `per_page` (number, optional): Number of results per page, default 20, max 50
- `page` (number, optional): Page number for pagination, default 1
- `sort_by` (string, optional): Sort order ('relevant' or 'newest'), default 'relevant'
- `include_attribution` (boolean, optional): Whether to include attribution links (defaults to ENABLE_ATTRIBUTION_LINKS env var)

**Returns:**
```json
{
  "results": [
    {
      "id": "pexels_12345",
      "title": "Photo title or alt text",
      "description": "Image description",
      "url": "https://...",
      "thumbnail": "https://...",
      "width": 1920,
      "height": 1080,
      "photographer": "John Doe",
      "photographer_url": "https://...",
      "source": "Pexels",
      "license": "Free to use, attribution appreciated",
      "attribution_url": "https://...",
      "tags": ["sunset", "beach"]
    }
  ],
  "total_results": 20,
  "query": "sunset beach",
  "page": 1,
  "per_page": 20
}
```

### get_image_details

Get detailed information about a specific image.

**Parameters:**
- `id` (string, required): Provider-prefixed image ID (e.g., "pexels_12345")
- `include_attribution` (boolean, optional): Whether to include attribution links

**Returns:**
```json
{
  "id": "pexels_12345",
  "title": "Photo title",
  "description": "Image description",
  "url": "https://...",
  "thumbnail": "https://...",
  "width": 1920,
  "height": 1080,
  "photographer": "John Doe",
  "photographer_url": "https://...",
  "source": "Pexels",
  "license": "Free to use, attribution appreciated",
  "attribution_url": "https://...",
  "tags": ["sunset", "beach"]
}
```

### download_image

Download an image to local storage or retrieve as base64 data.

**Parameters:**
- `image_id` (string, required): Image ID in format provider_id (e.g., "pexels_123456")
- `size` (string, optional): Image size variant (thumbnail, small, medium, large, original), default "original"
- `output_path` (string, optional): Path to save the image locally

**Returns:**

When saving to disk:
```json
{
  "success": true,
  "message": "Image downloaded successfully to /path/to/image.jpg",
  "path": "/path/to/image.jpg",
  "size": 245632,
  "content_type": "image/jpeg"
}
```

When returning base64:
```json
{
  "success": true,
  "message": "Image data retrieved successfully",
  "data": "base64_encoded_image_data...",
  "size": 245632,
  "content_type": "image/jpeg",
  "encoding": "base64"
}
```

## Size Mapping

The server maps size parameters to the appropriate URLs for each provider:

### Pexels Size Mapping
- `thumbnail` → src.medium
- `small` → src.medium
- `medium` → src.large
- `large` → src.large
- `original` → src.original (with size constraints removed)

### Unsplash Size Mapping
- `thumbnail` → urls.small
- `small` → urls.small
- `medium` → urls.regular
- `large` → urls.regular
- `original` → urls.full

## Running the Server

### Direct execution
```bash
npm start
```

### With npx
```bash
npx stocky-mcp
```

## API Keys

### Getting a Pexels API Key
1. Visit https://www.pexels.com/api/
2. Click "Register" and create an account
3. Navigate to your API settings
4. Copy your API key

### Getting an Unsplash API Key
1. Visit https://unsplash.com/developers
2. Click "Create an app"
3. Follow the terms and accept them
4. Copy your Access Key

## Examples

### Search for sunset images
```json
{
  "query": "sunset",
  "per_page": 10,
  "providers": ["pexels", "unsplash"]
}
```

### Get details for a specific image
```json
{
  "id": "pexels_123456"
}
```

### Download an image at medium size
```json
{
  "image_id": "unsplash_abc123",
  "size": "medium",
  "output_path": "/home/user/pictures/image.jpg"
}
```

### Download and get base64 data
```json
{
  "image_id": "pexels_789012",
  "size": "large"
}
```

## License

Free to use under Unsplash License for Unsplash images, and per Pexels' free license for Pexels images.

## Development Notes

- The server uses native Node.js `fetch` for HTTP requests (requires Node 18+)
- All tools return JSON responses wrapped in MCP content blocks
- Input validation is performed using Zod schemas
- TypeScript strict mode is enabled for type safety
- Image size clamping: `per_page` is clamped to a maximum of 50 results

## Troubleshooting

### No results returned
- Ensure API keys are correctly set in environment variables
- Verify your search query is not empty
- Check that at least one API key is configured

### Images not downloading
- Verify the image ID format (should start with "pexels_" or "unsplash_")
- Check that the output path is absolute (for reliable saving)
- Ensure the parent directory has write permissions

### Attribution links not showing
- Set `ENABLE_ATTRIBUTION_LINKS=true` in environment variables
- Or pass `include_attribution: true` when calling tools

## TypeScript Configuration

The project uses modern TypeScript with:
- Target: ES2022
- Module resolution: Node16
- Strict mode enabled
- ES modules (`.mts` imports with `.js` extensions)