# 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);
```