如何用 Qovia MCP 伺服器為 Claude Code 和 Cursor 接入即時社交媒體資料
Give Claude Code and Cursor live social media data with an MCP server
這篇教學示範如何把 Qovia MCP 伺服器接入 Claude Code、Cursor 和 VS Code,讓它們查詢多個平台的即時社交媒體資料。伺服器預設提供搜尋端點、讀取端點規格、呼叫端點及查詢帳戶四個工具;搜尋與讀取規格可匿名使用,實際呼叫則需要 API key。文章亦示範以無狀態 Streamable HTTP 的 JSON-RPC POST,透過 Python 重複執行查詢。
Ask an AI coding assistant "what are people saying about oat-milk lattes on Xiaohongshu this week?" and it will either guess or tell you it can't browse. The fix is an MCP server that exposes real data endpoints as tools. This walkthrough connects the Qovia MCP server (social, e-commerce and web data across Douyin, Xiaohongshu, Weibo, Bilibili, WeChat Official Accounts, TikTok, YouTube, X, Reddit and more) to Claude Code, Cursor and VS Code, then shows how to drive the same server from a plain Python script so your research is reproducible.
Disclosure: I build Qovia, and this article was drafted with AI assistance and checked by hand against the live docs and server.
What the server looks like
- URL:
https://api.qovia.ai/mcp - Transport: Streamable HTTP, stateless (each JSON-RPC request stands alone, answered with JSON; no session).
- Auth: the same key as the REST API,
Authorization: Bearer sk-qovia-....
By default it exposes only four tools, so it doesn't flood the model's context with 150+ endpoint schemas:
| Tool | What it does | Needs key |
|---|---|---|
search_endpoints |
Find endpoints by keyword (English or Chinese) and/or platform slug; returns ids, titles, price per call, live status | No |
get_endpoint |
One endpoint in full: description, JSON Schema params, example params, response example, price | No |
call_endpoint |
Run an endpoint (endpoint_id + params); returns data plus billing meta |
Yes |
get_account |
Balance, plan and rate limit (not charged) | Yes |
The model's loop is: search, read the schema, call. Discovery is free and keyless, so you can explore the catalog before paying for anything. New accounts get 50 trial calls, and only successful, non-empty results are billed.
Connect a client
Create a key at https://qovia.ai/console/?tab=keys and keep it in an environment variable, not in your repo.
Claude Code
export QOVIA_KEY=sk-qovia-... # your own key
claude mcp add --transport http qovia https://api.qovia.ai/mcp \
--header "Authorization: Bearer $QOVIA_KEY"
Cursor (~/.cursor/mcp.json)
{
"mcpServers": {
"qovia": {
"url": "https://api.qovia.ai/mcp",
"headers": { "Authorization": "Bearer sk-qovia-..." }
}
}
}
VS Code (.vscode/mcp.json) prompts for the key on first use and never writes it to the file, which makes it safe to commit:
{
"servers": {
"qovia": {
"type": "http",
"url": "https://api.qovia.ai/mcp",
"headers": { "Authorization": "Bearer ${input:qovia-key}" }
}
},
"inputs": [
{ "type": "promptString", "id": "qovia-key", "description": "Qovia API key", "password": true }
]
}
Claude Desktop uses OAuth for remote connectors, so bridge with mcp-remote:
{
"mcpServers": {
"qovia": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.qovia.ai/mcp", "--header", "Authorization:${QOVIA_AUTH}"],
"env": { "QOVIA_AUTH": "Bearer sk-qovia-..." }
}
}
}
Restart the client and qovia shows up in the tool list.
Optional: direct tools for one platform
If you work on one platform all day, you can skip the search step. Append ?tools= with platform slugs (or ?tools=all):
https://api.qovia.ai/mcp?tools=xiaohongshu,weibo
Every live endpoint on those platforms becomes its own tool, named after its id with dots turned into underscores (raw.xiaohongshu.search becomes raw_xiaohongshu_search). The four default tools stay. Keep the list short: each tool adds its schema to the context.
Prompts that work well
Research prompts go best when they name the platform, the time window and the shape of the output you want:
Using the qovia tools, search Xiaohongshu for notes about "燕麦拿铁" published this week, sorted by most liked. Give me a table of the top 10 with title, likes, saves, comments and author, then summarise the three recurring complaints.
A typical tool trace:
search_endpoints {"query": "xiaohongshu search"}
get_endpoint {"endpoint_id": "raw.xiaohongshu.search"}
call_endpoint {"endpoint_id": "raw.xiaohongshu.search",
"params": {"keyword": "燕麦拿铁", ...}}
get_endpoint is what keeps the model honest: it reads the real parameter schema (for example, Xiaohongshu search V4 takes note_type of all|video|image and note_time of all|day|week) instead of inventing flags. Unknown parameters are rejected with 400 invalid_params, which the model can see and correct.
Some other prompts worth trying:
- "Find the WeChat Official Accounts that publish about cross-border payments, then list their latest articles." (
wechatplatform) - "Pull the Weibo hot search list and tell me which entries relate to EVs."
- "Compare comments on this Bilibili video and this YouTube video on the same product."
Each call_endpoint result includes charged_usd and your balance, so you can ask the assistant to keep a running cost total.
Do the same thing from Python
Chat is great for exploration, but once a query is useful you want it in a script. Because the server is stateless Streamable HTTP, you don't even need an MCP SDK: a JSON-RPC POST per call is enough.
# qovia_mcp.py - minimal client for the Qovia MCP server
import json
import os
import requests
MCP_URL = "https://api.qovia.ai/mcp"
HEADERS = {
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
}
if os.environ.get("QOVIA_KEY"):
HEADERS["Authorization"] = f"Bearer {os.environ['QOVIA_KEY']}"
_id = 0
def rpc(method, params=None):
global _id
_id += 1
body = {"jsonrpc": "2.0", "id": _id, "method": method}
if params is not None:
body["params"] = params
r = requests.post(MCP_URL, headers=HEADERS, json=body, timeout=60)
r.raise_for_status()
msg = r.json()
if "error" in msg:
raise RuntimeError(msg["error"])
return msg["result"]
def tool(name, **arguments):
result = rpc("tools/call", {"name": name, "arguments": arguments})
if result.get("isError"):
raise RuntimeError(result["content"][0]["text"])
# Prefer structured output; fall back to the text block.
if "structuredContent" in result:
return result["structuredContent"]
return json.loads(result["content"][0]["text"])
if __name__ == "__main__":
# 1. Discovery: free, no key needed
for t in rpc("tools/list")["tools"]:
print("tool:", t["name"])
found = tool("search_endpoints", query="xiaohongshu search")
for ep in found["endpoints"]:
print(ep["id"], ep["price_usd_per_call"], "live" if ep["available"] else "soon")
detail = tool("get_endpoint", endpoint_id="raw.xiaohongshu.search")
print(json.dumps(detail, ensure_ascii=False)[:600])
Run it without a key first; tools/list, search_endpoints and get_endpoint all work anonymously:
tool: search_endpoints
tool: get_endpoint
tool: call_endpoint
tool: get_account
raw.xiaohongshu.search 0.005 live
raw.xiaohongshu.search_v4 0.02536 live
raw.pgy.note_search 0.01 live
raw.pgy.note_search_v2 0.02536 live
Now add a paid call. Xiaohongshu note search returns the platform's own result: items, each with model_type and a note (id, title, liked_count, collected_count, comments_count, user, ...). There is no has_more; you page until a page comes back empty, and empty pages are not charged.
# xhs_top_notes.py
import os
from qovia_mcp import tool
assert os.environ.get("QOVIA_KEY"), "set QOVIA_KEY first"
print(tool("get_account")) # balance and rate limit, not charged
KEYWORD = "燕麦拿铁"
notes = []
for page in range(1, 4):
out = tool("call_endpoint",
endpoint_id="raw.xiaohongshu.search",
params={"keyword": KEYWORD, "page": page})
data = out.get("data") or {}
items = [i["note"] for i in data.get("items", []) if i.get("model_type") == "note"]
if not items:
break
notes.extend(items)
print(f"page {page}: {len(items)} notes, charged {out.get('meta', {}).get('charged_usd')}")
notes.sort(key=lambda n: int(n.get("liked_count") or 0), reverse=True)
for n in notes[:10]:
print(f"{n.get('liked_count'):>7} likes {n.get('collected_count'):>6} saves "
f"{n.get('title') or '(no title)'} @{n['user']['nickname']}")
The shape of call_endpoint's output mirrors the REST envelope (data plus meta with charged_usd). If your version prints it under a different key, print(out.keys()) once and adjust; the point is that what the assistant saw in chat and what your script sees are the same data.
Handling errors and limits
- Errors come back as
{"error": {"code", "message", ...}}and are never charged. Branch oncode:invalid_params(fix your params),rate_limited/endpoint_busy(back off and honourRetry-After),insufficient_balance,upstream_timeout(retry later). - The account limit is 60 requests per minute (600 after the first top-up). A three-page loop is fine; a 500-keyword sweep needs a
time.sleep. - Keep keys out of prompts and committed configs. The VS Code
inputspattern and environment variables are the easy wins.
When to use MCP vs. the REST API
Use the MCP server when a person is asking open questions and the model should pick the endpoint. Use the REST API (GET https://api.qovia.ai/v1/raw/xiaohongshu/search?keyword=...) in production jobs where the endpoint is fixed and you want one less moving part. Calls are billed identically either way, so prototyping in Claude or Cursor and then hardening in Python costs nothing extra.
Docs: MCP setup, API reference, and the whole catalog as one file for your model's context at llms-full.txt.
來源:DEV Community · dev.to