Consume MCP services — connect AI agents to external tools for screenshots, DNS, WHOIS, SSL, OCR, and blockchain queries with three-tier auth.
Connect AI agents to external MCP services for web intelligence, blockchain data, document processing, and more. Production patterns for authentication, payments, and multi-tool workflows.
MCP (Model Context Protocol) lets AI agents call external tools through a standardized interface. Instead of building every capability from scratch, agents connect to MCP services that provide specialized tools — screenshots, DNS lookups, blockchain queries, OCR, and more.
This skill teaches you to:
Build vs. consume decision matrix:
| Factor | Build Your Own | Use MCP Service |
|---|---|---|
| Time to value | Days–weeks | Minutes |
| Infrastructure | Your servers, your ops | Managed |
| Cost at low volume | High (fixed costs) | Free tier available |
| Cost at high volume | Lower marginal | Pay-per-call |
| Customization | Full control | Limited to API |
| Reliability | Your SLA | Provider's SLA |
When to consume: You need web screenshots, DNS/WHOIS lookups, SSL checks, OCR, or blockchain data. These are commodity capabilities — don't rebuild them.
When to build: You need proprietary data access, custom business logic, or sub-millisecond latency.
SSE (Server-Sent Events) — HTTP-based, works across networks:
Endpoint: https://mcp.skills.ws/mcp/sse
Protocol: HTTP GET (SSE stream) + HTTP POST (tool calls)
stdio — Local process, used for CLI tools:
Command: npx @mcp/some-local-server
Protocol: JSON-RPC over stdin/stdout
For remote services, SSE is the standard transport.
Always verify a service is up before configuring:
curl -s https://mcp.skills.ws/health
# {"status":"ok","services":["screenshot","whois","blockchain"]}
A production MCP service providing web intelligence and blockchain tools.
# Response is JSON with base64 image — extract and decode to get the PNG
curl -s "https://mcp.skills.ws/api/screenshot?url=https://example.com" \
| jq -r '.image' | sed 's|data:image/png;base64,||' | base64 -d > screenshot.png
Parameters:
url (required) — URL to capturewidth — Viewport width (default: 1280)height — Viewport height (default: 800)fullPage — Capture full scrollable page (default: false)format — png or jpeg (default: png)curl -s "https://mcp.skills.ws/api/whois?domain=example.com"
Returns: registrar, creation/expiry dates, nameservers, registrant info (when available).
curl -s "https://mcp.skills.ws/api/dns?domain=example.com&type=A"
Parameters:
domain (required)type — A, AAAA, MX, NS, TXT, CNAME, SOA (default: A)curl -s "https://mcp.skills.ws/api/ssl?domain=example.com"
Returns: issuer, validity dates, SANs, certificate chain, protocol support.
curl -s "https://mcp.skills.ws/api/ocr?url=https://example.com/receipt.png"
# Native token balance
curl -s "https://mcp.skills.ws/api/chain/balance?address=0x...&chain=celo"
# ERC20 token balance
curl -s "https://mcp.skills.ws/api/chain/erc20?address=0x...&token=0xTOKEN_ADDRESS&chain=base"
# Transaction details
curl -s "https://mcp.skills.ws/api/chain/tx?hash=0x...&chain=ethereum"
Supported chains: Ethereum, Base, Arbitrum, Optimism, Polygon, Celo.
10 API calls per day per IP address. No signup needed.
# Just call it — no headers required
curl -s "https://mcp.skills.ws/api/dns?domain=example.com"
Rate limit headers in response:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
When exhausted:
{
"error": "Daily free limit reached",
"limit": 10,
"upgrade": {
"stripe": "POST /billing/checkout for unlimited API key ($9/mo)",
"x402": {
"price": 0.005,
"currency": "USD",
"receiver": "0x087ae921CE8d07a4dE6BdacAceD475e9080B2aDF",
"networks": ["base", "celo"],
"accepts": ["USDC", "USDT"]
}
}
}
Unlimited calls with a subscription API key.
Get a key:
# Create checkout session
curl -s -X POST "https://mcp.skills.ws/billing/checkout" | jq .url
# Opens Stripe checkout → pay → receive API key
Use the key:
curl -s "https://mcp.skills.ws/api/whois?domain=example.com" \
-H "X-Api-Key: mcp_your_key_here"
Pay with stablecoins per request — no subscription needed. Ideal for AI agents with crypto wallets.
Supported:
0x087ae921CE8d07a4dE6BdacAceD475e9080B2aDFPayment header format:
const payment = {
network: "base", // "base" or "celo"
token: "USDC", // "USDC" or "USDT"
txHash: "0xabc123...", // Transaction hash proving payment
amount: "0.005", // USD amount (must be >= 0.005)
receiver: "0x087ae921CE8d07a4dE6BdacAceD475e9080B2aDF"
};
const header = Buffer.from(JSON.stringify(payment)).toString('base64');
Making a paid call:
# After sending $0.005 USDC to the receiver address:
PAYMENT=$(echo -n '{"network":"base","token":"USDC","txHash":"0xYOUR_TX_HASH","amount":"0.005","receiver":"0x087ae921CE8d07a4dE6BdacAceD475e9080B2aDF"}' | base64)
curl -s "https://mcp.skills.ws/api/dns?domain=example.com" \
-H "X-Payment: $PAYMENT"
x402 flow in JavaScript:
import { encodeFunctionData, parseUnits } from 'viem';
import { base } from 'viem/chains';
const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const RECEIVER = '0x087ae921CE8d07a4dE6BdacAceD475e9080B2aDF';
const PRICE = parseUnits('0.005', 6); // USDC has 6 decimals
// 1. Send USDC payment
const txHash = await walletClient.sendTransaction({
to: USDC_BASE,
data: encodeFunctionData({
abi: [{
name: 'transfer',
type: 'function',
inputs: [
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ type: 'bool' }]
}],
args: [RECEIVER, PRICE]
}),
chain: base
});
// 2. Build payment proof
const payment = Buffer.from(JSON.stringify({
network: 'base',
token: 'USDC',
txHash,
amount: '0.005',
receiver: RECEIVER
})).toString('base64');
// 3. Make authenticated API call
const res = await fetch('https://mcp.skills.ws/api/screenshot?url=https://example.com', {
headers: { 'X-Payment': payment }
});
x402 flow in Python:
import base64, json, requests
payment = {
"network": "base",
"token": "USDC",
"txHash": "0xYOUR_TX_HASH",
"amount": "0.005",
"receiver": "0x087ae921CE8d07a4dE6BdacAceD475e9080B2aDF"
}
header = base64.b64encode(json.dumps(payment).encode()).decode()
response = requests.get(
"https://mcp.skills.ws/api/dns",
params={"domain": "example.com"},
headers={"X-Payment": header}
)
print(response.json())
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"skills-ws": {
"url": "https://mcp.skills.ws/mcp/sse",
"headers": {
"X-Api-Key": "mcp_your_key_here"
}
}
}
}
For free tier (no key needed):
{
"mcpServers": {
"skills-ws": {
"url": "https://mcp.skills.ws/mcp/sse"
}
}
}
# Add as MCP server
claude mcp add skills-ws https://mcp.skills.ws/mcp/sse
# With API key
claude mcp add skills-ws https://mcp.skills.ws/mcp/sse --header "X-Api-Key: mcp_your_key_here"
Or in .claude/settings.json:
{
"mcpServers": {
"skills-ws": {
"type": "sse",
"url": "https://mcp.skills.ws/mcp/sse",
"headers": {
"X-Api-Key": "mcp_your_key_here"
}
}
}
}
In Cursor settings → MCP Servers:
{
"skills-ws": {
"url": "https://mcp.skills.ws/mcp/sse",
"headers": {
"X-Api-Key": "mcp_your_key_here"
}
}
}
In openclaw.json:
{
"mcp": {
"servers": {
"skills-ws": {
"transport": "sse",
"url": "https://mcp.skills.ws/mcp/sse",
"headers": {
"X-Api-Key": "mcp_your_key_here"
}
}
}
}
}
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Process response |
| 400 | Bad request | Fix parameters |
| 401 | Invalid API key | Check/regenerate key |
| 402 | Payment required | x402 payment invalid or insufficient |
| 429 | Rate limited | Wait or upgrade tier |
| 500 | Server error | Retry with backoff |
| 503 | Service unavailable | Retry later |
async function mcpCall(url, headers = {}, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const res = await fetch(url, { headers });
if (res.status === 429) {
const waitMs = Math.min(1000 * Math.pow(2, i), 30000);
console.log(`Rate limited. Waiting ${waitMs}ms...`);
await new Promise(r => setTimeout(r, waitMs));
continue;
}
if (res.status === 402) {
const body = await res.json();
console.log('Payment required:', body.x402);
throw new Error('x402 payment needed');
}
if (res.status === 401) {
throw new Error('Invalid API key');
}
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`);
}
return await res.json();
} catch (err) {
if (i === maxRetries - 1) throw err;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
}
}
// Usage
const dns = await mcpCall(
'https://mcp.skills.ws/api/dns?domain=example.com',
{ 'X-Api-Key': 'mcp_your_key_here' }
);
import time
import requests
def mcp_call(url, headers=None, max_retries=3):
for i in range(max_retries):
try:
res = requests.get(url, headers=headers or {}, timeout=30)
if res.status_code == 429:
wait = min(2 ** i, 30)
print(f"Rate limited. Waiting {wait}s...")
time.sleep(wait)
continue
if res.status_code == 402:
print("Payment required:", res.json().get("x402"))
raise Exception("x402 payment needed")
if res.status_code == 401:
raise Exception("Invalid API key")
res.raise_for_status()
return res.json()
except requests.exceptions.RequestException as e:
if i == max_retries - 1:
raise
time.sleep(2 ** i)
# Usage
dns = mcp_call(
"https://mcp.skills.ws/api/dns?domain=example.com",
headers={"X-Api-Key": "mcp_your_key_here"}
)
async function auditWebsite(domain, apiKey) {
const headers = { 'X-Api-Key': apiKey };
const base = 'https://mcp.skills.ws/api';
const [dns, ssl, whois] = await Promise.all([
mcpCall(`${base}/dns?domain=${domain}&type=A`, headers),
mcpCall(`${base}/ssl?domain=${domain}`, headers),
mcpCall(`${base}/whois?domain=${domain}`, headers),
]);
// Take screenshot after DNS resolves
const screenshot = await fetch(
`${base}/screenshot?url=https://${domain}&fullPage=true`,
{ headers }
);
return {
domain,
dns: dns.records,
ssl: {
issuer: ssl.certificate.issuer,
validUntil: ssl.certificate.validUntil,
daysRemaining: ssl.certificate.daysUntilExpiry,
},
whois: {
registrar: whois.whois?.registrar,
expires: whois.whois?.expiryDate, // field names vary by registrar/TLD
},
screenshot: await screenshot.arrayBuffer(),
};
}
async function processReceipt(imageUrl, apiKey) {
const headers = { 'X-Api-Key': apiKey };
// Extract text from receipt image
const ocr = await fetch(
`https://mcp.skills.ws/api/ocr?url=${encodeURIComponent(imageUrl)}`,
{ headers }
);
const { text } = await ocr.json();
// Parse extracted text for amounts, dates, vendor
return {
rawText: text,
// Further parsing with regex or LLM
};
}
async function monitorDomains(domains, apiKey) {
const headers = { 'X-Api-Key': apiKey };
const base = 'https://mcp.skills.ws/api';
const alerts = [];
for (const domain of domains) {
const [ssl, whois] = await Promise.all([
mcpCall(`${base}/ssl?domain=${domain}`, headers),
mcpCall(`${base}/whois?domain=${domain}`, headers),
]);
// Alert if SSL expires within 30 days
if (ssl.certificate.daysUntilExpiry < 30) {
alerts.push({
domain,
type: 'ssl_expiry',
message: `SSL expires in ${ssl.certificate.daysUntilExpiry} days`,
});
}
// Alert if domain expires within 60 days (field names vary by registrar)
const domainDays = Math.floor(
(new Date(whois.whois?.expiryDate) - Date.now()) / 86400000
);
if (domainDays < 60) {
alerts.push({
domain,
type: 'domain_expiry',
message: `Domain expires in ${domainDays} days`,
});
}
}
return alerts;
}
async function walletDashboard(address, chains, apiKey) {
const headers = { 'X-Api-Key': apiKey };
const base = 'https://mcp.skills.ws/api/chain';
const balances = await Promise.all(
chains.map(chain =>
mcpCall(`${base}/balance?address=${address}&chain=${chain}`, headers)
.then(data => ({ chain, ...data }))
)
);
return {
address,
balances,
};
}
Cache responses for data that doesn't change frequently:
const cache = new Map();
const CACHE_TTL = {
dns: 300000, // 5 minutes
whois: 86400000, // 24 hours
ssl: 3600000, // 1 hour
screenshot: 60000, // 1 minute
};
async function cachedMcpCall(tool, params, headers) {
const key = `${tool}:${JSON.stringify(params)}`;
const cached = cache.get(key);
if (cached && Date.now() - cached.time < (CACHE_TTL[tool] || 60000)) {
return cached.data;
}
const query = new URLSearchParams(params).toString();
const data = await mcpCall(
`https://mcp.skills.ws/api/${tool}?${query}`,
headers
);
cache.set(key, { data, time: Date.now() });
return data;
}
Promise.all() for parallel requestsBreak-even calculation:
POST /admin/keys (requires admin secret) to generate a new key, then POST /admin/revoke to revoke the old oneasync function resilientDnsLookup(domain) {
try {
// Primary: MCP service
return await mcpCall(
`https://mcp.skills.ws/api/dns?domain=${domain}`,
{ 'X-Api-Key': API_KEY }
);
} catch (err) {
// Fallback: local dig command
const { execSync } = await import('child_process');
const result = execSync(`dig +short ${domain} A`).toString().trim();
return { records: result.split('\n').filter(Boolean) };
}
}
For building agents that dynamically discover and call MCP tools:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
// Connect to MCP service
const transport = new SSEClientTransport(
new URL('https://mcp.skills.ws/mcp/sse'),
{
requestInit: {
headers: { 'X-Api-Key': 'mcp_your_key_here' }
}
}
);
const client = new Client({ name: 'my-agent', version: '1.0.0' });
await client.connect(transport);
// Discover available tools
const { tools } = await client.listTools();
console.log('Available tools:', tools.map(t => t.name));
// Call a tool
const result = await client.callTool({
name: 'dns',
arguments: { domain: 'example.com', type: 'MX' }
});
console.log('DNS result:', result.content[0].text);
// Cleanup
await client.close();
from mcp import ClientSession
from mcp.client.sse import sse_client
async def main():
headers = {"X-Api-Key": "mcp_your_key_here"}
async with sse_client("https://mcp.skills.ws/mcp/sse", headers=headers) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# List tools
tools = await session.list_tools()
for tool in tools.tools:
print(f" {tool.name}: {tool.description}")
# Call a tool
result = await session.call_tool("whois", {"domain": "example.com"})
print(result)
import asyncio
asyncio.run(main())
# Free tier — check remaining calls via response headers
curl -sI "https://mcp.skills.ws/api/dns?domain=test.com" | grep RateLimit
# X-RateLimit-Limit: 10
# X-RateLimit-Remaining: 6
curl -s "https://mcp.skills.ws/admin/stats" \
-H "X-Admin-Secret: your_admin_secret" | jq .
Returns:
{
"freeUsers": 42,
"totalFreeRequests": 156,
"keys": { "total": 5, "active": 4, "revoked": 1 },
"requests": { "free": 156, "apikey": 1203, "x402": 47, "blocked": 12 },
"freeLimit": 10,
"x402Price": 0.005
}
Upgrade to API key ($9/mo) or use x402 pay-per-call.
Key may have been revoked (subscription cancelled). Generate a new one via /billing/checkout.
x402 payment header is malformed, amount is too low, or txHash was already used (replay prevention).
Reconnect with exponential backoff. SSE connections may timeout after extended idle periods.
Screenshots take 3-10s depending on page complexity. DNS/WHOIS/SSL are typically <1s. Use timeouts appropriately.
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/health | GET | None | Service status |
/mcp/sse | GET | Optional | MCP SSE transport |
/api/screenshot | GET | Any tier | Webpage capture |
/api/pdf | GET | Any tier | PDF generation |
/api/html2md | GET | Any tier | URL to Markdown |
/api/whois | GET | Any tier | Domain WHOIS |
/api/dns | GET | Any tier | DNS records |
/api/ssl | GET | Any tier | SSL certificate |
/api/ocr | GET | Any tier | Image text extraction |
/api/chain/balance | GET | Any tier | Native token balance |
/api/chain/erc20 | GET | Any tier | ERC20 token balance |
/api/chain/tx | GET | Any tier | Transaction details |
/billing/checkout | POST | None | Get API key ($9/mo) |
/billing/success | GET | None | Retrieve key after payment |
Default service endpoint: https://mcp.skills.ws