Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/use-generic-datasource-stock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Align the datasource plugin with the generic two-tool workflow.
35 changes: 35 additions & 0 deletions apps/kimi-code/test/utils/kimi-datasource-plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
2 changes: 1 addition & 1 deletion plugins/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
6 changes: 6 additions & 0 deletions plugins/official/kimi-datasource/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`.
30 changes: 7 additions & 23 deletions plugins/official/kimi-datasource/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`。

Expand Down Expand Up @@ -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`**:完整数据落盘路径
Expand All @@ -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 一批,多了分批调。

格式:

Expand All @@ -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 我们这边修不了,要后端服务侧改
105 changes: 17 additions & 88 deletions plugins/official/kimi-datasource/bin/kimi-datasource.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
type: 'string',
description: 'Ticker code list separated by commas, for example 600519.SH or 0700.HK.',
},
type: {
data_source_name: {
type: 'string',
enum: ['realtime_price', 'realtime_tech', 'open_summary', 'close_summary'],
description: 'Realtime stock query type.',
description: 'Data source name returned or documented by get_data_source_desc.',
},
time: {
api_name: {
type: 'string',
description: 'Optional time parameter for supported realtime endpoints.',
description: 'API name from the data source description.',
},
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'],
},
},
{
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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}.`);
Expand Down
2 changes: 1 addition & 1 deletion plugins/official/kimi-datasource/kimi.plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kimi-datasource",
"version": "3.0.0",
"version": "3.1.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Publish the removed MCP tool as a major change

This release removes the public query_stock MCP tool, so any existing prompt, automation, or saved workflow that calls mcp__plugin-kimi-datasource-data__query_stock will now get Unknown tool, but the plugin is advertised as a 3.0.03.1.0 update. Since removing a command/tool is a breaking change, either keep a compatibility shim for query_stock or ship this as a major version so marketplace users are not offered a non-breaking upgrade that breaks existing calls.

Useful? React with 👍 / 👎.

"description": "Finance, macro, enterprise, and academic data tools for Kimi Code.",
"keywords": ["finance", "data-source", "mcp"],
"mcpServers": {
Expand Down
Loading