npm package: @goclaw/perplexity-mcp
Description: Remote MCP server wrapping Perplexity API — ask, search, and deep research tools
Latest version: 0.1.1

# @goclaw/perplexity-mcp

Remote MCP server that wraps the [Perplexity AI](https://www.perplexity.ai/) API, exposing three tools for AI assistants to fetch real-time web information.

## Tools

| Tool | Description | Model |
|---|---|---|
| `perplexity_ask` | Ask a question, get an AI answer with citations | `sonar-pro` (default) |
| `perplexity_search` | Structured web search results (title, URL, snippet, date) | Perplexity `/search` |
| `perplexity_research` | Deep research report with extensive citations | `sonar-deep-research` |

## Requirements

- Node.js >= 20
- Perplexity API key → [perplexity.ai/settings/api](https://www.perplexity.ai/settings/api)

## Environment Variables

| Variable | Required | Description |
|---|---|---|
| `PERPLEXITY_API_KEY` | Yes | Perplexity API key |
| `MCP_AUTH_TOKEN` | Yes | Bearer token clients use to authenticate with this server |
| `PORT` | No | HTTP port (default: `3000`) |
| `CORS_ORIGIN` | No | Allowed CORS origin (default: none) |

## Quick Start

### Option A: npx (no install)

```bash
PERPLEXITY_API_KEY=pplx-... MCP_AUTH_TOKEN=my-secret npx @goclaw/perplexity-mcp
```

### Option B: Global install

```bash
npm install -g @goclaw/perplexity-mcp
PERPLEXITY_API_KEY=pplx-... MCP_AUTH_TOKEN=my-secret perplexity-mcp
```

### Option C: Docker

```bash
docker run -p 3000:3000 \
  -e PERPLEXITY_API_KEY=pplx-... \
  -e MCP_AUTH_TOKEN=my-secret \
  goclaw/perplexity-mcp
```

### Option D: Docker Compose

Copy `.env.example` to `.env`, fill in your values, then:

```bash
docker compose up -d
```

## Deploy to Fly.io

```bash
# Install Fly CLI: https://fly.io/docs/hands-on/install-flyctl/
fly launch --copy-config --name goclaw-perplexity-mcp
fly secrets set PERPLEXITY_API_KEY=pplx-... MCP_AUTH_TOKEN=my-secret
fly deploy
```

TLS is automatic. Your endpoint will be `https://goclaw-perplexity-mcp.fly.dev/mcp`.

## Deploy to VPS (self-host)

```bash
docker compose up -d
# Put nginx in front for TLS (Let's Encrypt)
```

## MCP Client Configuration

### Claude Desktop (`claude_desktop_config.json`)

```json
{
  "mcpServers": {
    "perplexity": {
      "type": "streamable-http",
      "url": "https://your-server.example.com/mcp",
      "headers": {
        "Authorization": "Bearer your-mcp-auth-token"
      }
    }
  }
}
```

### Any MCP client (programmatic)

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("https://your-server.example.com/mcp"),
  { headers: { Authorization: "Bearer your-mcp-auth-token" } }
);

const client = new Client({ name: "my-client", version: "1.0.0" }, { capabilities: {} });
await client.connect(transport);

// Call a tool
const result = await client.callTool({
  name: "perplexity_ask",
  arguments: { query: "What happened in AI this week?" }
});
```

## Tool Reference

### `perplexity_ask`

```json
{
  "query": "string (required) — question or topic",
  "model": "sonar | sonar-pro | sonar-reasoning (default: sonar-pro)",
  "domains": ["example.com"] ,
  "recency": "day | week | month | year"
}
```

Returns markdown with inline citations `[^1]` and a **Sources** section.

### `perplexity_search`

```json
{
  "query": "string (required)",
  "max_results": "number (default: 10, max: 50)",
  "domains": ["example.com"],
  "recency": "day | week | month | year"
}
```

Returns a JSON array: `[{ title, url, snippet, published_date }]`

### `perplexity_research`

```json
{
  "query": "string (required) — research topic",
  "focus": "string (optional) — system hint to constrain research"
}
```

Returns a long markdown report with footnote citations. May take up to 60 seconds.

## Health Check

```bash
curl https://your-server.example.com/health
# → {"status":"ok","sessions":0}
```

## Security Notes

- Bearer token comparison is constant-time (SHA-256 hash via `crypto.timingSafeEqual`)
- API keys are never logged
- Run behind TLS in production (Fly.io provides this automatically)

## Development

```bash
pnpm install
pnpm dev          # tsx watch mode
pnpm build        # compile TypeScript
pnpm test         # vitest
```

## License

MIT — see [LICENSE](LICENSE)