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.
Files changed (53) hide show
  1. {tina_python-0.6.6/tina_python.egg-info → tina_python-0.6.7}/PKG-INFO +26 -20
  2. {tina_python-0.6.6 → tina_python-0.6.7}/README.md +23 -17
  3. {tina_python-0.6.6 → tina_python-0.6.7}/pyproject.toml +3 -3
  4. {tina_python-0.6.6 → tina_python-0.6.7}/setup.py +1 -1
  5. {tina_python-0.6.6 → tina_python-0.6.7}/tina/__init__.py +1 -1
  6. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/agent.py +65 -5
  7. {tina_python-0.6.6 → tina_python-0.6.7}/tina/mcp/client.py +145 -6
  8. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/tui/tui.py +28 -11
  9. {tina_python-0.6.6 → tina_python-0.6.7/tina_python.egg-info}/PKG-INFO +26 -20
  10. {tina_python-0.6.6 → tina_python-0.6.7}/tina_python.egg-info/requires.txt +2 -2
  11. {tina_python-0.6.6 → tina_python-0.6.7}/LICENSE +0 -0
  12. {tina_python-0.6.6 → tina_python-0.6.7}/MANIFEST.in +0 -0
  13. {tina_python-0.6.6 → tina_python-0.6.7}/setup.cfg +0 -0
  14. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/__init__.py +0 -0
  15. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/__init__.py +0 -0
  16. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/agent_response.py +0 -0
  17. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/agent_runtime.py +0 -0
  18. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/context_manager.py +0 -0
  19. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/events.py +0 -0
  20. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/executor.py +0 -0
  21. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/keyword_actions.py +0 -0
  22. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/prompt.py +0 -0
  23. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/state.py +0 -0
  24. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/core/tools.py +0 -0
  25. {tina_python-0.6.6 → tina_python-0.6.7}/tina/agent/multimodal_agent.py +0 -0
  26. {tina_python-0.6.6 → tina_python-0.6.7}/tina/core/__init__.py +0 -0
  27. {tina_python-0.6.6 → tina_python-0.6.7}/tina/core/error.py +0 -0
  28. {tina_python-0.6.6 → tina_python-0.6.7}/tina/core/logger.py +0 -0
  29. {tina_python-0.6.6 → tina_python-0.6.7}/tina/llm/BaseNewAPI.py +0 -0
  30. {tina_python-0.6.6 → tina_python-0.6.7}/tina/llm/__init__.py +0 -0
  31. {tina_python-0.6.6 → tina_python-0.6.7}/tina/llm/base_api.py +0 -0
  32. {tina_python-0.6.6 → tina_python-0.6.7}/tina/llm/base_multimodal_api.py +0 -0
  33. {tina_python-0.6.6 → tina_python-0.6.7}/tina/llm/ollama_api.py +0 -0
  34. {tina_python-0.6.6 → tina_python-0.6.7}/tina/mcp/__init__.py +0 -0
  35. {tina_python-0.6.6 → tina_python-0.6.7}/tina/mcp/mcp_tools_executor.py +0 -0
  36. {tina_python-0.6.6 → tina_python-0.6.7}/tina/py.typed +0 -0
  37. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/__init__.py +0 -0
  38. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/agent_worker.py +0 -0
  39. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/deepseek.py +0 -0
  40. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/doc_parser.py +0 -0
  41. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/env_reader.py +0 -0
  42. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/multimodal_formatter.py +0 -0
  43. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/output_parser.py +0 -0
  44. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/run_agent_in_cli.py +0 -0
  45. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/system_tools.py +0 -0
  46. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/timer.py +0 -0
  47. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/tui/__init__.py +0 -0
  48. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/tui/context_manager.py +0 -0
  49. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/tui/token.py +0 -0
  50. {tina_python-0.6.6 → tina_python-0.6.7}/tina/utils/type_mapper.py +0 -0
  51. {tina_python-0.6.6 → tina_python-0.6.7}/tina_python.egg-info/SOURCES.txt +0 -0
  52. {tina_python-0.6.6 → tina_python-0.6.7}/tina_python.egg-info/dependency_links.txt +0 -0
  53. {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.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
- config={
720
- "type": "stdio",
721
- "command": "npx",
722
- "args": ["@playwright/mcp@latest"]
723
- }
719
+ type="stdio",
720
+ command="npx",
721
+ args=["@playwright/mcp@latest"],
724
722
  )
