@caidazi/mcp 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: caidazi-stock-screener
3
+ description: 当用户用自然语言描述股票、ETF 或市场筛选条件,并希望得到候选标的时使用。
4
+ ---
5
+
6
+ # 财搭子智能筛选
7
+
8
+ 用这个 skill 处理自然语言选股。公开 skill 必须调用服务端筛选 wrapper,不要在本地构造原始数据查询。
9
+
10
+ ## 适用场景
11
+
12
+ - "帮我筛选低估值高股息股票"
13
+ - "找最近资金流入强的半导体"
14
+ - "选一些港股科技 ETF"
15
+ - "A股里找盈利改善的中盘股"
16
+ - "我的自选里筛一下最近比较强的"
17
+
18
+ 不要用于单个已命名标的、完整市场摘要、私有组合诊断或精确回测。
19
+
20
+ ## 可用工具
21
+
22
+ - `screen_stocks`
23
+ - `get_caidazi_user_watchlist` 或 `get_caidazi_positions_summary`,仅当用户明确限定在财搭子自选或持仓中筛选时使用。
24
+ - `extract_assets`,仅当用户问题里包含需要规范化的具体标的名称时使用。
25
+
26
+ ## 流程
27
+
28
+ 1. 把用户请求作为一个完整的自然语言筛选 query。
29
+ 2. 推断 `market`:
30
+ - A股、沪深、CN、默认中文股票请求:`CN`
31
+ - 港股:`HK`
32
+ - 美股:`US`
33
+ - ETF 或基金限定:`ETF`
34
+ 3. 推断 `limit`;默认 10,最多 30。
35
+ 4. 如果用户说"我的自选",先调用 `get_caidazi_user_watchlist`;如果说"我的持仓",先调用 `get_caidazi_positions_summary(mask_sensitive=true)`;再把这个范围写进传给 `screen_stocks` 的自然语言 query。
36
+ 5. 对同一个用户请求只调用一次 `screen_stocks(query, market, limit)`。
37
+ 6. 展示工具返回的候选标的和筛选解释。
38
+
39
+ ## 澄清策略
40
+
41
+ 筛选条件过于空泛时最多问一个问题。
42
+
43
+ 需要提问的例子:
44
+
45
+ - "帮我选点股票",且没有风格、市场或条件。
46
+ - "选新能源",但用户明显需要精确子方向。
47
+
48
+ 不需要提问的例子:
49
+
50
+ - "低 PE 高股息"
51
+ - "A股半导体最近放量"
52
+ - "我的自选里找资金强的"
53
+
54
+ ## 输出结构
55
+
56
+ 1. 筛选条件理解。
57
+ 2. 候选表格:名称、代码、市场、入选原因。
58
+ 3. 重要限制或风险提示。
59
+ 4. 数据时间。
60
+ 5. 下一步建议:比较候选,或深入研究某个标的。
61
+
62
+ 把结果表述为候选,不要表述为推荐。
63
+
64
+ ## 空结果
65
+
66
+ 如果没有候选:
67
+
68
+ - 说明条件可能过严;
69
+ - 给出一两个更宽松的改写方向;
70
+ - 不要编造标的。
71
+
72
+ ## 错误处理
73
+
74
+ - API Key 无效:引导用户到 财搭子 App -> 大发 agent -> 左上角 skill icon -> Skills 页面重新领取。
75
+ - 工具调用失败:按工具返回的 `message` 说明,不自行推断后台策略。
76
+ - 请求过于频繁:提示稍后重试。
77
+ - 账户资产不可用:说明需要账户关联 key,可到 财搭子 App -> 大发 agent -> 左上角 skill icon -> Skills 页面领取或绑定;如果可以,继续做公开市场筛选。
78
+
79
+ ## 边界
80
+
81
+ - 不要构造原始数据查询。
82
+ - 不要暴露筛选字段、候选生成、排序或数据映射细节。
83
+ - 不要承诺未来收益。
84
+ - 除非用户明确要求调整条件,同一请求不要多次调用筛选 wrapper。
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: caidazi-user-assets
3
+ description: 当用户想查看或使用自己在财搭子的自选、持仓、账户关联资产或个人资产上下文时使用。
4
+ ---
5
+
6
+ # 财搭子用户资产
7
+
8
+ 用这个 skill 通过账户关联 API Key 访问用户在财搭子里的数据资产。它是外部 Agent 回到财搭子主端的桥,不是组合诊断引擎。
9
+
10
+ ## 适用场景
11
+
12
+ - "查看我的自选"
13
+ - "我的持仓有哪些"
14
+ - "用我的自选做对比"
15
+ - "看一下我的持仓受这个消息影响吗"
16
+ - "从我的资产里筛一下"
17
+
18
+ 不要用于交易、账户修改、订阅设置或深度组合诊断。
19
+
20
+ ## 可用工具
21
+
22
+ - `get_caidazi_user_watchlist`
23
+ - `get_caidazi_positions_summary`
24
+ - `get_caidazi_portfolio_snapshot`
25
+
26
+ ## 流程
27
+
28
+ 1. 判断 `asset_type`:
29
+ - watchlist:用户说自选、watchlist、关注资产。
30
+ - holdings:用户说持仓、托管、positions。
31
+ - all:用户说我的资产、个人上下文,或同时需要自选和持仓。
32
+ 2. 自选调用 `get_caidazi_user_watchlist`,持仓调用 `get_caidazi_positions_summary(mask_sensitive=true)`,全部资产调用 `get_caidazi_portfolio_snapshot(mask_sensitive=true)`。
33
+ 3. 如果工具返回 `requires_login`、`account_not_linked` 或 `ASSET_PERMISSION_REQUIRED`,提示用户到 财搭子 App -> 大发 agent -> 左上角 skill icon -> Skills 页面领取或绑定 API Key。
34
+ 4. 如果返回数据,只总结工具返回的资产和安全公开字段。
35
+ 5. 如果下一步是研究,把返回代码交给 `caidazi-asset-research`、`caidazi-stock-screener`、`caidazi-market-pulse` 或 `caidazi-finance-search`。
36
+
37
+ ## 输出结构
38
+
39
+ 自选:
40
+
41
+ - 总数量;
42
+ - 可见的重点资产;
43
+ - 数据缺失或延迟提醒;
44
+ - 下一步可做什么。
45
+
46
+ 持仓:
47
+
48
+ - 总数量;
49
+ - 资产名称和代码;
50
+ - 如果工具返回了高层持仓信息,可以概括;
51
+ - 避免给具体交易指令。
52
+
53
+ 全部资产:
54
+
55
+ - 清楚区分自选和持仓。
56
+
57
+ ## 数据边界
58
+
59
+ 严格使用工具返回的数据。不要推断未返回的成本、数量、盈亏或风险偏好。字段未返回就说明不可用。
60
+
61
+ ## 示例
62
+
63
+ 用户:
64
+
65
+ > 看下我的自选
66
+
67
+ 动作:
68
+
69
+ - 调用 `get_caidazi_user_watchlist()`;
70
+ - 总结数量和可见资产。
71
+
72
+ 用户:
73
+
74
+ > 我的持仓受今天热点影响吗?
75
+
76
+ 动作:
77
+
78
+ - 调用 `get_caidazi_positions_summary(mask_sensitive=true)`;
79
+ - 把返回代码交给市场脉搏或事件影响 skill。
80
+
81
+ 用户:
82
+
83
+ > 从我的自选里选几个强一点的
84
+
85
+ 动作:
86
+
87
+ - 调用 `get_caidazi_user_watchlist()`;
88
+ - 把资产范围传给智能筛选。
89
+
90
+ ## 交接
91
+
92
+ - 研究某个返回资产:使用 `caidazi-asset-research`。
93
+ - 比较多个返回资产:使用 `compare_assets`。
94
+ - 在返回资产中筛选:使用 `caidazi-stock-screener`。
95
+ - 分析新闻或政策影响:使用 `caidazi-finance-search`,必要时再结合 `caidazi-market-pulse`。
96
+
97
+ ## 错误处理
98
+
99
+ - API Key 无效:引导用户到 财搭子 App -> 大发 agent -> 左上角 skill icon -> Skills 页面重新领取。
100
+ - API Key 未关联财搭子账户:解释同一路径可以领取或绑定。
101
+ - 工具调用失败:按工具返回的 `message` 总结,不自行推断后台策略。
102
+ - 工具未开放:说明账户资产访问暂未对外部 Agent 开放。
103
+
104
+ ## 边界
105
+
106
+ - 不要暴露 API Key。
107
+ - 不要声称拥有完整券商账户访问权。
108
+ - 不要修改自选或持仓。
109
+ - 用户没有要求时,不要主动使用私有资产。
110
+ - 不要仅基于账户上下文给买卖指令。
package/manifest.yaml ADDED
@@ -0,0 +1,162 @@
1
+ schema_version: 1
2
+ bundle: caidazi-skills
3
+ version: 0.2.0
4
+ env:
5
+ primary_api_key: CAIDAZI_API_KEY
6
+ mcp:
7
+ server_name: caidazi
8
+ transport: stdio
9
+ npm_package: "@caidazi/mcp"
10
+ github_package: "github:caidazi/dafa-skills"
11
+ command: npx
12
+ args:
13
+ - -y
14
+ - "@caidazi/mcp"
15
+ unpublished_fallback:
16
+ command: npx
17
+ args:
18
+ - -y
19
+ - "github:caidazi/dafa-skills"
20
+ use_when: "The npm package is not published or the registry returns package-not-found."
21
+ hosted_endpoint: "https://<caidazi-https-mcp-endpoint>/mcp"
22
+ hosted_endpoint_kind: production-placeholder
23
+ security:
24
+ tls_required_for_bearer: true
25
+ allow_bearer_over_plain_http: false
26
+ local_bridge_instruction: "Default install uses the npm stdio MCP bridge. The bridge exposes standard MCP tools to the Agent and calls the Caidazi backend internally."
27
+ hosted_endpoint_instruction: "Use the hosted HTTPS MCP endpoint only after it passes MCP initialize/list-tools health checks."
28
+ auth:
29
+ type: bearer
30
+ header: Authorization
31
+ env_ref: CAIDAZI_API_KEY
32
+ format: "Bearer {value}"
33
+ secret: true
34
+ redact: true
35
+ installation:
36
+ default_mode: stdio-mcp-bridge
37
+ supported_modes:
38
+ - stdio-mcp-bridge
39
+ - hosted-mcp
40
+ - skills-only
41
+ stdio_mcp_bridge:
42
+ config_instruction: "Use the current Agent's documented MCP configuration flow. Do not guess legacy config paths."
43
+ server_name_ref: mcp.server_name
44
+ transport_ref: mcp.transport
45
+ command_ref: mcp.command
46
+ args_ref: mcp.args
47
+ unpublished_fallback_ref: mcp.unpublished_fallback
48
+ auth_env_ref: CAIDAZI_API_KEY
49
+ package_ref: mcp.npm_package
50
+ success_condition: "The current Agent discovers the tools listed in required_mcp.public_tools through MCP."
51
+ hosted_mcp:
52
+ enabled: false
53
+ endpoint_ref: mcp.hosted_endpoint
54
+ enable_when: "An official HTTPS MCP transport endpoint is deployed and passes health checks."
55
+ skills_only:
56
+ report_as: "Skills-only mode"
57
+ limitation: "No Caidazi tools are available; the Agent can only follow the skill output structure."
58
+ backend_api:
59
+ test_base_url: "http://101.126.22.17:5011"
60
+ endpoint_kind: temporary-plain-http
61
+ tool_registry_path: "/api/tools/registered"
62
+ tool_call_path: "/api/tools/call"
63
+ used_by: "@caidazi/mcp stdio bridge"
64
+ auth:
65
+ type: bearer
66
+ header: Authorization
67
+ env_ref: CAIDAZI_API_KEY
68
+ format: "Bearer {value}"
69
+ secret: true
70
+ redact: true
71
+ safety:
72
+ requires_allow_http_env: CAIDAZI_ALLOW_HTTP
73
+ use_only_when_user_confirms_temporary_test_env: true
74
+ do_not_expose_as_agent_mcp_endpoint: true
75
+ do_not_store_api_key_in_repo_or_shared_config: true
76
+ publication:
77
+ npm_registry: "https://registry.npmjs.org/"
78
+ package: "@caidazi/mcp"
79
+ publish_required_for_default_npx_install: true
80
+ initial_publish_command: "npm publish"
81
+ publish_requires:
82
+ - "npm account logged in on this machine"
83
+ - "permission to publish packages under the @caidazi scope"
84
+ - "confirmed repository license policy"
85
+ skills:
86
+ included:
87
+ - caidazi-market-pulse
88
+ - caidazi-asset-research
89
+ - caidazi-stock-screener
90
+ - caidazi-user-assets
91
+ - caidazi-finance-search
92
+ - caidazi-fund-etf-research
93
+ - caidazi-macro-research
94
+ - caidazi-portfolio-review
95
+ required_mcp:
96
+ public_tools:
97
+ - extract_assets
98
+ - get_hot_report
99
+ - get_real_time_market_summary
100
+ - get_market_analysis
101
+ - get_macro_analysis
102
+ - get_asset_overview
103
+ - investment_search_pro
104
+ - compare_assets
105
+ - screen_stocks
106
+ - get_etf_constituents
107
+ - get_index_related_etfs
108
+ - get_stock_belongings
109
+ account_tools:
110
+ - get_caidazi_user_watchlist
111
+ - get_caidazi_positions_summary
112
+ - get_caidazi_portfolio_snapshot
113
+ smoke_safe_tools:
114
+ - extract_assets
115
+ - get_hot_report
116
+ - get_real_time_market_summary
117
+ - get_market_analysis
118
+ - get_macro_analysis
119
+ smoke_forbidden_tools:
120
+ - get_caidazi_user_watchlist
121
+ - get_caidazi_positions_summary
122
+ - get_caidazi_portfolio_snapshot
123
+ blocked_surface:
124
+ - raw database access
125
+ - internal account tools
126
+ - internal monitor or subscription tools
127
+ - local file or UI-only tools
128
+ verification:
129
+ skill_indexing:
130
+ required_when_supported: true
131
+ fallback_mode: MCP-only
132
+ mcp_connection:
133
+ server_name_ref: mcp.server_name
134
+ must_connect_when_secure_transport_available: true
135
+ health_check_required: true
136
+ agent_health_check_required: true
137
+ tools_discovery_required: true
138
+ backend_bridge:
139
+ test_registry_ref: backend_api.tool_registry_path
140
+ tool_call_ref: backend_api.tool_call_path
141
+ plain_http_requires_explicit_opt_in: true
142
+ api_key:
143
+ never_print_full_key: true
144
+ never_read_with_printenv: true
145
+ never_store_in_repo: true
146
+ missing_key_instruction: "Ask the user to open 财搭子 App -> 大发 agent 页面 -> 左上角 skill icon -> Skills 页面, then set CAIDAZI_API_KEY through the current Agent's secure configuration path."
147
+ network_smoke_test:
148
+ require_tls_for_authenticated_calls: true
149
+ allowed_tools_ref: required_mcp.smoke_safe_tools
150
+ forbidden_tools_ref: required_mcp.smoke_forbidden_tools
151
+ post_install:
152
+ intro_public: "财搭子已接入。你可以让我做市场脉搏、单标的研究、多标的比较、自然语言选股、基金/ETF 研究、宏观分析和资讯检索。"
153
+ intro_account: "如果你的 API Key 已绑定财搭子账户,还可以查看自选、持仓和组合快照,并做轻量复盘。"
154
+ ask: "你想先做哪类任务?"
155
+ sample_queries:
156
+ public:
157
+ - 今天 A 股市场热点是什么?
158
+ - 帮我看下宁德时代最近的核心矛盾。
159
+ - 比亚迪和宁德时代谁更值得跟踪?
160
+ - 找出最近资金强、估值不贵的新能源股票。
161
+ account:
162
+ - 我的自选里今天哪些最值得关注?
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@caidazi/mcp",
3
+ "version": "0.2.0",
4
+ "description": "Caidazi MCP stdio bridge for generic AI agents.",
5
+ "type": "module",
6
+ "keywords": [
7
+ "caidazi",
8
+ "mcp",
9
+ "model-context-protocol",
10
+ "ai-agent",
11
+ "finance"
12
+ ],
13
+ "homepage": "https://github.com/caidazi/dafa-skills#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/caidazi/dafa-skills/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/caidazi/dafa-skills.git"
20
+ },
21
+ "bin": {
22
+ "caidazi-mcp": "bin/caidazi-mcp.js"
23
+ },
24
+ "files": [
25
+ "bin",
26
+ "src",
27
+ "README.md",
28
+ "manifest.yaml",
29
+ "caidazi-*/SKILL.md"
30
+ ],
31
+ "scripts": {
32
+ "prepack": "npm test",
33
+ "test": "node --test",
34
+ "validate": "node ./bin/caidazi-mcp.js validate"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "dependencies": {
43
+ "@modelcontextprotocol/sdk": "^1.29.0"
44
+ },
45
+ "devDependencies": {}
46
+ }
@@ -0,0 +1,23 @@
1
+ export const ALLOWED_TOOL_NAMES = Object.freeze([
2
+ "extract_assets",
3
+ "get_hot_report",
4
+ "get_real_time_market_summary",
5
+ "get_market_analysis",
6
+ "get_macro_analysis",
7
+ "get_asset_overview",
8
+ "investment_search_pro",
9
+ "compare_assets",
10
+ "screen_stocks",
11
+ "get_etf_constituents",
12
+ "get_index_related_etfs",
13
+ "get_stock_belongings",
14
+ "get_caidazi_user_watchlist",
15
+ "get_caidazi_positions_summary",
16
+ "get_caidazi_portfolio_snapshot",
17
+ ]);
18
+
19
+ export const ALLOWED_TOOL_SET = new Set(ALLOWED_TOOL_NAMES);
20
+
21
+ export function isAllowedTool(toolName) {
22
+ return ALLOWED_TOOL_SET.has(toolName);
23
+ }
package/src/cli.js ADDED
@@ -0,0 +1,60 @@
1
+ import { loadConfig } from "./config.js";
2
+ import { runStdioServer } from "./mcp-server.js";
3
+ import { CaidaziRestClient } from "./rest-client.js";
4
+
5
+ const HELP = `Caidazi MCP bridge
6
+
7
+ Usage:
8
+ caidazi-mcp Start the stdio MCP server
9
+ caidazi-mcp validate [--probe] Validate backend reachability
10
+
11
+ Environment:
12
+ CAIDAZI_API_KEY Required bearer token
13
+ CAIDAZI_BASE_URL Optional backend base URL (default: http://101.126.22.17:5011)
14
+ CAIDAZI_ALLOW_HTTP Required as "true" when CAIDAZI_BASE_URL uses http://
15
+ CAIDAZI_TIMEOUT_MS Optional request timeout in milliseconds (default: 30000)
16
+ `;
17
+
18
+ export async function main({ argv = process.argv.slice(2), env = process.env } = {}) {
19
+ const [command, ...rest] = argv;
20
+
21
+ if (command === "--help" || command === "-h" || command === "help") {
22
+ process.stdout.write(HELP);
23
+ return;
24
+ }
25
+
26
+ if (command === "validate") {
27
+ await validate({ argv: rest, env });
28
+ return;
29
+ }
30
+
31
+ await runStdioServer({ argv, env });
32
+ }
33
+
34
+ async function validate({ argv, env }) {
35
+ const config = loadConfig({ argv: argv.filter((arg) => arg !== "--probe"), env });
36
+ const client = new CaidaziRestClient(config);
37
+ const tools = await client.listTools();
38
+
39
+ process.stdout.write(`Caidazi backend reachable: ${tools.length} public tools exposed\n`);
40
+ for (const tool of tools.slice(0, 12)) {
41
+ process.stdout.write(`- ${tool.name}\n`);
42
+ }
43
+
44
+ if (argv.includes("--probe")) {
45
+ const result = await client.callTool("extract_assets", { text: "贵州茅台" });
46
+ process.stdout.write(`Probe extract_assets succeeded: ${summarizeProbe(result)}\n`);
47
+ }
48
+ }
49
+
50
+ function summarizeProbe(result) {
51
+ if (typeof result === "string") {
52
+ return result.slice(0, 120);
53
+ }
54
+
55
+ if (result && typeof result === "object") {
56
+ return JSON.stringify(result).slice(0, 120);
57
+ }
58
+
59
+ return String(result);
60
+ }
package/src/config.js ADDED
@@ -0,0 +1,82 @@
1
+ const DEFAULT_BASE_URL = "http://101.126.22.17:5011";
2
+ const DEFAULT_TIMEOUT_MS = 30000;
3
+
4
+ export function loadConfig({ env = process.env, argv = process.argv.slice(2) } = {}) {
5
+ const options = parseArgs(argv);
6
+ const apiKeyEnv = options.apiKeyEnv || "CAIDAZI_API_KEY";
7
+ const apiKey = options.apiKey || env[apiKeyEnv];
8
+
9
+ if (!apiKey) {
10
+ throw new Error(`${apiKeyEnv} is required. Set it in your Agent's secret or environment configuration.`);
11
+ }
12
+
13
+ const baseUrl = normalizeBaseUrl(
14
+ options.baseUrl ||
15
+ env.CAIDAZI_BASE_URL ||
16
+ env.CAIDAZI_API_BASE_URL ||
17
+ DEFAULT_BASE_URL,
18
+ );
19
+ const allowHttp = options.allowHttp === "true" || env.CAIDAZI_ALLOW_HTTP === "true";
20
+ const timeoutMs = parsePositiveInteger(
21
+ options.timeoutMs || env.CAIDAZI_TIMEOUT_MS,
22
+ DEFAULT_TIMEOUT_MS,
23
+ );
24
+
25
+ if (baseUrl.startsWith("http://") && !allowHttp) {
26
+ throw new Error(
27
+ "Plain HTTP backend requires explicit CAIDAZI_ALLOW_HTTP=true because bearer tokens are sent to the backend.",
28
+ );
29
+ }
30
+
31
+ return {
32
+ apiKey,
33
+ apiKeyEnv,
34
+ baseUrl,
35
+ allowHttp,
36
+ timeoutMs,
37
+ };
38
+ }
39
+
40
+ export function parseArgs(argv) {
41
+ const options = {};
42
+
43
+ for (let index = 0; index < argv.length; index += 1) {
44
+ const arg = argv[index];
45
+ if (!arg.startsWith("--")) {
46
+ continue;
47
+ }
48
+
49
+ const [rawName, inlineValue] = arg.slice(2).split("=", 2);
50
+ const name = rawName.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
51
+ const value = inlineValue ?? argv[index + 1];
52
+
53
+ if (inlineValue === undefined) {
54
+ index += 1;
55
+ }
56
+
57
+ if (value === undefined) {
58
+ throw new Error(`Missing value for --${rawName}`);
59
+ }
60
+
61
+ options[name] = value;
62
+ }
63
+
64
+ return options;
65
+ }
66
+
67
+ function normalizeBaseUrl(value) {
68
+ return String(value || DEFAULT_BASE_URL).replace(/\/+$/, "");
69
+ }
70
+
71
+ function parsePositiveInteger(value, fallback) {
72
+ if (value === undefined || value === null || value === "") {
73
+ return fallback;
74
+ }
75
+
76
+ const parsed = Number.parseInt(value, 10);
77
+ if (!Number.isFinite(parsed) || parsed <= 0) {
78
+ throw new Error(`Expected a positive integer timeout, got: ${value}`);
79
+ }
80
+
81
+ return parsed;
82
+ }
@@ -0,0 +1,60 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ } from "@modelcontextprotocol/sdk/types.js";
7
+
8
+ import { loadConfig } from "./config.js";
9
+ import { CaidaziRestClient } from "./rest-client.js";
10
+ import { createMcpTool, resultToContent } from "./tool-schema.js";
11
+
12
+ export function createServer({ client, version = "0.1.0" }) {
13
+ const server = new Server(
14
+ {
15
+ name: "caidazi",
16
+ version,
17
+ },
18
+ {
19
+ capabilities: {
20
+ tools: {},
21
+ },
22
+ instructions:
23
+ "Caidazi tools provide market pulse, asset research, stock screening, fund/ETF research, macro analysis, finance search, user assets, and portfolio review.",
24
+ },
25
+ );
26
+
27
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
28
+ const tools = await client.listTools();
29
+ return {
30
+ tools: tools.map(createMcpTool),
31
+ };
32
+ });
33
+
34
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
35
+ const toolName = request.params.name;
36
+ const parameters = request.params.arguments || {};
37
+
38
+ try {
39
+ const result = await client.callTool(toolName, parameters);
40
+ return {
41
+ content: resultToContent(result),
42
+ };
43
+ } catch (error) {
44
+ return {
45
+ isError: true,
46
+ content: resultToContent(error instanceof Error ? error.message : String(error)),
47
+ };
48
+ }
49
+ });
50
+
51
+ return server;
52
+ }
53
+
54
+ export async function runStdioServer({ env = process.env, argv = process.argv.slice(2) } = {}) {
55
+ const config = loadConfig({ env, argv });
56
+ const client = new CaidaziRestClient(config);
57
+ const server = createServer({ client, version: process.env.npm_package_version || "0.1.0" });
58
+ const transport = new StdioServerTransport();
59
+ await server.connect(transport);
60
+ }