tina-python 0.6.6__tar.gz → 0.6.7__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.7}/PKG-INFO +26 -20
- {tina_python-0.6.6 → tina_python-0.6.7}/README.md +23 -17
- {tina_python-0.6.6 → tina_python-0.6.7}/pyproject.toml +3 -3
- {tina_python-0.6.6 → tina_python-0.6.7}/setup.py +1 -1
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/__init__.py +1 -1
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/agent.py +65 -5
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/mcp/client.py +145 -6
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/tui/tui.py +28 -11
- {tina_python-0.6.6 → tina_python-0.6.7/tina_python.egg-info}/PKG-INFO +26 -20
- {tina_python-0.6.6 → tina_python-0.6.7}/tina_python.egg-info/requires.txt +2 -2
- {tina_python-0.6.6 → tina_python-0.6.7}/LICENSE +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/MANIFEST.in +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/setup.cfg +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/agent_response.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/agent_runtime.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/context_manager.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/events.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/executor.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/keyword_actions.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/prompt.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/state.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/tools.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/multimodal_agent.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/core/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/core/error.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/core/logger.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/llm/BaseNewAPI.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/llm/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/llm/base_api.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/llm/base_multimodal_api.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/llm/ollama_api.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/mcp/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/mcp/mcp_tools_executor.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/py.typed +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/agent_worker.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/deepseek.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/doc_parser.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/env_reader.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/multimodal_formatter.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/output_parser.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/run_agent_in_cli.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/system_tools.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/timer.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/tui/__init__.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/tui/context_manager.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/tui/token.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/type_mapper.py +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina_python.egg-info/SOURCES.txt +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/tina_python.egg-info/dependency_links.txt +0 -0
- {tina_python-0.6.6 → tina_python-0.6.7}/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.7
|
|
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.7"
|
|
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
|
|
@@ -498,9 +498,10 @@ class TinaTUI(App):
|
|
|
498
498
|
|
|
499
499
|
# 渲染泵间隔:界面更新与滚动统一在这里做,与 chunk 速率解耦
|
|
500
500
|
PUMP_INTERVAL = 0.1
|
|
501
|
-
#
|
|
502
|
-
|
|
503
|
-
|
|
501
|
+
# 流式块重渲染的最小间隔(秒)。采用增量 append 后,开销与新增内容成正比,
|
|
502
|
+
# 因此只需轻微随长度放宽,长文本也能保持较顺滑的刷新。
|
|
503
|
+
RENDER_INTERVAL = 0.06
|
|
504
|
+
RENDER_INTERVAL_MAX = 0.2
|
|
504
505
|
|
|
505
506
|
# 确认框内联显示参数的上限,超过则只显示提示(按 Ctrl+O 查看完整参数)
|
|
506
507
|
CONFIRM_INLINE_ARGS = 200
|
|
@@ -1149,7 +1150,7 @@ class TinaTUI(App):
|
|
|
1149
1150
|
collapsed=bool(block.metadata.get("collapsed", False)),
|
|
1150
1151
|
markup=bool(block.metadata.get("markup", False)),
|
|
1151
1152
|
)
|
|
1152
|
-
return Markdown(
|
|
1153
|
+
return Markdown(classes="assistant-message")
|
|
1153
1154
|
|
|
1154
1155
|
def _ensure_widget(self, block: TuiBlock) -> Any:
|
|
1155
1156
|
widget = self._widgets.get(block.id)
|
|
@@ -1160,15 +1161,31 @@ class TinaTUI(App):
|
|
|
1160
1161
|
self._messages_widget().mount(widget)
|
|
1161
1162
|
return widget
|
|
1162
1163
|
|
|
1164
|
+
def _render_markdown(self, widget: Markdown, content: str) -> Any:
|
|
1165
|
+
"""增量渲染助手 Markdown:只解析新增片段,避免整篇重解析。
|
|
1166
|
+
|
|
1167
|
+
`Markdown.update()` 每次都会整篇重解析并重建所有子块,流式输出时呈
|
|
1168
|
+
O(n²) 开销,正文越长越卡。挂载完成后改用 `append()`,只解析新增片段,
|
|
1169
|
+
开销与新增内容成正比。
|
|
1170
|
+
|
|
1171
|
+
注意:`_initial_markdown` 要始终保持为最新内容,供 `Markdown._on_mount`
|
|
1172
|
+
首次渲染使用,避免挂载时用空串覆盖已写入的内容。
|
|
1173
|
+
"""
|
|
1174
|
+
widget._initial_markdown = content
|
|
1175
|
+
current = widget.source
|
|
1176
|
+
if content == current:
|
|
1177
|
+
return None # 内容未变化,跳过昂贵的 Markdown 解析
|
|
1178
|
+
if widget.is_mounted and content.startswith(current):
|
|
1179
|
+
# 纯追加:append() 只解析新增部分
|
|
1180
|
+
return widget.append(content[len(current):])
|
|
1181
|
+
# 尚未挂载完成 或 内容被替换/回退:整篇刷新一次
|
|
1182
|
+
return widget.update(content)
|
|
1183
|
+
|
|
1163
1184
|
def _apply_block(self, block: TuiBlock) -> Any:
|
|
1164
1185
|
"""把块更新到组件;Markdown 为异步渲染,返回可等待对象"""
|
|
1165
1186
|
widget = self._ensure_widget(block)
|
|
1166
1187
|
if isinstance(widget, Markdown):
|
|
1167
|
-
|
|
1168
|
-
# Textual 挂载时会用 _initial_markdown 再渲染一次,这里同步成最新内容,
|
|
1169
|
-
# 避免它用空串覆盖已流式写入的内容。
|
|
1170
|
-
widget._initial_markdown = block.content
|
|
1171
|
-
return widget.update(block.content)
|
|
1188
|
+
return self._render_markdown(widget, block.content)
|
|
1172
1189
|
if isinstance(widget, (ReasoningBlock, ToolBlock, ResultBlock)):
|
|
1173
1190
|
widget.set_block(block)
|
|
1174
1191
|
elif block.role in ("user", "error", "system"):
|
|
@@ -1178,10 +1195,10 @@ class TinaTUI(App):
|
|
|
1178
1195
|
return None
|
|
1179
1196
|
|
|
1180
1197
|
def _should_render(self, block_id: int, length: int = 0) -> bool:
|
|
1181
|
-
"""
|
|
1198
|
+
"""流式节流:增量 append 足够便宜,仅轻微随长度放宽间隔"""
|
|
1182
1199
|
now = time.monotonic()
|
|
1183
1200
|
interval = min(
|
|
1184
|
-
self.RENDER_INTERVAL_MAX, self.RENDER_INTERVAL + length /
|
|
1201
|
+
self.RENDER_INTERVAL_MAX, self.RENDER_INTERVAL + length / 100000
|
|
1185
1202
|
)
|
|
1186
1203
|
last = self._last_render.get(block_id, 0.0)
|
|
1187
1204
|
if now - last >= interval:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tina-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.7
|
|
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")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|