@dshtrading/connector-jin10 0.6.0 → 0.6.2

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.
package/lib/errors.d.ts CHANGED
@@ -7,6 +7,29 @@ declare class Jin10Error extends Error {
7
7
  readonly code: TradingErrorCode;
8
8
  constructor(code: TradingErrorCode, message: string, cause?: unknown);
9
9
  }
10
+ /** MCP initialize 握手中可机器识别的契约违约种类。 */
11
+ type Jin10McpContractViolationKind = 'initialize_result_invalid' | 'unsupported_protocol_version' | 'missing_capability';
12
+ /** 结构化契约违约载荷:错误码之外,调用方可按 kind 分支处理而不必解析文案。 */
13
+ interface Jin10McpContractViolation {
14
+ readonly kind: Jin10McpContractViolationKind;
15
+ readonly method: string;
16
+ /** unsupported_protocol_version:客户端声明/支持的版本。 */
17
+ readonly requestedVersion?: string;
18
+ readonly negotiatedVersion?: string;
19
+ readonly supportedVersions?: readonly string[];
20
+ /** missing_capability:缺失的服务端能力与触发它的方法。 */
21
+ readonly capability?: 'tools' | 'resources';
22
+ readonly detail?: string;
23
+ }
24
+ /**
25
+ * MCP 协议契约违约(initialize 结果非法、协商版本不受支持、方法能力未声明)。
26
+ * 错误码沿用统一词汇 TRADING_EXCHANGE_ERROR(本包对上游坏形状的既有口径),
27
+ * 可分支信息放在 violation 上——不新造跨包错误码,也不把契约违约伪装成网络故障。
28
+ */
29
+ declare class Jin10McpContractError extends Jin10Error {
30
+ readonly violation: Jin10McpContractViolation;
31
+ constructor(violation: Jin10McpContractViolation, message: string);
32
+ }
10
33
  /**
11
34
  * 上游业务错误消息 → 错误码。
12
35
  * 限流文案必须单独识别(决定「今天别再试」的语义);「不支持该品种」= 代码不在
@@ -15,4 +38,4 @@ declare class Jin10Error extends Error {
15
38
  */
16
39
  declare function codeForUpstreamMessage(message: string): TradingErrorCode;
17
40
  //#endregion
18
- export { Jin10Error, TOKEN_HINT, codeForUpstreamMessage };
41
+ export { Jin10Error, Jin10McpContractError, Jin10McpContractViolation, Jin10McpContractViolationKind, TOKEN_HINT, codeForUpstreamMessage };
package/lib/errors.js CHANGED
@@ -12,6 +12,19 @@ var Jin10Error = class extends Error {
12
12
  }
13
13
  };
