AI assistants, particularly models in the Claude family, demonstrate strong reasoning and code-generation capabilities. However, they remain limited by their training data cutoffs and cannot natively query live information. Standard search engines are also built for human eyes, meaning their responses are full of tracking links, heavy HTML scripts, and promotional clutter that balloon context windows and degrade LLM performance.
Anthropic's Model Context Protocol (MCP) provides an open standard for bidirectional tool communication between LLM clients (like Claude Desktop and Claude Code) and external data systems. By integrating SiftQ, a neural-ranked search API built for AI agents, into a custom MCP server, you can give Claude immediate access to real-time, pre-distilled web facts under sub-180ms latency.
This guide details how to build and configure your own Claude MCP server utilizing the SiftQ Web Search API using both Python and TypeScript, ending with an automated alternative using SiftQ's native CLI.
Why Use SiftQ Instead of Raw Search Engines?
Connecting Claude to standard search APIs often leads to three challenges: excessive latency, messy raw text parsing, and rapid token exhaustion. SiftQ addresses these constraints directly:
Model-Agnostic Retrieval: SiftQ acts strictly as a retrieval layer rather than forcing you into a specific LLM wrapper. This makes it a direct fit for Claude's native system prompting.
Minimal Latency: At sub-180ms p50 latency on its fast retrieval route, SiftQ prevents Claude's agent loops from hanging.
Pre-Structured Output: Instead of returning raw HTML strings that you must clean up manually, the SiftQ API Reference outlines distinct schemas for specific query sources—including webpages, academic articles (scholar), podcasts, videos, images, and documents.
Token Efficiency: Parameters like conciseSnippet truncate blocks down to 3 high-impact sentences, keeping your Claude prompt windows free from token bloat and lowering your total inferencing cost.
Prerequisites
Before writing code, ensure you have:
-
A SiftQ account and API key. You can claim 1,000 free queries per month on the SiftQ Pricing dashboard.
-
Either Python (3.10+) or Node.js (v18+) installed on your machine.
-
The Claude Desktop client (available for macOS and Windows) installed for local integration testing.
Once you have your SIFTQ_API_KEY, we can build the server.
Option 1: Python Implementation with FastMCP
Anthropic supports Python developers with FastMCP, a high-level framework that simplifies tool declaration using decorators.
1. Project Initialization
Create a clean directory and initialize a virtual environment:
Claude will automatically pass the query through your local MCP server to the SiftQ Web Search API, ingest the clean snippet block, and formulate an answer with references.
You can also run search-specific workflows across scopes:
"Search academic research paper sources for post-training reinforcement learning hacking." (Claude automatically switches scope to scholar).
"Locate PDF files regarding financial filings for NVDA." (Claude utilizes the document scope).
import os
import httpx
from mcp.server.fastmcp import FastMCP
# Initialize FastMCP server
mcp = FastMCP("SiftQ-Search")
SIFTQ_API_URL = "https://api.siftq.com/v1/search"
@mcp.tool()
async def search_web(
query: str,
scope: str = "webpage",
size: int = 5,
concise_snippet: bool = True
) -> str:
"""
Search the live web using SiftQ's high-speed index to retrieve real-time context.
Args:
query: The search query or natural language question.
scope: The source pool to query (webpage, scholar, podcast, video, image, document).
size: Number of results to retrieve (1 to 20).
concise_snippet: If true, limits snippets to ~3 sentences to conserve tokens.
"""
api_key = os.environ.get("SIFTQ_API_KEY")
if not api_key:
return "Error: SIFTQ_API_KEY environment variable is not set."
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
# SiftQ expects size as a string representation
payload = {
"q": query,
"scope": scope,
"size": str(size),
"conciseSnippet": concise_snippet
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(SIFTQ_API_URL, headers=headers, json=payload, timeout=15.0)
if response.status_code != 200:
return f"Error from SiftQ API: {response.status_code} - {response.text}"
data = response.json()
# SiftQ pluralizes array keys based on requested scopes (e.g., webpages, scholars, podcasts)
scope_key = f"{scope}s"
results = data.get(scope_key, [])
if not results:
return f"No results found for: '{query}' within scope '{scope}'."
formatted_results = []
for idx, item in enumerate(results, start=1):
title = item.get("title", "Untitled")
link = item.get("link", "No link available")
snippet = item.get("snippet", "No snippet available")
score = item.get("score", "N/A")
formatted_results.append(
f"[{idx}] Title: {title}\n"
f" Link: {link}\n"
f" Relevance Score: {score}\n"
f" Snippet: {snippet}\n"
)
return "\n---\n".join(formatted_results)
except httpx.RequestError as exc:
return f"An error occurred while connecting to SiftQ: {exc}"
if __name__ == "__main__":
mcp.run(transport="stdio")
mkdir siftq-mcp-typescript
cd siftq-mcp-typescript
npm init -y
npm install @modelcontextprotocol/sdk zod axios
npm install -D typescript @types/node ts-node
npx tsc --init
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
mkdir src
touch src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import axios from "axios";
const SIFTQ_API_URL = "https://api.siftq.com/v1/search";
const server = new Server(
{
name: "siftq-search-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Define search tool
server.tool(
"search_web",
{
query: z.string().describe("The search query or natural language question to search on the web."),
scope: z
.enum(["webpage", "scholar", "podcast", "video", "image", "document"])
.default("webpage")
.describe("The context source pool to search within."),
size: z
.string()
.default("5")
.describe("The number of results to fetch (between 1 and 20). Must be passed as a string."),
conciseSnippet: z
.boolean()
.default(true)
.describe("Truncate snippets to ~3 sentences of exact-match text to minimize token bloat."),
includeSummary: z
.boolean()
.default(false)
.describe("Whether to append an LLM summary of the top hits (adds 1-3s latency)."),
},
async ({ query, scope, size, conciseSnippet, includeSummary }) => {
const apiKey = process.env.SIFTQ_API_KEY;
if (!apiKey) {
return {
isError: true,
content: [
{
type: "text",
text: "Error: SIFTQ_API_KEY environment variable is not defined.",
},
],
};
}
try {
const response = await axios.post(
SIFTQ_API_URL,
{
q: query,
scope,
size,
conciseSnippet,
includeSummary,
},
{
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
timeout: 15000,
}
);
const data = response.data;
const scopeKey = `${scope}s`;
const results = data[scopeKey] || [];
if (results.length === 0) {
return {
content: [
{
type: "text",
text: `No results found for query: "${query}" in scope "${scope}".`,
},
],
};
}
let outputText = "";
if (includeSummary && data.summary) {
outputText += `Summary of results:\n${data.summary}\n\n---\n\n`;
}
const formatted = results.map((item: any, idx: number) => {
return `[${idx + 1}] Title: ${item.title}\n` +
` Link: ${item.link || "N/A"}\n` +
` Relevance: ${item.score || "N/A"}\n` +
` Snippet: ${item.snippet || "N/A"}\n`;
}).join("\n");
outputText += formatted;
return {
content: [
{
type: "text",
text: outputText,
},
],
};
} catch (error: any) {
const errorMsg = error.response?.data?.errMsg || error.message;
return {
isError: true,
content: [
{
type: "text",
text: `SiftQ search failed: ${errorMsg}`,
},
],
};
}
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("SiftQ MCP server running on stdio transport");
}
main().catch((err) => {
console.error("Critical error starting SiftQ MCP server:", err);
process.exit(1);
});
npm run build
{
"mcpServers": {
"siftq-python-search": {
"command": "python3",
"args": [
"-u",
"/absolute/path/to/siftq-mcp-python/server.py"
],
"env": {
"SIFTQ_API_KEY": "your_actual_siftq_api_key_here"
}
}
}
}
{
"mcpServers": {
"siftq-typescript-search": {
"command": "node",
"args": [
"/absolute/path/to/siftq-mcp-typescript/dist/index.js"
],
"env": {
"SIFTQ_API_KEY": "your_actual_siftq_api_key_here"
}
}
}
}
[Claude picks search_web tool]
-> Invoking tool: search_web
-> Parameters: { "query": "Gemini updates this week", "scope": "webpage" }
{
"mcpServers": {
"siftq-official": {
"command": "npx",
"args": [
"-y",
"@siftq/mcp-server"
],
"env": {
"SIFTQ_API_KEY": "your_actual_siftq_api_key_here"
}
}
}
}Alternative: Running the Official SiftQ MCP Package
If you prefer to bypass coding your own server, SiftQ maintains an official, pre-packaged MCP server that can be run with a single command.
To run the pre-built Node package directly, simply update your claude_desktop_config.json to leverage npx: