mcp-query-table 0.3.0__tar.gz → 0.3.2__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.
Files changed (25) hide show
  1. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/PKG-INFO +40 -28
  2. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/README.md +39 -27
  3. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/__init__.py +2 -0
  4. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/__main__.py +7 -6
  5. mcp_query_table-0.3.2/mcp_query_table/_version.py +1 -0
  6. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/enums.py +1 -1
  7. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/providers/baidu.py +4 -4
  8. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/providers/n.py +2 -2
  9. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/providers/yuanbao.py +3 -3
  10. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/server.py +15 -10
  11. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/tool.py +85 -37
  12. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table.egg-info/PKG-INFO +40 -28
  13. mcp_query_table-0.3.0/mcp_query_table/_version.py +0 -1
  14. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/LICENSE +0 -0
  15. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/providers/__init__.py +0 -0
  16. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/sites/__init__.py +0 -0
  17. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/sites/eastmoney.py +0 -0
  18. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/sites/iwencai.py +0 -0
  19. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table/sites/tdx.py +0 -0
  20. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table.egg-info/SOURCES.txt +0 -0
  21. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table.egg-info/dependency_links.txt +0 -0
  22. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table.egg-info/requires.txt +0 -0
  23. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/mcp_query_table.egg-info/top_level.txt +0 -0
  24. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/pyproject.toml +0 -0
  25. {mcp_query_table-0.3.0 → mcp_query_table-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp_query_table
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: query table from website, support MCP
5
5
  Author-email: wukan <wu-kan@163.com>
6
6
  License: MIT License
@@ -70,31 +70,31 @@ from mcp_query_table import *
70
70
 
71
71
 
72
72
  async def main() -> None:
73
- async with BrowserManager(port=9222, browser_path=None, debug=True) as bm:
74
- # 问财需要保证浏览器宽度>768,防止界面变成适应手机
75
- page = await bm.get_page()
76
- df = await query(page, '收益最好的200只ETF', query_type=QueryType.ETF, max_page=1, site=Site.THS)
77
- print(df.to_markdown())
78
- df = await query(page, '年初至今收益率前50', query_type=QueryType.Fund, max_page=1, site=Site.TDX)
79
- print(df.to_csv())
80
- df = await query(page, '流通市值前10的行业板块', query_type=QueryType.Index, max_page=1, site=Site.TDX)
81
- print(df.to_csv())
82
- # TODO 东财翻页要提前登录
83
- df = await query(page, '今日涨幅前5的概念板块;', query_type=QueryType.Board, max_page=3, site=Site.EastMoney)
84
- print(df)
85
-
86
- output = await chat(page, "1+2等于多少?", provider=Provider.YuanBao)
87
- print(output)
88
- output = await chat(page, "3+4等于多少?", provider=Provider.YuanBao, create=True)
89
- print(output)
90
-
91
- print('done')
92
- bm.release_page(page)
93
- await page.wait_for_timeout(2000)
73
+ async with BrowserManager(cdp_endpoint="http://127.0.0.1:9222", executable_path=None, debug=True) as bm:
74
+ # 问财需要保证浏览器宽度>768,防止界面变成适应手机
75
+ page = await bm.get_page()
76
+ df = await query(page, '收益最好的200只ETF', query_type=QueryType.ETF, max_page=1, site=Site.THS)
77
+ print(df.to_markdown())
78
+ df = await query(page, '年初至今收益率前50', query_type=QueryType.Fund, max_page=1, site=Site.TDX)
79
+ print(df.to_csv())
80
+ df = await query(page, '流通市值前10的行业板块', query_type=QueryType.Index, max_page=1, site=Site.TDX)
81
+ print(df.to_csv())
82
+ # TODO 东财翻页要提前登录
83
+ df = await query(page, '今日涨幅前5的概念板块;', query_type=QueryType.Board, max_page=3, site=Site.EastMoney)
84
+ print(df)
85
+
86
+ output = await chat(page, "1+2等于多少?", provider=Provider.YuanBao)
87
+ print(output)
88
+ output = await chat(page, "3+4等于多少?", provider=Provider.YuanBao, create=True)
89
+ print(output)
90
+
91
+ print('done')
92
+ bm.release_page(page)
93
+ await page.wait_for_timeout(2000)
94
94
 
95
95
 
96
96
  if __name__ == '__main__':
97
- asyncio.run(main())
97
+ asyncio.run(main())
98
98
 
99
99
  ```
100
100
 
@@ -132,7 +132,7 @@ if __name__ == '__main__':
132
132
 
133
133
  确保可以在控制台中执行`python -m mcp_query_table -h`。如果不能,可能要先`pip install mcp_query_table`
134
134
 
135
- 在`Cline`中可以配置如下。其中`command`是`python`的绝对路径,`browser_path`是`Chrome`的绝对路径。
135
+ 在`Cline`中可以配置如下。其中`command`是`python`的绝对路径,`executable_path`是`Chrome`的绝对路径。
136
136
 
137
137
  ### STDIO方式
138
138
 
@@ -146,7 +146,9 @@ if __name__ == '__main__':
146
146
  "mcp_query_table",
147
147
  "--format",
148
148
  "markdown",
149
- "--browser_path",
149
+ "--cdp_endpoint",
150
+ "http://127.0.0.1:9222",
151
+ "--executable_path",
150
152
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
151
153
  ]
152
154
  }
@@ -159,11 +161,20 @@ if __name__ == '__main__':
159
161
  先在控制台中执行如下命令,启动`MCP`服务
160
162
 
161
163
  ```commandline
162
- python -m mcp_query_table --format markdown --browser_path "C:\Program Files\Google\Chrome\Application\chrome.exe" --transport sse --mcp_port 8000
164
+ python -m mcp_query_table --format markdown --transport sse --port 8000
163
165
  ```
164
166
 
165
167
  然后就可以连接到`MCP`服务了
166
- http://localhost:8000/sse
168
+
169
+ ```json
170
+ {
171
+ "mcpServers": {
172
+ "mcp_query_table": {
173
+ "url": "http://127.0.0.1:8000/sse"
174
+ }
175
+ }
176
+ }
177
+ ```
167
178
 
168
179
  ## 使用`MCP Inspector`进行调试
169
180
 
@@ -171,7 +182,8 @@ http://localhost:8000/sse
171
182
  npx @modelcontextprotocol/inspector python -m mcp_query_table --format markdown
172
183
  ```
173
184
 
174
- 打开浏览器并翻页是一个比较耗时的操作,会导致`MCP Inspector`页面超时,可以`http://localhost:5173/?timeout=600000` 表示超时时间为600秒
185
+ 打开浏览器并翻页是一个比较耗时的操作,会导致`MCP Inspector`页面超时,可以`http://localhost:5173/?timeout=600000`
186
+ 表示超时时间为600秒
175
187
 
176
188
  第一次尝试编写`MCP`项目,可能会有各种问题,欢迎大家交流。
177
189
 
@@ -31,31 +31,31 @@ from mcp_query_table import *
31
31
 
32
32
 
33
33
  async def main() -> None:
34
- async with BrowserManager(port=9222, browser_path=None, debug=True) as bm:
35
- # 问财需要保证浏览器宽度>768,防止界面变成适应手机
36
- page = await bm.get_page()
37
- df = await query(page, '收益最好的200只ETF', query_type=QueryType.ETF, max_page=1, site=Site.THS)
38
- print(df.to_markdown())
39
- df = await query(page, '年初至今收益率前50', query_type=QueryType.Fund, max_page=1, site=Site.TDX)
40
- print(df.to_csv())
41
- df = await query(page, '流通市值前10的行业板块', query_type=QueryType.Index, max_page=1, site=Site.TDX)
42
- print(df.to_csv())
43
- # TODO 东财翻页要提前登录
44
- df = await query(page, '今日涨幅前5的概念板块;', query_type=QueryType.Board, max_page=3, site=Site.EastMoney)
45
- print(df)
46
-
47
- output = await chat(page, "1+2等于多少?", provider=Provider.YuanBao)
48
- print(output)
49
- output = await chat(page, "3+4等于多少?", provider=Provider.YuanBao, create=True)
50
- print(output)
51
-
52
- print('done')
53
- bm.release_page(page)
54
- await page.wait_for_timeout(2000)
34
+ async with BrowserManager(cdp_endpoint="http://127.0.0.1:9222", executable_path=None, debug=True) as bm:
35
+ # 问财需要保证浏览器宽度>768,防止界面变成适应手机
36
+ page = await bm.get_page()
37
+ df = await query(page, '收益最好的200只ETF', query_type=QueryType.ETF, max_page=1, site=Site.THS)
38
+ print(df.to_markdown())
39
+ df = await query(page, '年初至今收益率前50', query_type=QueryType.Fund, max_page=1, site=Site.TDX)
40
+ print(df.to_csv())
41
+ df = await query(page, '流通市值前10的行业板块', query_type=QueryType.Index, max_page=1, site=Site.TDX)
42
+ print(df.to_csv())
43
+ # TODO 东财翻页要提前登录
44
+ df = await query(page, '今日涨幅前5的概念板块;', query_type=QueryType.Board, max_page=3, site=Site.EastMoney)
45
+ print(df)
46
+
47
+ output = await chat(page, "1+2等于多少?", provider=Provider.YuanBao)
48
+ print(output)
49
+ output = await chat(page, "3+4等于多少?", provider=Provider.YuanBao, create=True)
50
+ print(output)
51
+
52
+ print('done')
53
+ bm.release_page(page)
54
+ await page.wait_for_timeout(2000)
55
55
 
56
56
 
57
57
  if __name__ == '__main__':
58
- asyncio.run(main())
58
+ asyncio.run(main())
59
59
 
60
60
  ```
61
61
 
@@ -93,7 +93,7 @@ if __name__ == '__main__':
93
93
 
94
94
  确保可以在控制台中执行`python -m mcp_query_table -h`。如果不能,可能要先`pip install mcp_query_table`
95
95
 
96
- 在`Cline`中可以配置如下。其中`command`是`python`的绝对路径,`browser_path`是`Chrome`的绝对路径。
96
+ 在`Cline`中可以配置如下。其中`command`是`python`的绝对路径,`executable_path`是`Chrome`的绝对路径。
97
97
 
98
98
  ### STDIO方式
99
99
 
@@ -107,7 +107,9 @@ if __name__ == '__main__':
107
107
  "mcp_query_table",
108
108
  "--format",
109
109
  "markdown",
110
- "--browser_path",
110
+ "--cdp_endpoint",
111
+ "http://127.0.0.1:9222",
112
+ "--executable_path",
111
113
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
112
114
  ]
113
115
  }
@@ -120,11 +122,20 @@ if __name__ == '__main__':
120
122
  先在控制台中执行如下命令,启动`MCP`服务
121
123
 
122
124
  ```commandline
123
- python -m mcp_query_table --format markdown --browser_path "C:\Program Files\Google\Chrome\Application\chrome.exe" --transport sse --mcp_port 8000
125
+ python -m mcp_query_table --format markdown --transport sse --port 8000
124
126
  ```
125
127
 
126
128
  然后就可以连接到`MCP`服务了
127
- http://localhost:8000/sse
129
+
130
+ ```json
131
+ {
132
+ "mcpServers": {
133
+ "mcp_query_table": {
134
+ "url": "http://127.0.0.1:8000/sse"
135
+ }
136
+ }
137
+ }
138
+ ```
128
139
 
129
140
  ## 使用`MCP Inspector`进行调试
130
141
 
@@ -132,7 +143,8 @@ http://localhost:8000/sse
132
143
  npx @modelcontextprotocol/inspector python -m mcp_query_table --format markdown
133
144
  ```
134
145
 
135
- 打开浏览器并翻页是一个比较耗时的操作,会导致`MCP Inspector`页面超时,可以`http://localhost:5173/?timeout=600000` 表示超时时间为600秒
146
+ 打开浏览器并翻页是一个比较耗时的操作,会导致`MCP Inspector`页面超时,可以`http://localhost:5173/?timeout=600000`
147
+ 表示超时时间为600秒
136
148
 
137
149
  第一次尝试编写`MCP`项目,可能会有各种问题,欢迎大家交流。
138
150
 
@@ -2,3 +2,5 @@ from ._version import __version__
2
2
 
3
3
  from .enums import QueryType, Site, Provider
4
4
  from .tool import BrowserManager, query, chat
5
+
6
+ TIMEOUT = 1000 * 60 * 2 # 2分钟,在抓取EventStream数据时等待数据返回,防止外层30秒超时
@@ -10,19 +10,20 @@ def main():
10
10
 
11
11
  parser.add_argument("--format", type=str, help="输出格式",
12
12
  default='markdown', choices=['markdown', 'csv', 'json'])
13
- parser.add_argument("--cdp_port", type=int, help="浏览器远程调试端口",
14
- default=9222)
15
- parser.add_argument("--browser_path", type=str, help="浏览器类型",
13
+ parser.add_argument("--cdp_endpoint", type=str, help="浏览器CDP调试地址",
14
+ default="http://127.0.0.1:9222")
15
+ parser.add_argument("--executable_path", type=str, help="浏览器类型",
16
16
  default=r'C:\Program Files\Google\Chrome\Application\chrome.exe')
17
17
 
18
18
  parser.add_argument("--transport", type=str, help="传输类型",
19
19
  default='stdio', choices=['stdio', 'sse'])
20
- parser.add_argument("--mcp_host", type=str, help="MCP服务端地址",
20
+ parser.add_argument("--host", type=str, help="MCP服务端绑定地址",
21
21
  default='0.0.0.0')
22
- parser.add_argument("--mcp_port", type=int, help="MCP服务端端口",
22
+ parser.add_argument("--port", type=int, help="MCP服务端绑定端口",
23
23
  default='8000')
24
24
  args = parser.parse_args()
25
- serve(args.format, args.cdp_port, args.browser_path, args.transport, args.mcp_host, args.mcp_port)
25
+ serve(args.format, args.cdp_endpoint, args.executable_path,
26
+ args.transport, args.host, args.port)
26
27
 
27
28
 
28
29
  if __name__ == "__main__":
@@ -0,0 +1 @@
1
+ __version__ = "0.3.2"
@@ -23,6 +23,6 @@ class Site(Enum):
23
23
 
24
24
  class Provider(Enum):
25
25
  """提供商"""
26
- N = '纳米搜索' # 360 纳米搜索
26
+ Nami = '纳米搜索' # 360 纳米搜索
27
27
  YuanBao = '腾讯元宝' # 腾讯元宝
28
28
  BaiDu = '百度AI搜索' # 百度AI搜索
@@ -7,11 +7,11 @@ import json
7
7
 
8
8
  from playwright.async_api import Page
9
9
 
10
+ import mcp_query_table
10
11
  from mcp_query_table.tool import GlobalVars
11
12
 
12
13
  _PAGE0_ = "https://chat.baidu.com/search"
13
14
  _PAGE1_ = "https://chat.baidu.com/aichat/api/conversation"
14
- _TIMEOUT_ = 1000 * 120
15
15
 
16
16
  G = GlobalVars()
17
17
 
@@ -55,9 +55,9 @@ async def on_response(response):
55
55
  async def on_route(route):
56
56
  # 避免出现 Protocol error (Network.getResponseBody): No data found for resource with given identifier
57
57
  # print("on_route", route.request.url)
58
- if route.request.url.startswith(_PAGE1_):
58
+ if route.request.url == _PAGE1_:
59
59
  # TODO 为何只要转发一下就没事了?
60
- response = await route.fetch(timeout=_TIMEOUT_)
60
+ response = await route.fetch(timeout=mcp_query_table.TIMEOUT)
61
61
  await route.fulfill(response=response)
62
62
  else:
63
63
  await route.continue_()
@@ -74,7 +74,7 @@ async def chat(page: Page,
74
74
  await page.goto(_PAGE0_)
75
75
 
76
76
  await page.route(_PAGE1_, on_route)
77
- async with page.expect_response(_PAGE1_, timeout=_TIMEOUT_) as response_info:
77
+ async with page.expect_response(_PAGE1_, timeout=mcp_query_table.TIMEOUT) as response_info:
78
78
  await page.locator("#chat-input-box").fill(prompt)
79
79
  await page.locator("#chat-input-box").press("Enter")
80
80
  await on_response(await response_info.value)
@@ -5,12 +5,12 @@ import json
5
5
 
6
6
  from playwright.async_api import Page
7
7
 
8
+ import mcp_query_table
8
9
  from mcp_query_table.tool import GlobalVars
9
10
 
10
11
  _PAGE0_ = "https://www.n.cn"
11
12
  _PAGE1_ = "https://www.n.cn/search"
12
13
  _PAGE2_ = "https://www.n.cn/api/common/chat/v2"
13
- _TIMEOUT_ = 1000 * 120
14
14
 
15
15
  G = GlobalVars()
16
16
 
@@ -68,7 +68,7 @@ async def chat(page: Page,
68
68
  else:
69
69
  name = "提出后续问题,Enter发送,Shift+Enter 换行"
70
70
 
71
- async with page.expect_response(_PAGE2_, timeout=_TIMEOUT_) as response_info:
71
+ async with page.expect_response(_PAGE2_, timeout=mcp_query_table.TIMEOUT) as response_info:
72
72
  textbox = page.get_by_role("textbox", name=name)
73
73
  await textbox.fill(prompt)
74
74
  await textbox.press("Enter")
@@ -5,11 +5,11 @@ import json
5
5
 
6
6
  from playwright.async_api import Page
7
7
 
8
+ import mcp_query_table
8
9
  from mcp_query_table.tool import GlobalVars
9
10
 
10
11
  _PAGE0_ = "https://yuanbao.tencent.com/"
11
12
  _PAGE1_ = "https://yuanbao.tencent.com/api/chat"
12
- _TIMEOUT_ = 1000 * 120
13
13
 
14
14
  G = GlobalVars()
15
15
 
@@ -52,7 +52,7 @@ async def on_route(route):
52
52
  # print("on_route", route.request.url)
53
53
  if route.request.url.startswith(_PAGE1_):
54
54
  # TODO 这里会导致数据全部加载,逻辑变了,所以界面可能混乱
55
- response = await route.fetch(timeout=_TIMEOUT_)
55
+ response = await route.fetch(timeout=mcp_query_table.TIMEOUT)
56
56
  await route.fulfill(
57
57
  # 强行加utf-8,否则编码搞不定
58
58
  content_type="text/event-stream; charset=utf-8",
@@ -73,7 +73,7 @@ async def chat(page: Page,
73
73
  await page.goto(_PAGE0_)
74
74
 
75
75
  await page.route(f"{_PAGE1_}/*", on_route)
76
- async with page.expect_response(f"{_PAGE1_}/*", timeout=_TIMEOUT_) as response_info:
76
+ async with page.expect_response(f"{_PAGE1_}/*", timeout=mcp_query_table.TIMEOUT) as response_info:
77
77
  textbox = page.locator(".ql-editor")
78
78
  await textbox.fill(prompt)
79
79
  await textbox.press("Enter")
@@ -10,9 +10,12 @@ from mcp_query_table.tool import BrowserManager
10
10
 
11
11
 
12
12
  class QueryServer:
13
- def __init__(self, format: str = 'markdown', port: int = 9222, browser_path: Optional[str] = None) -> None:
13
+ def __init__(self,
14
+ format: str = 'markdown',
15
+ cdp_endpoint: Optional[str] = 'http://127.0.0.1:9222',
16
+ executable_path: Optional[str] = None) -> None:
14
17
  self.format: str = format
15
- self.browser = BrowserManager(port=port, browser_path=browser_path, debug=False)
18
+ self.browser = BrowserManager(cdp_endpoint=cdp_endpoint, executable_path=executable_path, debug=False)
16
19
 
17
20
  async def query(self, query_input: str, query_type: QueryType, max_page: int, site: Site):
18
21
  page = await self.browser.get_page()
@@ -54,19 +57,21 @@ async def query(
54
57
  async def chat(
55
58
  prompt: Annotated[str, Field(description="提示词。如:`9.9大还是9.11大?`")],
56
59
  create: Annotated[bool, Field(default=False, description="是否创建新对话")],
57
- provider: Annotated[Provider, Field(default=Provider.N, description="提供商。支持`纳米搜索`、`腾讯元宝`")]
60
+ provider: Annotated[
61
+ Provider, Field(default=Provider.Nami, description="提供商。支持`纳米搜索`、`腾讯元宝`、`百度AI搜索`")]
58
62
  ) -> str:
59
63
  return await qsv.chat(prompt, create, provider)
60
64
 
61
65
 
62
- def serve(format, cdp_port, browser_path, transport, mcp_host, mcp_port):
66
+ def serve(format, cdp_endpoint, executable_path, transport, host, port):
63
67
  qsv.format = format
64
- qsv.port = cdp_port
65
- qsv.browser_path = browser_path
66
- logger.info("serve:{},{},{},{}", qsv.format, qsv.port, qsv.browser_path, transport)
68
+ qsv.cdp_endpoint = cdp_endpoint
69
+ qsv.executable_path = executable_path
70
+ logger.info(f"{format=},{transport=}")
71
+ logger.info(f"{cdp_endpoint=},{executable_path=}")
67
72
  if transport == 'sse':
68
- logger.info("mcp:{},{}:{}", transport, mcp_host, mcp_port)
73
+ logger.info(f"{host=},{port=}", transport, host, port)
69
74
 
70
- mcp.settings.host = mcp_host
71
- mcp.settings.port = mcp_port
75
+ mcp.settings.host = host
76
+ mcp.settings.port = port
72
77
  mcp.run(transport=transport)
@@ -10,11 +10,6 @@ from playwright.async_api import async_playwright, Playwright, Page
10
10
 
11
11
  from mcp_query_table.enums import QueryType, Site, Provider
12
12
 
13
- browsers_path = {
14
- "chrome.exe": r"C:\Program Files\Google\Chrome\Application\chrome.exe",
15
- "msedge.exe": r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
16
- }
17
-
18
13
 
19
14
  def create_detached_process(command):
20
15
  # 设置通用参数
@@ -33,6 +28,29 @@ def create_detached_process(command):
33
28
  return subprocess.Popen(command, **kwargs)
34
29
 
35
30
 
31
+ def is_local_url(url: str) -> bool:
32
+ """判断url是否是本地地址"""
33
+ for local in ('localhost', '127.0.0.1'):
34
+ if local in url.lower():
35
+ return True
36
+ return False
37
+
38
+
39
+ def get_executable_path(executable_path) -> Optional[str]:
40
+ """获取浏览器可执行文件路径"""
41
+ browsers = {
42
+ "default": executable_path,
43
+ "chrome.exe": r"C:\Program Files\Google\Chrome\Application\chrome.exe",
44
+ "msedge.exe": r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
45
+ }
46
+ for k, v in browsers.items():
47
+ if v is None:
48
+ continue
49
+ if Path(v).exists():
50
+ return v
51
+ return None
52
+
53
+
36
54
  class BrowserManager:
37
55
  async def __aenter__(self):
38
56
  return self
@@ -40,29 +58,24 @@ class BrowserManager:
40
58
  async def __aexit__(self, exc_type, exc_val, exc_tb):
41
59
  await self.cleanup()
42
60
 
43
- def __init__(self, port: int = 9222, browser_path: Optional[str] = None, debug: bool = False):
61
+ def __init__(self,
62
+ cdp_endpoint: Optional[str] = None,
63
+ executable_path: Optional[str] = None,
64
+ debug: bool = False):
44
65
  """
45
66
 
46
67
  Parameters
47
68
  ----------
48
- port:int
49
- 浏览器调试端口
50
- browser_path
51
- 浏览器可执行路径。推荐使用chrome,因为Microsoft Edge必须在任务管理器中完全退出才能启动调试端口
69
+ cdp_endpoint:str
70
+ 浏览器CDP地址
71
+ executable_path:str
72
+ 浏览器可执行文件路径。推荐使用chrome,因为Microsoft Edge必须在任务管理器中完全退出才能启动调试端口
52
73
  debug:bool
53
74
  是否显示开发者工具
54
75
 
55
76
  """
56
- if browser_path is None:
57
- for k, v in browsers_path.items():
58
- if Path(v).exists():
59
- browser_path = v
60
- break
61
- if browser_path is None:
62
- raise ValueError("未找到浏览器可执行文件")
63
-
64
- self.port = port
65
- self.browser_path = browser_path
77
+ self.cdp_endpoint = cdp_endpoint or 'http://127.0.0.1:9222'
78
+ self.executable_path = executable_path
66
79
  self.debug = debug
67
80
 
68
81
  self.playwright: Optional[Playwright] = None
@@ -77,25 +90,18 @@ class BrowserManager:
77
90
  if self.playwright:
78
91
  await self.playwright.stop()
79
92
 
80
- async def _launch(self) -> None:
81
- """启动浏览器,并连接CDP协议
82
-
83
- References
84
- ----------
85
- https://blog.csdn.net/qq_30576521/article/details/142370538
86
-
87
- """
88
- endpoint = f"http://127.0.0.1:{self.port}"
89
- command = [self.browser_path, f'--remote-debugging-port={self.port}', '--start-maximized']
90
- name = Path(self.browser_path).name
93
+ async def _connect_to_local(self) -> None:
94
+ """连接本地浏览器"""
95
+ port = self.cdp_endpoint.split(':')[-1]
96
+ executable_path = get_executable_path(self.executable_path)
97
+ command = [executable_path, f'--remote-debugging-port={port}', '--start-maximized']
91
98
  if self.debug:
92
99
  command.append('--auto-open-devtools-for-tabs')
93
100
 
94
- self.playwright = await async_playwright().start()
95
-
96
101
  for i in range(2):
97
102
  try:
98
- self.browser = await self.playwright.chromium.connect_over_cdp(endpoint, timeout=10000, slow_mo=1000)
103
+ self.browser = await self.playwright.chromium.connect_over_cdp(self.cdp_endpoint,
104
+ timeout=10000, slow_mo=1000)
99
105
  break
100
106
  except:
101
107
  if i == 0:
@@ -105,7 +111,30 @@ class BrowserManager:
105
111
  continue
106
112
  if i == 1:
107
113
  raise ConnectionError(
108
- f"已提前打开了浏览器,但未开启远程调试端口?请关闭浏览器全部进程后重试 `taskkill /f /im {name}`")
114
+ f"已提前打开了浏览器,但未开启远程调试端口?请关闭浏览器全部进程后重试 `taskkill /f /im {Path(executable_path).name}`")
115
+
116
+ async def _connect_to_remote(self) -> None:
117
+ """连接远程浏览器"""
118
+ try:
119
+ self.browser = await self.playwright.chromium.connect_over_cdp(self.cdp_endpoint,
120
+ timeout=10000, slow_mo=1000)
121
+ except:
122
+ raise ConnectionError(f"连接远程浏览器失败,请检查CDP地址和端口是否正确。{self.cdp_endpoint}")
123
+
124
+ async def _launch(self) -> None:
125
+ """启动浏览器,并连接CDP协议
126
+
127
+ References
128
+ ----------
129
+ https://blog.csdn.net/qq_30576521/article/details/142370538
130
+
131
+ """
132
+ self.playwright = await async_playwright().start()
133
+
134
+ if is_local_url(self.cdp_endpoint):
135
+ await self._connect_to_local()
136
+ else:
137
+ await self._connect_to_remote()
109
138
 
110
139
  self.context = self.browser.contexts[0]
111
140
  # 复用打开的page
@@ -201,8 +230,27 @@ async def chat(
201
230
  page: Page,
202
231
  prompt: str = "9.9大还是9.11大?",
203
232
  create: bool = False,
204
- provider: Provider = Provider.N) -> str:
205
- if provider == Provider.N:
233
+ provider: Provider = Provider.Nami) -> str:
234
+ """大语言对话
235
+
236
+ Parameters
237
+ ----------
238
+ page : playwright.sync_api.Page
239
+ 页面
240
+ prompt : str, optional
241
+ 对话内容, by default "9.9大还是9.11大?"
242
+ create : bool, optional
243
+ 是否创建新对话, by default False
244
+ provider : Provider, optional
245
+ 提供商, by default Provider.N
246
+
247
+ Returns
248
+ -------
249
+ str
250
+ 对话结果
251
+
252
+ """
253
+ if provider == Provider.Nami:
206
254
  from mcp_query_table.providers.n import chat
207
255
  return await chat(page, prompt, create)
208
256
  if provider == Provider.YuanBao:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp_query_table
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: query table from website, support MCP
5
5
  Author-email: wukan <wu-kan@163.com>
6
6
  License: MIT License
@@ -70,31 +70,31 @@ from mcp_query_table import *
70
70
 
71
71
 
72
72
  async def main() -> None:
73
- async with BrowserManager(port=9222, browser_path=None, debug=True) as bm:
74
- # 问财需要保证浏览器宽度>768,防止界面变成适应手机
75
- page = await bm.get_page()
76
- df = await query(page, '收益最好的200只ETF', query_type=QueryType.ETF, max_page=1, site=Site.THS)
77
- print(df.to_markdown())
78
- df = await query(page, '年初至今收益率前50', query_type=QueryType.Fund, max_page=1, site=Site.TDX)
79
- print(df.to_csv())
80
- df = await query(page, '流通市值前10的行业板块', query_type=QueryType.Index, max_page=1, site=Site.TDX)
81
- print(df.to_csv())
82
- # TODO 东财翻页要提前登录
83
- df = await query(page, '今日涨幅前5的概念板块;', query_type=QueryType.Board, max_page=3, site=Site.EastMoney)
84
- print(df)
85
-
86
- output = await chat(page, "1+2等于多少?", provider=Provider.YuanBao)
87
- print(output)
88
- output = await chat(page, "3+4等于多少?", provider=Provider.YuanBao, create=True)
89
- print(output)
90
-
91
- print('done')
92
- bm.release_page(page)
93
- await page.wait_for_timeout(2000)
73
+ async with BrowserManager(cdp_endpoint="http://127.0.0.1:9222", executable_path=None, debug=True) as bm:
74
+ # 问财需要保证浏览器宽度>768,防止界面变成适应手机
75
+ page = await bm.get_page()
76
+ df = await query(page, '收益最好的200只ETF', query_type=QueryType.ETF, max_page=1, site=Site.THS)
77
+ print(df.to_markdown())
78
+ df = await query(page, '年初至今收益率前50', query_type=QueryType.Fund, max_page=1, site=Site.TDX)
79
+ print(df.to_csv())
80
+ df = await query(page, '流通市值前10的行业板块', query_type=QueryType.Index, max_page=1, site=Site.TDX)
81
+ print(df.to_csv())
82
+ # TODO 东财翻页要提前登录
83
+ df = await query(page, '今日涨幅前5的概念板块;', query_type=QueryType.Board, max_page=3, site=Site.EastMoney)
84
+ print(df)
85
+
86
+ output = await chat(page, "1+2等于多少?", provider=Provider.YuanBao)
87
+ print(output)
88
+ output = await chat(page, "3+4等于多少?", provider=Provider.YuanBao, create=True)
89
+ print(output)
90
+
91
+ print('done')
92
+ bm.release_page(page)
93
+ await page.wait_for_timeout(2000)
94
94
 
95
95
 
96
96
  if __name__ == '__main__':
97
- asyncio.run(main())
97
+ asyncio.run(main())
98
98
 
99
99
  ```
100
100
 
@@ -132,7 +132,7 @@ if __name__ == '__main__':
132
132
 
133
133
  确保可以在控制台中执行`python -m mcp_query_table -h`。如果不能,可能要先`pip install mcp_query_table`
134
134
 
135
- 在`Cline`中可以配置如下。其中`command`是`python`的绝对路径,`browser_path`是`Chrome`的绝对路径。
135
+ 在`Cline`中可以配置如下。其中`command`是`python`的绝对路径,`executable_path`是`Chrome`的绝对路径。
136
136
 
137
137
  ### STDIO方式
138
138
 
@@ -146,7 +146,9 @@ if __name__ == '__main__':
146
146
  "mcp_query_table",
147
147
  "--format",
148
148
  "markdown",
149
- "--browser_path",
149
+ "--cdp_endpoint",
150
+ "http://127.0.0.1:9222",
151
+ "--executable_path",
150
152
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
151
153
  ]
152
154
  }
@@ -159,11 +161,20 @@ if __name__ == '__main__':
159
161
  先在控制台中执行如下命令,启动`MCP`服务
160
162
 
161
163
  ```commandline
162
- python -m mcp_query_table --format markdown --browser_path "C:\Program Files\Google\Chrome\Application\chrome.exe" --transport sse --mcp_port 8000
164
+ python -m mcp_query_table --format markdown --transport sse --port 8000
163
165
  ```
164
166
 
165
167
  然后就可以连接到`MCP`服务了
166
- http://localhost:8000/sse
168
+
169
+ ```json
170
+ {
171
+ "mcpServers": {
172
+ "mcp_query_table": {
173
+ "url": "http://127.0.0.1:8000/sse"
174
+ }
175
+ }
176
+ }
177
+ ```
167
178
 
168
179
  ## 使用`MCP Inspector`进行调试
169
180
 
@@ -171,7 +182,8 @@ http://localhost:8000/sse
171
182
  npx @modelcontextprotocol/inspector python -m mcp_query_table --format markdown
172
183
  ```
173
184
 
174
- 打开浏览器并翻页是一个比较耗时的操作,会导致`MCP Inspector`页面超时,可以`http://localhost:5173/?timeout=600000` 表示超时时间为600秒
185
+ 打开浏览器并翻页是一个比较耗时的操作,会导致`MCP Inspector`页面超时,可以`http://localhost:5173/?timeout=600000`
186
+ 表示超时时间为600秒
175
187
 
176
188
  第一次尝试编写`MCP`项目,可能会有各种问题,欢迎大家交流。
177
189
 
@@ -1 +0,0 @@
1
- __version__ = "0.3.0"
File without changes