#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fetch from "node-fetch";
const APP_KEY = process.env.QIXIN_APP_KEY;
const SECRET_KEY = process.env.QIXIN_SECRET_KEY;
const BASE_URL = process.env.QIXIN_BASE_URL || "https://api.qixin.com/APIService";
if (!APP_KEY || !SECRET_KEY) {
console.error("错误:环境变量 QIXIN_APP_KEY 和 QIXIN_SECRET_KEY 必须设置。");
process.exit(1);
}
export interface ApiResponse<T = any> {
status: string;
data: T;
message?: string;
}
const QIXIN_STATUS_CODES: Record<string, string> = {
"200": "查询成功",
"201": "查询无结果",
"202": "查询正在进行中,请稍后再试",
"203": "数据正在更新,请稍后查询",
"207": "查询发生错误,请联系技术支持以获取帮助",
"208": "请求参数错误或为空,请检查您的输入是否正确",
"209": "接口查询异常,请联系技术支持以获取帮助",
"213": "今日调用次数已达上限,请明天再试或联系升级",
"214": "API密钥鉴权失败,请检查您的AppKey和SecretKey是否正确",
"216": "账户调用次数已达总额度上限,请联系升级",
"101": "AppKey无效,请检查您的AppKey配置",
"102": "账户余额不足,请及时充值",
"103": "AppKey已被停用,请联系技术支持",
"104": "IP白名单未设置或当前IP不在允许列表中,请检查您的IP白名单配置",
"105": "当前AppKey未授权调用此接口,请联系技术支持",
"109": "接口暂时不可用或已被停用,请联系技术支持",
"110": "您的账户已过期,请续费后使用",
"113": "您的账户尚未激活,请激活后使用"
};
async function fetchQixin(path: string, queryParams: Record<string, string | number | undefined>): Promise<{ isError: boolean; content: { type: "text"; text: string }[] }> {
const url = new URL(`${BASE_URL}${path}`);
Object.entries(queryParams).forEach(([key, value]) => {
if (value !== undefined) {
url.searchParams.set(key, value.toString());
}
});
url.searchParams.set("appkey", APP_KEY!);
url.searchParams.set("secret_key", SECRET_KEY!);
try {
const res = await fetch(url.toString());
if (!res.ok) {
return {
isError: true,
content: [{ type: "text", text: `Error: Failed to fetch ${path}: ${res.status} ${res.statusText}` }]
};
}
let responseData: ApiResponse;
try {
responseData = await res.json() as ApiResponse;
} catch (jsonError: any) {
return {
isError: true,
content: [{ type: "text", text: `Error: Failed to parse JSON response from ${path}: ${jsonError.message}` }]
};
}
if (responseData.status === "200") {
return {
isError: false,
content: [{ type: "text", text: JSON.stringify(responseData.data ?? null) }]
};
} else {
const userMessage = QIXIN_STATUS_CODES[responseData.status] || responseData.message || `未知业务状态码: ${responseData.status}`;
return {
isError: true,
content: [{ type: "text", text: `Error: ${userMessage}` }]
};
}
} catch (error: any) {
return {
isError: true,
content: [{ type: "text", text: `Error: ${error.message}` }]
};
}
}
async function main() {
const server = new McpServer({ name: "qixin-mcp-server", version: "1.0.0" });
server.tool(
"advSearch",
"根据关键词对企业进行模糊搜索(注意,首先用这个接口找到公司的全名)",
{
keyword: z.string().describe("企业相关关键字,输入字数>=2且不能仅输入\"公司\"或\"有限公司\""),
matchType: z.string().optional().describe("匹配类型,可选,多类型逗号分隔"),
region: z.string().optional().describe("地区编码,可选,省2位/市4位/区6位"),
skip: z.number().optional().describe("跳过条目数(默认0,单页返回10条数据)")
},
async ({ keyword, matchType, region, skip = 0 }) => {
return await fetchQixin("/v2/search/advSearch", { keyword, matchType, region, skip });
}
);
server.tool(
"getBasicInfo",
"【工商照面】企业工商照面及相关信息,包括统一社会信用代码、注册资本、经营范围、企业法定代表人等 ",
{ keyword: z.string().describe("企业全名,使用从advSearch接口获取") },
async ({ keyword }) => {
return await fetchQixin("/enterprise/getBasicInfoMCP", { keyword });
}
)
server.tool(
"getEnterpriseInfo",
"【企业信息全面排查】通过企业名称获取包括工商信息、股东结构、主要人员、对外投资、经营状况等全面数据。",
{ name: z.string().describe("企业全名,使用从advSearch接口获取的企业全称") },
async ({ name }) => {
return await fetchQixin("/getAllEntInfoByNameMCP", { name });
}
);
server.tool(
"getAllRiskInfo",
"【企业综合风险排查】通过企业名称调用,获取企业在工商、司法、经营、财务、税务等多维度风险信息。",
{ name: z.string().describe("企业全名,使用从advSearch接口获取的企业全称") },
async ({ name }) => {
return await fetchQixin("/reportData/getAllRiskInfoByNameMCP", { name });
}
);
server.tool(
"getAllPersonInfo",
"【企业董监高信息洞察】通过企业名称+人名调用,获取企业高管全面信息,包括个人任职、投资情况、风险信息等。",
{
ename: z.string().describe("企业全名,使用从advSearch接口获取的企业全称"),
pname: z.string().describe("人员姓名,使用从前面接口查到的创始人名字,或者用户提供的名字")
},
async ({ ename, pname }) => {
return await fetchQixin("/reportData/getAllPersonInfoMCP", { ename, pname });
}
);
server.tool(
"sumLawsuit",
"【整体诉讼统计信息】裁判文书、开庭公告、执行公告、失信公告、法院公告、立案信息、限制高消费、终本案件等。",
{ name: z.string().describe("企业全名,使用从advSearch接口获取的企业全称") },
async ({ name }) => {
return await fetchQixin("/sumLawsuitMCP", { name });
}
)
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});