feat(datasource): add wind, imf, gildata, sec_edgar, and sp_data sources (#2029)
Some checks are pending
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / Check flake.nix workspace sync (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions

* feat(datasource): add wind, imf, gildata, sec_edgar, and sp_data sources

* feat(datasource): strengthen source routing in skill and tool schema

* feat(datasource): retry on credential rotation and enforce single-source routing contract

* feat(datasource): neutral source selection with objective capability boundaries

* feat(datasource): note wind_search_fields field-name mapping in skill and schema
This commit is contained in:
qer 2026-07-22 22:45:55 +08:00 committed by GitHub
parent 64f053cf46
commit e0f2a41769
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 228 additions and 32 deletions

View file

@ -217,7 +217,70 @@ describe('kimi-datasource MCP server', () => {
}
});
it('registers yuandian_law in the get_data_source_desc enum', async () => {
it('retries with a rotated credential when the backend rejects the previous token', async () => {
const tempDir = await mkdtemp(join(tmpdir(), 'kimi-datasource-plugin-'));
const kimiHome = join(tempDir, 'kimi-home');
const credentialsFile = join(kimiHome, 'credentials', 'kimi-code.json');
const authorizations: Array<string | undefined> = [];
let child: ChildProcessWithoutNullStreams | undefined;
const server = createServer((request, response) => {
void handleCredentialRotationRequest(request, response, {
authorizations,
credentialsFile,
});
});
try {
await mkdir(join(kimiHome, 'credentials'), { recursive: true });
await writeFile(
credentialsFile,
JSON.stringify({ access_token: 'previous-token', expires_at: 1 }),
'utf8',
);
await listen(server);
const address = server.address();
if (address === null || typeof address === 'string') {
throw new Error('Expected an ephemeral TCP port for the test server.');
}
child = spawn(process.execPath, [SERVER_ENTRY], {
cwd: REPO_ROOT,
env: {
...process.env,
KIMI_CODE_HOME: kimiHome,
KIMI_DATASOURCE_API_URL: `http://127.0.0.1:${address.port}`,
},
stdio: ['pipe', 'pipe', 'pipe'],
});
const client = createRpcClient(child);
await client.request('initialize', {});
const result = await client.request('tools/call', {
name: 'get_data_source_desc',
arguments: { name: 'imf' },
});
expect(result.error).toBeUndefined();
expect(result.result).toEqual({
content: [
{
type: 'text',
text: expect.stringContaining('assistant complete result'),
},
],
});
expect(authorizations).toEqual(['Bearer previous-token', 'Bearer refreshed-token']);
} finally {
child?.stdin.end();
child?.kill();
await closeServer(server);
await rm(tempDir, { recursive: true, force: true });
}
});
it('returns the complete data-source routing contract when tools are listed', async () => {
const tempDir = await mkdtemp(join(tmpdir(), 'kimi-datasource-plugin-'));
const kimiHome = join(tempDir, 'kimi-home');
let child: ChildProcessWithoutNullStreams | undefined;
@ -241,11 +304,42 @@ describe('kimi-datasource MCP server', () => {
const tools = (
result.result as {
tools: Array<{ name: string; inputSchema: { properties: { name: { enum: string[] } } } }>;
tools: Array<{
name: string;
description: string;
inputSchema: {
properties: Record<string, { description?: string; enum?: string[] }>;
};
}>;
}
).tools;
const call = tools.find((tool) => tool.name === 'call_data_source_tool');
const desc = tools.find((tool) => tool.name === 'get_data_source_desc');
expect(desc?.inputSchema.properties.name.enum).toContain('yuandian_law');
expect(desc?.inputSchema.properties['name']?.enum).toEqual([
'stock_finance_data',
'yahoo_finance',
'world_bank_open_data',
'tianyancha',
'arxiv',
'scholar',
'yuandian_law',
'wind',
'imf',
'gildata',
'sec_edgar',
'sp_data',
]);
expect(call?.description).toContain(
'For a simple lookup, use one specialized source and stop after its first successful result',
);
expect(call?.description).toContain('When the user names a data source, use that source');
expect(call?.inputSchema.properties['data_source_name']?.description).toContain(
'When the user names a source, pass that source',
);
expect(desc?.description).toContain('choose exactly one specialized source');
expect(desc?.inputSchema.properties['name']?.description).toContain(
'yahoo_finance FX history is limited to about 2 years',
);
} finally {
child?.stdin.end();
child?.kill();
@ -371,6 +465,42 @@ async function handleMockDatasourceRequest(
}
}
async function handleCredentialRotationRequest(
request: IncomingMessage,
response: ServerResponse,
options: {
readonly authorizations: Array<string | undefined>;
readonly credentialsFile: string;
},
): Promise<void> {
try {
await readJson(request);
options.authorizations.push(request.headers.authorization);
response.setHeader('Content-Type', 'application/json');
if (options.authorizations.length === 1) {
await writeFile(
options.credentialsFile,
JSON.stringify({ access_token: 'refreshed-token', expires_at: 4_102_444_800 }),
'utf8',
);
response.statusCode = 401;
response.end(JSON.stringify({ error: 'expired access token' }));
return;
}
response.end(
JSON.stringify({
is_success: true,
result: { assistant: [{ type: 'text', text: 'assistant complete result' }] },
}),
);
} catch (error) {
response.statusCode = 500;
response.end(error instanceof Error ? error.message : String(error));
}
}
function listen(server: ReturnType<typeof createServer>): Promise<void> {
return new Promise((resolve, reject) => {
server.once('error', reject);

View file

@ -72,7 +72,7 @@ Pass a custom marketplace JSON path or URL to `/plugins marketplace <source>`, o
## Kimi Datasource
Kimi Datasource is the official Kimi Code data plugin. It lets you query financial market data, macroeconomic indicators, corporate registration records, academic literature, and Chinese laws and regulations in natural language — no manual API calls or data account registration required.
Kimi Datasource is the official Kimi Code data plugin. It lets you query financial market data, macroeconomic indicators, corporate registration records, academic literature, and Chinese laws and regulations in natural language — with professional finance sources such as Wind, IMF, Gildata, SEC EDGAR, and S&P Capital IQ built in, no manual API calls or data account registration required.
### Installation
@ -82,7 +82,7 @@ You must first complete OAuth login with a Kimi Code account via `/login`. The p
2. Find **Kimi Datasource** and press `Enter` to install
3. After installation completes, run `/reload` or `/new` to activate the plugin
The current latest version is v3.2.0. The plugin does not update automatically — to upgrade to a newer version, repeat the installation steps above.
The current latest version is v3.3.0. The plugin does not update automatically — to upgrade to a newer version, repeat the installation steps above.
### How to use
@ -100,6 +100,8 @@ Once installed, describe your need in natural language and Kimi Code will automa
**On-the-spot legal lookup**: Stuck on which statute governs a residence-right contract dispute? Pinpoint the relevant Civil Code articles — full text, authority level, and validity — then pull a few comparable precedents to back them up, without digging through statute databases.
**Institutional-grade US equity research**: Writing a deep dive on a US stock? Pull the 10-K filing, standardized XBRL metrics, top-50 holders, and consensus estimates in one go — SEC filings and S&P data without juggling multiple data terminals.
### Coverage
| Category | Scope |
@ -109,6 +111,11 @@ Once installed, describe your need in natural language and Kimi Code will automa
| Corporate data | Business registration, equity chain, legal risk, and related-entity graph for mainland Chinese companies |
| Academic literature | Millions of papers across physics, mathematics, CS, quantitative finance, economics — including preprints |
| Legal | Chinese laws, regulations, and judicial cases — semantic/keyword search and detail lookup for statutes across all authority levels (constitution, laws, judicial interpretations, departmental rules), plus ordinary and authoritative case search |
| Financial terminal (Wind) | A-share, fund, bond, and index quotes with financial indicators, company announcements and research reports, and macroeconomic data |
| International macro (IMF) | Official IMF datasets (IFS, BOP, DOTS, WEO, and more): exchange rates, CPI, balance of payments, trade, and GDP forecasts |
| Smart screening (Gildata) | Natural-language stock / fund / fund-manager screening, plus macro-industry data, research reports, announcements, and news |
| US filings (SEC EDGAR) | 8,000+ US-listed companies — 10-K/10-Q statements, XBRL metrics, Form 4 insider trades, 13F institutional holdings, and 8-K material events (back to 2009) |
| US fundamentals (S&P Capital IQ) | Standardized financial statements, valuation ratios, consensus estimates, holders and executives, competitor relationships, corporate events, and call transcripts |
### Billing and limitations

View file

@ -72,7 +72,7 @@ Plugins 把可复用的 Kimi Code CLI 能力打包成可安装单元——可以
## Kimi Datasource
Kimi Datasource 是 Kimi Code 官方数据插件,让你通过自然语言直接查询金融行情、宏观经济、企业工商、学术文献和中国法律法规,无需手动调用接口或申请任何数据账号。
Kimi Datasource 是 Kimi Code 官方数据插件,让你通过自然语言直接查询金融行情、宏观经济、企业工商、学术文献和中国法律法规,并接入 Wind、IMF、恒生聚源、SEC EDGAR、S&P Capital IQ 等专业金融数据源,无需手动调用接口或申请任何数据账号。
### 安装
@ -82,7 +82,7 @@ Kimi Datasource 是 Kimi Code 官方数据插件,让你通过自然语言直
2. 找到 **Kimi Datasource**,按 `Enter` 安装
3. 安装完成后运行 `/reload``/new` 激活 plugin
当前最新版本为 v3.2.0。插件安装后不会自动更新,如需升级到新版本,重新执行上述安装步骤即可。
当前最新版本为 v3.3.0。插件安装后不会自动更新,如需升级到新版本,重新执行上述安装步骤即可。
### 使用方式
@ -100,6 +100,8 @@ Kimi Datasource 是 Kimi Code 官方数据插件,让你通过自然语言直
**法律条文速查**:碰上居住权的合同纠纷,拿不准法条?一句话定位《民法典》相关条文原文、效力级别和时效性,再顺手拉几个相近判例佐证,不用翻法规库。
**机构级美股研究**:写美股深度报告?一句话拉出 10-K 年报原文、XBRL 标准化指标、前 50 大股东和分析师一致预期SEC 披露文件和 S&P 数据一次配齐,不用在多个数据终端之间来回切。
### 数据覆盖
| 类别 | 覆盖范围 |
@ -109,6 +111,11 @@ Kimi Datasource 是 Kimi Code 官方数据插件,让你通过自然语言直
| 企业数据 | 中国大陆境内企业工商信息、股权穿透、司法风险、关联图谱 |
| 学术文献 | 物理、数学、计算机、金融、经济等领域百万量级论文,支持预印本查询 |
| 法律法规 | 中国法律法规与司法案例:宪法、法律、司法解释、部门规章等各效力层次的法规语义/关键词检索与详情,普通及权威判例检索 |
| 综合金融终端Wind | A 股、基金、债券、指数行情与财务指标,上市公司公告研报,宏观经济数据 |
| 国际宏观IMF | IFS、BOP、DOTS、WEO 等官方数据集汇率、CPI、国际收支、贸易、GDP 预测 |
| 智能筛选(恒生聚源) | 自然语言选股 / 选基金 / 基金经理筛选,宏观行业数据、研报、公告与新闻 |
| 美股披露SEC EDGAR | 8,000+ 美股上市公司 10-K/10-Q 财报、XBRL 指标、Form 4 内部人交易、13F 机构持仓、8-K 重大事项2009 年至今) |
| 美股基本面S&P Capital IQ | 标准化财务报表、估值比率、分析师一致预期、股东与高管、竞争对手关系、公司事件与电话会纪要 |
### 计费与限制

View file

@ -5,7 +5,7 @@
"id": "kimi-datasource",
"tier": "official",
"displayName": "Kimi Datasource",
"version": "3.2.0",
"version": "3.3.0",
"description": "Official datasource workflows.",
"keywords": ["data", "mcp"],
"source": "./official/kimi-datasource"

View file

@ -1,5 +1,12 @@
# Changelog
## 3.3.0 - 2026-07-22
- Add five data sources: `wind` (万得), `imf` (IMF macro datasets), `gildata` (恒生聚源 smart screening), `sec_edgar` (US SEC filings), and `sp_data` (S&P Capital IQ, paid scope).
- Strengthen source routing: require one specialized source per simple lookup, stop after the first sufficient result, and route directly to a data source the user names.
- Document objective capability boundaries for every source in SKILL.md and the tool schema (e.g. yahoo_finance FX history is limited to about 2 years; minute-level intraday series live on `wind`), so the model can pick the source itself.
- Retry once with a credential refreshed by the Kimi Code host when the backend rejects the previous access token during rotation.
## 3.2.0 - 2026-06-10
- Add the `yuandian_law` data source (元典法律数据库) for Chinese laws/regulations and judicial case search.

View file

@ -1,7 +1,7 @@
---
name: kimi-datasource
description: |
Universal data-source assistant. Use this skill when the user wants external structured data such as stocks, financial reports, technical indicators, A-share/HK/US markets, global macroeconomics, Chinese enterprise registry information, arXiv papers, Google Scholar results, or Chinese laws/regulations and judicial cases.
Universal data-source assistant. Use this skill when the user wants external structured data such as stocks, financial reports, technical indicators, A-share/HK/US markets, global macroeconomics, Chinese enterprise registry information, arXiv papers, Google Scholar results, Chinese laws/regulations and judicial cases, Wind financial data (intraday/minute quotes, funds, bonds), IMF macro datasets (FX rates, CPI, GDP forecasts), Gildata smart screening, US SEC filings (10-K/10-Q, Form 4, 13F), or S&P Capital IQ fundamentals (top holders, consensus estimates, valuation ratios).
This plugin exposes tools via MCP server `plugin-kimi-datasource_data`; call them in the flow `mcp__plugin-kimi-datasource_data__get_data_source_desc``mcp__plugin-kimi-datasource_data__call_data_source_tool`.
---
@ -20,17 +20,37 @@ description: |
## 1. 这个 skill 提供什么能力
本 plugin 后面挂了 7 个外部数据源。每一行的"数据源名"就是传给 `get_data_source_desc``name`
本 plugin 后面挂了 12 个外部数据源。每一行的"数据源名"就是传给 `get_data_source_desc``name`
| 能力域 | 数据源名 | 典型问题 |
|---|---|---|
| **A股 / 港股 / 美股 行情和财务** | `stock_finance_data` | "茅台现在多少钱"、"宁德时代 2024 年财报"、"腾讯股东"、"杭州的人工智能股票" |
| **Yahoo Finance 全球金融** | `yahoo_finance` | "苹果分析师评级"、"AAPL 期权链"、"标普 500 历年价格" |
| **世界银行宏观经济** | `world_bank_open_data` | "中国历年 GDP"、"印度通胀率"、"各国人口增长对比" |
| **Yahoo Finance 全球金融** | `yahoo_finance` | "苹果分析师评级"、"AAPL 期权链"、"苹果前十大机构股东" |
| **世界银行历史宏观** | `world_bank_open_data` | "中国历年 GDP"、"印度通胀率"、"各国人口增长对比" |
| **中国企业工商信息** | `tianyancha` | "字节跳动股东"、"比亚迪司法风险"、"宁德时代专利" |
| **arXiv 论文预印本** | `arxiv` | "找 RAG 综述"、"下载 2406.xxxxx" |
| **Google Scholar 学术搜索** | `scholar` | "Hinton 最新论文"、"transformer 综述高引文献" |
| **中国法律法规 / 司法案例** | `yuandian_law` | "民法典关于居住权的规定"、"帮我查劳动合同解除的相关法条"、"找几个不当得利的判例" |
| **Wind 万得A股/基金/债券/宏观)** | `wind` | "茅台今天的分钟线"、"十年期国债收益率走势"、"基金净值查询" |
| **IMF 国际宏观(汇率 / CPI / 预测)** | `imf` | "美元兑人民币汇率"、"各国 GDP 增速预测"、"全球通胀率对比" |
| **恒生聚源智能筛选** | `gildata` | "筛选净利润增速超 30% 且 ROE 大于 15% 的股票"、"基金经理筛选" |
| **美股 SEC 披露文件** | `sec_edgar` | "特斯拉 10-K 年报"、"苹果 10-Q 季报"、"Form 4 内部人交易"、"13F 机构持仓" |
| **S&P Capital IQ 美股基本面** | `sp_data` | "苹果分析师一致预期"、"美股估值比率对比"、"竞争对手关系" |
### 选源原则
1. **用户点名了数据源** → 直接用指定的源。
2. **没点名** → 按能力域从上表选最匹配的一个;结合下面的"能力边界参考"和用户问题的深度、范围自行判断。
3. **一次简单查询只选一个数据源**,不要并行读取其他源的 desc。选定的源成功返回且已经覆盖用户问题后立即回答不要为了补充字段、重新格式化或交叉验证继续调用其他 API。只有用户明确要求跨源对比时才能查询第二个数据源。
### 能力边界参考(客观事实,选源时考虑)
- `yahoo_finance` 的外汇历史最多 2 年;`imf` 提供长期的汇率、CPI、GDP 预测和国际收支序列
- `stock_finance_data` 的行情是实时/收盘快照;分钟级分时序列在 `wind`(另有基金、债券、国债收益率)
- 股东 / 机构持仓:`yahoo_finance``sec_edgar`13F`sp_data`S&P 标准化持有人)都覆盖,口径和深度不同
- `world_bank_open_data` 是 50 年以上的历史宏观序列;要 IMF 的预测值用 `imf`
- `gildata` 的查询输入是自然语言条件(选股 / 选基金 / 基金经理筛选),`tianyancha` 是企业工商档案
- `wind``indexes`/`indicators` 参数要求 Wind 原生字段名PE/PB/ROE/总市值这类常用字段先调 `wind_search_fields` 映射(支持别名和中文,一次查一个),不要硬猜字段名
**不支持的能力**:通用 Web 搜索 / 实时新闻。问到这类问题,告诉用户当前数据源不覆盖。
@ -39,13 +59,13 @@ description: |
后端可用 API 经常会调整,**这份 skill 故意不抄具体的 API 名和参数表**。每次调用前你都应当现场问数据源:"你都有什么接口?"
```
1. 根据用户问题,从上表挑一个 data_source_name
1. 根据用户问题,从上表挑一个 data_source_name
2. 执行 get_data_source_desc读取该数据源的 Markdown 文档
3. 仔细读返回的 Markdown里面列了
- 该数据源整体说明(含 ticker 格式、全局约束)
- 每个 API 的描述 / 必填参数 / 可选参数 / 默认值 / 取值范围
4. 选最匹配的 API按文档拼 params
5. 执行 call_data_source_tool
5. 执行一次 call_data_source_tool;结果成功且已经覆盖问题时停止调用
6. 读返回结果,用用户提问时使用的语言回答
```

View file

@ -18,7 +18,7 @@ import { arch, homedir, hostname, release, type } from 'node:os';
import path from 'node:path';
import readline from 'node:readline';
const VERSION = '3.2.0';
const VERSION = '3.3.0';
const DEFAULT_KIMI_CODE_OAUTH_HOST = 'https://auth.kimi.com';
const DEFAULT_KIMI_CODE_BASE_URL = 'https://api.kimi.com/coding/v1';
const API_URL = datasourceApiUrl();
@ -29,13 +29,14 @@ const TOOLS = [
{
name: 'call_data_source_tool',
description:
"Dispatch a call to any registered data source's API via the Kimi Code gateway. Always call get_data_source_desc(name) first to learn that source's available APIs and required params, then construct this call with api_name and params taken from that description.",
"Dispatch one call to the data source selected for the user's request. Always call get_data_source_desc(name) first, then use an api_name and params from that description. For a simple lookup, use one specialized source and stop after its first successful result; do not query fallback or comparison sources unless the user explicitly asks for a cross-source comparison. When the user names a data source, use that source.",
inputSchema: {
type: 'object',
properties: {
data_source_name: {
type: 'string',
description: 'Data source name returned or documented by get_data_source_desc.',
description:
'The data source selected via get_data_source_desc. When the user names a source, pass that source.',
},
api_name: {
type: 'string',
@ -52,7 +53,7 @@ const TOOLS = [
{
name: 'get_data_source_desc',
description:
'Get the current API documentation for one Kimi data source before calling a specific API.',
'Get the current API documentation for one Kimi data source before calling a specific API. For a simple lookup, choose exactly one specialized source; do not inspect fallback or comparison sources unless the user explicitly asks for a cross-source comparison.',
inputSchema: {
type: 'object',
properties: {
@ -66,8 +67,21 @@ const TOOLS = [
'arxiv',
'scholar',
'yuandian_law',
'wind',
'imf',
'gildata',
'sec_edgar',
'sp_data',
],
description: 'Data source name.',
description:
'Data source name. Capabilities: stock_finance_data / yahoo_finance = general quotes and financials ' +
'(yahoo_finance FX history is limited to about 2 years); world_bank_open_data = historical macro; ' +
'imf = FX rates, CPI, GDP forecasts, balance of payments; tianyancha = CN company registry; ' +
'arxiv / scholar = papers; yuandian_law = CN laws and cases; ' +
'wind = A-share intraday minute series, funds, bonds (map PE/PB/ROE-style field names via wind_search_fields first); ' +
'gildata = natural-language stock/fund screening; ' +
'sec_edgar = US filings (10-K/10-Q, S-1, Form 4, 13F, 8-K); ' +
'sp_data = S&P fundamentals (consensus estimates, valuation ratios, transcripts).',
},
},
required: ['name'],
@ -301,15 +315,12 @@ async function loadAccessToken() {
if (token.length === 0) {
throw new Error('Kimi Code credentials do not contain access_token. Run /login again.');
}
const expiresAt = typeof parsed.expires_at === 'number' ? parsed.expires_at : 0;
if (expiresAt > 0 && expiresAt <= Math.floor(Date.now() / 1000)) {
throw new Error('Kimi Code access_token has expired. Run /login again and retry.');
}
return { kimiHome, token };
}
async function callKimiTool(method, params, trace = {}) {
const { kimiHome, token } = await loadAccessToken();
const { kimiHome, token: initialToken } = await loadAccessToken();
let token = initialToken;
const toolCallId = randomUUID();
trace.toolCallId = toolCallId;
const controller = new AbortController();
@ -317,15 +328,29 @@ async function callKimiTool(method, params, trace = {}) {
controller.abort();
}, REQUEST_TIMEOUT_MS);
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: await buildHeaders(kimiHome, token, toolCallId),
body: JSON.stringify({ method, params }),
signal: controller.signal,
});
const request = async (accessToken) => {
const response = await fetch(API_URL, {
method: 'POST',
headers: await buildHeaders(kimiHome, accessToken, toolCallId),
body: JSON.stringify({ method, params }),
signal: controller.signal,
});
return { response, text: await response.text() };
};
let { response, text } = await request(token);
if (response.status === 401) {
const refreshed = await loadAccessToken();
if (refreshed.token !== token) {
token = refreshed.token;
({ response, text } = await request(token));
}
}
trace.requestId = extractRequestId(response.headers);
const text = await response.text();
if (!response.ok) {
if (response.status === 401) {
throw new Error('Kimi Code access_token was rejected. Run /login again and retry.');
}
throw new Error(`HTTP ${response.status} error: ${text}`);
}
try {

View file

@ -1,6 +1,6 @@
{
"name": "kimi-datasource",
"version": "3.2.0",
"version": "3.3.0",
"description": "Finance, macro, enterprise, academic, and legal data tools for Kimi Code.",
"keywords": ["finance", "data-source", "mcp", "legal"],
"mcpServers": {