Skip to main content
Glama
Camcdonou
by Camcdonou
README.md
# πŸ” 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

A4.2/5.0

Scored across 1 tool

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count3/5

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.

Completeness4/5

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.

Maintenance

ActivityMaintained
ResponsivenessNo issues