14
14
  /**
15
+ * MCP 协议契约违约(initialize 结果非法、协商版本不受支持、方法能力未声明)。
16
+ * 错误码沿用统一词汇 TRADING_EXCHANGE_ERROR(本包对上游坏形状的既有口径),
17
+ * 可分支信息放在 violation 上——不新造跨包错误码,也不把契约违约伪装成网络故障。
18
+ */
19
+ var Jin10McpContractError = class extends Jin10Error {
20
+ violation;
21
+ constructor(violation, message) {
22
+ super("TRADING_EXCHANGE_ERROR", message);
23
+ this.name = "Jin10McpContractError";
24
+ this.violation = Object.freeze({ ...violation });
25
+ }
26
+ };
27
+ /**
15
28
  * 上游业务错误消息 → 错误码。
16
29
  * 限流文案必须单独识别(决定「今天别再试」的语义);「不支持该品种」= 代码不在
17
30
  * quote://codes 名册内(本出口实测文案,2026-09-13),映射到 UNSUPPORTED_SYMBOL
@@ -23,4 +36,4 @@ function codeForUpstreamMessage(message) {
23
36
  return "TRADING_EXCHANGE_ERROR";
24
37
  }
25
38
  //#endregion
26
- export { Jin10Error, TOKEN_HINT, codeForUpstreamMessage };
39
+ export { Jin10Error, Jin10McpContractError, TOKEN_HINT, codeForUpstreamMessage };
package/lib/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- import { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, Jin10McpOptions, McpContentPart, McpResourceDescriptor, McpToolDescriptor, McpToolResult, parseMcpBody } from "./mcp.js";
1
+ import { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, Jin10McpOptions, McpContentPart, McpToolDescriptor, McpToolResult, PROTOCOL_VERSION_HEADER, SUPPORTED_PROTOCOL_VERSIONS, parseMcpBody } from "./mcp.js";
2
2
  import { Jin10ArticleDetail, Jin10ArticleSummary, Jin10CalendarEntry, Jin10Instrument, Jin10Kline, Jin10NewsItem, Jin10NewsPage, Jin10Quote, flashTitle, parseArticle, parseCalendar, parseFlashItems, parseFlashPage, parseJin10Time, parseKlines, parseNewsItems, parseNewsPage, parseQuote, parseQuoteCodes } from "./parse.js";
3
3
  import { JIN10_HEAT_LEVELS, JIN10_WEB_APP_ID, JIN10_WEB_FLASH_CHANNELS, JIN10_WEB_FLASH_ENDPOINT, JIN10_WEB_PAGE_SIZE, JIN10_WEB_X_VERSION, Jin10FlashItem, Jin10HeatLevel, Jin10HotFlashOptions, fetchJin10HotFlash, isJin10HeatLevel, parseWebFlashItems } from "./web-flash.js";
4
4
  import { JIN10_RATES_APP_ID, JIN10_RATES_ENDPOINT, JIN10_RATES_X_VERSION, Jin10RateEntry, fetchJin10Rates, parseWebRates } from "./web-rates.js";
5
5
  import { FLASH_PAGE_SIZE, INSTRUMENT_CACHE_TTL_MS, Jin10PageOptions, Jin10Service, SEARCH_FLASH_MAX } from "./service.js";
6
6
  import { Config, apply, inject, name } from "./plugin.js";
7
- import { Jin10Error, TOKEN_HINT, codeForUpstreamMessage } from "./errors.js";
7
+ import { Jin10Error, Jin10McpContractError, Jin10McpContractViolation, Jin10McpContractViolationKind, TOKEN_HINT, codeForUpstreamMessage } from "./errors.js";
8
8
  import { TRADING_FLASH_FEED_KEY, createJin10FlashFeed } from "./flash-service.js";
9
9
  import { MACRO_CALENDAR_MAX, TRADING_MACRO_FEED_KEY, createJin10MacroFeed } from "./macro-service.js";
10
- import { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TICKER_POLL_MS, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote } from "./market-data.js";
10
+ import { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote } from "./market-data.js";
11
11
  import { JIN10_REGIONS, Jin10Region, regionOfFlagUrl, regionOfTitle } from "./regions.js";
12
12
  import { createJin10Tools } from "./tools.js";
13
- export { Config, DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, FLASH_PAGE_SIZE, GLOBAL_MARKET, INSTRUMENT_CACHE_TTL_MS, JIN10_HEAT_LEVELS, JIN10_PROVIDER, JIN10_RATES_APP_ID, JIN10_RATES_ENDPOINT, JIN10_RATES_X_VERSION, JIN10_REGIONS, JIN10_WEB_APP_ID, JIN10_WEB_FLASH_CHANNELS, JIN10_WEB_FLASH_ENDPOINT, JIN10_WEB_PAGE_SIZE, JIN10_WEB_X_VERSION, Jin10ArticleDetail, Jin10ArticleSummary, Jin10CalendarEntry, Jin10Error, Jin10FlashItem, Jin10HeatLevel, Jin10HotFlashOptions, Jin10Instrument, Jin10Kline, Jin10McpClient, Jin10McpOptions, Jin10NewsItem, Jin10NewsPage, Jin10PageOptions, Jin10Quote, Jin10RateEntry, Jin10Region, Jin10Service, MACRO_CALENDAR_MAX, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, McpContentPart, McpResourceDescriptor, McpToolDescriptor, McpToolResult, SEARCH_FLASH_MAX, TICKER_POLL_MS, TOKEN_HINT, TRADING_FLASH_FEED_KEY, TRADING_GLOBAL_MARKET_DATA_KEY, TRADING_MACRO_FEED_KEY, aggregateKlines, apply, codeForUpstreamMessage, createJin10FlashFeed, createJin10GlobalMarketDataService, createJin10MacroFeed, createJin10Tools, fetchJin10HotFlash, fetchJin10Rates, flashTitle, inject, isJin10HeatLevel, name, parseArticle, parseCalendar, parseFlashItems, parseFlashPage, parseJin10Time, parseKlines, parseMcpBody, parseNewsItems, parseNewsPage, parseQuote, parseQuoteCodes, parseWebFlashItems, parseWebRates, regionOfFlagUrl, regionOfTitle, resolveIntervalMinutes, tickerFromQuote };
13
+ export { Config, DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, FLASH_PAGE_SIZE, GLOBAL_MARKET, INSTRUMENT_CACHE_TTL_MS, JIN10_HEAT_LEVELS, JIN10_PROVIDER, JIN10_RATES_APP_ID, JIN10_RATES_ENDPOINT, JIN10_RATES_X_VERSION, JIN10_REGIONS, JIN10_WEB_APP_ID, JIN10_WEB_FLASH_CHANNELS, JIN10_WEB_FLASH_ENDPOINT, JIN10_WEB_PAGE_SIZE, JIN10_WEB_X_VERSION, Jin10ArticleDetail, Jin10ArticleSummary, Jin10CalendarEntry, Jin10Error, Jin10FlashItem, Jin10HeatLevel, Jin10HotFlashOptions, Jin10Instrument, Jin10Kline, Jin10McpClient, Jin10McpContractError, Jin10McpContractViolation, Jin10McpContractViolationKind, Jin10McpOptions, Jin10NewsItem, Jin10NewsPage, Jin10PageOptions, Jin10Quote, Jin10RateEntry, Jin10Region, Jin10Service, MACRO_CALENDAR_MAX, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, McpContentPart, McpToolDescriptor, McpToolResult, PROTOCOL_VERSION_HEADER, SEARCH_FLASH_MAX, SUPPORTED_PROTOCOL_VERSIONS, TOKEN_HINT, TRADING_FLASH_FEED_KEY, TRADING_GLOBAL_MARKET_DATA_KEY, TRADING_MACRO_FEED_KEY, aggregateKlines, apply, codeForUpstreamMessage, createJin10FlashFeed, createJin10GlobalMarketDataService, createJin10MacroFeed, createJin10Tools, fetchJin10HotFlash, fetchJin10Rates, flashTitle, inject, isJin10HeatLevel, name, parseArticle, parseCalendar, parseFlashItems, parseFlashPage, parseJin10Time, parseKlines, parseMcpBody, parseNewsItems, parseNewsPage, parseQuote, parseQuoteCodes, parseWebFlashItems, parseWebRates, regionOfFlagUrl, regionOfTitle, resolveIntervalMinutes, tickerFromQuote };
package/lib/index.js CHANGED
@@ -1,13 +1,13 @@
1
- import { Jin10Error, TOKEN_HINT, codeForUpstreamMessage } from "./errors.js";
1
+ import { Jin10Error, Jin10McpContractError, TOKEN_HINT, codeForUpstreamMessage } from "./errors.js";
2
2
  import { JIN10_REGIONS, regionOfFlagUrl, regionOfTitle } from "./regions.js";
3
3
  import { flashTitle, parseArticle, parseCalendar, parseFlashItems, parseFlashPage, parseJin10Time, parseKlines, parseNewsItems, parseNewsPage, parseQuote, parseQuoteCodes } from "./parse.js";
4
4
  import { JIN10_HEAT_LEVELS, JIN10_WEB_APP_ID, JIN10_WEB_FLASH_CHANNELS, JIN10_WEB_FLASH_ENDPOINT, JIN10_WEB_PAGE_SIZE, JIN10_WEB_X_VERSION, fetchJin10HotFlash, isJin10HeatLevel, parseWebFlashItems } from "./web-flash.js";
5
5
  import { TRADING_FLASH_FEED_KEY, createJin10FlashFeed } from "./flash-service.js";
6
6
  import { MACRO_CALENDAR_MAX, TRADING_MACRO_FEED_KEY, createJin10MacroFeed } from "./macro-service.js";
7
- import { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TICKER_POLL_MS, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote } from "./market-data.js";
8
- import { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, parseMcpBody } from "./mcp.js";
7
+ import { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote } from "./market-data.js";
8
+ import { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, PROTOCOL_VERSION_HEADER, SUPPORTED_PROTOCOL_VERSIONS, parseMcpBody } from "./mcp.js";
9
9
  import { JIN10_RATES_APP_ID, JIN10_RATES_ENDPOINT, JIN10_RATES_X_VERSION, fetchJin10Rates, parseWebRates } from "./web-rates.js";
10
10
  import { FLASH_PAGE_SIZE, INSTRUMENT_CACHE_TTL_MS, Jin10Service, SEARCH_FLASH_MAX } from "./service.js";
11
11
  import { createJin10Tools } from "./tools.js";
12
12
  import { Config, apply, inject, name } from "./plugin.js";
13
- export { Config, DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, FLASH_PAGE_SIZE, GLOBAL_MARKET, INSTRUMENT_CACHE_TTL_MS, JIN10_HEAT_LEVELS, JIN10_PROVIDER, JIN10_RATES_APP_ID, JIN10_RATES_ENDPOINT, JIN10_RATES_X_VERSION, JIN10_REGIONS, JIN10_WEB_APP_ID, JIN10_WEB_FLASH_CHANNELS, JIN10_WEB_FLASH_ENDPOINT, JIN10_WEB_PAGE_SIZE, JIN10_WEB_X_VERSION, Jin10Error, Jin10McpClient, Jin10Service, MACRO_CALENDAR_MAX, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, SEARCH_FLASH_MAX, TICKER_POLL_MS, TOKEN_HINT, TRADING_FLASH_FEED_KEY, TRADING_GLOBAL_MARKET_DATA_KEY, TRADING_MACRO_FEED_KEY, aggregateKlines, apply, codeForUpstreamMessage, createJin10FlashFeed, createJin10GlobalMarketDataService, createJin10MacroFeed, createJin10Tools, fetchJin10HotFlash, fetchJin10Rates, flashTitle, inject, isJin10HeatLevel, name, parseArticle, parseCalendar, parseFlashItems, parseFlashPage, parseJin10Time, parseKlines, parseMcpBody, parseNewsItems, parseNewsPage, parseQuote, parseQuoteCodes, parseWebFlashItems, parseWebRates, regionOfFlagUrl, regionOfTitle, resolveIntervalMinutes, tickerFromQuote };
13
+ export { Config, DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, FLASH_PAGE_SIZE, GLOBAL_MARKET, INSTRUMENT_CACHE_TTL_MS, JIN10_HEAT_LEVELS, JIN10_PROVIDER, JIN10_RATES_APP_ID, JIN10_RATES_ENDPOINT, JIN10_RATES_X_VERSION, JIN10_REGIONS, JIN10_WEB_APP_ID, JIN10_WEB_FLASH_CHANNELS, JIN10_WEB_FLASH_ENDPOINT, JIN10_WEB_PAGE_SIZE, JIN10_WEB_X_VERSION, Jin10Error, Jin10McpClient, Jin10McpContractError, Jin10Service, MACRO_CALENDAR_MAX, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, PROTOCOL_VERSION_HEADER, SEARCH_FLASH_MAX, SUPPORTED_PROTOCOL_VERSIONS, TOKEN_HINT, TRADING_FLASH_FEED_KEY, TRADING_GLOBAL_MARKET_DATA_KEY, TRADING_MACRO_FEED_KEY, aggregateKlines, apply, codeForUpstreamMessage, createJin10FlashFeed, createJin10GlobalMarketDataService, createJin10MacroFeed, createJin10Tools, fetchJin10HotFlash, fetchJin10Rates, flashTitle, inject, isJin10HeatLevel, name, parseArticle, parseCalendar, parseFlashItems, parseFlashPage, parseJin10Time, parseKlines, parseMcpBody, parseNewsItems, parseNewsPage, parseQuote, parseQuoteCodes, parseWebFlashItems, parseWebRates, regionOfFlagUrl, regionOfTitle, resolveIntervalMinutes, tickerFromQuote };
@@ -12,16 +12,12 @@ declare const TRADING_GLOBAL_MARKET_DATA_KEY = "tradingGlobalMarketData";
12
12
  declare const MINUTE_BARS_PER_CALL = 100;
13
13
  /** 本仓最多拼的分钟窗口(3 次上游调用);再深的历史上游没有,不做假数据。 */
