tina-python 0.6.6__tar.gz → 0.6.8__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.
- {tina_python-0.6.6/tina_python.egg-info → tina_python-0.6.8}/PKG-INFO +26 -20
- {tina_python-0.6.6 → tina_python-0.6.8}/README.md +23 -17
- {tina_python-0.6.6 → tina_python-0.6.8}/pyproject.toml +3 -3
- {tina_python-0.6.6 → tina_python-0.6.8}/setup.py +1 -1
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/__init__.py +1 -1
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/agent.py +65 -5
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/mcp/client.py +145 -6
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/tui/context_manager.py +18 -3
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/tui/tui.py +1545 -1411
- {tina_python-0.6.6 → tina_python-0.6.8/tina_python.egg-info}/PKG-INFO +26 -20
- {tina_python-0.6.6 → tina_python-0.6.8}/tina_python.egg-info/requires.txt +2 -2
- {tina_python-0.6.6 → tina_python-0.6.8}/LICENSE +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/MANIFEST.in +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/setup.cfg +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/core/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/core/agent_response.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/core/agent_runtime.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/core/context_manager.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/core/events.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/core/executor.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/core/keyword_actions.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/core/prompt.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/core/state.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/core/tools.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/agent/multimodal_agent.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/core/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/core/error.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/core/logger.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/llm/BaseNewAPI.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/llm/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/llm/base_api.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/llm/base_multimodal_api.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/llm/ollama_api.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/mcp/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/mcp/mcp_tools_executor.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/py.typed +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/agent_worker.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/deepseek.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/doc_parser.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/env_reader.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/multimodal_formatter.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/output_parser.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/run_agent_in_cli.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/system_tools.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/timer.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/tui/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/tui/token.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina/utils/type_mapper.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina_python.egg-info/SOURCES.txt +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina_python.egg-info/dependency_links.txt +0 -0
- {tina_python-0.6.6 → tina_python-0.6.8}/tina_python.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tina-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: tina is in your computer!
|
|
5
5
|
Home-page: https://gitee.com/wang-churi/tina
|
|
6
6
|
Author: 王出日
|
|
@@ -20,14 +20,14 @@ Requires-Dist: python-dotenv
|
|
|
20
20
|
Provides-Extra: tui
|
|
21
21
|
Requires-Dist: textual>=0.80; extra == "tui"
|
|
22
22
|
Provides-Extra: mcp
|
|
23
|
-
Requires-Dist: mcp; extra == "mcp"
|
|
23
|
+
Requires-Dist: mcp>=2.0; extra == "mcp"
|
|
24
24
|
Provides-Extra: test
|
|
25
25
|
Requires-Dist: pytest>=9.0; extra == "test"
|
|
26
26
|
Requires-Dist: pytest-asyncio>=1.4; extra == "test"
|
|
27
27
|
Requires-Dist: pytest-cov>=7.0; extra == "test"
|
|
28
28
|
Provides-Extra: all
|
|
29
29
|
Requires-Dist: textual>=0.80; extra == "all"
|
|
30
|
-
Requires-Dist: mcp; extra == "all"
|
|
30
|
+
Requires-Dist: mcp>=2.0; extra == "all"
|
|
31
31
|
Dynamic: author
|
|
32
32
|
Dynamic: home-page
|
|
33
33
|
Dynamic: license-file
|
|
@@ -716,23 +716,31 @@ mcp = MCPClient()
|
|
|
716
716
|
# 添加 stdio 类型 MCP 服务
|
|
717
717
|
mcp.add_server(
|
|
718
718
|
server_id="playwright",
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
"args": ["@playwright/mcp@latest"]
|
|
723
|
-
}
|
|
719
|
+
type="stdio",
|
|
720
|
+
command="npx",
|
|
721
|
+
args=["@playwright/mcp@latest"],
|
|
724
722
|
)
|
|
725
723
|
|
|
726
|
-
# 添加
|
|
724
|
+
# 添加 Streamable HTTP 类型 MCP 服务(推荐,取代旧 SSE)
|
|
727
725
|
mcp.add_server(
|
|
728
726
|
server_id="my-service",
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
727
|
+
type="http", # 也支持 streamable_http / streamableHttp
|
|
728
|
+
url="http://localhost:8080/mcp",
|
|
729
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}, # 可选
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
# 添加 SSE 类型 MCP 服务(旧传输,仍兼容)
|
|
733
|
+
mcp.add_server(
|
|
734
|
+
server_id="my-legacy-service",
|
|
735
|
+
type="sse",
|
|
736
|
+
url="http://localhost:8080/sse",
|
|
737
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}, # 可选
|
|
733
738
|
)
|
|
734
739
|
```
|
|
735
740
|
|
|
741
|
+
> `type` 为必填项,用于区分传输类型;`command`/`args`/`env` 与 `url`/`headers`
|
|
742
|
+
> 分别对应 stdio 与远程类型,编辑器会根据 `type` 给出对应的参数提示。
|
|
743
|
+
|
|
736
744
|
### 4.3 在 Agent 中使用 MCP
|
|
737
745
|
|
|
738
746
|
```python
|
|
@@ -743,11 +751,9 @@ from tina.mcp import MCPClient
|
|
|
743
751
|
mcp = MCPClient()
|
|
744
752
|
mcp.add_server(
|
|
745
753
|
server_id="playwright",
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
"args": ["@playwright/mcp@latest"]
|
|
750
|
-
}
|
|
754
|
+
type="stdio",
|
|
755
|
+
command="npx",
|
|
756
|
+
args=["@playwright/mcp@latest"],
|
|
751
757
|
)
|
|
752
758
|
|
|
753
759
|
llm = BaseAPI()
|
|
@@ -768,8 +774,8 @@ for chunk in result:
|
|
|
768
774
|
### 4.4 MCP 服务管理
|
|
769
775
|
|
|
770
776
|
```python
|
|
771
|
-
#
|
|
772
|
-
agent.add_mcp_server("server_id",
|
|
777
|
+
# 添加服务(参数与 MCPClient.add_server 一致)
|
|
778
|
+
agent.add_mcp_server("server_id", type="http", url="http://localhost:8080/mcp")
|
|
773
779
|
|
|
774
780
|
# 移除服务
|
|
775
781
|
agent.remove_mcp_server("server_id")
|
|
@@ -681,23 +681,31 @@ mcp = MCPClient()
|
|
|
681
681
|
# 添加 stdio 类型 MCP 服务
|
|
682
682
|
mcp.add_server(
|
|
683
683
|
server_id="playwright",
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
"args": ["@playwright/mcp@latest"]
|
|
688
|
-
}
|
|
684
|
+
type="stdio",
|
|
685
|
+
command="npx",
|
|
686
|
+
args=["@playwright/mcp@latest"],
|
|
689
687
|
)
|
|
690
688
|
|
|
691
|
-
# 添加
|
|
689
|
+
# 添加 Streamable HTTP 类型 MCP 服务(推荐,取代旧 SSE)
|
|
692
690
|
mcp.add_server(
|
|
693
691
|
server_id="my-service",
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
692
|
+
type="http", # 也支持 streamable_http / streamableHttp
|
|
693
|
+
url="http://localhost:8080/mcp",
|
|
694
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}, # 可选
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
# 添加 SSE 类型 MCP 服务(旧传输,仍兼容)
|
|
698
|
+
mcp.add_server(
|
|
699
|
+
server_id="my-legacy-service",
|
|
700
|
+
type="sse",
|
|
701
|
+
url="http://localhost:8080/sse",
|
|
702
|
+
headers={"Authorization": "Bearer YOUR_API_KEY"}, # 可选
|
|
698
703
|
)
|
|
699
704
|
```
|
|
700
705
|
|
|
706
|
+
> `type` 为必填项,用于区分传输类型;`command`/`args`/`env` 与 `url`/`headers`
|
|
707
|
+
> 分别对应 stdio 与远程类型,编辑器会根据 `type` 给出对应的参数提示。
|
|
708
|
+
|
|
701
709
|
### 4.3 在 Agent 中使用 MCP
|
|
702
710
|
|
|
703
711
|
```python
|
|
@@ -708,11 +716,9 @@ from tina.mcp import MCPClient
|
|
|
708
716
|
mcp = MCPClient()
|
|
709
717
|
mcp.add_server(
|
|
710
718
|
server_id="playwright",
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
"args": ["@playwright/mcp@latest"]
|
|
715
|
-
}
|
|
719
|
+
type="stdio",
|
|
720
|
+
command="npx",
|
|
721
|
+
args=["@playwright/mcp@latest"],
|
|
716
722
|
)
|
|
717
723
|
|
|
718
724
|
llm = BaseAPI()
|
|
@@ -733,8 +739,8 @@ for chunk in result:
|
|
|
733
739
|
### 4.4 MCP 服务管理
|
|
734
740
|
|
|
735
741
|
```python
|
|
736
|
-
#
|
|
737
|
-
agent.add_mcp_server("server_id",
|
|
742
|
+
# 添加服务(参数与 MCPClient.add_server 一致)
|
|
743
|
+
agent.add_mcp_server("server_id", type="http", url="http://localhost:8080/mcp")
|
|
738
744
|
|
|
739
745
|
# 移除服务
|
|
740
746
|
agent.remove_mcp_server("server_id")
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tina-python"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.8"
|
|
8
8
|
description = "tina is in your computer!"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -32,7 +32,7 @@ tui = [
|
|
|
32
32
|
"textual>=0.80",
|
|
33
33
|
]
|
|
34
34
|
mcp = [
|
|
35
|
-
"mcp",
|
|
35
|
+
"mcp>=2.0",
|
|
36
36
|
]
|
|
37
37
|
test = [
|
|
38
38
|
"pytest>=9.0",
|
|
@@ -41,7 +41,7 @@ test = [
|
|
|
41
41
|
]
|
|
42
42
|
all = [
|
|
43
43
|
"textual>=0.80",
|
|
44
|
-
"mcp",
|
|
44
|
+
"mcp>=2.0",
|
|
45
45
|
]
|
|
46
46
|
|
|
47
47
|
[tool.pytest.ini_options]
|
|
@@ -10,7 +10,17 @@ Agent类:基础智能体类,默认支持API调用
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import re
|
|
13
|
-
from typing import
|
|
13
|
+
from typing import (
|
|
14
|
+
List,
|
|
15
|
+
Literal,
|
|
16
|
+
Union,
|
|
17
|
+
Generator,
|
|
18
|
+
Dict,
|
|
19
|
+
Any,
|
|
20
|
+
Optional,
|
|
21
|
+
AsyncGenerator,
|
|
22
|
+
overload,
|
|
23
|
+
)
|
|
14
24
|
|
|
15
25
|
from tina.agent.core.agent_response import AgentResponse, ToolCall
|
|
16
26
|
from tina.agent.core.state import AgentState
|
|
@@ -18,7 +28,7 @@ from tina.agent.core.state import AgentState
|
|
|
18
28
|
from ..llm.base_api import BaseAPI
|
|
19
29
|
from .core.tools import Tools
|
|
20
30
|
from .core.keyword_actions import KeywordActions
|
|
21
|
-
from ..mcp.client import MCPClient
|
|
31
|
+
from ..mcp.client import MCPClient, RemoteType, StdioType
|
|
22
32
|
from .core.prompt import Prompt
|
|
23
33
|
from .core.context_manager import ContextManager
|
|
24
34
|
from .core.agent_runtime import ToolCallingAgentRuntime, BaseAgentRuntime
|
|
@@ -466,19 +476,69 @@ class Agent:
|
|
|
466
476
|
def get_tools_call(self) -> list:
|
|
467
477
|
return self.context_manager.get_tool_calls()
|
|
468
478
|
|
|
479
|
+
@overload
|
|
480
|
+
def add_mcp_server(
|
|
481
|
+
self,
|
|
482
|
+
server_id: str,
|
|
483
|
+
*,
|
|
484
|
+
type: StdioType,
|
|
485
|
+
command: str,
|
|
486
|
+
args: Optional[List[str]] = None,
|
|
487
|
+
env: Optional[Dict[str, str]] = None,
|
|
488
|
+
max_retries: int = 3,
|
|
489
|
+
timeout: int = 90,
|
|
490
|
+
) -> bool: ...
|
|
491
|
+
|
|
492
|
+
@overload
|
|
469
493
|
def add_mcp_server(
|
|
470
|
-
self,
|
|
494
|
+
self,
|
|
495
|
+
server_id: str,
|
|
496
|
+
*,
|
|
497
|
+
type: RemoteType,
|
|
498
|
+
url: str,
|
|
499
|
+
headers: Optional[Dict[str, str]] = None,
|
|
500
|
+
max_retries: int = 3,
|
|
501
|
+
timeout: int = 90,
|
|
502
|
+
) -> bool: ...
|
|
503
|
+
|
|
504
|
+
def add_mcp_server(
|
|
505
|
+
self,
|
|
506
|
+
server_id: str,
|
|
507
|
+
*,
|
|
508
|
+
type: str,
|
|
509
|
+
command: Optional[str] = None,
|
|
510
|
+
args: Optional[List[str]] = None,
|
|
511
|
+
env: Optional[Dict[str, str]] = None,
|
|
512
|
+
url: Optional[str] = None,
|
|
513
|
+
headers: Optional[Dict[str, str]] = None,
|
|
514
|
+
max_retries: int = 3,
|
|
515
|
+
timeout: int = 90,
|
|
471
516
|
) -> bool:
|
|
472
517
|
"""
|
|
473
518
|
添加MCP服务器
|
|
474
519
|
Args:
|
|
475
520
|
server_id:服务器ID
|
|
476
|
-
|
|
521
|
+
type:传输类型(`stdio` / `sse` / `http`)
|
|
522
|
+
command:`stdio` 类型的启动命令
|
|
523
|
+
args:`stdio` 类型的命令参数
|
|
524
|
+
env:`stdio` 类型的环境变量
|
|
525
|
+
url:`sse` / `http` 类型的服务地址
|
|
526
|
+
headers:`sse` / `http` 类型的请求头
|
|
477
527
|
max_retries:最大重试次数
|
|
478
528
|
timeout:超时时间
|
|
479
529
|
"""
|
|
480
530
|
if self.mcp_client is not None:
|
|
481
|
-
return self.mcp_client.add_server(
|
|
531
|
+
return self.mcp_client.add_server(
|
|
532
|
+
server_id,
|
|
533
|
+
type=type,
|
|
534
|
+
command=command,
|
|
535
|
+
args=args,
|
|
536
|
+
env=env,
|
|
537
|
+
url=url,
|
|
538
|
+
headers=headers,
|
|
539
|
+
max_retries=max_retries,
|
|
540
|
+
timeout=timeout,
|
|
541
|
+
)
|
|
482
542
|
else:
|
|
483
543
|
raise ValueError("MCP客户端未初始化,请先初始化MCP客户端")
|
|
484
544
|
|
|
@@ -3,7 +3,16 @@ import concurrent.futures
|
|
|
3
3
|
import json
|
|
4
4
|
import uuid
|
|
5
5
|
import threading
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import (
|
|
7
|
+
Dict,
|
|
8
|
+
List,
|
|
9
|
+
Any,
|
|
10
|
+
Optional,
|
|
11
|
+
Union,
|
|
12
|
+
Tuple,
|
|
13
|
+
Literal,
|
|
14
|
+
overload,
|
|
15
|
+
)
|
|
7
16
|
from contextlib import AsyncExitStack
|
|
8
17
|
from datetime import datetime
|
|
9
18
|
from ..core import logger # 假设你有统一的logger
|
|
@@ -12,12 +21,78 @@ try:
|
|
|
12
21
|
from mcp import ClientSession, StdioServerParameters
|
|
13
22
|
from mcp.client.stdio import stdio_client
|
|
14
23
|
from mcp.client.sse import sse_client
|
|
24
|
+
from mcp.client.streamable_http import (
|
|
25
|
+
streamable_http_client,
|
|
26
|
+
create_mcp_http_client,
|
|
27
|
+
)
|
|
15
28
|
|
|
16
29
|
MCP_AVAILABLE = True
|
|
17
30
|
except ImportError:
|
|
18
31
|
MCP_AVAILABLE = False
|
|
19
32
|
|
|
20
33
|
|
|
34
|
+
# 远程传输类型别名:Streamable HTTP 在不同客户端里叫法不一,这里统一归一化。
|
|
35
|
+
# 官方新规范为 Streamable HTTP,`sse` 为旧版(已废弃)传输。
|
|
36
|
+
_REMOTE_TYPE_ALIASES = {
|
|
37
|
+
"sse": "sse",
|
|
38
|
+
"http": "http",
|
|
39
|
+
"https": "http",
|
|
40
|
+
"streamable_http": "http",
|
|
41
|
+
"streamablehttp": "http",
|
|
42
|
+
"streamable-http": "http",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _normalize_server_type(server_type: str) -> str:
|
|
47
|
+
return _REMOTE_TYPE_ALIASES.get(str(server_type).lower(), str(server_type).lower())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
StdioType = Literal["stdio"]
|
|
51
|
+
RemoteType = Literal[
|
|
52
|
+
"sse",
|
|
53
|
+
"http",
|
|
54
|
+
"https",
|
|
55
|
+
"streamable_http",
|
|
56
|
+
"streamableHttp",
|
|
57
|
+
"streamable-http",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _build_server_config(
|
|
62
|
+
server_type: str,
|
|
63
|
+
*,
|
|
64
|
+
command: Optional[str] = None,
|
|
65
|
+
args: Optional[List[str]] = None,
|
|
66
|
+
env: Optional[Dict[str, str]] = None,
|
|
67
|
+
url: Optional[str] = None,
|
|
68
|
+
headers: Optional[Dict[str, str]] = None,
|
|
69
|
+
) -> Dict[str, Any]:
|
|
70
|
+
"""把扁平化参数组装为内部 config 字典,并做必要校验。"""
|
|
71
|
+
stype = _normalize_server_type(server_type)
|
|
72
|
+
if stype == "stdio":
|
|
73
|
+
if not command:
|
|
74
|
+
raise ValueError("stdio 类型必须提供 command 参数")
|
|
75
|
+
config: Dict[str, Any] = {
|
|
76
|
+
"type": "stdio",
|
|
77
|
+
"command": command,
|
|
78
|
+
"args": list(args) if args else [],
|
|
79
|
+
}
|
|
80
|
+
if env:
|
|
81
|
+
config["env"] = dict(env)
|
|
82
|
+
return config
|
|
83
|
+
if stype in ("sse", "http"):
|
|
84
|
+
if not url:
|
|
85
|
+
raise ValueError(f"{server_type} 类型必须提供 url 参数")
|
|
86
|
+
config = {"type": stype, "url": url}
|
|
87
|
+
if headers:
|
|
88
|
+
config["headers"] = dict(headers)
|
|
89
|
+
return config
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Unsupported server type: {server_type} "
|
|
92
|
+
f"(supported: stdio, sse, http/streamable_http)"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
21
96
|
def _tool_input_schema(tool: Any) -> Dict[str, Any]:
|
|
22
97
|
"""获取 MCP 工具的输入 schema,兼容 SDK 的字段改名
|
|
23
98
|
|
|
@@ -143,7 +218,8 @@ class MCPClient:
|
|
|
143
218
|
stack = AsyncExitStack()
|
|
144
219
|
stop = asyncio.Event()
|
|
145
220
|
try:
|
|
146
|
-
server_type = config.get("type", "")
|
|
221
|
+
server_type = _normalize_server_type(config.get("type", ""))
|
|
222
|
+
headers = config.get("headers") or None
|
|
147
223
|
if server_type == "stdio":
|
|
148
224
|
params = StdioServerParameters(
|
|
149
225
|
command=config["command"],
|
|
@@ -155,12 +231,28 @@ class MCPClient:
|
|
|
155
231
|
ClientSession(transport[0], transport[1])
|
|
156
232
|
)
|
|
157
233
|
elif server_type == "sse":
|
|
158
|
-
transport = await stack.enter_async_context(
|
|
234
|
+
transport = await stack.enter_async_context(
|
|
235
|
+
sse_client(config["url"], headers=headers)
|
|
236
|
+
)
|
|
237
|
+
session = await stack.enter_async_context(
|
|
238
|
+
ClientSession(transport[0], transport[1])
|
|
239
|
+
)
|
|
240
|
+
elif server_type == "http":
|
|
241
|
+
# Streamable HTTP:单端点,取代旧的 SSE 传输。
|
|
242
|
+
# headers 需通过预配置的 httpx 客户端传入;该客户端由 stack 负责关闭。
|
|
243
|
+
http_client = create_mcp_http_client(headers=headers)
|
|
244
|
+
await stack.enter_async_context(http_client)
|
|
245
|
+
transport = await stack.enter_async_context(
|
|
246
|
+
streamable_http_client(config["url"], http_client=http_client)
|
|
247
|
+
)
|
|
159
248
|
session = await stack.enter_async_context(
|
|
160
249
|
ClientSession(transport[0], transport[1])
|
|
161
250
|
)
|
|
162
251
|
else:
|
|
163
|
-
raise ValueError(
|
|
252
|
+
raise ValueError(
|
|
253
|
+
f"Unsupported server type: {server_type} "
|
|
254
|
+
f"(supported: stdio, sse, http/streamable_http)"
|
|
255
|
+
)
|
|
164
256
|
|
|
165
257
|
await session.initialize()
|
|
166
258
|
res = await session.list_tools()
|
|
@@ -191,14 +283,61 @@ class MCPClient:
|
|
|
191
283
|
|
|
192
284
|
# --- 同步接口桥接 ---
|
|
193
285
|
|
|
286
|
+
@overload
|
|
194
287
|
def add_server(
|
|
195
288
|
self,
|
|
196
289
|
server_id: str,
|
|
197
|
-
|
|
290
|
+
*,
|
|
291
|
+
type: StdioType,
|
|
292
|
+
command: str,
|
|
293
|
+
args: Optional[List[str]] = None,
|
|
294
|
+
env: Optional[Dict[str, str]] = None,
|
|
295
|
+
max_retries: int = 1,
|
|
296
|
+
timeout: int = 90,
|
|
297
|
+
) -> bool: ...
|
|
298
|
+
|
|
299
|
+
@overload
|
|
300
|
+
def add_server(
|
|
301
|
+
self,
|
|
302
|
+
server_id: str,
|
|
303
|
+
*,
|
|
304
|
+
type: RemoteType,
|
|
305
|
+
url: str,
|
|
306
|
+
headers: Optional[Dict[str, str]] = None,
|
|
307
|
+
max_retries: int = 1,
|
|
308
|
+
timeout: int = 90,
|
|
309
|
+
) -> bool: ...
|
|
310
|
+
|
|
311
|
+
def add_server(
|
|
312
|
+
self,
|
|
313
|
+
server_id: str,
|
|
314
|
+
*,
|
|
315
|
+
type: str,
|
|
316
|
+
command: Optional[str] = None,
|
|
317
|
+
args: Optional[List[str]] = None,
|
|
318
|
+
env: Optional[Dict[str, str]] = None,
|
|
319
|
+
url: Optional[str] = None,
|
|
320
|
+
headers: Optional[Dict[str, str]] = None,
|
|
198
321
|
max_retries: int = 1,
|
|
199
322
|
timeout: int = 90,
|
|
200
323
|
) -> bool:
|
|
201
|
-
"""添加并连接一个 MCP 服务器,失败时按 max_retries
|
|
324
|
+
"""添加并连接一个 MCP 服务器,失败时按 max_retries 重试。
|
|
325
|
+
|
|
326
|
+
参数按传输类型扁平化:
|
|
327
|
+
|
|
328
|
+
- ``type="stdio"``:需要 ``command``,可选 ``args`` / ``env``。
|
|
329
|
+
- ``type="http"``(或 ``streamable_http`` 等别名)/ ``type="sse"``:
|
|
330
|
+
需要 ``url``,可选 ``headers``。
|
|
331
|
+
"""
|
|
332
|
+
config = _build_server_config(
|
|
333
|
+
type,
|
|
334
|
+
command=command,
|
|
335
|
+
args=args,
|
|
336
|
+
env=env,
|
|
337
|
+
url=url,
|
|
338
|
+
headers=headers,
|
|
339
|
+
)
|
|
340
|
+
|
|
202
341
|
with self._lock:
|
|
203
342
|
if server_id in self.servers:
|
|
204
343
|
return True
|
|
@@ -378,8 +378,9 @@ class TuiMessageStore:
|
|
|
378
378
|
index = chunk.get("tool_index")
|
|
379
379
|
|
|
380
380
|
if index is not None:
|
|
381
|
-
# 并发调用:按 index
|
|
382
|
-
|
|
381
|
+
# 并发调用:按 index 定位,绝不按名字串到别的调用上。
|
|
382
|
+
# 已完成的旧卡片不复用(每次 LLM 响应的 index 都从 0 重新开始)
|
|
383
|
+
block = self._routed_tool_by_index(index)
|
|
383
384
|
if block is None:
|
|
384
385
|
block = self._start_tool(name or "unknown", index=index)
|
|
385
386
|
else:
|
|
@@ -403,7 +404,7 @@ class TuiMessageStore:
|
|
|
403
404
|
index = call.get("index")
|
|
404
405
|
call_id = call.get("id")
|
|
405
406
|
|
|
406
|
-
block = self.
|
|
407
|
+
block = self._routed_tool_by_index(index)
|
|
407
408
|
if block is None and call_id:
|
|
408
409
|
block = self._tool_by_id.get(call_id)
|
|
409
410
|
if block is None and name:
|
|
@@ -449,6 +450,20 @@ class TuiMessageStore:
|
|
|
449
450
|
self._notify("update", block)
|
|
450
451
|
return block
|
|
451
452
|
|
|
453
|
+
@staticmethod
|
|
454
|
+
def _is_active_tool(block: TuiBlock | None) -> bool:
|
|
455
|
+
"""工具卡片是否仍在进行中(可用于承接后续流式片段)"""
|
|
456
|
+
return block is not None and block["status"] in ("building", "calling")
|
|
457
|
+
|
|
458
|
+
def _routed_tool_by_index(self, index: int | None) -> TuiBlock | None:
|
|
459
|
+
"""按 index 取当前轮次的工具卡片;已完成的不复用,避免跨轮次串卡"""
|
|
460
|
+
if index is None:
|
|
461
|
+
return None
|
|
462
|
+
block = self._tool_by_index.get(index)
|
|
463
|
+
if not self._is_active_tool(block):
|
|
464
|
+
return None
|
|
465
|
+
return block
|
|
466
|
+
|
|
452
467
|
def _start_tool(
|
|
453
468
|
self, name: str, index: int | None = None, tool_id: str | None = None
|
|
454
469
|
) -> TuiBlock:
|