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

```
├── .dockerignore
├── .gitignore
├── docker-compose.yml
├── Dockerfile
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── server.ts
└── tsconfig.json
```

# Files

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

```
# Version control
.git
.gitignore

# Dependencies
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build output
dist
build

# Environment files
.env
.env.*

# IDE files
.vscode
.idea
*.swp
*.swo

# OS files
.DS_Store
Thumbs.db

# Docker
.docker
docker-compose.yml
.dockerignore

# Logs
logs
*.log

# Test coverage
coverage

# Documentation
README.md

```

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

```
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# TypeScript build output
dist/
build/

# Environment variables
.env
.env.local
.env.*.local

# IDE
.vscode/
.idea/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Logs
logs/
*.log

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Docker
.docker/

```

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

```markdown
# Brave Search MCP with SSE Support

This is a Model Context Protocol (MCP) server that provides Brave Search capabilities with Server-Sent Events (SSE) integration. It can be deployed to Coolify and used as a real-time search service.

## Features

- Brave Search API integration through MCP
- Real-time search results using SSE
- Docker and Coolify ready
- TypeScript implementation
- Express.js SSE endpoint

## Prerequisites

- Brave Search API key
- Node.js 18+
- Docker (for containerized deployment)
- Coolify instance

## Local Development

1. Clone the repository
2. Create a `.env` file with your Brave API key:
   ```
   BRAVE_API_KEY=your_api_key_here
   PORT=3001
   ```
3. Install dependencies:
   ```bash
   npm install
   ```
4. Start development server:
   ```bash
   npm run dev
   ```

## Docker Deployment

1. Build and run using docker-compose:
   ```bash
   docker-compose up --build
   ```

## Coolify Deployment

1. In your Coolify dashboard, create a new service
2. Choose "Deploy from Source"
3. Configure the following:
   - Repository URL: Your repository URL
   - Branch: main
   - Build Command: `npm run build`
   - Start Command: `npm start`
   - Port: 3001
   - Environment Variables:
     - BRAVE_API_KEY=your_api_key_here
     - PORT=3001

## Using the SSE Integration

### SSE Endpoint
```
GET http://your-server:3001/sse
```

The SSE endpoint provides real-time search results. Connect to it using the EventSource API:

```javascript
const eventSource = new EventSource('http://your-server:3001/sse');

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  // Handle the search results
  console.log(data);
};

eventSource.onerror = (error) => {
  console.error('SSE Error:', error);
  eventSource.close();
};
```

### Messages Endpoint
```
POST http://your-server:3001/messages
Content-Type: application/json

{
  "query": "your search query",
  "count": 10  // optional, default: 10, max: 20
}
```

Use this endpoint to trigger searches that will be broadcast to all connected SSE clients.

## MCP Usage

The server provides the following MCP tool:

- `brave_web_search`: Performs a web search using the Brave Search API
  ```typescript
  {
    query: string;    // Search query
    count?: number;   // Number of results (1-20, default: 10)
  }
  ```

## Error Handling

- The server broadcasts errors to all connected SSE clients
- Errors are formatted as:
  ```json
  {
    "type": "error",
    "error": "error message"
  }
  ```

## Notes

- The SSE connection will stay open until the client closes it
- Each search result is broadcast to all connected clients
- The server automatically handles disconnections and cleanup
- For production deployment, consider implementing authentication for the messages endpoint

```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
version: '3.8'

services:
  brave-search-mcp:
    build: .
    ports:
      - "3001:3001"
    environment:
      - BRAVE_API_KEY=${BRAVE_API_KEY}
      - PORT=3001
    restart: unless-stopped

```

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

```dockerfile
FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy source code
COPY . .

# Build TypeScript
RUN npm run build

# Expose port for SSE
EXPOSE 3001

# Start the server
CMD ["npm", "start"]

```

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

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

```

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

