Sign In

@clervo/mcp

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@clervo/mcp - npm Package Compare versions

Comparing version
0.2.0
to
0.3.0
+2
dist/run.d.ts
#!/usr/bin/env node
export {};
#!/usr/bin/env node
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { createClervoMcpServer } from './server.js';
const server = createClervoMcpServer();
const transport = new StdioServerTransport();
try {
await server.connect(transport);
}
catch (error) {
process.stderr.write(`clervo-mcp failed: ${error instanceof Error ? error.message : 'unknown_error'}\n`);
process.exitCode = 1;
}
//# sourceMappingURL=run.js.map
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,MAAM,GAAG,qBAAqB,EAAE,CAAC;AACvC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAE7C,IAAI,CAAC;IACH,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC;IACzG,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC"}
import { type ClervoSearchRequest } from '@clervo/sdk';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
export declare const CLERVO_MCP_TOOLS: readonly [Readonly<{
name: "search_web";
operationId: "search.web";
}>, Readonly<{
name: "search_answer";
operationId: "search.answer";
}>];
export interface ClervoSearchClient {
search: {
web(request: ClervoSearchRequest, options?: {
idempotencyKey?: string;
mode?: 'preview' | 'challenge';
}): Promise<unknown>;
answer(request: ClervoSearchRequest, options?: {
idempotencyKey?: string;
mode?: 'preview' | 'challenge';
}): Promise<unknown>;
};
}
export interface ToolInput {
query: string;
maxResults?: number | undefined;
language?: string | undefined;
region?: string | undefined;
idempotencyKey?: string | undefined;
mode?: 'preview' | 'challenge' | undefined;
}
export interface ToolResult {
[key: string]: unknown;
content: Array<{
type: 'text';
text: string;
}>;
isError?: boolean;
}
export declare function createToolHandlers(client: ClervoSearchClient): {
search_web(input: ToolInput): Promise<ToolResult>;
search_answer(input: ToolInput): Promise<ToolResult>;
};
export declare function createClervoMcpServer(options?: {
client?: ClervoSearchClient;
baseUrl?: string;
}): McpServer;
import { ClervoClient, ClervoPaymentRequiredError, ClervoProblemError, recoveryActionFor, } from '@clervo/sdk';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
export const CLERVO_MCP_TOOLS = Object.freeze([
Object.freeze({ name: 'search_web', operationId: 'search.web' }),
Object.freeze({ name: 'search_answer', operationId: 'search.answer' }),
]);
function text(value) {
return { content: [{ type: 'text', text: JSON.stringify(value) }] };
}
function failure(error) {
const recovery = recoveryActionFor(error);
if (error instanceof ClervoPaymentRequiredError) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: 'payment_required',
status: 402,
payable: false,
problem: error.problem,
...(recovery === undefined ? {} : { recovery }),
}),
}],
isError: true,
};
}
if (error instanceof ClervoProblemError) {
return {
content: [{
type: 'text',
text: JSON.stringify({
error: 'clervo_problem',
status: error.status,
problem: error.problem,
...(recovery === undefined ? {} : { recovery }),
}),
}],
isError: true,
};
}
return {
content: [{ type: 'text', text: JSON.stringify({ error: 'clervo_call_failed' }) }],
isError: true,
};
}
export function createToolHandlers(client) {
const execute = async (productId, input) => {
try {
const request = {
query: input.query,
...(input.maxResults === undefined ? {} : { maxResults: input.maxResults }),
...(input.language === undefined ? {} : { language: input.language }),
...(input.region === undefined ? {} : { region: input.region }),
};
const options = {
...(input.idempotencyKey === undefined ? {} : { idempotencyKey: input.idempotencyKey }),
...(input.mode === undefined ? {} : { mode: input.mode }),
};
const value = productId === 'search.web'
? await client.search.web(request, options)
: await client.search.answer(request, options);
return text(value);
}
catch (error) {
return failure(error);
}
};
return Object.freeze({
search_web: (input) => execute('search.web', input),
search_answer: (input) => execute('search.answer', input),
});
}
const inputSchema = {
query: z.string().trim().min(1).max(2_000).describe('The evidence query.'),
maxResults: z.number().int().min(1).max(10).optional(),
language: z.string().regex(/^[a-z]{2,3}$/u).optional(),
region: z.string().regex(/^[A-Z]{2}$/u).optional(),
idempotencyKey: z.string().min(8).max(128).optional(),
mode: z.enum(['preview', 'challenge']).default('preview').describe('Preview executes the local sample route. Challenge returns a non-payable 402 and never pays.'),
};
export function createClervoMcpServer(options = {}) {
const baseUrl = options.baseUrl ?? process.env.CLERVO_BASE_URL;
const client = options.client ?? (baseUrl === undefined ? undefined : new ClervoClient({ baseUrl }));
const server = new McpServer({ name: 'clervo', version: '0.3.0' });
const handlers = client === undefined ? undefined : createToolHandlers(client);
server.registerTool('search_web', {
title: 'Clervo web evidence preview',
description: 'Runs the repository-local search.web preview. Public availability and payment are not claimed.',
inputSchema,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
}, async (input) => handlers === undefined
? { content: [{ type: 'text', text: JSON.stringify({ error: 'clervo_base_url_required' }) }], isError: true }
: handlers.search_web(input));
server.registerTool('search_answer', {
title: 'Clervo cited answer preview',
description: 'Runs the repository-local search.answer preview with synthesis forced on. Public availability and payment are not claimed.',
inputSchema,
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
}, async (input) => handlers === undefined
? { content: [{ type: 'text', text: JSON.stringify({ error: 'clervo_base_url_required' }) }], isError: true }
: handlers.search_answer(input));
return server;
}
//# sourceMappingURL=server.js.map
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
+47
-8
{
"name": "@clervo/mcp",
"version": "0.2.0",
"description": "Clervo x402 Gateway MCP server — 23 AI models, 8 free. One wallet, pay per call in USDC.",
"version": "0.3.0",
"description": "Clervo MCP server generated from the frozen two-operation distribution candidate.",
"license": "UNLICENSED",
"type": "module",
"bin": { "clervo-mcp": "./bin/serve.js" },
"main": "./src/index.js",
"keywords": ["mcp", "clervo", "x402", "ai", "claude", "gpt", "usdc", "solana", "agent", "llm"],
"license": "MIT",
"engines": { "node": ">=18" },
"files": ["bin/", "src/", "README.md"]
"keywords": [
"clervo",
"mcp",
"model-context-protocol",
"search"
],
"homepage": "https://clervo.dev/docs/mcp",
"bugs": {
"url": "https://github.com/clervo/clervo/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/clervo/clervo.git",
"directory": "packages/mcp"
},
"engines": {
"node": ">=20"
},
"files": [
"dist",
"README.md"
],
"bin": {
"clervo-mcp": "./dist/run.js"
},
"exports": {
".": {
"types": "./dist/server.d.ts",
"import": "./dist/server.js"
}
},
"scripts": {
"build": "npm run build --workspace @clervo/sdk && tsc --project tsconfig.json",
"prepack": "npm run build"
},
"dependencies": {
"@clervo/sdk": "0.3.0",
"@modelcontextprotocol/sdk": "1.30.0",
"zod": "4.4.3"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}
+27
-54

@@ -1,61 +0,34 @@

# @clervo/mcp
# `@clervo/mcp`
MCP server for the [Clervo x402 Gateway](https://api.clervo.dev) — 23 AI models, 8 free. Pay per call in USDC on Solana.
Local stdio MCP server for Clervo's frozen distribution candidate.
## Install in Claude Code
It exposes exactly two tools:
```bash
claude mcp add clervo -s user -- npx -y @clervo/mcp@latest
```
- `search_web` → `search.web`
- `search_answer` → `search.answer`
Restart Claude Code. Done.
Set `CLERVO_BASE_URL` to an explicitly selected Clervo preview endpoint. No
public deployment is assumed. The tools never sign, pay, retry payment, or
convert a non-payable `402` challenge into success.
## What you get
```json
{
"mcpServers": {
"clervo": {
"command": "npx",
"args": ["-y", "@clervo/mcp"],
"env": {
"CLERVO_BASE_URL": "http://127.0.0.1:8080"
}
}
}
}
```
| Tool | What it does |
|------|-------------|
| `clervo_chat` | Call any AI model — 8 free (no wallet), 11 paid (USDC) |
| `clervo_models` | List all models with pricing |
| `clervo_status` | Look up operation receipts |
The package writes protocol messages to stdout and operational failures to
stderr only.
## Free models (no payment needed)
Just call them:
- `groq/llama-3.1-8b-instant` — 170ms, fastest
- `groq/llama-3.3-70b` — large, fast
- `sambanova/deepseek-v3.2` — strong reasoning
- `sambanova/llama-3.3-70b` — large general
- `hcn/qwen3.6-35b` — fast small
- `hcn/step-3.7-flash` — reasoning
- `hcn/deepseek-v4-pro` — strongest open model
- `hcn/auto` — auto-routed
## Paid models (10-20% cheaper than BlockRun)
- Claude Haiku 4.5 — $0.002/req
- Claude Sonnet 4.6 / 5 — $0.015/req
- Claude Opus 4.7 / 5 — $0.084/req
- GPT-5.4 Mini — $0.005/req
- GPT-5.5 / 5.6 Sol — $0.035/req
## Example
In Claude Code, just say:
> "Use clervo_chat with groq/llama-3.1-8b-instant to explain quantum computing in 3 sentences"
Or for Claude:
> "Use clervo_chat with tongkhokr/claude-opus-5 to review this code"
## Configuration
Set `CLERVO_API_URL` to override the default (`https://api.clervo.dev`).
## Links
- API: https://api.clervo.dev
- Models: https://api.clervo.dev/v1/models
- Quickstart: https://api.clervo.dev/quickstart.md
- OpenAPI: https://api.clervo.dev/openapi.json
Known future payment failures include the same single recovery action as both
SDKs. The server never performs that action, signs, pays, or retries on the
agent's behalf. Unknown settlement and payment timeouts remain blocked until
the original idempotency key is reconciled.
#!/usr/bin/env node
import { serve } from '../src/index.js';
serve();
/**
* @clervo/mcp — MCP server for Clervo x402 Gateway
*
* Install: claude mcp add clervo -s user -- npx -y @clervo/mcp@latest
*
* Tools exposed:
* clervo_chat — call any model (free or paid)
* clervo_models — list available models with pricing
* clervo_search — web search (free, no API key)
* clervo_scrape — URL to markdown (free, no API key)
* clervo_status — check operation status / receipt
*/
import { createInterface } from 'node:readline';
const API = process.env.CLERVO_API_URL || 'https://api.clervo.dev';
// MCP protocol handler
const tools = [
{
name: 'clervo_chat',
description: 'Call an AI model through Clervo x402 Gateway. 13 free models available without payment. Use "groq/llama-3.1-8b-instant" for fastest (170ms) or "groq/llama-3.3-70b" for best free quality.',
inputSchema: {
type: 'object',
required: ['model', 'message'],
properties: {
model: {
type: 'string',
description: 'Model ID. Free: groq/llama-3.1-8b-instant, groq/llama-3.3-70b, groq/qwen3.6-27b, groq/gpt-oss-120b, sambanova/llama-3.3-70b, nvidia/nemotron-ultra-550b, nvidia/deepseek-v4-flash. Paid: tongkhokr/claude-sonnet-5, tongkhokr/claude-opus-5, quickai/gpt-5.4-mini, quickai/gpt-5.5.',
},
message: { type: 'string', description: 'The user message to send.' },
system: { type: 'string', description: 'Optional system prompt.' },
max_tokens: { type: 'number', description: 'Max output tokens (default 1024).' },
},
},
},
{
name: 'clervo_search',
description: 'Search the web. Returns structured results with titles, URLs, and snippets. Free, no API key needed. Use for finding current information, documentation, or research.',
inputSchema: {
type: 'object',
required: ['query'],
properties: {
query: { type: 'string', description: 'Search query (max 500 chars).' },
max_results: { type: 'number', description: 'Number of results (1-10, default 5).' },
},
},
},
{
name: 'clervo_scrape',
description: 'Convert any URL to clean markdown. Free, no API key needed. Use for reading web pages, documentation, or extracting content from URLs.',
inputSchema: {
type: 'object',
required: ['url'],
properties: {
url: { type: 'string', description: 'The URL to scrape and convert to markdown.' },
},
},
},
{
name: 'clervo_models',
description: 'List all available Clervo models with pricing. Shows free models (no payment needed) and paid models (x402 USDC on Base). 26 models across Groq, SambaNova, Nvidia, Claude, and GPT families.',
inputSchema: { type: 'object', properties: {} },
},
{
name: 'clervo_status',
description: 'Look up an operation by ID to get status, receipt, and cost information.',
inputSchema: {
type: 'object',
required: ['operation_id'],
properties: {
operation_id: { type: 'string', description: 'The operation ID from x-clervo-operation-id header.' },
},
},
},
];
async function handleChat({ model, message, system, max_tokens }) {
const crypto = await import('node:crypto');
const messages = [];
if (system) messages.push({ role: 'system', content: system });
messages.push({ role: 'user', content: message });
const r = await fetch(`${API}/v1/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json', 'idempotency-key': crypto.randomUUID() },
body: JSON.stringify({ model, messages, max_completion_tokens: max_tokens || 1024 }),
});
if (r.status === 402) {
return { content: [{ type: 'text', text: `Payment required for "${model}". Use a free model instead:\n- groq/llama-3.1-8b-instant (fastest, 170ms)\n- groq/llama-3.3-70b (best free quality)\n- nvidia/nemotron-ultra-550b (largest free model)` }] };
}
const j = await r.json();
if (r.status !== 200) {
return { content: [{ type: 'text', text: `Error (${r.status}): ${j.error?.message || JSON.stringify(j)}` }], isError: true };
}
const content = j.choices?.[0]?.message?.content || '';
const opId = r.headers.get('x-clervo-operation-id') || '';
return {
content: [{ type: 'text', text: content }],
...(opId ? { _meta: { operationId: opId, model: j.model, usage: j.usage } } : {}),
};
}
async function handleSearch({ query, max_results }) {
const r = await fetch(`${API}/v1/search`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ query, max_results: max_results || 5 }),
});
const j = await r.json();
if (r.status !== 200) {
return { content: [{ type: 'text', text: `Search error: ${j.error?.message || 'unavailable'}` }], isError: true };
}
let text = `Search results for "${j.query}":\n\n`;
(j.results || []).forEach((result, i) => {
text += `${i + 1}. ${result.title}\n ${result.url}\n ${result.snippet}\n\n`;
});
return { content: [{ type: 'text', text: text.trim() }] };
}
async function handleScrape({ url }) {
const r = await fetch(`${API}/v1/scrape`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ url }),
});
const j = await r.json();
if (r.status !== 200) {
return { content: [{ type: 'text', text: `Scrape error: ${j.error?.message || j.message || 'failed'}` }], isError: true };
}
return { content: [{ type: 'text', text: j.content || 'No content returned.' }] };
}
async function handleModels() {
const r = await fetch(`${API}/v1/models`);
const j = await r.json();
const free = j.data.filter(m => m.lifecycle === 'free_beta');
const paid = j.data.filter(m => m.lifecycle === 'paid_beta');
let text = `Clervo x402 Gateway — ${j.data.length} models | Base mainnet USDC\n\n`;
text += `FREE (no wallet needed, just call):\n`;
free.forEach(m => { text += ` ${m.id} — ${m.description || m.name}\n`; });
text += `\nPAID (x402 USDC on Base, 20% cheaper than BlockRun):\n`;
paid.forEach(m => {
const price = m.paid_pricing?.amount || m.pricing?.amount || '?';
text += ` ${m.id} — $${price}/req — ${m.description || m.name}\n`;
});
text += `\nServices: search (POST /v1/search), scrape (POST /v1/scrape) — both FREE`;
text += `\nQuickstart: ${API}/quickstart.md`;
return { content: [{ type: 'text', text }] };
}
async function handleStatus({ operation_id }) {
const r = await fetch(`${API}/v1/operations/${operation_id}`);
if (r.status === 404) return { content: [{ type: 'text', text: 'Operation not found.' }], isError: true };
const j = await r.json();
return { content: [{ type: 'text', text: JSON.stringify(j, null, 2) }] };
}
// MCP stdio transport
export function serve() {
const rl = createInterface({ input: process.stdin });
function send(msg) {
process.stdout.write(JSON.stringify(msg) + '\n');
}
rl.on('line', async (line) => {
let msg;
try { msg = JSON.parse(line); } catch { return; }
if (msg.method === 'initialize') {
send({ jsonrpc: '2.0', id: msg.id, result: {
protocolVersion: '2024-11-05',
capabilities: { tools: {} },
serverInfo: { name: 'clervo', version: '0.2.0' },
}});
} else if (msg.method === 'notifications/initialized') {
// no response needed
} else if (msg.method === 'tools/list') {
send({ jsonrpc: '2.0', id: msg.id, result: { tools } });
} else if (msg.method === 'tools/call') {
const { name, arguments: args } = msg.params;
let result;
try {
if (name === 'clervo_chat') result = await handleChat(args);
else if (name === 'clervo_search') result = await handleSearch(args);
else if (name === 'clervo_scrape') result = await handleScrape(args);
else if (name === 'clervo_models') result = await handleModels();
else if (name === 'clervo_status') result = await handleStatus(args);
else result = { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
} catch (e) {
result = { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true };
}
send({ jsonrpc: '2.0', id: msg.id, result });
} else if (msg.id) {
send({ jsonrpc: '2.0', id: msg.id, error: { code: -32601, message: 'Method not found' } });
}
});
rl.on('close', () => {
setTimeout(() => process.exit(0), 100);
});
}