14
14
  declare const MAX_MINUTE_WINDOW = 300;
15
- /** Ticker 轮询周期(上游无推送通道)。 */
16
- declare const TICKER_POLL_MS = 5000;
17
15
  declare function resolveIntervalMinutes(interval: string): number | undefined;
18
16
  /** 按分钟数聚合(桶起点 = 本地时间轴对齐;量能求和,缺量按 0)。 */
19
17
  declare function aggregateKlines(bars: readonly Kline[], minutes: number): Kline[];
20
18
  /** get_quote → Ticker(缺失最新价 = 上游形状异常,如实报错,不编价格)。 */
21
19
  declare function tickerFromQuote(quote: Jin10Quote): Ticker;
22
20
  /** global 市场数据面实现(纯对象;与快讯面同款,避免 Service 构造签名的类型漂移)。 */
23
- declare function createJin10GlobalMarketDataService(feed: Jin10Service, options?: {
24
- pollMs?: number;
25
- }): MarketDataService;
21
+ declare function createJin10GlobalMarketDataService(feed: Jin10Service): MarketDataService;
26
22
  //#endregion
27
- export { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TICKER_POLL_MS, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote };
23
+ export { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote };
@@ -10,8 +10,14 @@ const TRADING_GLOBAL_MARKET_DATA_KEY = "tradingGlobalMarketData";
10
10
  const MINUTE_BARS_PER_CALL = 100;