725
723
 
726
- # 添加 SSE 类型 MCP 服务
724
+ # 添加 Streamable HTTP 类型 MCP 服务(推荐,取代旧 SSE)
727
725
  mcp.add_server(
728
726
  server_id="my-service",
729
- config={
730
- "type": "sse",
731
- "url": "http://localhost:8080/sse"
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
- config={
747
- "type": "stdio",
748
- "command": "npx",
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", config)
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
- config={
685
- "type": "stdio",
686
- "command": "npx",
687
- "args": ["@playwright/mcp@latest"]
688
- }
684
+ type="stdio",
685
+ command="npx",
686
+ args=["@playwright/mcp@latest"],
689
687
  )
690
688
 
691
- # 添加 SSE 类型 MCP 服务
689
+ # 添加 Streamable HTTP 类型 MCP 服务(推荐,取代旧 SSE)
692
690
  mcp.add_server(
693
691
  server_id="my-service",
694
- config={
695
- "type": "sse",
696
- "url": "http://localhost:8080/sse"
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
- config={
712
- "type": "stdio",
713
- "command": "npx",
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", config)
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.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]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="tina-python",
5
- version="0.6.6",
5
+ version="0.6.7",
6
6
  packages=find_packages(),
7
7
  package_data={"tina": ["py.typed"]},
8
8
  install_requires=["httpx", "python-dotenv"],
@@ -19,4 +19,4 @@ __all__ = [
19
19
  "AgentState",
20
20
  "AgentEvents",
21
21
  ]
22
- __version__ = "0.6.6"
22
+ __version__ = "0.6.7"
@@ -10,7 +10,17 @@ Agent类:基础智能体类,默认支持API调用
10
10
  from __future__ import annotations
11
11
 
12
12
  import re
13
- from typing import List, Literal, Union, Generator, Dict, Any, AsyncGenerator, overload
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, server_id: str, config: Dict[str, Any], max_retries=3, timeout=90
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
- config:服务器配置
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(server_id, config, max_retries, timeout)
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 Dict, List, Any, Optional, Union, Tuple
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", "").lower()
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(sse_client(config["url"]))
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(f"Unsupported server type: {server_type}")
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
- config: Dict[str, Any],
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
- # 流式块重渲染的最小间隔(秒),内容越长间隔越大,避免 O(n²) 重解析
502
- RENDER_INTERVAL = 0.08
503
- RENDER_INTERVAL_MAX = 0.5
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("", classes="assistant-message")
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
- # Markdown.update() 异步全量重解析,必须在渲染泵里按节流调用。
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 / 20000
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.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
- config={
720
- "type": "stdio",
721
- "command": "npx",
722
- "args": ["@playwright/mcp@latest"]
723
- }
719
+ type="stdio",
720
+ command="npx",
721
+ args=["@playwright/mcp@latest"],
724
722
  )
725
723
 
726
- # 添加 SSE 类型 MCP 服务
724
+ # 添加 Streamable HTTP 类型 MCP 服务(推荐,取代旧 SSE)
727
725
  mcp.add_server(
728
726
  server_id="my-service",
729
- config={
730
- "type": "sse",
731
- "url": "http://localhost:8080/sse"
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
- config={
747
- "type": "stdio",
748
- "command": "npx",
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", config)
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")
@@ -3,10 +3,10 @@ python-dotenv
3
3
 
4
4
  [all]
5
5
  textual>=0.80
6
- mcp
6
+ mcp>=2.0
7
7
 
8
8
  [mcp]
9
- mcp
9
+ mcp>=2.0
10
10
 
11
11
  [test]
12
12
  pytest>=9.0
File without changes
File without changes
File without changes
File without changes