1. 这是什么
GraphQL 是同一个数据底座上的第二种取数方式。你写一段查询,声明要哪些字段、传什么参数,服务端按需返回,不需要为每个接口记不同的 URL 和参数格式。
用 MCP,当……
调用方是大模型客户端(WorkBuddy、Claude、Cursor 等),希望模型自己挑工具、自己组合,不需要你写代码。
用 GraphQL,当……
调用方是你自己的后端、脚本或 BI,字段固定、需要稳定契约,想一次拿到结果、自己控制解析和落库。
两者的关系:底层是同一批上游接口、同一套计费和权限,只是入口不同。已经接入 MCP 的账号无需额外开通,同一个 Token 直接可用。
2. 端点与鉴权
| 项目 | 值 |
|---|---|
| 端点 | https://mcp.yushanshuju.com/graphql |
| 方法 | POST |
| Content-Type | application/json |
| 鉴权头 | Authorization: Bearer <TOKEN> |
| Token 来源 | 在 控制台 → Token 管理 中创建,与 MCP 通用 |
3. 快速开始
curl
终端执行
curl -s https://mcp.yushanshuju.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $YUSHAN_TOKEN" \
-d '{"query":"{ queryCompanyBasicInfo(entname: \"深圳市腾讯计算机系统有限公司\") }"}'
Python
graphql_demo.py
import json, os, httpx
TOKEN = os.environ["YUSHAN_TOKEN"]
URL = "https://mcp.yushanshuju.com/graphql"
QUERY = """
query Basic($name: String!) {
queryCompanyBasicInfo(entname: $name)
}
"""
with httpx.Client(timeout=30) as c:
r = c.post(
URL,
headers={"Authorization": f"Bearer {TOKEN}"},
json={"query": QUERY, "variables": {"name": "深圳市腾讯计算机系统有限公司"}},
)
r.raise_for_status()
payload = r.json()
raw = payload["data"]["queryCompanyBasicInfo"]
inner = json.loads(raw) # 返回的是 JSON 字符串,再解析一次
basic = inner["retdata"]["BASIC"]
print(basic["ENTNAME"], basic["FRNAME"]) # 内层字段名是全大写
JavaScript
fetch
const res = await fetch("https://mcp.yushanshuju.com/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.YUSHAN_TOKEN}`,
},
body: JSON.stringify({
query: `query { fuzzyQueryCompany(keyWord: "腾讯") }`,
}),
});
const body = await res.json();
const data = JSON.parse(body.data.fuzzyQueryCompany);
console.log(data.retdata);
建议用变量传参(如上面 Python 示例的
$name),不要拼接字符串,避免企业名里的引号和特殊字符破坏查询语法。4. 字段速查
共 13 个查询字段,全部只读,没有 Mutation。返回值均为 JSON 字符串,解析方式见下一节。
| 字段 | 参数 | 返回内容 |
|---|---|---|
| fuzzyQueryCompany | keyWord | 关键词模糊搜索,拿到工商全称,是后续查询的起点 |
| queryCompanyBasicInfo | entname | 信用代码、法人、注册资本、成立日期、经营范围、地址 |
| queryCompanyOverseasInvestments | entName | 对外投资的目标公司、认缴金额、持股比例 |
| queryCompanyChangeRecords | entName | 注册资本、法人、股东、经营范围等历次变更记录 |
| queryCompanyCourtLitigationRelatedInfo | name | 涉诉案件、案由、当事人角色、裁判结果 |
| queryCompanyAbnormalBusinessOperation | keyWord | 经营异常名录:列入原因、决定机关、移出记录 |
| queryCompanyTrademarkList | entName | 商标名称、类别、注册号、法律状态 |
| queryCompanyPatentList | entName | 专利申请号、名称、类型、授权状态 |
| queryCompanySoftwareCopyrightInfo | entName | 软著登记号、软件名称、版本、登记日期 |
| verifyCompanyNameAndPresident | operName, entName | 法人两要素核验,返回 0 一致 / 1 不一致 |
| queryCompanySeriousViolationBlacklist | entname / creditcode / regno / orgcode / enttype | 严重违法失信名单:列入与移出日期、事由、决定机关,附企业档案。前四个至少传一个 |
| queryCompanyGlobalInfo | businessName / businessRegistrationNumber | 境外与离岸主体:注册状态、管辖区、税号、成立日期、标准化登记号 |
| parseAddress | address | 地址解析:省市区、街道、道路、门牌、楼层、房间、邮编、POI |
一次查询里调用多个字段
GraphQL 允许在同一个请求里并列多个字段,服务端会并行执行。这样一次 HTTP 往返就能拿到多个维度——这是它相对单个 REST 接口最实际的好处。
多个字段并列
query DueDiligence($name: String!, $key: String!) {
queryCompanyBasicInfo(entname: $name)
queryCompanyCourtLitigationRelatedInfo(name: $name)
queryCompanyAbnormalBusinessOperation(keyWord: $key)
queryCompanyPatentList(entName: $name)
}
5. 返回格式
字段的返回值是 字符串,里面装着上游原始 JSON。这是为了让不同上游接口的结构原样透出,不做二次改造。
响应示例 · queryCompanyBasicInfo
{
"data": {
"queryCompanyBasicInfo": "{\"retcode\":\"000000\",\"retmsg\":\"...\",\"retdata\":{
\"BASIC\":{\"ENTNAME\":\"深圳市腾讯计算机系统有限公司\",\"FRNAME\":\"马化腾\",\"CREDITCODE\":\"91440300708461136T\"},
\"PERSON\":[ ... ],
\"SHAREHOLDER\":[ ... ]
}}"
}
}
所以解析要两步:先取 data.字段名 拿到字符串,再对它做一次 JSON 解析。注意字符串里的引号是转义的。
retdata 内部还有分组,字段名是大写
以工商基本信息为例,retdata 下面是三个节点,不是直接的字段:
| 节点 | 类型 | 内容 |
|---|---|---|
| retdata.BASIC | 对象 | 工商主体信息:ENTNAME 企业名称、FRNAME 法定代表人、CREDITCODE 信用代码、REGCAP 注册资本、ESDATE 成立日期、ENTSTATUS 经营状态、ZSOPSCOPE 经营范围、DOM 地址 |
| retdata.PERSON | 数组 | 主要人员(董事、监事、高管)列表 |
| retdata.SHAREHOLDER | 数组 | 股东及出资信息列表 |
内层字段名是全大写,和查询参数名不是一套写法。参数用
entname(小写),返回里是 ENTNAME(大写)。上面这个 BASIC / PERSON / SHAREHOLDER 结构是工商基本信息的实际返回;其余字段由各自上游接口定义,结构不一样,建议先用控制台跑一次看你需要的那个字段长什么样。业务状态看 retcode,不是看 HTTP 码
| retcode | 含义 | 怎么处理 |
|---|---|---|
| 000000 | 成功 | 读 retdata |
| err_* | 上游业务错误(如查无此企业、参数不合法) | 按业务失败处理,不要重试 |
| warn_* | 上游告警 | 记录并按提示修正参数 |
企业名必须是工商全称。不确定全称就先调
fuzzyQueryCompany,拿到全称再查详情,否则多数字段会返回空结果——而空结果同样计费。6. 控制台用法
控制台地址:https://mcp.yushanshuju.com/graphql,是一个标准 GraphiQL 界面,可以直接在浏览器里写查询、看返回,不需要装任何工具。
- 打开页面,地址栏不要带任何参数直接访问
https://mcp.yushanshuju.com/graphql。页面只加载界面,不会执行任何查询。 - 在左下角 Headers 面板填入 Token点开
Headers,把内容改成一行:{"Authorization": "Bearer ysk_你的TOKEN"}
保存后这一栏会显示一个绿色小圆点。 - 重新拉取一次 Schema点了 Headers 之后必须点界面上的“重新加载 Schema”按钮(通常是一个刷新图标)。不点的话左侧文档树是空的,执行查询会返回 401。
- 写查询并执行在中间编辑区输入查询,例如
{ fuzzyQueryCompany(keyWord: "腾讯") },点顶部 ▶ 按钮。右侧显示结果。 - 结果看起来“带反斜杠”是正常的因为返回的是字符串形式 JSON,控制台会显示成
"{\"retcode\":...}"。复制出来二次解析即可。
刷新页面后如果又变成 401,说明 Headers 没被保存,重做第 2、3 步即可。浏览器不同,GraphiQL 的存储行为略有差异。
页面白屏?控制台的前端资源从公共 CDN 加载,部分内网或网络受限环境会加载失败。此时不要依赖控制台,直接用第 3 节的 curl 或 Python 示例调用,功能完全一致。
7. 常见错误
| 现象 | 原因 | 处理 |
|---|---|---|
| {"error":"unauthorized"} | 没带 Authorization 头,或 Token 写错/已停用 | 检查 Bearer 后的完整字符串;到控制台确认 Token 状态 |
| 左侧文档树空白 | 填 Headers 后没重新拉 Schema | 点刷新 Schema 按钮 |
| Cannot query field "xxx" | 字段名拼错,或用了 REST 的路径名 | 对照第 4 节表格;字段是 camelCase,首字母小写 |
| Unknown argument "entName" | 参数名大小写不对,该字段要的是 entname | 逐字段核对,别统一套用 |
返回 retcode 非 000000 | 企业名不是工商全称、或该企业无此维度数据 | 先用 fuzzyQueryCompany 取全称 |
| HTTP 200 但 data 为空 | 查询语法合法但字段执行失败 | 看 errors 数组里的 message 和 path |
| 页面白屏 | CDN 资源加载失败 | 改用 curl / SDK 调用 |
8. 限制说明
- 只读。没有 Mutation,不能通过 GraphQL 创建、修改或删除任何数据。
- GET 不能执行查询。GET 只用于返回控制台界面;执行查询必须用 POST,这是为了防止凭证出现在 URL 里被日志记录。
- 列表类字段只返回上游第一页,条数因接口而异。专利 / 商标 / 软著 / 变更记录每页 20 条;涉诉每页 20 条(MCP 工具支持
page翻页,上游单次最多取前 10000 条);模糊搜索固定只给前 10 条且上游不支持翻页;对外投资一次性返回全部。GraphQL 侧统一不支持page入参,需要翻页请改用 MCP 工具(query_company_patent_list、query_company_court_litigation_related_info等)。请以返回体的retdata._meta为准:total是真实总量、total_pages是总页数(上游给了才有)、pagination_supported表示该接口能否翻页、has_more表示是否还有下一页。不要用返回条数推断总量。 - 参数长度上限 128 字符,超长会被本地直接拒绝,不消耗上游调用。
- 不支持嵌套查询。字段之间不能互相嵌套,一次一个企业维度。
- 计费按字段计。一次请求里写了几个字段,就算几次调用;缓存命中的部分不计费。