11
11
  /** 本仓最多拼的分钟窗口(3 次上游调用);再深的历史上游没有,不做假数据。 */
12
12
  const MAX_MINUTE_WINDOW = 300;
13
- /** Ticker 轮询周期(上游无推送通道)。 */
14
- const TICKER_POLL_MS = 5e3;
13
+ /**
14
+ * 本市场**不提供常驻 ticker 订阅**:`global_quote` / `global_klines` 的 UI/价格
15
+ * 路径预算为 **0 常驻**——上游配额按工具分池,固定间隔轮询按秒折算必然超出一个
16
+ * 数量级。分配口径与折算数值的家是 dsh-trading-bot 的
17
+ * `packages/tradectl/bin/feeds-budget.mjs` 与 `docs/guides/connectors-guide.md`
18
+ * 的「金十额度预算分配」节(本仓只引用规则,不复写数值);GUI 侧节奏执行见
19
+ * client-ui-trading 的 quote-budget.ts。要价格一律按需:`getTicker` / `getKlines`。
20
+ */
15
21
  /** 支持的周期 → 每根分钟数(api Interval 的子集:分钟级来源能诚实支撑的部分)。 */
16
22
  const INTERVAL_MINUTES = {
17
23
  "1m": 1,
@@ -88,8 +94,7 @@ function minuteBarToKline(bar) {
88
94
  };
89
95
  }
90
96
  /** global 市场数据面实现(纯对象;与快讯面同款,避免 Service 构造签名的类型漂移)。 */
