scp-py-client 0.0.1__py3-none-any.whl
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.
scp_client/__init__.py
ADDED
scp_client/scp_client.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
from mcp.client.streamable_http import streamable_http_client
|
|
2
|
+
from mcp.client.sse import sse_client
|
|
3
|
+
from mcp import ClientSession
|
|
4
|
+
import httpx
|
|
5
|
+
import uuid
|
|
6
|
+
import json
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class fetch_scp_Client:
|
|
11
|
+
"""使用MCP SDK的非流式客户端"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, server_url: str, transport_type: Literal["streamable-http", "sse"] = "streamable-http", headers_config: dict={}):
|
|
14
|
+
self.server_url = server_url
|
|
15
|
+
self.session = None
|
|
16
|
+
self.transport_type = transport_type
|
|
17
|
+
self.headers_config = headers_config
|
|
18
|
+
|
|
19
|
+
async def connect(self):
|
|
20
|
+
"""建立连接并初始化会话"""
|
|
21
|
+
print(f"\n{'='*80}")
|
|
22
|
+
print("连接到MCP服务器")
|
|
23
|
+
print(f"{'='*80}")
|
|
24
|
+
print(f"服务器地址: {self.server_url}")
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# 建立streamable-http传输连接
|
|
28
|
+
if self.transport_type == "streamable-http":
|
|
29
|
+
self.transport = streamable_http_client(
|
|
30
|
+
url=self.server_url,
|
|
31
|
+
http_client=httpx.AsyncClient(headers = self.headers_config)
|
|
32
|
+
)
|
|
33
|
+
self.read, self.write, self.get_session_id = await self.transport.__aenter__()
|
|
34
|
+
session_id = self.get_session_id()
|
|
35
|
+
|
|
36
|
+
# 建立sse传输连接
|
|
37
|
+
elif self.transport_type == "sse":
|
|
38
|
+
self.transport = sse_client(url=self.server_url, headers=self.headers_config)
|
|
39
|
+
self.read, self.write = await self.transport.__aenter__()
|
|
40
|
+
session_id = str(uuid.uuid4())
|
|
41
|
+
|
|
42
|
+
else:
|
|
43
|
+
raise ValueError(f"不支持的传输类型: {self.transport_type}")
|
|
44
|
+
|
|
45
|
+
# 创建客户端会话
|
|
46
|
+
self.session_ctx = ClientSession(self.read, self.write)
|
|
47
|
+
self.session = await self.session_ctx.__aenter__()
|
|
48
|
+
|
|
49
|
+
# 初始化会话
|
|
50
|
+
await self.session.initialize()
|
|
51
|
+
print(f"✓ 连接成功")
|
|
52
|
+
print(f"✓ 会话ID: {session_id}")
|
|
53
|
+
print(f"{'='*80}\n")
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"✗ 连接失败: {e}")
|
|
58
|
+
import traceback
|
|
59
|
+
traceback.print_exc()
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
async def disconnect(self):
|
|
63
|
+
"""断开连接"""
|
|
64
|
+
try:
|
|
65
|
+
if self.session:
|
|
66
|
+
await self.session_ctx.__aexit__(None, None, None)
|
|
67
|
+
if hasattr(self, 'transport'):
|
|
68
|
+
await self.transport.__aexit__(None, None, None)
|
|
69
|
+
print("\n✓ 已断开连接\n")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
print(f"✗ 断开连接时出错: {e}")
|
|
72
|
+
|
|
73
|
+
async def list_tools(self):
|
|
74
|
+
"""列出所有可用工具"""
|
|
75
|
+
print(f"\n{'='*80}")
|
|
76
|
+
print("列出所有可用工具")
|
|
77
|
+
print(f"{'='*80}")
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
tools_list = await self.session.list_tools()
|
|
81
|
+
print(f"\n可用工具 (共{len(tools_list.tools)}个):\n")
|
|
82
|
+
|
|
83
|
+
for i, tool in enumerate(tools_list.tools, 1):
|
|
84
|
+
print(f"{i:2d}. {tool.name}")
|
|
85
|
+
if tool.description:
|
|
86
|
+
desc_line = tool.description
|
|
87
|
+
print(f" {desc_line}")
|
|
88
|
+
|
|
89
|
+
print(f"\n✓ 工具列表获取成功")
|
|
90
|
+
return tools_list.tools
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"✗ 列出工具失败: {e}")
|
|
94
|
+
return []
|
|
95
|
+
|
|
96
|
+
def parse_result(self, result):
|
|
97
|
+
"""解析MCP工具调用结果"""
|
|
98
|
+
try:
|
|
99
|
+
if hasattr(result, 'content') and result.content:
|
|
100
|
+
content = result.content[0]
|
|
101
|
+
if hasattr(content, 'text'):
|
|
102
|
+
return json.loads(content.text)
|
|
103
|
+
|
|
104
|
+
return str(result)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return {"error": f"解析结果失败: {e}", "raw": str(result)}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class stream_scp_Client:
|
|
110
|
+
"""使用MCP SDK的流式客户端"""
|
|
111
|
+
|
|
112
|
+
def __init__(self, server_url: str, headers_config: dict={}):
|
|
113
|
+
self.server_url = server_url
|
|
114
|
+
self.session = None
|
|
115
|
+
self.headers_config = headers_config
|
|
116
|
+
|
|
117
|
+
async def handle_notification(self, message):
|
|
118
|
+
"""处理通知"""
|
|
119
|
+
streaming_buffer = []
|
|
120
|
+
# 解析通知对象
|
|
121
|
+
if hasattr(message, 'root'):
|
|
122
|
+
actual = message.root
|
|
123
|
+
else:
|
|
124
|
+
actual = message
|
|
125
|
+
|
|
126
|
+
method = getattr(actual, "method", "")
|
|
127
|
+
params = getattr(actual, "params", None)
|
|
128
|
+
|
|
129
|
+
# 只处理 notifications/message
|
|
130
|
+
if method == "notifications/message" and params:
|
|
131
|
+
data = getattr(params, 'data', None) or {}
|
|
132
|
+
|
|
133
|
+
if isinstance(data, dict):
|
|
134
|
+
data_type = data.get("type", "")
|
|
135
|
+
|
|
136
|
+
if data_type == "stream_chunk":
|
|
137
|
+
# 实时显示流式内容
|
|
138
|
+
content = data.get("content", "")
|
|
139
|
+
print(content, end="", flush=True)
|
|
140
|
+
streaming_buffer.append(content)
|
|
141
|
+
|
|
142
|
+
elif data_type == "stream_complete":
|
|
143
|
+
# 流式完成
|
|
144
|
+
print()
|
|
145
|
+
|
|
146
|
+
async def connect(self):
|
|
147
|
+
"""建立连接并初始化会话"""
|
|
148
|
+
print(f"\n{'='*80}")
|
|
149
|
+
print("连接到MCP服务器")
|
|
150
|
+
print(f"{'='*80}")
|
|
151
|
+
print(f"服务器地址: {self.server_url}")
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
# 建立streamable-http传输连接
|
|
155
|
+
self.transport = streamable_http_client(
|
|
156
|
+
url=self.server_url,
|
|
157
|
+
http_client=httpx.AsyncClient(headers = self.headers_config)
|
|
158
|
+
)
|
|
159
|
+
self.read, self.write, self.get_session_id = await self.transport.__aenter__()
|
|
160
|
+
|
|
161
|
+
# 创建客户端会话
|
|
162
|
+
self.session_ctx = ClientSession(self.read, self.write, message_handler=self.handle_notification)
|
|
163
|
+
self.session = await self.session_ctx.__aenter__()
|
|
164
|
+
|
|
165
|
+
# 初始化会话
|
|
166
|
+
await self.session.initialize()
|
|
167
|
+
session_id = self.get_session_id()
|
|
168
|
+
|
|
169
|
+
print(f"✓ 连接成功")
|
|
170
|
+
print(f"✓ 会话ID: {session_id}")
|
|
171
|
+
print(f"{'='*80}\n")
|
|
172
|
+
return True
|
|
173
|
+
|
|
174
|
+
except Exception as e:
|
|
175
|
+
print(f"✗ 连接失败: {e}")
|
|
176
|
+
import traceback
|
|
177
|
+
traceback.print_exc()
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
async def disconnect(self):
|
|
181
|
+
"""断开连接"""
|
|
182
|
+
try:
|
|
183
|
+
if self.session:
|
|
184
|
+
await self.session_ctx.__aexit__(None, None, None)
|
|
185
|
+
if hasattr(self, 'transport'):
|
|
186
|
+
await self.transport.__aexit__(None, None, None)
|
|
187
|
+
print("\n✓ 已断开连接\n")
|
|
188
|
+
except Exception as e:
|
|
189
|
+
print(f"✗ 断开连接时出错: {e}")
|
|
190
|
+
|
|
191
|
+
async def list_tools(self):
|
|
192
|
+
"""列出所有可用工具"""
|
|
193
|
+
print(f"\n{'='*80}")
|
|
194
|
+
print("列出所有可用工具")
|
|
195
|
+
print(f"{'='*80}")
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
tools_list = await self.session.list_tools()
|
|
199
|
+
print(f"\n可用工具 (共{len(tools_list.tools)}个):\n")
|
|
200
|
+
|
|
201
|
+
for i, tool in enumerate(tools_list.tools, 1):
|
|
202
|
+
print(f"{i:2d}. {tool.name}")
|
|
203
|
+
if tool.description:
|
|
204
|
+
desc_line = tool.description
|
|
205
|
+
print(f" {desc_line}")
|
|
206
|
+
|
|
207
|
+
print(f"\n✓ 工具列表获取成功")
|
|
208
|
+
return tools_list.tools
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print(f"✗ 列出工具失败: {e}")
|
|
212
|
+
return []
|
|
213
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: scp-py-client
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: SCP Client - SCP协议客户端库,支持流式和非流式通信
|
|
5
|
+
Author: hanxiaozuo
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/hanxiaozuo/scp-client
|
|
8
|
+
Project-URL: Repository, https://github.com/hanxiaozuo/scp-client
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: mcp>=1.25.0
|
|
12
|
+
Requires-Dist: httpx>=0.28.0
|
|
13
|
+
Requires-Dist: httpx-sse>=0.4.0
|
|
14
|
+
|
|
15
|
+
# scp-client
|
|
16
|
+
|
|
17
|
+
SCP(Science Context Protocol)相关服务的客户端工具库。
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install scp-client
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 使用
|
|
26
|
+
|
|
27
|
+
### 非流式(streamable-http / sse)
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import asyncio
|
|
31
|
+
from scp_client import fetch_scp_Client
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def main():
|
|
35
|
+
client = fetch_scp_Client(
|
|
36
|
+
server_url="https://your-mcp-server.example.com/mcp",
|
|
37
|
+
transport_type="streamable-http", # or "sse"
|
|
38
|
+
headers_config={},
|
|
39
|
+
)
|
|
40
|
+
await client.connect()
|
|
41
|
+
await client.list_tools()
|
|
42
|
+
await client.disconnect()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
asyncio.run(main())
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 流式(streamable-http)
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import asyncio
|
|
53
|
+
from scp_client import stream_scp_Client
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def main():
|
|
57
|
+
client = stream_scp_Client(
|
|
58
|
+
server_url="https://your-mcp-server.example.com/mcp",
|
|
59
|
+
headers_config={},
|
|
60
|
+
)
|
|
61
|
+
await client.connect()
|
|
62
|
+
await client.list_tools()
|
|
63
|
+
await client.disconnect()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
asyncio.run(main())
|
|
68
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
scp_client/__init__.py,sha256=3KKpSuzsvCMLRnYDTUxJA1VV0oNced7HUNHMUcfkgQs,195
|
|
2
|
+
scp_client/scp_client.py,sha256=q3V2hrQ_9SQ8UT2tDPGoeUG_PiG5krH-_20i_nl2Lxk,7638
|
|
3
|
+
scp_py_client-0.0.1.dist-info/METADATA,sha256=S_oUykvf4m7pvbwp5ofgO3qtE6og83_CiEaYTs6bVOU,1445
|
|
4
|
+
scp_py_client-0.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
5
|
+
scp_py_client-0.0.1.dist-info/top_level.txt,sha256=8l-5l1L2JQmdqV8KOdfJZbFaNP4fJxpeZQvTw8yuyEw,11
|
|
6
|
+
scp_py_client-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
scp_client
|