From b9860e9f6ec65eb5dfdabbad54f1a87d69f4f00a Mon Sep 17 00:00:00 2001 From: qer Date: Fri, 29 May 2026 19:51:23 +0800 Subject: [PATCH] feat: align datasource plugin with generic workflow (#215) --- .changeset/use-generic-datasource-stock.md | 5 + .../test/utils/kimi-datasource-plugin.test.ts | 35 ++++++ plugins/marketplace.json | 2 +- plugins/official/kimi-datasource/CHANGELOG.md | 6 + plugins/official/kimi-datasource/SKILL.md | 30 ++--- .../kimi-datasource/bin/kimi-datasource.mjs | 105 +++--------------- .../official/kimi-datasource/kimi.plugin.json | 2 +- 7 files changed, 72 insertions(+), 113 deletions(-) create mode 100644 .changeset/use-generic-datasource-stock.md create mode 100644 plugins/official/kimi-datasource/CHANGELOG.md diff --git a/.changeset/use-generic-datasource-stock.md b/.changeset/use-generic-datasource-stock.md new file mode 100644 index 000000000..b41f46ab9 --- /dev/null +++ b/.changeset/use-generic-datasource-stock.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Align the datasource plugin with the generic two-tool workflow. diff --git a/apps/kimi-code/test/utils/kimi-datasource-plugin.test.ts b/apps/kimi-code/test/utils/kimi-datasource-plugin.test.ts index 815e4f8d2..3219cc80d 100644 --- a/apps/kimi-code/test/utils/kimi-datasource-plugin.test.ts +++ b/apps/kimi-code/test/utils/kimi-datasource-plugin.test.ts @@ -11,6 +11,41 @@ const REPO_ROOT = join(import.meta.dirname, '../../../..'); const SERVER_ENTRY = join(REPO_ROOT, 'plugins/official/kimi-datasource/bin/kimi-datasource.mjs'); describe('kimi-datasource MCP server', () => { + it('exposes the same two generic tools as the Python plugin', async () => { + const tempDir = await mkdtemp(join(tmpdir(), 'kimi-datasource-plugin-')); + const kimiHome = join(tempDir, 'kimi-home'); + let child: ChildProcessWithoutNullStreams | undefined; + + try { + await mkdir(join(kimiHome, 'credentials'), { recursive: true }); + await writeFile( + join(kimiHome, 'credentials', 'kimi-code.json'), + JSON.stringify({ access_token: 'test-token', expires_at: 4_102_444_800 }), + 'utf8', + ); + child = spawn(process.execPath, [SERVER_ENTRY], { + cwd: REPO_ROOT, + env: { + ...process.env, + KIMI_CODE_HOME: kimiHome, + }, + stdio: ['pipe', 'pipe', 'pipe'], + }); + const client = createRpcClient(child); + + await client.request('initialize', {}); + const result = await client.request('tools/list', {}); + + expect(result.error).toBeUndefined(); + const tools = (result.result as { tools: Array<{ name: string }> }).tools; + expect(tools.map((tool) => tool.name)).toEqual(['call_data_source_tool', 'get_data_source_desc']); + } finally { + child?.stdin.end(); + child?.kill(); + await rm(tempDir, { recursive: true, force: true }); + } + }); + it('prefers assistant text and writes response files', async () => { const tempDir = await mkdtemp(join(tmpdir(), 'kimi-datasource-plugin-')); const kimiHome = join(tempDir, 'kimi-home'); diff --git a/plugins/marketplace.json b/plugins/marketplace.json index dd18a9cd2..ffa4fef84 100644 --- a/plugins/marketplace.json +++ b/plugins/marketplace.json @@ -5,7 +5,7 @@ "id": "kimi-datasource", "tier": "official", "displayName": "Kimi Datasource", - "version": "3.0.0", + "version": "3.1.0", "description": "Official datasource workflows.", "keywords": ["data", "mcp"], "source": "./official/kimi-datasource" diff --git a/plugins/official/kimi-datasource/CHANGELOG.md b/plugins/official/kimi-datasource/CHANGELOG.md new file mode 100644 index 000000000..087ef264c --- /dev/null +++ b/plugins/official/kimi-datasource/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## 3.1.0 - 2026-05-29 + +- Align the MCP server with the Python plugin's generic two-tool workflow. +- Remove the `query_stock` shortcut; use `get_data_source_desc` before `call_data_source_tool`. diff --git a/plugins/official/kimi-datasource/SKILL.md b/plugins/official/kimi-datasource/SKILL.md index 4375de522..364b00eb7 100644 --- a/plugins/official/kimi-datasource/SKILL.md +++ b/plugins/official/kimi-datasource/SKILL.md @@ -2,20 +2,19 @@ name: kimi-datasource description: | 通用数据源助手。当用户要查股票/财报/技术指标/全球宏观经济/中国企业工商/学术论文这类外部数据时,使用这个 skill。 - 本 plugin 通过 MCP server `plugin-kimi-datasource-data` 提供工具;优先调用 `mcp__plugin-kimi-datasource-data__query_stock`、`mcp__plugin-kimi-datasource-data__get_data_source_desc`、`mcp__plugin-kimi-datasource-data__call_data_source_tool`。 + 本 plugin 通过 MCP server `plugin-kimi-datasource-data` 提供工具;按 `mcp__plugin-kimi-datasource-data__get_data_source_desc` → `mcp__plugin-kimi-datasource-data__call_data_source_tool` 的流程调用。 --- # kimi-datasource — 通用数据源助手 ## 0. 调用方式 -本 skill 使用 datasource MCP server 注册的三个工具,不要通过 Bash 手动执行脚本: +本 skill 使用 datasource MCP server 注册的两个工具,不要通过 Bash 手动执行脚本: -- `mcp__plugin-kimi-datasource-data__query_stock` - `mcp__plugin-kimi-datasource-data__get_data_source_desc` - `mcp__plugin-kimi-datasource-data__call_data_source_tool` -这三个工具由 Kimi Code 托管执行,参数直接按 tool schema 传 JSON。 +这两个工具由 Kimi Code 托管执行,参数直接按 tool schema 传 JSON。 工具会读取 `$KIMI_CODE_HOME/credentials/kimi-code.json`。如果没有登录凭据,让用户先在 Kimi Code 里执行 `/login`。 @@ -98,7 +97,7 @@ A 股 `.SH/.SZ/.BJ`,港股 `.HK`,美股 `.US` 等。用户通常只说中文 ## 4. 怎么读返回结果 -`call_data_source_tool` 和 `query_stock` 的 stdout 一般含两段: +`call_data_source_tool` 的 stdout 一般含两段: 1. **`data_preview`**:CSV 头 + 前几行(通常 1~3 行),方便你直接答简单问题 2. **`CSV 数据已写入:/tmp/xxx.csv`**:完整数据落盘路径 @@ -110,23 +109,9 @@ A 股 `.SH/.SZ/.BJ`,港股 `.HK`,美股 `.US` 等。用户通常只说中文 如果接口返回失败,提示文字一般会写明原因(参数不对 / 不支持 / 数据空等)。把人话原因反馈给用户,不要硬走第二次。 -## 5. `query_stock` — 实时行情快捷命令 +## 5. `watchlist.json` — 用户自选股 -对**实时**类的常见股票查询,可以**不走** `get_data_source_desc → call_data_source_tool` 这套流程,直接用 `query_stock`,省一次调用。它等价于 `stock_finance_data` 的实时接口子集。 - -调用 `mcp__plugin-kimi-datasource-data__query_stock`,参数形如 `{"ticker":"600519.SH","type":"realtime_price","file_path":"/tmp/stock_600519.csv"}`。 - -适用场景(且仅限): -- 看当前价 / 当日分钟 K 线(`type=realtime_price`) -- 看实时技术指标 MA/MACD/KDJ/RSI/BOLL 等(`type=realtime_tech`,**仅 A 股**,港股/美股会报错) -- 开盘摘要(`type=open_summary`) -- 收盘摘要(`type=close_summary`,**港股盘后看当天数据必须用这个**,`realtime_price` 拿不到) - -涉及**历史日 K / 财报 / 公告 / 股东 / 财务指标 / 选股 / 业务分部 / 预测**等任何"非实时"的股票查询,走标准 `get_data_source_desc → call_data_source_tool` 流程,不要在 `query_stock` 上硬凑。 - -## 6. `watchlist.json` — 用户自选股 - -`${KIMI_SKILL_DIR}/watchlist.json` 是用户的自选股列表。用户问"看一下我的自选股"时,读这个文件然后按每 3 只一组分批执行 `query_stock`。 +`${KIMI_SKILL_DIR}/watchlist.json` 是用户的自选股列表。用户问"看一下我的自选股"时,读这个文件,再走标准 `get_data_source_desc("stock_finance_data") → call_data_source_tool` 流程查实时行情;文档里的实时接口最多 3 个 ticker 一批,多了分批调。 格式: @@ -141,10 +126,9 @@ A 股 `.SH/.SZ/.BJ`,港股 `.HK`,美股 `.US` 等。用户通常只说中文 - 两者都有时顺便算盈亏:`(当前价 - hold_cost) * hold_quantity` - 用户说"帮我加 XX 到自选股"时:先 web_search 核对代码,再追加到 JSON 数组 -## 7. 注意事项 +## 6. 注意事项 - **不要凭记忆猜股票代码 / 企业全称**。错代码会让接口静默返回错数据,用户察觉不到 - **不要在没读 desc 的情况下硬传 `api_name`**。后端会报 `API_NOT_FOUND`。除非这次会话里你已经读过该数据源的 desc 并记得参数 - **不要给投资建议**。给完数据加一句"AI 生成,不构成投资建议"即可 -- **不要在 `query_stock` 上塞历史/财报查询**。它只覆盖实时类,别的会失败 - 如果某个数据源接口返回的报错明显是后端 bug(参数 schema 自相矛盾、内部 Python 报错等),**汇报错误给用户,不要硬试**——这种 bug 我们这边修不了,要后端服务侧改 diff --git a/plugins/official/kimi-datasource/bin/kimi-datasource.mjs b/plugins/official/kimi-datasource/bin/kimi-datasource.mjs index df3c293c8..e67f2e498 100755 --- a/plugins/official/kimi-datasource/bin/kimi-datasource.mjs +++ b/plugins/official/kimi-datasource/bin/kimi-datasource.mjs @@ -18,44 +18,33 @@ import { arch, homedir, hostname, release, type } from 'node:os'; import path from 'node:path'; import readline from 'node:readline'; -const VERSION = '3.0.0'; +const VERSION = '3.1.0'; const API_URL = process.env.KIMI_DATASOURCE_API_URL ?? 'https://api.kimi.com/coding/v1/tools'; const REQUEST_TIMEOUT_MS = 30_000; const PROTOCOL_VERSION = '2025-06-18'; -const VALID_STOCK_QUERY_TYPES = new Set([ - 'realtime_price', - 'realtime_tech', - 'open_summary', - 'close_summary', -]); const TOOLS = [ { - name: 'query_stock', + name: 'call_data_source_tool', description: - 'Query realtime stock price, realtime technical indicators, open summaries, or close summaries for up to 3 tickers.', + "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.", inputSchema: { type: 'object', properties: { - ticker: { + data_source_name: { type: 'string', - description: 'Ticker code list separated by commas, for example 600519.SH or 0700.HK.', + description: 'Data source name returned or documented by get_data_source_desc.', }, - type: { + api_name: { type: 'string', - enum: ['realtime_price', 'realtime_tech', 'open_summary', 'close_summary'], - description: 'Realtime stock query type.', + description: 'API name from the data source description.', }, - time: { - type: 'string', - description: 'Optional time parameter for supported realtime endpoints.', - }, - file_path: { - type: 'string', - description: 'Optional CSV output path. When omitted, the tool chooses a temporary path.', + params: { + type: 'object', + description: 'API parameters that match the data source description.', }, }, - required: ['ticker'], + required: ['data_source_name', 'api_name', 'params'], }, }, { @@ -81,70 +70,9 @@ const TOOLS = [ required: ['name'], }, }, - { - name: 'call_data_source_tool', - description: 'Call one API from a Kimi data source after reading get_data_source_desc.', - inputSchema: { - type: 'object', - properties: { - data_source_name: { - type: 'string', - description: 'Data source name returned or documented by get_data_source_desc.', - }, - api_name: { - type: 'string', - description: 'API name from the data source description.', - }, - params: { - type: 'object', - description: 'API parameters that match the data source description.', - }, - }, - required: ['data_source_name', 'api_name', 'params'], - }, - }, ]; const HANDLERS = { - query_stock: { - method: 'get_stock_realtime_price', - buildParams(args) { - const ticker = requiredString(args, 'ticker'); - const tickerList = ticker - .split(',') - .map((item) => item.trim()) - .filter(Boolean); - if (tickerList.length === 0) throw new Error('Missing required argument: ticker.'); - if (tickerList.length > 3) { - throw new Error('ticker accepts at most 3 values separated by commas.'); - } - - const queryType = optionalString(args, 'type') ?? 'realtime_price'; - if (!VALID_STOCK_QUERY_TYPES.has(queryType)) { - throw new Error( - `type must be one of ${JSON.stringify([...VALID_STOCK_QUERY_TYPES])}; received: ${queryType}`, - ); - } - - const params = { - ticker, - type: queryType, - file_path: optionalString(args, 'file_path') ?? defaultStockFilePath(ticker, queryType), - }; - const time = optionalString(args, 'time'); - if (time !== undefined) params.time = time; - return params; - }, - format(text, params) { - return `${text}\n\nCSV data written to: ${params.file_path}`; - }, - }, - get_data_source_desc: { - method: 'get_data_source_desc', - buildParams(args) { - return { name: requiredString(args, 'name') }; - }, - }, call_data_source_tool: { method: 'call_data_source_tool', buildParams(args) { @@ -155,6 +83,12 @@ const HANDLERS = { }; }, }, + get_data_source_desc: { + method: 'get_data_source_desc', + buildParams(args) { + return { name: requiredString(args, 'name') }; + }, + }, }; async function handleRequest(message) { @@ -412,11 +346,6 @@ function extractChannelText(value) { return undefined; } -function defaultStockFilePath(ticker, queryType) { - const safeTicker = ticker.replaceAll(',', '_').replaceAll('.', '_'); - return `/tmp/stock_${safeTicker}_${queryType}.csv`; -} - function requiredString(args, field) { const value = optionalString(args, field); if (value === undefined) throw new Error(`Missing required argument: ${field}.`); diff --git a/plugins/official/kimi-datasource/kimi.plugin.json b/plugins/official/kimi-datasource/kimi.plugin.json index cfbfce121..9fd1274a2 100644 --- a/plugins/official/kimi-datasource/kimi.plugin.json +++ b/plugins/official/kimi-datasource/kimi.plugin.json @@ -1,6 +1,6 @@ { "name": "kimi-datasource", - "version": "3.0.0", + "version": "3.1.0", "description": "Finance, macro, enterprise, and academic data tools for Kimi Code.", "keywords": ["finance", "data-source", "mcp"], "mcpServers": {