91
- function createJin10GlobalMarketDataService(feed, options = {}) {
92
- const pollMs = options.pollMs ?? 5e3;
97
+ function createJin10GlobalMarketDataService(feed) {
93
98
  const requireCode = (symbol) => {
94
99
  const trimmed = symbol.trim().toUpperCase();
95
100
  if (trimmed === "") throw new Jin10Error("TRADING_UNKNOWN", "jin10 global: symbol must not be empty");
@@ -128,25 +133,12 @@ function createJin10GlobalMarketDataService(feed, options = {}) {
128
133
  const wanted = Math.max(1, Math.min(Math.floor(limit), Math.floor(300 / minutes)));
129
134
  return aggregateKlines(await loadMinuteWindow(requireCode(symbol), wanted * minutes), minutes).slice(-wanted);
130
135
  },
131
- subscribeTicker(symbol, listener) {
132
- let stopped = false;
133
- const tick = async () => {
134
- if (stopped) return;
135
- try {
136
- const ticker = await loadTicker(symbol);
137
- if (!stopped) listener(ticker);
138
- } catch (error) {
139
- if (!stopped) console.warn("[dsh-trading] jin10 ticker poll failed for " + symbol + ":", error);
140
- }
141
- };
142
- tick();
143
- const timer = setInterval(() => {
144
- tick();
145
- }, pollMs);
146
- return { dispose() {
147
- stopped = true;
148
- clearInterval(timer);
149
- } };
136
+ /**
137
+ * 常驻 ticker 订阅显式拒绝(见模块头注释):本市场没有常驻轮询预算,静默
138
+ * 轮询正是预算失控的形态。要价格请按需 `getTicker`。
139
+ */
140
+ subscribeTicker(symbol) {
141
+ throw new Jin10Error("TRADING_NOT_IMPLEMENTED", "jin10 global: 不提供常驻 ticker 订阅(global_quote 的 UI/价格路径预算为 0 常驻,见 connectors-guide「金十额度预算分配」);请按需调用 getTicker。symbol=" + symbol);
150
142
  },
151
143
  async listInstruments() {
152
144
  return (await feed.listInstruments()).map((item) => ({
@@ -157,4 +149,4 @@ function createJin10GlobalMarketDataService(feed, options = {}) {
157
149
  };
158
150
  }
159
151
  //#endregion
160
- export { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TICKER_POLL_MS, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote };
152
+ export { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote };
package/lib/mcp.d.ts CHANGED
@@ -2,18 +2,22 @@
2
2
  declare const DEFAULT_ENDPOINT = "https://mcp.jin10.com/mcp";
3
3
  declare const DEFAULT_PROTOCOL_VERSION = "2025-11-25";
4
4
  declare const DEFAULT_TIMEOUT_MS = 15000;
5
+ /**
6
+ * 本客户端能说的 Streamable HTTP 协议版本(官方 revision):
7
+ * - `2025-11-25`:金十本出口 spike 实测协商版本;
8
+ * - `2025-06-18`:官方首个定义 `MCP-Protocol-Version` 头的 revision。
9
+ * 上游协商出集合之外的版本时按官方「客户端 SHOULD disconnect」报结构化错误,
10
+ * 不降级猜测协议语义。
11
+ */
12
+ declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
13
+ /** initialize 之后每个 HTTP 请求都必须携带的协商版本头(官方 Streamable HTTP)。 */
14
+ declare const PROTOCOL_VERSION_HEADER = "MCP-Protocol-Version";
5
15
  interface McpToolDescriptor {
6
16
  name: string;
7
17
  description?: string;
8
18
  inputSchema?: unknown;
9
19
  outputSchema?: unknown;
10
20
  }
11
- interface McpResourceDescriptor {
12
- uri: string;
13
- name?: string;
14
- mimeType?: string;
15
- description?: string;
16
- }
17
21
  /** 可读文本段(structuredContent 缺席时的兜底来源)。 */
18
22
  interface McpContentPart {
19
23
  type?: string;
@@ -33,6 +37,8 @@ interface Jin10McpOptions {
33
37
  timeoutMs?: number;
34
38
  /** 依赖注入的 fetch(测试用 mock;缺省 globalThis.fetch)。 */
35
39
  fetchImpl?: typeof globalThis.fetch;
40
+ /** 可选会话级取消信号:与单次请求超时合并,取消报「request aborted」。 */
41
+ signal?: AbortSignal;
36
42
  /** 协议版本(缺省 2025-11-25)。 */
37
43
  protocolVersion?: string;
38
44
  clientName?: string;
@@ -58,12 +64,16 @@ declare class Jin10McpClient {
58
64
  private readonly protocolVersion;
59
65
  private readonly clientName;
60
66
  private readonly clientVersion;
67
+ private readonly signal;
61
68
  private nextId;
62
69
  private sessionId;
63
70
  private handshake;
71
+ /** initialize 协商出的协议版本(其后每个请求的 MCP-Protocol-Version 头来源)。 */
72
+ private negotiatedProtocolVersion;
73
+ /** initialize 声明的服务端能力表(方法族能力闸门的判据)。 */
74
+ private serverCapabilities;
64
75
  constructor(options: Jin10McpOptions);
65
76
  listTools(): Promise<McpToolDescriptor[]>;
66
- listResources(): Promise<McpResourceDescriptor[]>;
67
77
  /** 读资源正文(quote://codes 等;返回全部 text 段拼接)。 */
68
78
  readResource(uri: string): Promise<string>;
69
79
  /** 原始 tools/call(不做业务解包;协议/HTTP 错误在此抛出)。 */
@@ -74,8 +84,22 @@ declare class Jin10McpClient {
74
84
  */
75
85
  callToolData(tool: string, args?: Record<string, unknown>): Promise<unknown>;
76
86
  private ensureHandshake;
87
+ /** 丢弃一次未完成的握手留下的全部状态(initialize 之后、initialized 之前的任何失败)。 */
88
+ private resetHandshakeState;
77
89
  private performHandshake;
90
+ /**
91
+ * 校验 initialize 结果并返回服务端能力表;违反官方 lifecycle 契约时抛结构化错误。
92
+ * 依据:官方 revision 2025-11-25 lifecycle——服务端 MUST 回 protocolVersion 与
93
+ * capabilities;客户端不支持的协商版本 SHOULD disconnect。
94
+ */
95
+ private acceptInitializeResult;
96
+ /** 客户端可接受的协商版本:声明的版本 + 官方可说的版本集合。 */
97
+ private supportedVersions;
98
+ /** 方法族能力闸门:能力未声明时在发请求前抛结构化错误(缺能力 ≠ 上游 4xx)。 */
99
+ private assertMethodCapability;
100
+ /** 单次请求信号:调用方取消信号与超时窗口合并,任一触发都以 AbortError 结束。 */
101
+ private requestSignal;
78
102
  private rpc;
79
103
  }
80
104
  //#endregion
81
- export { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, Jin10McpOptions, McpContentPart, McpResourceDescriptor, McpToolDescriptor, McpToolResult, parseMcpBody };
105
+ export { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, Jin10McpOptions, McpContentPart, McpToolDescriptor, McpToolResult, PROTOCOL_VERSION_HEADER, SUPPORTED_PROTOCOL_VERSIONS, parseMcpBody };
package/lib/mcp.js CHANGED
@@ -1,20 +1,42 @@
1
- import { Jin10Error, TOKEN_HINT, codeForUpstreamMessage } from "./errors.js";
1
+ import { Jin10Error, Jin10McpContractError, TOKEN_HINT, codeForUpstreamMessage } from "./errors.js";
2
2
  //#region src/mcp.ts
3
3
  /**
4
4
  * MCP 传输层(Streamable HTTP / JSON-RPC 2.0)。
5
5
  *
6
6
  * 标准流程(用户 2026-09-13 给定契约,本出口实测复核):
7
- * initialize → notifications/initialized → tools/list / resources/list / resources/read → tools/call
7
+ * initialize → notifications/initialized → tools/list / resources/read → tools/call
8
8
  * 响应形态两种都要吃:`application/json` 直出 JSON,或 `text/event-stream` 里
9
9
  * `event: message` + 单行 `data: {…}`(金十本出口实测为后者)。若上游回
10
10
  * `mcp-session-id` 头则后续请求原样回带(本出口当前无会话头,属可选能力)。
11
11
  *
12
12
  * 结果读取:`result.structuredContent` 优先,`result.content[].text` 仅在
13
13
  * structuredContent 缺席时作可读文本兜底(用户契约 3/4)。
14
+ *
15
+ * 握手契约(官方 MCP revision 2025-11-25 / 2025-06-18,本仓 spike 原始证据
16
+ * `spikes/impl-jin10-mcp/EVIDENCE/01-initialize.json`):客户端必须校验 initialize
17
+ * 结果里的 `protocolVersion` 与 `capabilities`;协商版本用于其后每个 HTTP 请求的
18
+ * `MCP-Protocol-Version` 头;未声明 `tools` / `resources` 能力时对应方法族直接报
19
+ * 结构化契约错误,不发业务请求(不重造宿主 MCP、不升级 SDK)。
14
20
  */
15
21
  const DEFAULT_ENDPOINT = "https://mcp.jin10.com/mcp";
16
22
  const DEFAULT_PROTOCOL_VERSION = "2025-11-25";
17
23
  const DEFAULT_TIMEOUT_MS = 15e3;
24
+ /**
25
+ * 本客户端能说的 Streamable HTTP 协议版本(官方 revision):
26
+ * - `2025-11-25`:金十本出口 spike 实测协商版本;
27
+ * - `2025-06-18`:官方首个定义 `MCP-Protocol-Version` 头的 revision。
28
+ * 上游协商出集合之外的版本时按官方「客户端 SHOULD disconnect」报结构化错误,
29
+ * 不降级猜测协议语义。
30
+ */
31
+ const SUPPORTED_PROTOCOL_VERSIONS = Object.freeze(["2025-11-25", "2025-06-18"]);
32
+ /** initialize 之后每个 HTTP 请求都必须携带的协商版本头(官方 Streamable HTTP)。 */
33
+ const PROTOCOL_VERSION_HEADER = "MCP-Protocol-Version";
34
+ /** 服务端能力 → 依赖它的 JSON-RPC 方法族(缺能力即拒发,不靠上游 4xx 兜底)。 */
35
+ const METHOD_CAPABILITY = Object.freeze({
36
+ "tools/list": "tools",
37
+ "tools/call": "tools",
38
+ "resources/read": "resources"
39
+ });
18
40
  /** 把响应体解析成 JSON-RPC 消息(直出 JSON 或 SSE 单行 data 均可)。 */
19
41
  function parseMcpBody(text) {
20
42
  const trimmed = text.trim();
@@ -45,10 +67,16 @@ function parseMcpBody(text) {
45
67
  function isRecord(value) {
46
68
  return typeof value === "object" && value !== null && !Array.isArray(value);
47
69
  }
48
- function networkError(method, cause) {
70
+ /**
71
+ * 传输层失败:超时与取消分别显式标注(前者带超时窗口,后者标明调用方取消),
72
+ * 不混成一句 network failure,也不在这里重试(重试由调用方显式发起)。
73
+ */
74
+ function networkError(method, cause, timeoutMs) {
49
75
  const reason = cause instanceof Error ? cause.message : String(cause);
50
- const timedOut = cause instanceof Error && (cause.name === "TimeoutError" || cause.name === "AbortError");
51
- return new Jin10Error("TRADING_NETWORK", `jin10 mcp ${method}: ${timedOut ? "request timed out" : "network failure"} — ${reason}`, cause);
76
+ const name = cause instanceof Error ? cause.name : "";
77
+ if (name === "TimeoutError") return new Jin10Error("TRADING_NETWORK", `jin10 mcp ${method}: request timed out after ${timeoutMs}ms`, cause);
78
+ if (name === "AbortError") return new Jin10Error("TRADING_NETWORK", `jin10 mcp ${method}: request aborted before completion — ${reason}`, cause);
79
+ return new Jin10Error("TRADING_NETWORK", `jin10 mcp ${method}: network failure — ${reason}`, cause);
52
80
  }
53
81
  var Jin10McpClient = class {
54
82
  endpoint;
@@ -58,14 +86,20 @@ var Jin10McpClient = class {
58
86
  protocolVersion;
59
87
  clientName;
60
88
  clientVersion;
89
+ signal;
61
90
  nextId = 1;
62
91
  sessionId;
63
92
  handshake;
93
+ /** initialize 协商出的协议版本(其后每个请求的 MCP-Protocol-Version 头来源)。 */
94
+ negotiatedProtocolVersion;
95
+ /** initialize 声明的服务端能力表(方法族能力闸门的判据)。 */
96
+ serverCapabilities;
64
97
  constructor(options) {
65
98
  this.endpoint = options.endpoint ?? "https://mcp.jin10.com/mcp";
66
99
  this.token = options.token;
67
100
  this.timeoutMs = options.timeoutMs ?? 15e3;
68
101
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
102
+ this.signal = options.signal;
69
103
  this.protocolVersion = options.protocolVersion ?? "2025-11-25";
70
104
  this.clientName = options.clientName ?? "dsh-trading-connector-jin10";
71
105
  this.clientVersion = options.clientVersion ?? "0.2.1";
@@ -81,17 +115,6 @@ var Jin10McpClient = class {
81
115
  ...tool.outputSchema !== void 0 ? { outputSchema: tool.outputSchema } : {}
82
116
  }));
83
117
  }
84
- async listResources() {
85
- const result = await this.rpc("resources/list", {});
86
- const resources = isRecord(result) ? result.resources : void 0;
87
- if (!Array.isArray(resources)) throw new Jin10Error("TRADING_EXCHANGE_ERROR", "jin10 mcp resources/list: unexpected payload (expected result.resources[])");
88
- return resources.filter(isRecord).map((resource) => ({
89
- uri: String(resource.uri ?? ""),
90
- ...typeof resource.name === "string" ? { name: resource.name } : {},
91
- ...typeof resource.mimeType === "string" ? { mimeType: resource.mimeType } : {},
92
- ...typeof resource.description === "string" ? { description: resource.description } : {}
93
- }));
94
- }
95
118
  /** 读资源正文(quote://codes 等;返回全部 text 段拼接)。 */
96
119
  async readResource(uri) {
97
120
  const result = await this.rpc("resources/read", { uri });
@@ -131,12 +154,19 @@ var Jin10McpClient = class {
131
154
  async ensureHandshake() {
132
155
  if (this.handshake === void 0) this.handshake = this.performHandshake().catch((error) => {
133
156
  this.handshake = void 0;
157
+ this.resetHandshakeState();
134
158
  throw error;
135
159
  });
136
160
  return this.handshake;
137
161
  }
162
+ /** 丢弃一次未完成的握手留下的全部状态(initialize 之后、initialized 之前的任何失败)。 */
163
+ resetHandshakeState() {
164
+ this.negotiatedProtocolVersion = void 0;
165
+ this.serverCapabilities = void 0;
166
+ this.sessionId = void 0;
167
+ }
138
168
  async performHandshake() {
139
- await this.rpc("initialize", {
169
+ const result = await this.rpc("initialize", {
140
170
  protocolVersion: this.protocolVersion,
141
171
  capabilities: {},
142
172
  clientInfo: {
@@ -144,13 +174,73 @@ var Jin10McpClient = class {
144
174
  version: this.clientVersion
145
175
  }
146
176
  }, { skipHandshake: true });
177
+ this.serverCapabilities = this.acceptInitializeResult(result);
147
178
  await this.rpc("notifications/initialized", void 0, {
148
179
  notification: true,
149
180
  skipHandshake: true
150
181
  });
151
182
  }
183
+ /**
184
+ * 校验 initialize 结果并返回服务端能力表;违反官方 lifecycle 契约时抛结构化错误。
185
+ * 依据:官方 revision 2025-11-25 lifecycle——服务端 MUST 回 protocolVersion 与
186
+ * capabilities;客户端不支持的协商版本 SHOULD disconnect。
187
+ */
188
+ acceptInitializeResult(result) {
189
+ if (!isRecord(result)) throw new Jin10McpContractError({
190
+ kind: "initialize_result_invalid",
191
+ method: "initialize",
192
+ detail: "result is not an object"
193
+ }, "jin10 mcp initialize: result is not an object");
194
+ const negotiated = result.protocolVersion;
195
+ if (typeof negotiated !== "string" || negotiated.trim().length === 0) throw new Jin10McpContractError({
196
+ kind: "initialize_result_invalid",
197
+ method: "initialize",
198
+ detail: "missing protocolVersion"
199
+ }, "jin10 mcp initialize: server did not return a protocolVersion");
200
+ const supported = this.supportedVersions();
201
+ if (!supported.includes(negotiated)) throw new Jin10McpContractError({
202
+ kind: "unsupported_protocol_version",
203
+ method: "initialize",
204
+ requestedVersion: this.protocolVersion,
205
+ negotiatedVersion: negotiated,
206
+ supportedVersions: supported
207
+ }, `jin10 mcp initialize: server negotiated unsupported protocol version "${negotiated}" (client supports ${supported.join(", ")})`);
208
+ const capabilities = result.capabilities;
209
+ if (!isRecord(capabilities)) throw new Jin10McpContractError({
210
+ kind: "initialize_result_invalid",
211
+ method: "initialize",
212
+ detail: "missing capabilities"
213
+ }, "jin10 mcp initialize: server did not return a capabilities object");
214
+ this.negotiatedProtocolVersion = negotiated;
215
+ return capabilities;
216
+ }
217
+ /** 客户端可接受的协商版本:声明的版本 + 官方可说的版本集合。 */
218
+ supportedVersions() {
219
+ return SUPPORTED_PROTOCOL_VERSIONS.includes(this.protocolVersion) ? SUPPORTED_PROTOCOL_VERSIONS : Object.freeze([this.protocolVersion, ...SUPPORTED_PROTOCOL_VERSIONS]);
220
+ }
221
+ /** 方法族能力闸门:能力未声明时在发请求前抛结构化错误(缺能力 ≠ 上游 4xx)。 */
222
+ assertMethodCapability(method) {
223
+ const capability = METHOD_CAPABILITY[method];
224
+ if (capability === void 0) return;
225
+ const declared = this.serverCapabilities;
226
+ if (declared !== void 0 && isRecord(declared[capability])) return;
227
+ throw new Jin10McpContractError({
228
+ kind: "missing_capability",
229
+ method,
230
+ capability
231
+ }, `jin10 mcp ${method}: server did not declare the "${capability}" capability during initialize; refusing to send the request`);
232
+ }
233
+ /** 单次请求信号:调用方取消信号与超时窗口合并,任一触发都以 AbortError 结束。 */
234
+ requestSignal() {
235
+ const timeout = AbortSignal.timeout(this.timeoutMs);
236
+ if (this.signal === void 0) return timeout;
237
+ return AbortSignal.any([this.signal, timeout]);
238
+ }
152
239
  async rpc(method, params, options = {}) {
153
- if (options.skipHandshake !== true) await this.ensureHandshake();
240
+ if (options.skipHandshake !== true) {
241
+ await this.ensureHandshake();
242
+ this.assertMethodCapability(method);
243
+ }
154
244
  const token = this.token();
155
245
  if (token === void 0 || token.trim().length === 0) throw new Jin10Error("TRADING_CREDENTIALS_MISSING", `jin10 mcp ${method}: missing MCP token. ${TOKEN_HINT}`);
156
246
  const payload = {
@@ -167,13 +257,14 @@ var Jin10McpClient = class {
167
257
  "content-type": "application/json",
168
258
  accept: "application/json, text/event-stream",
169
259
  authorization: `Bearer ${token.trim()}`,
260
+ ...this.negotiatedProtocolVersion !== void 0 ? { [PROTOCOL_VERSION_HEADER]: this.negotiatedProtocolVersion } : {},
170
261
  ...this.sessionId !== void 0 ? { "mcp-session-id": this.sessionId } : {}
171
262
  },
172
263
  body: JSON.stringify(payload),
173
- signal: AbortSignal.timeout(this.timeoutMs)
264
+ signal: this.requestSignal()
174
265
  });
175
266
  } catch (error) {
176
- throw networkError(method, error);
267
+ throw networkError(method, error, this.timeoutMs);
177
268
  }
178
269
  const sessionId = response.headers.get("mcp-session-id");
179
270
  if (sessionId !== null && sessionId.length > 0) this.sessionId = sessionId;
@@ -190,7 +281,7 @@ var Jin10McpClient = class {
190
281
  throw new Jin10Error("TRADING_EXCHANGE_ERROR", `jin10 mcp ${method}: HTTP ${response.status}${body ? ` — ${body.slice(0, 200)}` : ""}`);
191
282
  }
192
283
  const message = parseMcpBody(await response.text().catch((error) => {
193
- throw networkError(method, error);
284
+ throw networkError(method, error, this.timeoutMs);
194
285
  }));
195
286
  if (message === void 0) return void 0;
196
287
  if (isRecord(message.error)) {
@@ -219,4 +310,4 @@ function textEnvelope(result) {
219
310
  }
220
311
  }
221
312
  //#endregion
222
- export { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, parseMcpBody };
313
+ export { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, PROTOCOL_VERSION_HEADER, SUPPORTED_PROTOCOL_VERSIONS, parseMcpBody };
package/lib/tools.js CHANGED
@@ -1,3 +1,4 @@
1
+ import "./macro-service.js";
1
2
  import "./service.js";
2
3
  import { defineTool } from "@deepseek-ai/dsh-tools";
3
4
  //#region src/tools.ts
@@ -186,11 +187,11 @@ function createJin10Tools(service) {
186
187
  description: `Current-week economic calendar from ${SOURCE} (Monday–Sunday, Beijing time): release time, importance stars, consensus / previous / actual values and the stated market impact. Values are the publisher snapshot as of the call — the same release may be revised; treat actual=null as "not published yet". Upstream rate limit: 1500 calls per tool per Beijing calendar day.`,
187
188
  parameters: { limit: {
188
189
  type: "number",
189
- description: "Max entries to return (integer 1..250; default 40, earliest first)"
190
+ description: `Max entries to return (integer 1..400; default 40, earliest first)`
190
191
  } },
191
192
  output: textOutput,
192
193
  async execute(raw) {
193
- const limit = readLimit((raw ?? {}).limit, 40, 250);
194
+ const limit = readLimit((raw ?? {}).limit, 40, 400);
194
195
  const entries = await service.listCalendar(limit);
195
196
  if (entries.length === 0) return "econ_calendar: the upstream calendar is empty for the current week.";
196
197
  return [`econ_calendar — ${entries.length} entr(ies) for the current week (Beijing time), source: ${SOURCE}:`, ...entries.map(renderCalendarLine)].join("\n");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dshtrading/connector-jin10",
3
3
  "description": "金十数据 MCP 连接器:把 mcp.jin10.com 的行情/快讯/资讯/财经日历封装成本仓的连接器(市场无关,host 面工具行)",
4
- "version": "0.6.0",
4
+ "version": "0.6.2",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -25,11 +25,11 @@
25
25
  ],
26
26
  "license": "PolyForm-Noncommercial-1.0.0",
27
27
  "dependencies": {
28
- "@dshtrading/api": "^0.6.0"
28
+ "@dshtrading/api": "^0.6.2"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@deepseek-ai/cordis": ">=4.0.0",
32
- "@deepseek-ai/dsh-tools": ">=0.2.0-rc.2",
32
+ "@deepseek-ai/dsh-tools": ">=0.2.1-rc.2",
33
33
  "@deepseek-ai/schemastery": ">=3.18.0"
34
34
  },
35
35
  "devDependencies": {