syn-search-mcp
# π syn-search-mcp
An [MCP](https://modelcontextprotocol.io) server that adds web search using [Synthetic's Search API](https://synthetic.new). Built for [OpenCode](https://opencode.ai), works with any MCP client.
Port of the [pi-synthetic-search](../syn-search) Pi extension to a standalone MCP server.
## Features
- **`synthetic_search` tool** β automatically available to the LLM alongside built-in tools
- **4-tier payload control** to protect your context window:
- **Result count limit** β caps at 5 results by default (configurable, max 10)
- **`detail_level` parameter** β LLM chooses its own tradeoff:
- `summary` β title + URL + date only (~50 tokens/result)
- `snippet` β + 300-char text excerpt (~100 tokens/result)
- `ai-summary` β full text sent to AI summarizer with optional focus prompt (~300-500 tokens total)
- `full` β complete untruncated text (LLM opts in for deep reads)
- **`summary_prompt` parameter** β when using `ai-summary`, the LLM can provide conversation context to focus the summarizer on what matters
- **Overall truncation** β safety net at 2000 lines / 50KB; full output saved to a temp file the LLM can `read`
- **AI Summarizer** β uses Synthetic's chat completions API (`syn:small:text`) to condense search results into focused, query-relevant summaries
- **Graceful error handling** β missing key, 401, 429, network failures
- **Abort-aware** β respects client cancellation (e.g. Esc in OpenCode) during in-flight requests
## Setup
### 1. Build
```bash
cd syn-search-mcp
npm install
npm run build
```
Requires Node.js 18+.
### 2. Get a Synthetic API key
Sign up at [synthetic.new](https://synthetic.new) and grab your API key (starts with `syn_`).
### 3. Add to OpenCode
**Global** β `~/.config/opencode/opencode.jsonc`:
```jsonc
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"syn-search": {
"type": "local",
"command": ["node", "/path/to/syn-search-mcp/dist/index.js"],
"environment": {
"SYNTHETIC_API_KEY": "{env:SYNTHETIC_API_KEY}"
},
"enabled": true
}
}
}
```
**Project-local** β same object under `mcp` in an `opencode.json` at the project root.
The key is read from `SYNTHETIC_API_KEY` (set in your shell or passed via the `environment` block). You can also hardcode it there instead of `{env:...}`.
### 4. Restart OpenCode
The tool is exposed as `syn-search_synthetic_search`. Ask it anything that needs search:
```
Search the web for the latest TypeScript 5.x features
```
## Tool parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `query` | string | *(required)* | Search terms |
| `detail_level` | `"summary"` \| `"snippet"` \| `"ai-summary"` \| `"full"` | `"snippet"` | Result detail level |
| `summary_prompt` | string | *(none)* | Context/instructions for the AI summarizer (only used with `ai-summary`) |
| `max_results` | number | `5` | Max results to return (1β10) |
### detail_level guide
| Mode | Output size | Tokens (est.) | Use case |
|------|------------|---------------|----------|
| `summary` | ~500B | ~150 | Quick scan β "does anything relevant exist?" |
| `snippet` | ~2KB | ~550 | Relevance check β "is this worth digging into?" |
| `ai-summary` | ~1-3KB | ~300-500 | **Real questions** β focused, context-aware extraction |
| `full` | Up to 300KB+ | ~66K+ | Deep dive β only when raw content is needed |
### ai-summary example
```json
{
"query": "Docker multi-stage builds",
"detail_level": "ai-summary",
"summary_prompt": "User is trying to reduce a Node.js Docker image from 1.2GB to under 200MB. Focus on layer caching, Alpine vs Debian, and COPY vs ADD patterns."
}
```
The summarizer becomes a lightweight sub-agent β the main LLM delegates research focus via `summary_prompt`, and only relevant info returns to the context window.
### Truncation
When output exceeds 2000 lines or 50KB, it's truncated with a notice; the full output is written to a temp file:
```
[Output truncated: showing 1210 of 8954 lines (48.8KB of 297.3KB).
7744 lines (248.5KB) omitted.
Full output saved to: /tmp/syn-search-mcp-XXXX/search-results.txt
β use the read tool to view it.]
```
## Environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| `SYNTHETIC_API_KEY` | *(required)* | Synthetic API key (`syn_...`) |
| `SYN_SEARCH_SUMMARIZER_MODEL` | `syn:small:text` | Chat model used for `ai-summary` |
| `SYN_SEARCH_MAX_LINES` | `2000` | Truncation line limit |
| `SYN_SEARCH_MAX_BYTES` | `51200` | Truncation byte limit |
| `SYN_SEARCH_DEBUG` | *(unset)* | Set to `1` to log to stderr |
## Error handling
| Scenario | Behavior |
|----------|----------|
| `SYNTHETIC_API_KEY` not set | isError result with setup instructions |
| Invalid API key (401) | isError result with key-check guidance |
| Rate limited (429) | isError result with retry advice |
| Network failure | isError result with connection guidance |
| Empty results | Informational message (not an error) |
| Request cancelled (Esc) | `"Synthetic API request was cancelled."` |
| Summarizer returns empty content | isError result suggesting `detail_level='full'` |
Errors return `isError: true` tool results so the LLM knows the search failed and can react accordingly.
## Testing
```bash
# smoke test against the live API (uses SYNTHETIC_API_KEY from your env)
node scripts/smoke-test.mjs summary
node scripts/smoke-test.mjs ai-summary
# interactive MCP inspector
npx @modelcontextprotocol/inspector node dist/index.js
```
## License
MIT
TDQS
Scored across 1 tool
Only one tool exists, so there is no possibility of selecting the wrong tool for a task. The single tool's purpose is clearly defined as web search.
The single tool name 'synthetic_search' follows a clean verb_noun pattern. With only one tool, there is no conflicting naming convention to introduce confusion.
A server with just one tool feels thin, even for a narrowly scoped search service. However, the tool is substantive and handles multiple detail levels, so it is a borderline case rather than an egregious under-provision.
The tool covers the core search lifecycle wellβretrieving results, summaries, and full text. It lacks explicit pagination or advanced filtering options, but the built-in truncation and temp-file output mitigate common gaps.