datasinking 0.2.11__tar.gz → 0.2.13__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasinking
3
- Version: 0.2.11
3
+ Version: 0.2.13
4
4
  Summary: Python client for DataSinking — full-text Asian financial reports (China, Korea, Japan, Taiwan) as Markdown.
5
5
  Author: DataSinking
6
6
  License: MIT
@@ -41,20 +41,21 @@ with YAML frontmatter, preserved headings, paragraphs and tables.
41
41
 
42
42
  ## MCP server
43
43
 
44
- Ship DataSinking to any AI agent (Claude / Cursor / Codex / Windsurf) as an
44
+ Ship DataSinking to any AI agent (Claude / Cursor / Codex / Devin Desktop) as an
45
45
  [MCP](https://modelcontextprotocol.io) server — 6 tools: list exchanges, list stocks,
46
46
  list reports, fetch a report, list sections, fetch one section (token-friendly for RAG).
47
47
 
48
48
  ### Hosted — nothing to install
49
49
 
50
- Point any MCP client at our endpoint and you're done. No package, no Python, no local server:
50
+ One URL, no package, no Python, no local server. Most clients take this shape:
51
51
 
52
52
  ```json
53
53
  {
54
54
  "mcpServers": {
55
55
  "datasinking": {
56
56
  "type": "http",
57
- "url": "https://api.datasink.ing/mcp?apikey=YOUR_KEY"
57
+ "url": "https://api.datasink.ing/mcp",
58
+ "headers": { "Authorization": "Bearer YOUR_KEY" }
58
59
  }
59
60
  }
60
61
  }
@@ -67,9 +68,12 @@ claude mcp add --transport http datasinking https://api.datasink.ing/mcp \
67
68
  --header "Authorization: Bearer YOUR_KEY"
68
69
  ```
69
70
 
70
- Your key rides inside the URL, so treat that config as a secret. Clients that support custom
71
- headers can send `Authorization: Bearer YOUR_KEY` instead — Claude Code redacts headers in its
72
- output but can't redact a URL.
71
+ A client that can't set a header can put the key in the URL instead —
72
+ `https://api.datasink.ing/mcp?apikey=YOUR_KEY` — but only do that when you must: a key in a URL
73
+ ends up in logs and screen shares, and no client can redact it.
74
+
75
+ Two clients don't fit the shape above: Codex's config is TOML, and Devin Desktop's remote field is
76
+ `serverUrl`, not `url`. Per-client files: [`docs/mcp/`](docs/mcp/).
73
77
 
74
78
  ### Local — run it yourself
75
79
 
@@ -104,8 +108,9 @@ kept in lockstep by [`check_mcp_parity.py`](check_mcp_parity.py).
104
108
 
105
109
  Full per-client setup: [`mcp-server.md`](mcp-server.md) (overview) ·
106
110
  [`docs/mcp/`](docs/mcp/) (one guide per client).
107
- Hosted endpoint is **POST-only and stateless** — `GET /mcp` returns 405, no session id, and every
108
- client needs a restart after a config change. That trips up most first connections.
111
+
112
+ The hosted endpoint is **POST-only and stateless** — `GET /mcp` returns 405, and no session id is
113
+ issued. Most clients read MCP config at launch, so restart one after changing its config.
109
114
 
110
115
  ![DataSinking MCP in Claude](docs/images/mcp-demo.png)
111
116
 
@@ -119,8 +124,8 @@ datasinking/
119
124
  ├── research/ # Research notes / blog posts (reproducing paper-style presentation)
120
125
  ├── datasinking/ # Python client + MCP server — pip install "datasinking[mcp]"
121
126
  ├── npm/ # The same MCP server on npm — npx -y datasinking-mcp (Node 18+)
122
- ├── docs/mcp/ # Per-client MCP setup: Claude Code, Claude Desktop, Cursor, Codex, WorkBuddy
123
- ├── mcp-server.md # MCP server overview — the two config shapes, tools, endpoint limits
127
+ ├── docs/mcp/ # Per-client MCP setup — Claude Code, Desktop, Cursor, Codex, WorkBuddy, Devin…
128
+ ├── mcp-server.md # MCP server overview — endpoint, per-client table, tools, troubleshooting
124
129
  ├── llm-examples.md # Ask an LLM — no code needed (8 end-to-end examples)
125
130
  ├── api-examples.md # 7 examples × 3 interfaces (curl / Python / LLM)
126
131
  └── README.md
@@ -135,9 +140,10 @@ errors that client actually produces:
135
140
  | Claude Desktop | [`docs/mcp/claude-desktop.md`](docs/mcp/claude-desktop.md) |
136
141
  | OpenAI Codex CLI | [`docs/mcp/codex.md`](docs/mcp/codex.md) |
137
142
  | WorkBuddy / CodeBuddy | [`docs/mcp/workbuddy.md`](docs/mcp/workbuddy.md) |
143
+ | Doubao Work / 豆包工作 | [`docs/mcp/doubao.md`](docs/mcp/doubao.md) |
138
144
  | Cursor | [`docs/mcp/cursor.md`](docs/mcp/cursor.md) |
139
- | DeepSeek | [`docs/mcp/deepseek.md`](docs/mcp/deepseek.md) |
140
- | Windsurf | [`docs/mcp/windsurf.md`](docs/mcp/windsurf.md) |
145
+ | DeepSeek Harness (`dsh`) | [`docs/mcp/deepseek.md`](docs/mcp/deepseek.md) |
146
+ | Devin Desktop | [`docs/mcp/windsurf.md`](docs/mcp/windsurf.md) |
141
147
 
142
148
  ## Quick start
143
149
 
@@ -18,20 +18,21 @@ with YAML frontmatter, preserved headings, paragraphs and tables.
18
18
 
19
19
  ## MCP server
20
20
 
21
- Ship DataSinking to any AI agent (Claude / Cursor / Codex / Windsurf) as an
21
+ Ship DataSinking to any AI agent (Claude / Cursor / Codex / Devin Desktop) as an
22
22
  [MCP](https://modelcontextprotocol.io) server — 6 tools: list exchanges, list stocks,
23
23
  list reports, fetch a report, list sections, fetch one section (token-friendly for RAG).
24
24
 
25
25
  ### Hosted — nothing to install
26
26
 
27
- Point any MCP client at our endpoint and you're done. No package, no Python, no local server:
27
+ One URL, no package, no Python, no local server. Most clients take this shape:
28
28
 
29
29
  ```json
30
30
  {
31
31
  "mcpServers": {
32
32
  "datasinking": {
33
33
  "type": "http",
34
- "url": "https://api.datasink.ing/mcp?apikey=YOUR_KEY"
34
+ "url": "https://api.datasink.ing/mcp",
35
+ "headers": { "Authorization": "Bearer YOUR_KEY" }
35
36
  }
36
37
  }
37
38
  }
@@ -44,9 +45,12 @@ claude mcp add --transport http datasinking https://api.datasink.ing/mcp \
44
45
  --header "Authorization: Bearer YOUR_KEY"
45
46
  ```
46
47
 
47
- Your key rides inside the URL, so treat that config as a secret. Clients that support custom
48
- headers can send `Authorization: Bearer YOUR_KEY` instead — Claude Code redacts headers in its
49
- output but can't redact a URL.
48
+ A client that can't set a header can put the key in the URL instead —
49
+ `https://api.datasink.ing/mcp?apikey=YOUR_KEY` — but only do that when you must: a key in a URL
50
+ ends up in logs and screen shares, and no client can redact it.
51
+
52
+ Two clients don't fit the shape above: Codex's config is TOML, and Devin Desktop's remote field is
53
+ `serverUrl`, not `url`. Per-client files: [`docs/mcp/`](docs/mcp/).
50
54
 
51
55
  ### Local — run it yourself
52
56
 
@@ -81,8 +85,9 @@ kept in lockstep by [`check_mcp_parity.py`](check_mcp_parity.py).
81
85
 
82
86
  Full per-client setup: [`mcp-server.md`](mcp-server.md) (overview) ·
83
87
  [`docs/mcp/`](docs/mcp/) (one guide per client).
84
- Hosted endpoint is **POST-only and stateless** — `GET /mcp` returns 405, no session id, and every
85
- client needs a restart after a config change. That trips up most first connections.
88
+
89
+ The hosted endpoint is **POST-only and stateless** — `GET /mcp` returns 405, and no session id is
90
+ issued. Most clients read MCP config at launch, so restart one after changing its config.
86
91
 
87
92
  ![DataSinking MCP in Claude](docs/images/mcp-demo.png)
88
93
 
@@ -96,8 +101,8 @@ datasinking/
96
101
  ├── research/ # Research notes / blog posts (reproducing paper-style presentation)
97
102
  ├── datasinking/ # Python client + MCP server — pip install "datasinking[mcp]"
98
103
  ├── npm/ # The same MCP server on npm — npx -y datasinking-mcp (Node 18+)
99
- ├── docs/mcp/ # Per-client MCP setup: Claude Code, Claude Desktop, Cursor, Codex, WorkBuddy
100
- ├── mcp-server.md # MCP server overview — the two config shapes, tools, endpoint limits
104
+ ├── docs/mcp/ # Per-client MCP setup — Claude Code, Desktop, Cursor, Codex, WorkBuddy, Devin…
105
+ ├── mcp-server.md # MCP server overview — endpoint, per-client table, tools, troubleshooting
101
106
  ├── llm-examples.md # Ask an LLM — no code needed (8 end-to-end examples)
102
107
  ├── api-examples.md # 7 examples × 3 interfaces (curl / Python / LLM)
103
108
  └── README.md
@@ -112,9 +117,10 @@ errors that client actually produces:
112
117
  | Claude Desktop | [`docs/mcp/claude-desktop.md`](docs/mcp/claude-desktop.md) |
113
118
  | OpenAI Codex CLI | [`docs/mcp/codex.md`](docs/mcp/codex.md) |
114
119
  | WorkBuddy / CodeBuddy | [`docs/mcp/workbuddy.md`](docs/mcp/workbuddy.md) |
120
+ | Doubao Work / 豆包工作 | [`docs/mcp/doubao.md`](docs/mcp/doubao.md) |
115
121
  | Cursor | [`docs/mcp/cursor.md`](docs/mcp/cursor.md) |
116
- | DeepSeek | [`docs/mcp/deepseek.md`](docs/mcp/deepseek.md) |
117
- | Windsurf | [`docs/mcp/windsurf.md`](docs/mcp/windsurf.md) |
122
+ | DeepSeek Harness (`dsh`) | [`docs/mcp/deepseek.md`](docs/mcp/deepseek.md) |
123
+ | Devin Desktop | [`docs/mcp/windsurf.md`](docs/mcp/windsurf.md) |
118
124
 
119
125
  ## Quick start
120
126
 
@@ -1,11 +1,11 @@
1
- # -*- coding: utf-8 -*-
2
- """版本号的**唯一来源**。
3
-
4
- pyproject.toml 用 `dynamic = ["version"]` 从这里读,`client.py` 的 User-Agent 也读它。
5
-
6
- 为什么单独抽一个文件:之前 pyproject.toml 和 `__init__.py` 各写各的版本号,
7
- 结果 `__version__` 长期停在 0.1.0 没跟上(发 0.2.4 时才发现,User-Agent 里也一直
8
- 写着旧版本)。现在只有这一处需要改。
9
- """
10
-
11
- __version__ = "0.2.11"
1
+ # -*- coding: utf-8 -*-
2
+ """版本号的**唯一来源**。
3
+
4
+ pyproject.toml 用 `dynamic = ["version"]` 从这里读,`client.py` 的 User-Agent 也读它。
5
+
6
+ 为什么单独抽一个文件:之前 pyproject.toml 和 `__init__.py` 各写各的版本号,
7
+ 结果 `__version__` 长期停在 0.1.0 没跟上(发 0.2.4 时才发现,User-Agent 里也一直
8
+ 写着旧版本)。现在只有这一处需要改。
9
+ """
10
+
11
+ __version__ = "0.2.13"
@@ -3,7 +3,7 @@
3
3
 
4
4
  Expose the DataSinking API — full-text financial reports across Asia
5
5
  (China, Korea, Japan, Taiwan) as clean Markdown — to AI agents (Claude, Cursor,
6
- Codex, DeepSeek, Windsurf, …).
6
+ Codex, DeepSeek, Devin Desktop, …).
7
7
 
8
8
  Install the MCP extra::
9
9
 
@@ -33,6 +33,25 @@ try:
33
33
  except ImportError: # pragma: no cover
34
34
  from mcp.server.mcpserver import MCPServer as FastMCP # mcp v2
35
35
 
36
+ # ⚠️ **工具里必须抛 ToolError,不能抛 RuntimeError。**
37
+ #
38
+ # mcp v2(实测 2.2.0)对两者的处理完全不同:
39
+ # 抛 RuntimeError → 客户端收到 isError:true,但文本是**通用句**
40
+ # 「Error executing tool <名字>」,**消息内容被剥掉**,只进 stderr
41
+ # 抛 ToolError → 客户端收到 isError:true,文本带上你的原话
42
+ # 「Error executing tool <名字>: <你的消息>」
43
+ #
44
+ # 这不是细节:get_section 的描述让模型「用真实标题重试」,标题列表就在消息里;
45
+ # 缺 API key 的提示也只在这里。用 RuntimeError 的话,模型看到的是一个**没有任何线索**的
46
+ # 失败 —— 2026-09-22 用真 key 走 uvx 发布版实测确认(`Error executing tool get_section`,32 字符)。
47
+ try:
48
+ from mcp.server.fastmcp.exceptions import ToolError # mcp v1
49
+ except ImportError: # pragma: no cover
50
+ try:
51
+ from mcp.server.mcpserver.exceptions import ToolError # mcp v2
52
+ except ImportError: # 极老的 mcp 没有这个类:退回普通异常(消息会丢,但不至于起不来)
53
+ ToolError = RuntimeError # type: ignore[assignment,misc]
54
+
36
55
  # ⚠️ 参数描述必须写成 `Annotated[T, Field(description=...)]`,**不能只靠 docstring 的 Args 段**。
37
56
  # mcp v2(MCPServer)不再解析 docstring 的 Args —— 实测把整段 Args 当散文塞进工具描述,
38
57
  # 参数级 description 全是空的(2026-09-22 用真实 stdio 握手验证)。v1 两种都认,所以这样写两边通用。
@@ -70,7 +89,7 @@ def _get(path: str, params: Optional[dict] = None) -> dict:
70
89
  也没有可重试的标题(2026-09-22 用真 key 实测,三份实现里这份丢得最干净)。
71
90
  """
72
91
  if not API_KEY:
73
- raise RuntimeError(
92
+ raise ToolError(
74
93
  "Missing DATASINK_API_KEY environment variable (get a free key at https://datasink.ing)"
75
94
  )
76
95
  p = dict(params or {})
@@ -87,7 +106,8 @@ def _get(path: str, params: Optional[dict] = None) -> dict:
87
106
  available = body.get("available")
88
107
  if isinstance(available, list):
89
108
  msg += "\nAvailable sections: " + json.dumps(available, ensure_ascii=False)
90
- raise RuntimeError(msg)
109
+ # ToolError 而非 RuntimeError —— 否则 mcp v2 把 msg 剥成通用句,见文件顶部注释
110
+ raise ToolError(msg)
91
111
  return r.json()
92
112
 
93
113
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datasinking
3
- Version: 0.2.11
3
+ Version: 0.2.13
4
4
  Summary: Python client for DataSinking — full-text Asian financial reports (China, Korea, Japan, Taiwan) as Markdown.
5
5
  Author: DataSinking
6
6
  License: MIT
@@ -41,20 +41,21 @@ with YAML frontmatter, preserved headings, paragraphs and tables.
41
41
 
42
42
  ## MCP server
43
43
 
44
- Ship DataSinking to any AI agent (Claude / Cursor / Codex / Windsurf) as an
44
+ Ship DataSinking to any AI agent (Claude / Cursor / Codex / Devin Desktop) as an
45
45
  [MCP](https://modelcontextprotocol.io) server — 6 tools: list exchanges, list stocks,
46
46
  list reports, fetch a report, list sections, fetch one section (token-friendly for RAG).
47
47
 
48
48
  ### Hosted — nothing to install
49
49
 
50
- Point any MCP client at our endpoint and you're done. No package, no Python, no local server:
50
+ One URL, no package, no Python, no local server. Most clients take this shape:
51
51
 
52
52
  ```json
53
53
  {
54
54
  "mcpServers": {
55
55
  "datasinking": {
56
56
  "type": "http",
57
- "url": "https://api.datasink.ing/mcp?apikey=YOUR_KEY"
57
+ "url": "https://api.datasink.ing/mcp",
58
+ "headers": { "Authorization": "Bearer YOUR_KEY" }
58
59
  }
59
60
  }
60
61
  }
@@ -67,9 +68,12 @@ claude mcp add --transport http datasinking https://api.datasink.ing/mcp \
67
68
  --header "Authorization: Bearer YOUR_KEY"
68
69
  ```
69
70
 
70
- Your key rides inside the URL, so treat that config as a secret. Clients that support custom
71
- headers can send `Authorization: Bearer YOUR_KEY` instead — Claude Code redacts headers in its
72
- output but can't redact a URL.
71
+ A client that can't set a header can put the key in the URL instead —
72
+ `https://api.datasink.ing/mcp?apikey=YOUR_KEY` — but only do that when you must: a key in a URL
73
+ ends up in logs and screen shares, and no client can redact it.
74
+
75
+ Two clients don't fit the shape above: Codex's config is TOML, and Devin Desktop's remote field is
76
+ `serverUrl`, not `url`. Per-client files: [`docs/mcp/`](docs/mcp/).
73
77
 
74
78
  ### Local — run it yourself
75
79
 
@@ -104,8 +108,9 @@ kept in lockstep by [`check_mcp_parity.py`](check_mcp_parity.py).
104
108
 
105
109
  Full per-client setup: [`mcp-server.md`](mcp-server.md) (overview) ·
106
110
  [`docs/mcp/`](docs/mcp/) (one guide per client).
107
- Hosted endpoint is **POST-only and stateless** — `GET /mcp` returns 405, no session id, and every
108
- client needs a restart after a config change. That trips up most first connections.
111
+
112
+ The hosted endpoint is **POST-only and stateless** — `GET /mcp` returns 405, and no session id is
113
+ issued. Most clients read MCP config at launch, so restart one after changing its config.
109
114
 
110
115
  ![DataSinking MCP in Claude](docs/images/mcp-demo.png)
111
116
 
@@ -119,8 +124,8 @@ datasinking/
119
124
  ├── research/ # Research notes / blog posts (reproducing paper-style presentation)
120
125
  ├── datasinking/ # Python client + MCP server — pip install "datasinking[mcp]"
121
126
  ├── npm/ # The same MCP server on npm — npx -y datasinking-mcp (Node 18+)
122
- ├── docs/mcp/ # Per-client MCP setup: Claude Code, Claude Desktop, Cursor, Codex, WorkBuddy
123
- ├── mcp-server.md # MCP server overview — the two config shapes, tools, endpoint limits
127
+ ├── docs/mcp/ # Per-client MCP setup — Claude Code, Desktop, Cursor, Codex, WorkBuddy, Devin…
128
+ ├── mcp-server.md # MCP server overview — endpoint, per-client table, tools, troubleshooting
124
129
  ├── llm-examples.md # Ask an LLM — no code needed (8 end-to-end examples)
125
130
  ├── api-examples.md # 7 examples × 3 interfaces (curl / Python / LLM)
126
131
  └── README.md
@@ -135,9 +140,10 @@ errors that client actually produces:
135
140
  | Claude Desktop | [`docs/mcp/claude-desktop.md`](docs/mcp/claude-desktop.md) |
136
141
  | OpenAI Codex CLI | [`docs/mcp/codex.md`](docs/mcp/codex.md) |
137
142
  | WorkBuddy / CodeBuddy | [`docs/mcp/workbuddy.md`](docs/mcp/workbuddy.md) |
143
+ | Doubao Work / 豆包工作 | [`docs/mcp/doubao.md`](docs/mcp/doubao.md) |
138
144
  | Cursor | [`docs/mcp/cursor.md`](docs/mcp/cursor.md) |
139
- | DeepSeek | [`docs/mcp/deepseek.md`](docs/mcp/deepseek.md) |
140
- | Windsurf | [`docs/mcp/windsurf.md`](docs/mcp/windsurf.md) |
145
+ | DeepSeek Harness (`dsh`) | [`docs/mcp/deepseek.md`](docs/mcp/deepseek.md) |
146
+ | Devin Desktop | [`docs/mcp/windsurf.md`](docs/mcp/windsurf.md) |
141
147
 
142
148
  ## Quick start
143
149
 
File without changes
File without changes