```json
{
  "name": "brave-search-mcp",
  "version": "1.0.0",
  "description": "Brave Search MCP Server with SSE Support",
  "main": "dist/server.js",
  "type": "commonjs",
  "scripts": {
    "build": "tsc",
    "start": "node dist/server.js",
    "dev": "ts-node src/server.ts",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "mcp",
    "brave-search",
    "sse"
  ],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.7.0",
    "@types/node": "^22.13.10",
    "cors": "^2.8.5",
    "dotenv": "^16.4.7",
    "express": "^5.0.1",
    "typescript": "^5.8.2"
  },
  "devDependencies": {
    "@types/cors": "^2.8.17",
    "@types/express": "^5.0.1",
    "ts-node": "^10.9.2"
  }
}

```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
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 express, { Request, Response } from 'express';
import cors from 'cors';
import dotenv from 'dotenv';

dotenv.config();

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

const PORT = process.env.PORT || 3001;

// Store active SSE clients
const clients = new Set<Response>();

class BraveSearchServer {
  private server: Server;
  private expressApp: express.Express;

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

    this.expressApp = express();
    this.expressApp.use(cors());
    this.expressApp.use(express.json());

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

  private setupToolHandlers() {
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'brave_web_search',
          description: 'Performs a web search using the Brave Search API with SSE support',
          inputSchema: {
            type: 'object',
            properties: {
              query: {
                type: 'string',
                description: 'Search query (max 400 chars, 50 words)',
              },
              count: {
                type: 'number',
                description: 'Number of results (1-20, default 10)',
                default: 10,
              },
            },
            required: ['query'],
          },
        },
      ],
    }));

    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      if (request.params.name !== 'brave_web_search') {
        throw new McpError(
          ErrorCode.MethodNotFound,
          `Unknown tool: ${request.params.name}`
        );
      }

      const { query, count = 10 } = request.params.arguments as {
        query: string;
        count?: number;
      };

      try {
        const searchParams = new URLSearchParams({
          q: query,
          count: Math.min(Math.max(1, count), 20).toString()
        });

        const response = await fetch(
          `https://api.search.brave.com/res/v1/web/search?${searchParams}`,
          {
            method: 'GET',
            headers: {
              'Accept': 'application/json',
              'Accept-Encoding': 'gzip',
              'X-Subscription-Token': API_KEY || ''
            }
          }
        );

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const results = await response.json();
        
        // Broadcast results to all connected SSE clients
        this.broadcast(results);

        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(results, null, 2),
            },
          ],
        };
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : 'Unknown error';
        this.broadcastError(errorMessage);
        throw new McpError(ErrorCode.InternalError, errorMessage);
      }
    });
  }

  private setupSSEEndpoints() {
    // SSE endpoint
    this.expressApp.get('/sse', (req: Request, res: Response) => {
      res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive'
      });

      // Send initial connection established message
      res.write('data: {"type":"connected"}\n\n');

      // Add client to active connections
      clients.add(res);

      // Remove client on connection close
      req.on('close', () => {
        clients.delete(res);
      });
    });

    // Messages endpoint for manual search requests
    this.expressApp.post('/messages', async (req: Request, res: Response) => {
      try {
        const { query, count } = req.body;
        // Handle the search request directly
        const response = await fetch(
          `https://api.search.brave.com/res/v1/web/search?${new URLSearchParams({
            q: query,
            count: Math.min(Math.max(1, count || 10), 20).toString()
          })}`,
          {
            method: 'GET',
            headers: {
              'Accept': 'application/json',
              'Accept-Encoding': 'gzip',
              'X-Subscription-Token': API_KEY || ''
            }
          }
        );

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const results = await response.json();
        res.json(results);
      } catch (error) {
        res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
      }
    });
  }

  private broadcast(data: unknown) {
    const message = `data: ${JSON.stringify(data)}\n\n`;
    clients.forEach(client => {
      client.write(message);
    });
  }

  private broadcastError(error: unknown) {
    const errorMessage = error instanceof Error ? error.message : String(error);
    const message = `data: ${JSON.stringify({ type: 'error', error: errorMessage })}\n\n`;
    clients.forEach(client => {
      client.write(message);
    });
  }

  async run() {
    // Start Express server
    this.expressApp.listen(PORT, () => {
      console.error(`SSE server running on port ${PORT}`);
    });

    // Start MCP server
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Brave Search MCP server running on stdio');
  }
}

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

```