The Model Context Protocol lets AI agents call external tools through a standardized interface. Building a custom MCP server backed by Scavio API gives you full control over tool definitions, response formatting, and rate limiting while maintaining compatibility with any MCP client (Cursor, Claude Desktop, custom agents). This tutorial builds a Node.js MCP server with search and extract tools.
Prerequisites
- Node.js 18+
- npm or pnpm
- A Scavio API key from scavio.dev
- Basic familiarity with MCP protocol
Walkthrough
Step 1: Set up the MCP server project
Initialize the project with MCP SDK and HTTP dependencies.
# Create project
mkdir scavio-mcp-server && cd scavio-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
# Project structure:
# scavio-mcp-server/
# index.js
# package.json
# package.json - add type module:
# { "type": "module", "main": "index.js" }Step 2: Implement the MCP server with search tool
Create the MCP server with a search tool that calls Scavio API.
// index.js
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const API_KEY = process.env.SCAVIO_API_KEY;
const SH = { 'x-api-key': API_KEY, 'Content-Type': 'application/json' };
const server = new McpServer({ name: 'scavio-search', version: '1.0.0' });
server.tool('search',
{ query: { type: 'string', description: 'Search query' },
platform: { type: 'string', description: 'Platform: google, reddit, youtube, amazon, walmart', default: 'google' },
country: { type: 'string', description: 'Country code', default: 'us' } },
async ({ query, platform, country }) => {
const body = { query, country_code: country || 'us' };
if (platform && platform !== 'google') body.platform = platform;
const r = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: SH, body: JSON.stringify(body)
});
const data = await r.json();
const results = (data.organic_results || []).slice(0, 5)
.map(r => `${r.position}. ${r.title}\n ${r.link}\n ${(r.snippet || '').slice(0, 120)}`).join('\n\n');
return { content: [{ type: 'text', text: results || 'No results found.' }] };
}
);
console.error('Scavio MCP server starting...');Step 3: Add the extract tool
Add a URL content extraction tool to the MCP server.
// Add after the search tool definition:
server.tool('extract',
{ url: { type: 'string', description: 'URL to extract content from' } },
async ({ url }) => {
const r = await fetch('https://api.scavio.dev/api/v1/extract', {
method: 'POST', headers: SH, body: JSON.stringify({ url })
});
const data = await r.json();
const content = data.content || data.text || JSON.stringify(data);
return { content: [{ type: 'text', text: content.slice(0, 2000) }] };
}
);
// List available tools for debugging:
server.tool('list_platforms',
{},
async () => {
const platforms = ['google', 'youtube', 'amazon', 'walmart', 'reddit'];
const text = platforms.map(p => `- ${p}: Search ${p} results via Scavio API`).join('\n');
return { content: [{ type: 'text', text: `Available search platforms:\n${text}\n\nCost: $0.005 per search` }] };
}
);Step 4: Start the server and configure clients
Launch the MCP server and connect it to Cursor or Claude Desktop.
// Add at the end of index.js:
const transport = new StdioServerTransport();
await server.connect(transport);
// Run the server:
// SCAVIO_API_KEY=your_key node index.js
// Configure in Cursor (~/.cursor/mcp.json):
// {
// "mcpServers": {
// "scavio-custom": {
// "command": "node",
// "args": ["/path/to/scavio-mcp-server/index.js"],
// "env": { "SCAVIO_API_KEY": "your_key" }
// }
// }
// }
// Or in Claude Desktop (claude_desktop_config.json):
// {
// "mcpServers": {
// "scavio-custom": {
// "command": "node",
// "args": ["/path/to/scavio-mcp-server/index.js"],
// "env": { "SCAVIO_API_KEY": "your_key" }
// }
// }
// }Python Example
# Python MCP server alternative:
import os, requests
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
def search(query, platform=None):
body = {'query': query, 'country_code': 'us'}
if platform: body['platform'] = platform
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json=body).json()
for r in data.get('organic_results', [])[:3]:
print(f'{r["position"]}. {r["title"][:50]}')
search('mcp server tutorial')JavaScript Example
// Test the MCP server tools directly:
const API_KEY = process.env.SCAVIO_API_KEY;
const SH = { 'x-api-key': API_KEY, 'Content-Type': 'application/json' };
async function testSearch(query, platform) {
const body = { query, country_code: 'us' };
if (platform) body.platform = platform;
const data = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: SH, body: JSON.stringify(body)
}).then(r => r.json());
(data.organic_results || []).slice(0, 3).forEach(r =>
console.log(`${r.position}. ${r.title.slice(0, 50)}`)
);
}
await testSearch('mcp server tutorial');Expected Output
Scavio MCP server starting...
# In Cursor after configuration:
Cursor Settings > MCP:
scavio-custom: Connected (3 tools)
# Tools available:
- search: Multi-platform web search
- extract: URL content extraction
- list_platforms: Show available platforms
# Test query in Cursor Composer:
User: Search Reddit for MCP server best practices
Agent: [Calling scavio-custom.search with platform='reddit']
1. Building MCP Servers - Best Practices for 2026
https://reddit.com/r/...
Here are the key patterns I've found...
Cost per search: $0.005