astrbotmcp 0.3.1__py3-none-any.whl → 0.4.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.
- astrbot_mcp/astrbot_client.py +115 -0
- astrbot_mcp/config.py +0 -9
- astrbot_mcp/server.py +10 -2
- astrbot_mcp/tools/__init__.py +5 -4
- astrbot_mcp/tools/mcp_panel_tools.py +135 -0
- astrbot_mcp/tools/message/__init__.py +1 -2
- astrbot_mcp/tools/message/webchat.py +86 -55
- astrbot_mcp/tools/plugin_admin_tools.py +344 -0
- astrbot_mcp/tools/plugin_market_tools.py +28 -0
- astrbot_mcp/tools.py +6 -2
- {astrbotmcp-0.3.1.dist-info → astrbotmcp-0.4.1.dist-info}/METADATA +33 -42
- astrbotmcp-0.4.1.dist-info/RECORD +29 -0
- {astrbotmcp-0.3.1.dist-info → astrbotmcp-0.4.1.dist-info}/WHEEL +1 -1
- astrbotmcp-0.3.1.dist-info/RECORD +0 -27
- {astrbotmcp-0.3.1.dist-info → astrbotmcp-0.4.1.dist-info}/entry_points.txt +0 -0
- {astrbotmcp-0.3.1.dist-info → astrbotmcp-0.4.1.dist-info}/licenses/LICENSE.txt +0 -0
- {astrbotmcp-0.3.1.dist-info → astrbotmcp-0.4.1.dist-info}/top_level.txt +0 -0
astrbot_mcp/astrbot_client.py
CHANGED
|
@@ -402,6 +402,38 @@ class AstrBotClient:
|
|
|
402
402
|
response = await self._request("POST", "/api/config/astrbot/update", json_body=payload)
|
|
403
403
|
return response.json()
|
|
404
404
|
|
|
405
|
+
async def get_plugin_config(
|
|
406
|
+
self,
|
|
407
|
+
*,
|
|
408
|
+
plugin_name: str,
|
|
409
|
+
) -> Dict[str, Any]:
|
|
410
|
+
"""
|
|
411
|
+
Get plugin config via /api/config/get?plugin_name=<name>.
|
|
412
|
+
"""
|
|
413
|
+
response = await self._request(
|
|
414
|
+
"GET",
|
|
415
|
+
"/api/config/get",
|
|
416
|
+
params={"plugin_name": plugin_name},
|
|
417
|
+
)
|
|
418
|
+
return response.json()
|
|
419
|
+
|
|
420
|
+
async def update_plugin_config(
|
|
421
|
+
self,
|
|
422
|
+
*,
|
|
423
|
+
plugin_name: str,
|
|
424
|
+
config: Dict[str, Any],
|
|
425
|
+
) -> Dict[str, Any]:
|
|
426
|
+
"""
|
|
427
|
+
Update plugin config via /api/config/plugin/update?plugin_name=<name>.
|
|
428
|
+
"""
|
|
429
|
+
response = await self._request(
|
|
430
|
+
"POST",
|
|
431
|
+
"/api/config/plugin/update",
|
|
432
|
+
params={"plugin_name": plugin_name},
|
|
433
|
+
json_body=config,
|
|
434
|
+
)
|
|
435
|
+
return response.json()
|
|
436
|
+
|
|
405
437
|
async def list_session_rules(
|
|
406
438
|
self,
|
|
407
439
|
*,
|
|
@@ -458,6 +490,40 @@ class AstrBotClient:
|
|
|
458
490
|
response = await self._request("GET", "/api/plugin/market_list", params=params or None)
|
|
459
491
|
return response.json()
|
|
460
492
|
|
|
493
|
+
async def install_plugin_from_url(
|
|
494
|
+
self,
|
|
495
|
+
*,
|
|
496
|
+
url: str,
|
|
497
|
+
proxy: str | None = None,
|
|
498
|
+
) -> Dict[str, Any]:
|
|
499
|
+
"""
|
|
500
|
+
Install a plugin from repository URL via /api/plugin/install.
|
|
501
|
+
"""
|
|
502
|
+
payload: Dict[str, Any] = {"url": url}
|
|
503
|
+
if proxy:
|
|
504
|
+
payload["proxy"] = proxy
|
|
505
|
+
response = await self._request("POST", "/api/plugin/install", json_body=payload)
|
|
506
|
+
return response.json()
|
|
507
|
+
|
|
508
|
+
async def install_plugin_from_file(
|
|
509
|
+
self,
|
|
510
|
+
file_path: str,
|
|
511
|
+
) -> Dict[str, Any]:
|
|
512
|
+
"""
|
|
513
|
+
Install a plugin from uploaded zip file via /api/plugin/install-upload.
|
|
514
|
+
"""
|
|
515
|
+
send_name = os.path.basename(file_path)
|
|
516
|
+
with open(file_path, "rb") as f:
|
|
517
|
+
files = {
|
|
518
|
+
"file": (send_name, f, "application/zip"),
|
|
519
|
+
}
|
|
520
|
+
response = await self._request(
|
|
521
|
+
"POST",
|
|
522
|
+
"/api/plugin/install-upload",
|
|
523
|
+
files=files,
|
|
524
|
+
)
|
|
525
|
+
return response.json()
|
|
526
|
+
|
|
461
527
|
# ---- Chat / platform session APIs --------------------------------
|
|
462
528
|
|
|
463
529
|
async def create_platform_session(
|
|
@@ -692,3 +758,52 @@ class AstrBotClient:
|
|
|
692
758
|
"""
|
|
693
759
|
response = await self._request("GET", "/api/stat/version")
|
|
694
760
|
return response.json()
|
|
761
|
+
|
|
762
|
+
# ---- MCP panel APIs ----------------------------------------------
|
|
763
|
+
|
|
764
|
+
async def get_mcp_servers(self) -> Dict[str, Any]:
|
|
765
|
+
"""
|
|
766
|
+
Get MCP server list from panel API /api/tools/mcp/servers.
|
|
767
|
+
"""
|
|
768
|
+
response = await self._request("GET", "/api/tools/mcp/servers")
|
|
769
|
+
return response.json()
|
|
770
|
+
|
|
771
|
+
async def add_mcp_server(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
772
|
+
"""
|
|
773
|
+
Add MCP server via panel API /api/tools/mcp/add.
|
|
774
|
+
"""
|
|
775
|
+
response = await self._request("POST", "/api/tools/mcp/add", json_body=payload)
|
|
776
|
+
return response.json()
|
|
777
|
+
|
|
778
|
+
async def update_mcp_server(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
779
|
+
"""
|
|
780
|
+
Update MCP server via panel API /api/tools/mcp/update.
|
|
781
|
+
"""
|
|
782
|
+
response = await self._request("POST", "/api/tools/mcp/update", json_body=payload)
|
|
783
|
+
return response.json()
|
|
784
|
+
|
|
785
|
+
async def delete_mcp_server(self, *, name: str) -> Dict[str, Any]:
|
|
786
|
+
"""
|
|
787
|
+
Delete MCP server via panel API /api/tools/mcp/delete.
|
|
788
|
+
"""
|
|
789
|
+
response = await self._request(
|
|
790
|
+
"POST",
|
|
791
|
+
"/api/tools/mcp/delete",
|
|
792
|
+
json_body={"name": name},
|
|
793
|
+
)
|
|
794
|
+
return response.json()
|
|
795
|
+
|
|
796
|
+
async def test_mcp_server_connection(
|
|
797
|
+
self,
|
|
798
|
+
*,
|
|
799
|
+
mcp_server_config: Dict[str, Any],
|
|
800
|
+
) -> Dict[str, Any]:
|
|
801
|
+
"""
|
|
802
|
+
Test MCP connection via panel API /api/tools/mcp/test.
|
|
803
|
+
"""
|
|
804
|
+
response = await self._request(
|
|
805
|
+
"POST",
|
|
806
|
+
"/api/tools/mcp/test",
|
|
807
|
+
json_body={"mcp_server_config": mcp_server_config},
|
|
808
|
+
)
|
|
809
|
+
return response.json()
|
astrbot_mcp/config.py
CHANGED
|
@@ -15,7 +15,6 @@ class AstrBotSettings:
|
|
|
15
15
|
default_provider: str | None = None
|
|
16
16
|
default_model: str | None = None
|
|
17
17
|
file_root: str | None = None
|
|
18
|
-
direct_media_mode: str | None = None
|
|
19
18
|
disable_proxy: bool = True # 默认禁用代理,防止本地请求被代理拦截
|
|
20
19
|
|
|
21
20
|
|
|
@@ -41,10 +40,6 @@ def get_settings() -> AstrBotSettings:
|
|
|
41
40
|
- ASTRBOT_DEFAULT_PROVIDER: Default provider id to use for /api/chat/send.
|
|
42
41
|
- ASTRBOT_DEFAULT_MODEL: Default model id to use for /api/chat/send.
|
|
43
42
|
- ASTRBOTMCP_FILE_ROOT: Base directory for resolving relative local file_path.
|
|
44
|
-
- ASTRBOTMCP_DIRECT_MEDIA_MODE: How send_platform_message_direct handles local media:
|
|
45
|
-
- auto (default): try local path first, then fallback to upload+URL.
|
|
46
|
-
- local: always send local absolute paths to AstrBot platform adapters.
|
|
47
|
-
- upload: upload to AstrBot first and send an http(s) URL.
|
|
48
43
|
"""
|
|
49
44
|
base_url = _get_env("ASTRBOT_BASE_URL")
|
|
50
45
|
if not base_url:
|
|
@@ -69,9 +64,6 @@ def get_settings() -> AstrBotSettings:
|
|
|
69
64
|
default_provider = _get_env("ASTRBOT_DEFAULT_PROVIDER")
|
|
70
65
|
default_model = _get_env("ASTRBOT_DEFAULT_MODEL")
|
|
71
66
|
file_root = _get_env("ASTRBOTMCP_FILE_ROOT") or _get_env("ASTRBOT_MCP_FILE_ROOT")
|
|
72
|
-
direct_media_mode = _get_env("ASTRBOTMCP_DIRECT_MEDIA_MODE") or _get_env(
|
|
73
|
-
"ASTRBOT_MCP_DIRECT_MEDIA_MODE"
|
|
74
|
-
)
|
|
75
67
|
|
|
76
68
|
# 默认禁用代理,除非明确设置为false
|
|
77
69
|
disable_proxy_str = _get_env("ASTRBOTMCP_DISABLE_PROXY")
|
|
@@ -87,6 +79,5 @@ def get_settings() -> AstrBotSettings:
|
|
|
87
79
|
default_provider=default_provider,
|
|
88
80
|
default_model=default_model,
|
|
89
81
|
file_root=file_root,
|
|
90
|
-
direct_media_mode=direct_media_mode,
|
|
91
82
|
disable_proxy=disable_proxy,
|
|
92
83
|
)
|
astrbot_mcp/server.py
CHANGED
|
@@ -15,6 +15,7 @@ server = FastMCP(
|
|
|
15
15
|
"MCP server for interacting with an existing AstrBot instance. "
|
|
16
16
|
"Provides tools to read logs, list configured message platforms, "
|
|
17
17
|
"send message chains (including files) via the web chat API, "
|
|
18
|
+
"install/configure plugins, manage MCP panel config, "
|
|
18
19
|
"restart AstrBot core, read platform session message history, "
|
|
19
20
|
"and browse the AstrBot plugin market."
|
|
20
21
|
),
|
|
@@ -23,7 +24,6 @@ server = FastMCP(
|
|
|
23
24
|
# Register tools with FastMCP
|
|
24
25
|
server.tool(astrbot_tools.get_astrbot_logs, name="get_astrbot_logs")
|
|
25
26
|
server.tool(astrbot_tools.get_message_platforms, name="get_message_platforms")
|
|
26
|
-
server.tool(astrbot_tools.send_platform_message_direct, name="send_platform_message_direct")
|
|
27
27
|
server.tool(astrbot_tools.send_platform_message, name="send_platform_message")
|
|
28
28
|
server.tool(astrbot_tools.restart_astrbot, name="restart_astrbot")
|
|
29
29
|
server.tool(
|
|
@@ -31,6 +31,12 @@ server.tool(
|
|
|
31
31
|
name="get_platform_session_messages",
|
|
32
32
|
)
|
|
33
33
|
server.tool(astrbot_tools.browse_plugin_market, name="browse_plugin_market")
|
|
34
|
+
server.tool(astrbot_tools.install_astrbot_plugin, name="install_astrbot_plugin")
|
|
35
|
+
server.tool(
|
|
36
|
+
astrbot_tools.configure_astrbot_plugin_json,
|
|
37
|
+
name="configure_astrbot_plugin_json",
|
|
38
|
+
)
|
|
39
|
+
server.tool(astrbot_tools.manage_mcp_config_panel, name="manage_mcp_config_panel")
|
|
34
40
|
server.tool(astrbot_tools.list_astrbot_config_files, name="list_astrbot_config_files")
|
|
35
41
|
server.tool(astrbot_tools.inspect_astrbot_config, name="inspect_astrbot_config")
|
|
36
42
|
server.tool(astrbot_tools.apply_astrbot_config_ops, name="apply_astrbot_config_ops")
|
|
@@ -49,10 +55,12 @@ def astrbot_info():
|
|
|
49
55
|
"get_astrbot_logs",
|
|
50
56
|
"get_message_platforms",
|
|
51
57
|
"send_platform_message",
|
|
52
|
-
"send_platform_message_direct",
|
|
53
58
|
"restart_astrbot",
|
|
54
59
|
"get_platform_session_messages",
|
|
55
60
|
"browse_plugin_market",
|
|
61
|
+
"install_astrbot_plugin",
|
|
62
|
+
"configure_astrbot_plugin_json",
|
|
63
|
+
"manage_mcp_config_panel",
|
|
56
64
|
"list_astrbot_config_files",
|
|
57
65
|
"inspect_astrbot_config",
|
|
58
66
|
"apply_astrbot_config_ops",
|
astrbot_mcp/tools/__init__.py
CHANGED
|
@@ -20,11 +20,12 @@ from .control_tools import restart_astrbot
|
|
|
20
20
|
from .log_tools import get_astrbot_logs
|
|
21
21
|
from .message import (
|
|
22
22
|
send_platform_message,
|
|
23
|
-
send_platform_message_direct,
|
|
24
23
|
)
|
|
25
24
|
from .platform_tools import get_message_platforms
|
|
26
25
|
from .session_tools import get_platform_session_messages
|
|
27
26
|
from .plugin_market_tools import browse_plugin_market
|
|
27
|
+
from .plugin_admin_tools import install_astrbot_plugin, configure_astrbot_plugin_json
|
|
28
|
+
from .mcp_panel_tools import manage_mcp_config_panel
|
|
28
29
|
from .config_tools import (
|
|
29
30
|
list_astrbot_config_files,
|
|
30
31
|
inspect_astrbot_config,
|
|
@@ -40,7 +41,6 @@ from .helpers import (
|
|
|
40
41
|
_as_file_uri,
|
|
41
42
|
_attachment_download_url,
|
|
42
43
|
_astrbot_connect_hint,
|
|
43
|
-
_direct_media_mode,
|
|
44
44
|
_httpx_error_detail,
|
|
45
45
|
_resolve_local_file_path,
|
|
46
46
|
)
|
|
@@ -49,11 +49,13 @@ __all__ = [
|
|
|
49
49
|
# 工具函数
|
|
50
50
|
"get_astrbot_logs",
|
|
51
51
|
"get_message_platforms",
|
|
52
|
-
"send_platform_message_direct",
|
|
53
52
|
"send_platform_message",
|
|
54
53
|
"restart_astrbot",
|
|
55
54
|
"get_platform_session_messages",
|
|
56
55
|
"browse_plugin_market",
|
|
56
|
+
"install_astrbot_plugin",
|
|
57
|
+
"configure_astrbot_plugin_json",
|
|
58
|
+
"manage_mcp_config_panel",
|
|
57
59
|
"list_astrbot_config_files",
|
|
58
60
|
"inspect_astrbot_config",
|
|
59
61
|
"apply_astrbot_config_ops",
|
|
@@ -67,6 +69,5 @@ __all__ = [
|
|
|
67
69
|
"_attachment_download_url",
|
|
68
70
|
"_astrbot_connect_hint",
|
|
69
71
|
"_httpx_error_detail",
|
|
70
|
-
"_direct_media_mode",
|
|
71
72
|
"_as_file_uri",
|
|
72
73
|
]
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Literal, Optional
|
|
4
|
+
|
|
5
|
+
from ..astrbot_client import AstrBotClient
|
|
6
|
+
from .helpers import _astrbot_connect_hint, _httpx_error_detail
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def _get_astrbot_log_tail(
|
|
10
|
+
client: AstrBotClient,
|
|
11
|
+
*,
|
|
12
|
+
limit: int = 120,
|
|
13
|
+
) -> Dict[str, Any] | None:
|
|
14
|
+
try:
|
|
15
|
+
hist = await client.get_log_history()
|
|
16
|
+
except Exception as e:
|
|
17
|
+
return {
|
|
18
|
+
"status": "error",
|
|
19
|
+
"message": f"AstrBot API error: {getattr(getattr(e, 'response', None), 'status_code', None) or 'Unknown'}",
|
|
20
|
+
"detail": _httpx_error_detail(e),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if hist.get("status") != "ok":
|
|
24
|
+
return {
|
|
25
|
+
"status": hist.get("status"),
|
|
26
|
+
"message": hist.get("message"),
|
|
27
|
+
"raw": hist,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
logs = (hist.get("data") or {}).get("logs", [])
|
|
31
|
+
if not isinstance(logs, list):
|
|
32
|
+
return {
|
|
33
|
+
"status": "error",
|
|
34
|
+
"message": "Unexpected /api/log-history response shape (logs is not a list).",
|
|
35
|
+
"raw": hist,
|
|
36
|
+
}
|
|
37
|
+
return {"status": "ok", "logs": logs[-max(1, int(limit)) :]}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def manage_mcp_config_panel(
|
|
41
|
+
action: Literal["list", "add", "update", "delete", "test"] = "list",
|
|
42
|
+
name: Optional[str] = None,
|
|
43
|
+
server_config: Optional[Dict[str, Any]] = None,
|
|
44
|
+
active: Optional[bool] = None,
|
|
45
|
+
include_logs: bool = True,
|
|
46
|
+
log_tail_limit: int = 120,
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
"""
|
|
49
|
+
Manage AstrBot MCP config panel APIs.
|
|
50
|
+
|
|
51
|
+
Actions:
|
|
52
|
+
- list: GET /api/tools/mcp/servers
|
|
53
|
+
- add: POST /api/tools/mcp/add
|
|
54
|
+
- update: POST /api/tools/mcp/update
|
|
55
|
+
- delete: POST /api/tools/mcp/delete
|
|
56
|
+
- test: POST /api/tools/mcp/test
|
|
57
|
+
"""
|
|
58
|
+
client = AstrBotClient.from_env()
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
if action == "list":
|
|
62
|
+
raw = await client.get_mcp_servers()
|
|
63
|
+
elif action == "add":
|
|
64
|
+
if not name or not str(name).strip():
|
|
65
|
+
return {"status": "error", "message": "name is required for action='add'."}
|
|
66
|
+
if not isinstance(server_config, dict) or not server_config:
|
|
67
|
+
return {
|
|
68
|
+
"status": "error",
|
|
69
|
+
"message": "server_config is required for action='add'.",
|
|
70
|
+
}
|
|
71
|
+
payload = {"name": str(name).strip(), **server_config}
|
|
72
|
+
if active is not None:
|
|
73
|
+
payload["active"] = bool(active)
|
|
74
|
+
raw = await client.add_mcp_server(payload)
|
|
75
|
+
elif action == "update":
|
|
76
|
+
if not name or not str(name).strip():
|
|
77
|
+
return {"status": "error", "message": "name is required for action='update'."}
|
|
78
|
+
payload = {"name": str(name).strip()}
|
|
79
|
+
if isinstance(server_config, dict):
|
|
80
|
+
payload.update(server_config)
|
|
81
|
+
if active is not None:
|
|
82
|
+
payload["active"] = bool(active)
|
|
83
|
+
raw = await client.update_mcp_server(payload)
|
|
84
|
+
elif action == "delete":
|
|
85
|
+
if not name or not str(name).strip():
|
|
86
|
+
return {
|
|
87
|
+
"status": "error",
|
|
88
|
+
"message": "name is required for action='delete'.",
|
|
89
|
+
}
|
|
90
|
+
raw = await client.delete_mcp_server(name=str(name).strip())
|
|
91
|
+
else: # test
|
|
92
|
+
if not isinstance(server_config, dict) or not server_config:
|
|
93
|
+
return {
|
|
94
|
+
"status": "error",
|
|
95
|
+
"message": "server_config is required for action='test'.",
|
|
96
|
+
}
|
|
97
|
+
raw = await client.test_mcp_server_connection(
|
|
98
|
+
mcp_server_config=server_config
|
|
99
|
+
)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
payload = {
|
|
102
|
+
"status": "error",
|
|
103
|
+
"message": _astrbot_connect_hint(client),
|
|
104
|
+
"base_url": client.base_url,
|
|
105
|
+
"detail": _httpx_error_detail(e),
|
|
106
|
+
"action": action,
|
|
107
|
+
"name": name,
|
|
108
|
+
}
|
|
109
|
+
if include_logs:
|
|
110
|
+
payload["astrbot_logs_tail"] = await _get_astrbot_log_tail(
|
|
111
|
+
client, limit=log_tail_limit
|
|
112
|
+
)
|
|
113
|
+
return payload
|
|
114
|
+
|
|
115
|
+
payload: Dict[str, Any] = {
|
|
116
|
+
"status": raw.get("status", "ok"),
|
|
117
|
+
"message": raw.get("message"),
|
|
118
|
+
"action": action,
|
|
119
|
+
"name": name,
|
|
120
|
+
"raw": raw,
|
|
121
|
+
}
|
|
122
|
+
if action == "list" and raw.get("status") == "ok":
|
|
123
|
+
servers = raw.get("data") if isinstance(raw.get("data"), list) else []
|
|
124
|
+
payload["servers"] = servers
|
|
125
|
+
payload["mcp_server_errlogs"] = [
|
|
126
|
+
{"name": s.get("name"), "errlogs": s.get("errlogs")}
|
|
127
|
+
for s in servers
|
|
128
|
+
if isinstance(s, dict) and s.get("errlogs")
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
if include_logs:
|
|
132
|
+
payload["astrbot_logs_tail"] = await _get_astrbot_log_tail(
|
|
133
|
+
client, limit=log_tail_limit
|
|
134
|
+
)
|
|
135
|
+
return payload
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import os
|
|
4
5
|
from datetime import datetime, timedelta, timezone
|
|
5
|
-
from typing import Any, Dict, List,
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
6
7
|
|
|
7
8
|
from ...astrbot_client import AstrBotClient
|
|
8
9
|
from ..helpers import _httpx_error_detail, _resolve_local_file_path
|
|
9
|
-
from ..types import MessagePart
|
|
10
10
|
from .cache import (
|
|
11
11
|
_LAST_SAVED_MESSAGE_ID_BY_SESSION,
|
|
12
12
|
_LAST_SAVED_MESSAGE_ID_LOCK,
|
|
@@ -17,7 +17,6 @@ from .cache import (
|
|
|
17
17
|
_last_saved_key,
|
|
18
18
|
_session_cache_key,
|
|
19
19
|
)
|
|
20
|
-
from .direct import send_platform_message_direct
|
|
21
20
|
from .quote import _resolve_webchat_quotes
|
|
22
21
|
from .utils import _extract_plain_text_from_history_item, _normalize_history_message_id
|
|
23
22
|
|
|
@@ -52,16 +51,48 @@ async def _get_astrbot_log_tail(
|
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
|
|
54
|
+
def _normalize_media_sources(
|
|
55
|
+
value: Optional[List[str] | str],
|
|
56
|
+
*,
|
|
57
|
+
field_name: str,
|
|
58
|
+
) -> List[str]:
|
|
59
|
+
if value is None:
|
|
60
|
+
return []
|
|
61
|
+
if isinstance(value, list):
|
|
62
|
+
if not all(isinstance(item, str) for item in value):
|
|
63
|
+
raise ValueError(f"{field_name} must contain only string paths/URLs.")
|
|
64
|
+
return [item for item in value if item]
|
|
65
|
+
if not isinstance(value, str):
|
|
66
|
+
raise ValueError(f"{field_name} must be a list of strings or a string.")
|
|
67
|
+
|
|
68
|
+
raw = value.strip()
|
|
69
|
+
if not raw:
|
|
70
|
+
return []
|
|
71
|
+
|
|
72
|
+
# Tolerate MCP clients that serialize list args as JSON strings.
|
|
73
|
+
try:
|
|
74
|
+
parsed = json.loads(raw)
|
|
75
|
+
except json.JSONDecodeError:
|
|
76
|
+
return [raw]
|
|
77
|
+
|
|
78
|
+
if isinstance(parsed, str):
|
|
79
|
+
return [parsed] if parsed else []
|
|
80
|
+
if isinstance(parsed, list):
|
|
81
|
+
if not all(isinstance(item, str) for item in parsed):
|
|
82
|
+
raise ValueError(f"{field_name} JSON array must contain only strings.")
|
|
83
|
+
return [item for item in parsed if item]
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"{field_name} must be a string or a JSON string array of strings."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
55
89
|
async def send_platform_message(
|
|
56
|
-
|
|
57
|
-
message_chain: Optional[List[MessagePart]] = None,
|
|
90
|
+
message_chain: Optional[List[Dict[str, Any]]] = None,
|
|
58
91
|
message: Optional[str] = None,
|
|
59
|
-
images: Optional[List[str]] = None,
|
|
60
|
-
files: Optional[List[str]] = None,
|
|
61
|
-
videos: Optional[List[str]] = None,
|
|
62
|
-
records: Optional[List[str]] = None,
|
|
63
|
-
target_id: Optional[str] = None,
|
|
64
|
-
message_type: Literal["GroupMessage", "FriendMessage"] = "GroupMessage",
|
|
92
|
+
images: Optional[List[str] | str] = None,
|
|
93
|
+
files: Optional[List[str] | str] = None,
|
|
94
|
+
videos: Optional[List[str] | str] = None,
|
|
95
|
+
records: Optional[List[str] | str] = None,
|
|
65
96
|
session_id: Optional[str] = None,
|
|
66
97
|
conversation_id: Optional[str] = None,
|
|
67
98
|
use_last_session: bool = True,
|
|
@@ -74,53 +105,43 @@ async def send_platform_message(
|
|
|
74
105
|
enable_streaming: bool = True,
|
|
75
106
|
) -> Dict[str, Any]:
|
|
76
107
|
"""
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
- platform_id
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- 图片/文件/语音/视频: {"type": "image"|"file"|"record"|"video", "file_path": "本地路径或URL"} 或 {"type": "...", "url": "http(s) URL"}
|
|
85
|
-
- message / images / files / videos / records: 可选便捷参数;当未传 message_chain 时,会自动拼成消息链。
|
|
86
|
-
- session_id: 可选的平台会话 ID;如果为空,会自动为该平台创建新会话。
|
|
87
|
-
- selected_provider / selected_model: 可选,指定 AstrBot 内部的 provider/model。
|
|
88
|
-
- enable_streaming: 是否启用流式回复(影响 AstrBot 返回的 SSE 事件类型)。
|
|
108
|
+
Send a message through AstrBot WebUI chat API (`/api/chat/send`) only.
|
|
109
|
+
|
|
110
|
+
LLM caller rules:
|
|
111
|
+
- Do not pass `platform_id`, `target_id`, or adapter routing params.
|
|
112
|
+
- This tool always uses WebUI session mode.
|
|
113
|
+
- For plugin commands, send plain command text with no prefix.
|
|
114
|
+
Example: "抽老婆帮助" (not "/抽老婆帮助").
|
|
89
115
|
"""
|
|
90
116
|
client = AstrBotClient.from_env()
|
|
91
|
-
|
|
92
|
-
if target_id:
|
|
93
|
-
direct_result = await send_platform_message_direct(
|
|
94
|
-
platform_id=platform_id,
|
|
95
|
-
target_id=str(target_id),
|
|
96
|
-
message_chain=message_chain,
|
|
97
|
-
message=message,
|
|
98
|
-
images=images,
|
|
99
|
-
files=files,
|
|
100
|
-
videos=videos,
|
|
101
|
-
records=records,
|
|
102
|
-
message_type=message_type,
|
|
103
|
-
)
|
|
104
|
-
if isinstance(direct_result, dict):
|
|
105
|
-
direct_result.setdefault("mode", "direct")
|
|
106
|
-
return direct_result
|
|
107
|
-
|
|
108
117
|
mode = "webchat"
|
|
109
118
|
session_platform_id = "webchat"
|
|
110
119
|
routing_debug: Dict[str, Any] = {}
|
|
111
120
|
send_started_at = datetime.now(timezone.utc)
|
|
121
|
+
try:
|
|
122
|
+
images_list = _normalize_media_sources(images, field_name="images")
|
|
123
|
+
files_list = _normalize_media_sources(files, field_name="files")
|
|
124
|
+
videos_list = _normalize_media_sources(videos, field_name="videos")
|
|
125
|
+
records_list = _normalize_media_sources(records, field_name="records")
|
|
126
|
+
except ValueError as e:
|
|
127
|
+
return {
|
|
128
|
+
"status": "error",
|
|
129
|
+
"message": str(e),
|
|
130
|
+
"mode": mode,
|
|
131
|
+
"platform_id": session_platform_id,
|
|
132
|
+
}
|
|
112
133
|
|
|
113
134
|
if message_chain is None:
|
|
114
135
|
message_chain = []
|
|
115
136
|
if message:
|
|
116
137
|
message_chain.append({"type": "plain", "text": message})
|
|
117
|
-
for src in
|
|
138
|
+
for src in images_list:
|
|
118
139
|
message_chain.append({"type": "image", "file_path": src})
|
|
119
|
-
for src in
|
|
140
|
+
for src in files_list:
|
|
120
141
|
message_chain.append({"type": "file", "file_path": src})
|
|
121
|
-
for src in
|
|
142
|
+
for src in records_list:
|
|
122
143
|
message_chain.append({"type": "record", "file_path": src})
|
|
123
|
-
for src in
|
|
144
|
+
for src in videos_list:
|
|
124
145
|
message_chain.append({"type": "video", "file_path": src})
|
|
125
146
|
|
|
126
147
|
# 1. 确保有 session_id
|
|
@@ -154,7 +175,6 @@ async def send_platform_message(
|
|
|
154
175
|
"message": f"AstrBot API error: {e.response.status_code if hasattr(e, 'response') else 'Unknown'}",
|
|
155
176
|
"mode": mode,
|
|
156
177
|
"platform_id": session_platform_id,
|
|
157
|
-
"requested_platform_id": platform_id,
|
|
158
178
|
"base_url": client.base_url,
|
|
159
179
|
"detail": _httpx_error_detail(e),
|
|
160
180
|
}
|
|
@@ -354,7 +374,7 @@ async def send_platform_message(
|
|
|
354
374
|
return {
|
|
355
375
|
"status": "error",
|
|
356
376
|
"message": f"AstrBot API error: {e.response.status_code if hasattr(e, 'response') else 'Unknown'}",
|
|
357
|
-
"platform_id":
|
|
377
|
+
"platform_id": session_platform_id,
|
|
358
378
|
"session_id": used_session_id,
|
|
359
379
|
"base_url": client.base_url,
|
|
360
380
|
"detail": _httpx_error_detail(e),
|
|
@@ -364,7 +384,7 @@ async def send_platform_message(
|
|
|
364
384
|
return {
|
|
365
385
|
"status": "error",
|
|
366
386
|
"message": f"Invalid local file_path: {src!r}",
|
|
367
|
-
"platform_id":
|
|
387
|
+
"platform_id": session_platform_id,
|
|
368
388
|
"session_id": used_session_id,
|
|
369
389
|
"part": dict(part),
|
|
370
390
|
}
|
|
@@ -374,7 +394,7 @@ async def send_platform_message(
|
|
|
374
394
|
return {
|
|
375
395
|
"status": "error",
|
|
376
396
|
"message": str(e),
|
|
377
|
-
"platform_id":
|
|
397
|
+
"platform_id": session_platform_id,
|
|
378
398
|
"session_id": used_session_id,
|
|
379
399
|
"part": dict(part),
|
|
380
400
|
"hint": "Set ASTRBOTMCP_FILE_ROOT to control how relative paths are resolved.",
|
|
@@ -383,7 +403,7 @@ async def send_platform_message(
|
|
|
383
403
|
return {
|
|
384
404
|
"status": "error",
|
|
385
405
|
"message": f"Local file_path does not exist: {src!r}",
|
|
386
|
-
"platform_id":
|
|
406
|
+
"platform_id": session_platform_id,
|
|
387
407
|
"session_id": used_session_id,
|
|
388
408
|
"part": dict(part),
|
|
389
409
|
"hint": "If you passed a relative path, set ASTRBOTMCP_FILE_ROOT (or run the server in the correct working directory).",
|
|
@@ -398,7 +418,7 @@ async def send_platform_message(
|
|
|
398
418
|
return {
|
|
399
419
|
"status": "error",
|
|
400
420
|
"message": f"AstrBot API error: {e.response.status_code if hasattr(e, 'response') else 'Unknown'}",
|
|
401
|
-
"platform_id":
|
|
421
|
+
"platform_id": session_platform_id,
|
|
402
422
|
"session_id": used_session_id,
|
|
403
423
|
"base_url": client.base_url,
|
|
404
424
|
"detail": _httpx_error_detail(e),
|
|
@@ -440,7 +460,22 @@ async def send_platform_message(
|
|
|
440
460
|
"message": "message_chain did not produce any valid message parts",
|
|
441
461
|
"mode": mode,
|
|
442
462
|
"platform_id": session_platform_id,
|
|
443
|
-
"
|
|
463
|
+
"quote_debug": quote_debug,
|
|
464
|
+
"routing_debug": routing_debug,
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
has_content = any(
|
|
468
|
+
isinstance(part, dict)
|
|
469
|
+
and part.get("type") in ("plain", "image", "record", "file", "video")
|
|
470
|
+
for part in message_parts
|
|
471
|
+
)
|
|
472
|
+
if not has_content:
|
|
473
|
+
return {
|
|
474
|
+
"status": "error",
|
|
475
|
+
"message": "Message content is empty (reply-only is not allowed by WebUI API).",
|
|
476
|
+
"mode": mode,
|
|
477
|
+
"platform_id": session_platform_id,
|
|
478
|
+
"request_message_parts": message_parts,
|
|
444
479
|
"quote_debug": quote_debug,
|
|
445
480
|
"routing_debug": routing_debug,
|
|
446
481
|
}
|
|
@@ -477,7 +512,6 @@ async def send_platform_message(
|
|
|
477
512
|
),
|
|
478
513
|
"mode": mode,
|
|
479
514
|
"platform_id": session_platform_id,
|
|
480
|
-
"requested_platform_id": platform_id,
|
|
481
515
|
"session_id": used_session_id,
|
|
482
516
|
"selected_provider": effective_provider,
|
|
483
517
|
"selected_model": effective_model,
|
|
@@ -502,7 +536,6 @@ async def send_platform_message(
|
|
|
502
536
|
"message": "AstrBot returned no SSE events for /api/chat/send",
|
|
503
537
|
"mode": mode,
|
|
504
538
|
"platform_id": session_platform_id,
|
|
505
|
-
"requested_platform_id": platform_id,
|
|
506
539
|
"session_id": used_session_id,
|
|
507
540
|
"selected_provider": effective_provider,
|
|
508
541
|
"selected_model": effective_model,
|
|
@@ -544,7 +577,6 @@ async def send_platform_message(
|
|
|
544
577
|
"warning": "No reply events were observed on the /api/chat/send SSE stream; check AstrBot logs if you expected an LLM reply.",
|
|
545
578
|
"mode": mode,
|
|
546
579
|
"platform_id": session_platform_id,
|
|
547
|
-
"requested_platform_id": platform_id,
|
|
548
580
|
"session_id": used_session_id,
|
|
549
581
|
"selected_provider": effective_provider,
|
|
550
582
|
"selected_model": effective_model,
|
|
@@ -658,7 +690,6 @@ async def send_platform_message(
|
|
|
658
690
|
"status": "ok",
|
|
659
691
|
"mode": mode,
|
|
660
692
|
"platform_id": session_platform_id,
|
|
661
|
-
"requested_platform_id": platform_id,
|
|
662
693
|
"session_id": used_session_id,
|
|
663
694
|
"conversation_id": used_session_id,
|
|
664
695
|
"session_reused": session_reused,
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
5
|
+
|
|
6
|
+
from ..astrbot_client import AstrBotClient
|
|
7
|
+
from .config_tools import (
|
|
8
|
+
_add_key,
|
|
9
|
+
_append_list_item,
|
|
10
|
+
_get_node,
|
|
11
|
+
_parse_path,
|
|
12
|
+
_set_value,
|
|
13
|
+
_summarize_node,
|
|
14
|
+
)
|
|
15
|
+
from .helpers import _astrbot_connect_hint, _httpx_error_detail, _resolve_local_file_path
|
|
16
|
+
|
|
17
|
+
DEFAULT_PLUGIN_PROXY = "https://gh-proxy.com"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _looks_like_plugin_url(source: str) -> bool:
|
|
21
|
+
s = source.strip().lower()
|
|
22
|
+
return s.startswith(("http://", "https://", "git@", "ssh://"))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _resolve_plugin_name(plugin_path: Union[str, List[Any]]) -> str:
|
|
26
|
+
segs = _parse_path(plugin_path)
|
|
27
|
+
if not segs:
|
|
28
|
+
raise ValueError("plugin_path must not be empty.")
|
|
29
|
+
first = segs[0]
|
|
30
|
+
if not isinstance(first, str) or not first.strip():
|
|
31
|
+
raise ValueError("plugin_path must start with plugin name string.")
|
|
32
|
+
return first.strip()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def _get_astrbot_log_tail(
|
|
36
|
+
client: AstrBotClient,
|
|
37
|
+
*,
|
|
38
|
+
limit: int = 120,
|
|
39
|
+
) -> Dict[str, Any] | None:
|
|
40
|
+
try:
|
|
41
|
+
hist = await client.get_log_history()
|
|
42
|
+
except Exception as e:
|
|
43
|
+
return {
|
|
44
|
+
"status": "error",
|
|
45
|
+
"message": f"AstrBot API error: {getattr(getattr(e, 'response', None), 'status_code', None) or 'Unknown'}",
|
|
46
|
+
"detail": _httpx_error_detail(e),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if hist.get("status") != "ok":
|
|
50
|
+
return {
|
|
51
|
+
"status": hist.get("status"),
|
|
52
|
+
"message": hist.get("message"),
|
|
53
|
+
"raw": hist,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
logs = (hist.get("data") or {}).get("logs", [])
|
|
57
|
+
if not isinstance(logs, list):
|
|
58
|
+
return {
|
|
59
|
+
"status": "error",
|
|
60
|
+
"message": "Unexpected /api/log-history response shape (logs is not a list).",
|
|
61
|
+
"raw": hist,
|
|
62
|
+
}
|
|
63
|
+
return {"status": "ok", "logs": logs[-max(1, int(limit)) :]}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async def install_astrbot_plugin(
|
|
67
|
+
source: str,
|
|
68
|
+
proxy: Optional[str] = None,
|
|
69
|
+
prefer_proxy: bool = True,
|
|
70
|
+
include_logs: bool = True,
|
|
71
|
+
log_tail_limit: int = 120,
|
|
72
|
+
) -> Dict[str, Any]:
|
|
73
|
+
"""
|
|
74
|
+
Install an AstrBot plugin via repository URL or local zip path.
|
|
75
|
+
|
|
76
|
+
- URL source: POST /api/plugin/install
|
|
77
|
+
- Local zip source: POST /api/plugin/install-upload
|
|
78
|
+
"""
|
|
79
|
+
client = AstrBotClient.from_env()
|
|
80
|
+
|
|
81
|
+
if not isinstance(source, str) or not source.strip():
|
|
82
|
+
return {"status": "error", "message": "source must be a non-empty string."}
|
|
83
|
+
source = source.strip()
|
|
84
|
+
|
|
85
|
+
install_mode = "url" if _looks_like_plugin_url(source) else "zip_upload"
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
if install_mode == "url":
|
|
89
|
+
effective_proxy = proxy.strip() if isinstance(proxy, str) and proxy.strip() else None
|
|
90
|
+
if prefer_proxy and not effective_proxy:
|
|
91
|
+
effective_proxy = (
|
|
92
|
+
os.getenv("ASTRBOTMCP_PLUGIN_PROXY")
|
|
93
|
+
or os.getenv("ASTRBOT_MCP_PLUGIN_PROXY")
|
|
94
|
+
or DEFAULT_PLUGIN_PROXY
|
|
95
|
+
).strip()
|
|
96
|
+
result = await client.install_plugin_from_url(
|
|
97
|
+
url=source,
|
|
98
|
+
proxy=(effective_proxy if prefer_proxy else None),
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
resolved = _resolve_local_file_path(client, source)
|
|
102
|
+
if not resolved.lower().endswith(".zip"):
|
|
103
|
+
return {
|
|
104
|
+
"status": "error",
|
|
105
|
+
"message": "Local plugin source must be a .zip file path.",
|
|
106
|
+
"source": source,
|
|
107
|
+
"resolved_path": resolved,
|
|
108
|
+
}
|
|
109
|
+
result = await client.install_plugin_from_file(resolved)
|
|
110
|
+
effective_proxy = None
|
|
111
|
+
except FileNotFoundError:
|
|
112
|
+
return {
|
|
113
|
+
"status": "error",
|
|
114
|
+
"message": f"Local zip file_path does not exist: {source!r}",
|
|
115
|
+
"hint": "If you passed a relative path, set ASTRBOTMCP_FILE_ROOT or run MCP in the expected working directory.",
|
|
116
|
+
}
|
|
117
|
+
except ValueError as e:
|
|
118
|
+
return {"status": "error", "message": str(e)}
|
|
119
|
+
except Exception as e:
|
|
120
|
+
payload = {
|
|
121
|
+
"status": "error",
|
|
122
|
+
"message": _astrbot_connect_hint(client),
|
|
123
|
+
"base_url": client.base_url,
|
|
124
|
+
"detail": _httpx_error_detail(e),
|
|
125
|
+
"source": source,
|
|
126
|
+
"install_mode": install_mode,
|
|
127
|
+
}
|
|
128
|
+
if include_logs:
|
|
129
|
+
payload["astrbot_logs_tail"] = await _get_astrbot_log_tail(
|
|
130
|
+
client, limit=log_tail_limit
|
|
131
|
+
)
|
|
132
|
+
return payload
|
|
133
|
+
|
|
134
|
+
payload: Dict[str, Any] = {
|
|
135
|
+
"status": result.get("status", "ok"),
|
|
136
|
+
"message": result.get("message"),
|
|
137
|
+
"install_mode": install_mode,
|
|
138
|
+
"source": source,
|
|
139
|
+
"proxy": effective_proxy if install_mode == "url" and prefer_proxy else None,
|
|
140
|
+
"raw": result,
|
|
141
|
+
}
|
|
142
|
+
if include_logs:
|
|
143
|
+
payload["astrbot_logs_tail"] = await _get_astrbot_log_tail(
|
|
144
|
+
client, limit=log_tail_limit
|
|
145
|
+
)
|
|
146
|
+
return payload
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def configure_astrbot_plugin_json(
|
|
150
|
+
conf_id: str,
|
|
151
|
+
plugin_path: Union[str, List[Any]],
|
|
152
|
+
action: Literal["inspect", "apply"] = "inspect",
|
|
153
|
+
path: Union[str, List[Any], None] = None,
|
|
154
|
+
include_value: bool = True,
|
|
155
|
+
max_children: int = 80,
|
|
156
|
+
redact_secrets: bool = True,
|
|
157
|
+
max_string_length: int = 400,
|
|
158
|
+
ops: Optional[List[Dict[str, Any]]] = None,
|
|
159
|
+
create_missing: bool = True,
|
|
160
|
+
include_logs: bool = True,
|
|
161
|
+
log_tail_limit: int = 120,
|
|
162
|
+
) -> Dict[str, Any]:
|
|
163
|
+
"""
|
|
164
|
+
Configure plugin JSON by reusing AstrBot config-tool style operations.
|
|
165
|
+
|
|
166
|
+
Internally this uses plugin-specific endpoints:
|
|
167
|
+
- GET /api/config/get?plugin_name=<name>
|
|
168
|
+
- POST /api/config/plugin/update?plugin_name=<name>
|
|
169
|
+
"""
|
|
170
|
+
client = AstrBotClient.from_env()
|
|
171
|
+
try:
|
|
172
|
+
plugin_name = _resolve_plugin_name(plugin_path)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
return {
|
|
175
|
+
"status": "error",
|
|
176
|
+
"message": str(e),
|
|
177
|
+
"action": action,
|
|
178
|
+
"conf_id": conf_id,
|
|
179
|
+
"plugin_path": plugin_path,
|
|
180
|
+
}
|
|
181
|
+
try:
|
|
182
|
+
plugin_resp = await client.get_plugin_config(plugin_name=plugin_name)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
payload = {
|
|
185
|
+
"status": "error",
|
|
186
|
+
"message": _astrbot_connect_hint(client),
|
|
187
|
+
"base_url": client.base_url,
|
|
188
|
+
"detail": _httpx_error_detail(e),
|
|
189
|
+
"action": action,
|
|
190
|
+
"conf_id": conf_id,
|
|
191
|
+
"plugin_name": plugin_name,
|
|
192
|
+
}
|
|
193
|
+
if include_logs:
|
|
194
|
+
payload["astrbot_logs_tail"] = await _get_astrbot_log_tail(
|
|
195
|
+
client, limit=log_tail_limit
|
|
196
|
+
)
|
|
197
|
+
return payload
|
|
198
|
+
|
|
199
|
+
if plugin_resp.get("status") != "ok":
|
|
200
|
+
payload = {
|
|
201
|
+
"status": plugin_resp.get("status") or "error",
|
|
202
|
+
"message": plugin_resp.get("message") or "Failed to load plugin config.",
|
|
203
|
+
"action": action,
|
|
204
|
+
"conf_id": conf_id,
|
|
205
|
+
"plugin_name": plugin_name,
|
|
206
|
+
"raw": plugin_resp,
|
|
207
|
+
}
|
|
208
|
+
if include_logs:
|
|
209
|
+
payload["astrbot_logs_tail"] = await _get_astrbot_log_tail(
|
|
210
|
+
client, limit=log_tail_limit
|
|
211
|
+
)
|
|
212
|
+
return payload
|
|
213
|
+
|
|
214
|
+
data = plugin_resp.get("data") or {}
|
|
215
|
+
plugin_config = data.get("config")
|
|
216
|
+
plugin_metadata = data.get("metadata")
|
|
217
|
+
if not isinstance(plugin_config, dict):
|
|
218
|
+
payload = {
|
|
219
|
+
"status": "error",
|
|
220
|
+
"message": (
|
|
221
|
+
f"Plugin {plugin_name!r} has no configurable JSON payload in "
|
|
222
|
+
"/api/config/get."
|
|
223
|
+
),
|
|
224
|
+
"action": action,
|
|
225
|
+
"conf_id": conf_id,
|
|
226
|
+
"plugin_name": plugin_name,
|
|
227
|
+
"raw": plugin_resp,
|
|
228
|
+
}
|
|
229
|
+
if include_logs:
|
|
230
|
+
payload["astrbot_logs_tail"] = await _get_astrbot_log_tail(
|
|
231
|
+
client, limit=log_tail_limit
|
|
232
|
+
)
|
|
233
|
+
return payload
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
if action == "inspect":
|
|
237
|
+
path_segments = _parse_path(path)
|
|
238
|
+
node = _get_node(plugin_config, path_segments)
|
|
239
|
+
leaf_name = None
|
|
240
|
+
if path_segments and isinstance(path_segments[-1], str):
|
|
241
|
+
leaf_name = path_segments[-1]
|
|
242
|
+
summary = _summarize_node(
|
|
243
|
+
node,
|
|
244
|
+
max_children=max_children,
|
|
245
|
+
include_value=include_value,
|
|
246
|
+
redact_secrets=redact_secrets,
|
|
247
|
+
leaf_name=leaf_name,
|
|
248
|
+
max_string_length=max_string_length,
|
|
249
|
+
)
|
|
250
|
+
payload = {
|
|
251
|
+
"status": "ok",
|
|
252
|
+
"action": action,
|
|
253
|
+
"conf_id": conf_id,
|
|
254
|
+
"conf_id_ignored_for_plugin_api": True,
|
|
255
|
+
"plugin_name": plugin_name,
|
|
256
|
+
"plugin_path": _parse_path(plugin_path),
|
|
257
|
+
"effective_path": path_segments,
|
|
258
|
+
"node": summary,
|
|
259
|
+
"metadata": plugin_metadata,
|
|
260
|
+
}
|
|
261
|
+
else:
|
|
262
|
+
if not isinstance(ops, list) or not ops:
|
|
263
|
+
return {
|
|
264
|
+
"status": "error",
|
|
265
|
+
"message": "ops must be a non-empty list when action='apply'.",
|
|
266
|
+
}
|
|
267
|
+
changed: List[List[Any]] = []
|
|
268
|
+
counters = {"set": 0, "add_key": 0, "append": 0}
|
|
269
|
+
for idx, op in enumerate(ops):
|
|
270
|
+
if not isinstance(op, dict):
|
|
271
|
+
return {"status": "error", "message": f"ops[{idx}] must be an object."}
|
|
272
|
+
op_name = (op.get("op") or "").strip()
|
|
273
|
+
path_segments = _parse_path(op.get("path"))
|
|
274
|
+
if op_name == "set":
|
|
275
|
+
if not path_segments:
|
|
276
|
+
return {"status": "error", "message": "set op requires non-empty path"}
|
|
277
|
+
_set_value(
|
|
278
|
+
plugin_config,
|
|
279
|
+
path_segments,
|
|
280
|
+
op.get("value"),
|
|
281
|
+
create_missing=create_missing,
|
|
282
|
+
)
|
|
283
|
+
changed.append(path_segments)
|
|
284
|
+
counters["set"] += 1
|
|
285
|
+
elif op_name == "add_key":
|
|
286
|
+
key = op.get("key")
|
|
287
|
+
if not isinstance(key, str) or not key.strip():
|
|
288
|
+
return {
|
|
289
|
+
"status": "error",
|
|
290
|
+
"message": "add_key op requires non-empty string 'key'",
|
|
291
|
+
}
|
|
292
|
+
_add_key(
|
|
293
|
+
plugin_config,
|
|
294
|
+
path_segments,
|
|
295
|
+
key=key,
|
|
296
|
+
value=op.get("value"),
|
|
297
|
+
create_missing=create_missing,
|
|
298
|
+
)
|
|
299
|
+
changed.append(path_segments + [key])
|
|
300
|
+
counters["add_key"] += 1
|
|
301
|
+
elif op_name == "append":
|
|
302
|
+
_append_list_item(
|
|
303
|
+
plugin_config,
|
|
304
|
+
path_segments,
|
|
305
|
+
value=op.get("value"),
|
|
306
|
+
create_missing=create_missing,
|
|
307
|
+
)
|
|
308
|
+
changed.append(path_segments + ["-"])
|
|
309
|
+
counters["append"] += 1
|
|
310
|
+
else:
|
|
311
|
+
return {"status": "error", "message": f"Unsupported op: {op_name!r}"}
|
|
312
|
+
|
|
313
|
+
update_result = await client.update_plugin_config(
|
|
314
|
+
plugin_name=plugin_name,
|
|
315
|
+
config=plugin_config,
|
|
316
|
+
)
|
|
317
|
+
payload = {
|
|
318
|
+
"status": update_result.get("status", "ok"),
|
|
319
|
+
"message": update_result.get("message"),
|
|
320
|
+
"action": action,
|
|
321
|
+
"conf_id": conf_id,
|
|
322
|
+
"conf_id_ignored_for_plugin_api": True,
|
|
323
|
+
"plugin_name": plugin_name,
|
|
324
|
+
"plugin_path": _parse_path(plugin_path),
|
|
325
|
+
"applied": counters,
|
|
326
|
+
"changed_paths": changed,
|
|
327
|
+
"raw": update_result,
|
|
328
|
+
}
|
|
329
|
+
except Exception as e:
|
|
330
|
+
payload = {
|
|
331
|
+
"status": "error",
|
|
332
|
+
"message": str(e),
|
|
333
|
+
"action": action,
|
|
334
|
+
"conf_id": conf_id,
|
|
335
|
+
"plugin_name": plugin_name,
|
|
336
|
+
"plugin_path": _parse_path(plugin_path),
|
|
337
|
+
"path": path,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if include_logs:
|
|
341
|
+
payload["astrbot_logs_tail"] = await _get_astrbot_log_tail(
|
|
342
|
+
client, limit=log_tail_limit
|
|
343
|
+
)
|
|
344
|
+
return payload
|
|
@@ -92,6 +92,33 @@ def _matches_query(item: Dict[str, Any], query: str) -> bool:
|
|
|
92
92
|
return True
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
def _plugin_url(item: Dict[str, Any]) -> str | None:
|
|
96
|
+
# AstrBot plugin market convention: repository is stored in `repo`.
|
|
97
|
+
for key in ("repo", "url", "homepage", "website", "source"):
|
|
98
|
+
raw = item.get(key)
|
|
99
|
+
if raw is None:
|
|
100
|
+
continue
|
|
101
|
+
s = str(raw).strip()
|
|
102
|
+
if not s:
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
s = s.removeprefix("git+")
|
|
106
|
+
if s.startswith("git@github.com:"):
|
|
107
|
+
s = "https://github.com/" + s.split("git@github.com:", 1)[1]
|
|
108
|
+
elif s.startswith("github.com/"):
|
|
109
|
+
s = "https://" + s
|
|
110
|
+
elif "://" not in s:
|
|
111
|
+
if s.count("/") == 1 and " " not in s and not s.startswith("/"):
|
|
112
|
+
s = "https://github.com/" + s
|
|
113
|
+
elif "." in s.split("/", 1)[0]:
|
|
114
|
+
s = "https://" + s
|
|
115
|
+
|
|
116
|
+
if s.endswith(".git"):
|
|
117
|
+
s = s[:-4]
|
|
118
|
+
return s
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
95
122
|
async def _fetch_default_registry(timeout: float = 30.0) -> Dict[str, Any]:
|
|
96
123
|
url = "https://api.soulter.top/astrbot/plugins"
|
|
97
124
|
async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client:
|
|
@@ -194,6 +221,7 @@ async def browse_plugin_market(
|
|
|
194
221
|
"tags": [str(t) for t in tags],
|
|
195
222
|
"stars": _as_int(it.get("stars") or it.get("star") or 0),
|
|
196
223
|
"updated_at": it.get("updated_at"),
|
|
224
|
+
"url": _plugin_url(it),
|
|
197
225
|
}
|
|
198
226
|
)
|
|
199
227
|
|
astrbot_mcp/tools.py
CHANGED
|
@@ -19,10 +19,12 @@ Example:
|
|
|
19
19
|
from . import (
|
|
20
20
|
get_astrbot_logs,
|
|
21
21
|
get_message_platforms,
|
|
22
|
-
send_platform_message_direct,
|
|
23
22
|
send_platform_message,
|
|
24
23
|
restart_astrbot,
|
|
25
24
|
get_platform_session_messages,
|
|
25
|
+
install_astrbot_plugin,
|
|
26
|
+
configure_astrbot_plugin_json,
|
|
27
|
+
manage_mcp_config_panel,
|
|
26
28
|
list_astrbot_config_files,
|
|
27
29
|
inspect_astrbot_config,
|
|
28
30
|
apply_astrbot_config_ops,
|
|
@@ -33,10 +35,12 @@ from . import (
|
|
|
33
35
|
__all__ = [
|
|
34
36
|
"get_astrbot_logs",
|
|
35
37
|
"get_message_platforms",
|
|
36
|
-
"send_platform_message_direct",
|
|
37
38
|
"send_platform_message",
|
|
38
39
|
"restart_astrbot",
|
|
39
40
|
"get_platform_session_messages",
|
|
41
|
+
"install_astrbot_plugin",
|
|
42
|
+
"configure_astrbot_plugin_json",
|
|
43
|
+
"manage_mcp_config_panel",
|
|
40
44
|
"list_astrbot_config_files",
|
|
41
45
|
"inspect_astrbot_config",
|
|
42
46
|
"apply_astrbot_config_ops",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astrbotmcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -16,9 +16,7 @@ Dynamic: license-file
|
|
|
16
16
|
|
|
17
17
|
[](https://lobehub.com/mcp/xunxiing-astrbotmcp)
|
|
18
18
|
|
|
19
|
-
> **AstrBot 无法通过 MCP
|
|
20
|
-
|
|
21
|
-
### 警告与免责声明
|
|
19
|
+
> **AstrBot 无法通过 MCP 控制自身。本项目填补了这一空白,为Astrbot开发者提供AI AGENT时代调试插件的自动化工具**
|
|
22
20
|
|
|
23
21
|
⚠️ **本项目提供的是运维级控制能力,使用时请注意:**
|
|
24
22
|
|
|
@@ -27,33 +25,12 @@ Dynamic: license-file
|
|
|
27
25
|
3. **生产环境** - 建议仅在开发/测试环境使用控制面功能
|
|
28
26
|
4. **数据安全** - 日志可能包含敏感信息,注意脱敏处理
|
|
29
27
|
|
|
30
|
-
**本项目与 AstrBot 官方无直接关联,由社区独立维护。**
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
### 这个项目到底在干什么
|
|
35
|
-
|
|
36
|
-
#### AstrBot 自身的 MCP 控制面
|
|
37
|
-
|
|
38
|
-
通过 MCP tool 实现:
|
|
39
|
-
|
|
40
|
-
- **重启 AstrBot Core** - 进程级控制,直接调用 `/api/stat/restart-core`
|
|
41
|
-
- **运行状态监听** - 实时日志流、平台状态监控
|
|
42
|
-
- **配置热加载** - 动态读取/修改配置
|
|
43
|
-
- **发送信息** -自动化测试插件
|
|
44
|
-
- **浏览插件市场**
|
|
45
|
-
|
|
46
|
-
#### 为astrbot开发者提供AI AGENT时代调试插件的自动化工具
|
|
47
|
-
|
|
48
|
-
---
|
|
28
|
+
### **本项目与 AstrBot 官方无直接关联,由社区独立维护。**
|
|
49
29
|
|
|
50
30
|
### 快速开始
|
|
51
31
|
|
|
52
32
|
#### 安装
|
|
53
33
|
|
|
54
|
-
<details>
|
|
55
|
-
<summary>通过 PyPI 或 uv 安装</summary>
|
|
56
|
-
|
|
57
34
|
```bash
|
|
58
35
|
# 通过 PyPI 安装(推荐)
|
|
59
36
|
pip install astrbotmcp
|
|
@@ -62,7 +39,8 @@ pip install astrbotmcp
|
|
|
62
39
|
uv add astrbotmcp
|
|
63
40
|
```
|
|
64
41
|
|
|
65
|
-
|
|
42
|
+
<details>
|
|
43
|
+
<summary>通过 PyPI 或 uv 安装</summary>
|
|
66
44
|
|
|
67
45
|
```json
|
|
68
46
|
{
|
|
@@ -85,6 +63,8 @@ uv add astrbotmcp
|
|
|
85
63
|
}
|
|
86
64
|
```
|
|
87
65
|
|
|
66
|
+
安装完成后,您可以通过以下方式在 MCP 客户端中配置:
|
|
67
|
+
|
|
88
68
|
</details>
|
|
89
69
|
|
|
90
70
|
<details>
|
|
@@ -105,7 +85,8 @@ npm install -g astrbotmcp
|
|
|
105
85
|
"astrbot-mcp": {
|
|
106
86
|
"command": "npx",
|
|
107
87
|
"args": [
|
|
108
|
-
"
|
|
88
|
+
"-y",
|
|
89
|
+
"@xunxiing/astrbot-mcp@latest"
|
|
109
90
|
],
|
|
110
91
|
"env": {
|
|
111
92
|
"ASTRBOT_BASE_URL": "http://127.0.0.1:6185",
|
|
@@ -122,14 +103,15 @@ npm install -g astrbotmcp
|
|
|
122
103
|
|
|
123
104
|
#### 环境变量说明
|
|
124
105
|
|
|
125
|
-
| 变量
|
|
126
|
-
|
|
127
|
-
| `ASTRBOT_BASE_URL`
|
|
128
|
-
| `ASTRBOT_TIMEOUT`
|
|
129
|
-
| `ASTRBOT_USERNAME`
|
|
130
|
-
| `ASTRBOT_PASSWORD`
|
|
131
|
-
| `ASTRBOT_LOG_LEVEL`
|
|
132
|
-
| `ASTRBOTMCP_DISABLE_PROXY` | 是否禁用代理(防止本地请求被代理拦截)
|
|
106
|
+
| 变量 | 说明 | 默认值 |
|
|
107
|
+
| ---------------------------- | -------------------------------------------------------| ------------------------- |
|
|
108
|
+
| `ASTRBOT_BASE_URL` | AstrBot Dashboard 地址 | `http://127.0.0.1:6185` |
|
|
109
|
+
| `ASTRBOT_TIMEOUT` | HTTP 请求超时时间 | `30` |
|
|
110
|
+
| `ASTRBOT_USERNAME` | Dashboard 用户名 | - |
|
|
111
|
+
| `ASTRBOT_PASSWORD` | Dashboard 密码 | - |
|
|
112
|
+
| `ASTRBOT_LOG_LEVEL` | 日志级别 | `INFO` |
|
|
113
|
+
| `ASTRBOTMCP_DISABLE_PROXY` | 是否禁用代理(防止本地请求被代理拦截) | `true` |
|
|
114
|
+
| `ASTRBOTMCP_PLUGIN_PROXY` | 插件 URL 安装默认代理前缀(`install_astrbot_plugin`) | `https://gh-proxy.com` |
|
|
133
115
|
|
|
134
116
|
#### 代理配置说明
|
|
135
117
|
|
|
@@ -138,10 +120,9 @@ npm install -g astrbotmcp
|
|
|
138
120
|
**解决方案:**
|
|
139
121
|
|
|
140
122
|
1. **默认行为**:AstrBot MCP 默认禁用代理(`ASTRBOTMCP_DISABLE_PROXY=true`),确保本地请求直接发送到 AstrBot。
|
|
141
|
-
|
|
142
123
|
2. **如果需要使用代理**:设置 `ASTRBOTMCP_DISABLE_PROXY=false`,但请注意这可能导致本地 API 请求失败。
|
|
143
|
-
|
|
144
124
|
3. **推荐配置**:对于本地 AstrBot 实例,始终禁用代理:
|
|
125
|
+
|
|
145
126
|
```json
|
|
146
127
|
{
|
|
147
128
|
"mcpServers": {
|
|
@@ -180,13 +161,18 @@ npm install -g astrbotmcp
|
|
|
180
161
|
|
|
181
162
|
#### 消息工具
|
|
182
163
|
|
|
183
|
-
- `send_platform_message` - 通过 Web Chat API
|
|
184
|
-
- `send_platform_message_direct` - 直接发送到平台(绕过 LLM)
|
|
164
|
+
- `send_platform_message` - 通过 Web Chat API 发送消息链(仅 WebUI;无需 `platform_id` / `target_id`)
|
|
185
165
|
- `get_platform_session_messages` - 读取会话消息历史
|
|
186
166
|
|
|
187
167
|
#### 插件市场
|
|
188
168
|
|
|
189
169
|
- `browse_plugin_market` - 浏览插件市场(搜索/排序)
|
|
170
|
+
- `install_astrbot_plugin` - Install plugin via URL or local zip path (proxy enabled by default)
|
|
171
|
+
- `configure_astrbot_plugin_json` - Configure plugin JSON by reusing AstrBot config ops
|
|
172
|
+
|
|
173
|
+
#### MCP 面板
|
|
174
|
+
|
|
175
|
+
- `manage_mcp_config_panel` - Access MCP panel APIs (`list` / `add` / `update` / `delete` / `test`)
|
|
190
176
|
|
|
191
177
|
---
|
|
192
178
|
|
|
@@ -211,12 +197,18 @@ logs = get_astrbot_logs(wait_seconds=10)
|
|
|
211
197
|
```python
|
|
212
198
|
# 发送带图片的消息链
|
|
213
199
|
send_platform_message(
|
|
214
|
-
platform_id="webchat",
|
|
215
200
|
message="Hello from MCP",
|
|
216
201
|
images=["/path/to/image.png"]
|
|
217
202
|
)
|
|
218
203
|
```
|
|
219
204
|
|
|
205
|
+
```python
|
|
206
|
+
# 插件命令请直接发送纯文本,不要命令前缀
|
|
207
|
+
send_platform_message(
|
|
208
|
+
message="抽老婆帮助"
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
220
212
|
---
|
|
221
213
|
|
|
222
214
|
### 技术架构
|
|
@@ -256,4 +248,3 @@ uv run --project . astrbot-mcp
|
|
|
256
248
|
### 许可证
|
|
257
249
|
|
|
258
250
|
MIT License - 详见 [LICENSE](LICENSE.txt) 文件。
|
|
259
|
-
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
astrbot_mcp/__init__.py,sha256=nDTFGuA6IlvCywUVliWHmmFBkQQsnLVgnZDFYfdALbo,370
|
|
2
|
+
astrbot_mcp/astrbot_client.py,sha256=3rIgnNfLotbEtNA2erKBSLUW7qqy3NXzHyFRq2DKRd0,27868
|
|
3
|
+
astrbot_mcp/config.py,sha256=0O9rFUsEVMLuIxCQR00n_IQISK6fx8zY2OO2d2ywqjs,2695
|
|
4
|
+
astrbot_mcp/server.py,sha256=LfIJBeX3bQv7Yk2bUpWu8QcWGliZ3gNzMSIJj5XQvIg,3611
|
|
5
|
+
astrbot_mcp/tools.py,sha256=0Wz_bX0waHZm6UNXQeVHdJr8_yKQYlVLEyNUvw7wyEQ,1381
|
|
6
|
+
astrbot_mcp/tools/__init__.py,sha256=6qFhwjWl1TpQadR-3p8NUqH5-fjiJTbnyjwVvtwgFaw,2195
|
|
7
|
+
astrbot_mcp/tools/config_search_tool.py,sha256=qNCeN92dztolFRxsr8x9B1mdKkdnw4c0RrIzxmd2Tvc,5948
|
|
8
|
+
astrbot_mcp/tools/config_tools.py,sha256=-AcXQ76rydpEHBH_RrBE8QCG6nV_xVLSSTQ9XKAt7xs,21614
|
|
9
|
+
astrbot_mcp/tools/control_tools.py,sha256=MaCP20AaQbTbFPB3oExTj7VOVUM8Xz7UyMhklKMhHfw,2309
|
|
10
|
+
astrbot_mcp/tools/helpers.py,sha256=XGsAF1z1dLnGAs9ARyNynAilYgqOuw1hluP9ojYq5rA,3064
|
|
11
|
+
astrbot_mcp/tools/log_tools.py,sha256=iDmBPKrZuu4YLD4o6f7v_s9zGe9sILDXLiYmRaCZWlo,1958
|
|
12
|
+
astrbot_mcp/tools/mcp_panel_tools.py,sha256=962P5PYeII3V9N-ThE_BgNC6U6Add_e5IkAPx0VSeKI,4786
|
|
13
|
+
astrbot_mcp/tools/platform_tools.py,sha256=61dTxf2T6BYh9Z5Wos3MiJLCMNEwV5RdSSCAxapWeNQ,986
|
|
14
|
+
astrbot_mcp/tools/plugin_admin_tools.py,sha256=TwvOfaTbY4QEuQ4-7S5RjwjZU9PqV967xHBqIePzQhA,12259
|
|
15
|
+
astrbot_mcp/tools/plugin_market_tools.py,sha256=s2myc6K9hiNtLq0V7GE0TE5xMyU0vbBR_BKksQV3uE0,8267
|
|
16
|
+
astrbot_mcp/tools/session_tools.py,sha256=isEAydi3cM9IwdPjqZkD5PoFI5R_-jL_lBYBSCpxk48,21149
|
|
17
|
+
astrbot_mcp/tools/types.py,sha256=rT0izWeUxgO_qoHW1w0xU4iwYciUT2eQ5-vxT26oQWo,1243
|
|
18
|
+
astrbot_mcp/tools/message/__init__.py,sha256=z1XGiLertkhJROIYi6-dfOXr2EHmum8ZmX6ywl0V9EY,80
|
|
19
|
+
astrbot_mcp/tools/message/cache.py,sha256=IbqQ_efKqHzaArbyVgGwmfct_9Vknn_y6pKk17VJXDQ,776
|
|
20
|
+
astrbot_mcp/tools/message/direct.py,sha256=fEHW3GhI358o5AxQNek8hsEHsSMkTVVzMe_c-RdkZb8,9779
|
|
21
|
+
astrbot_mcp/tools/message/quote.py,sha256=dua9jo0d3dwTzASTpIVm64STZw_lXrpo8KOqWd-oT0Y,2335
|
|
22
|
+
astrbot_mcp/tools/message/utils.py,sha256=2xjq_uq09EucNpIQ_nyEmTGCWBMhYIvKEbZeToWsQMw,1902
|
|
23
|
+
astrbot_mcp/tools/message/webchat.py,sha256=3n_j32CvLQg4wuTh4m6YIRdGLmfyecK0U2rNbgq6zDU,30022
|
|
24
|
+
astrbotmcp-0.4.1.dist-info/licenses/LICENSE.txt,sha256=5AYBumh99nqD7WWRY18ySSOIUKrj3bkAhVAiY-k8ZRo,1061
|
|
25
|
+
astrbotmcp-0.4.1.dist-info/METADATA,sha256=UZWeiBiACuDqNKBIkGZJi5ZbkY7YR2x6VHvpCZRKl1E,7528
|
|
26
|
+
astrbotmcp-0.4.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
27
|
+
astrbotmcp-0.4.1.dist-info/entry_points.txt,sha256=XmfseRwldB3CJKlViESKuZNmw37qV2B57to8EQqvd5Q,56
|
|
28
|
+
astrbotmcp-0.4.1.dist-info/top_level.txt,sha256=yi4CO_u3RImIkeQ562K9EbEc0nnKVgHQupSZ_X1GEO0,12
|
|
29
|
+
astrbotmcp-0.4.1.dist-info/RECORD,,
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
astrbot_mcp/__init__.py,sha256=nDTFGuA6IlvCywUVliWHmmFBkQQsnLVgnZDFYfdALbo,370
|
|
2
|
-
astrbot_mcp/astrbot_client.py,sha256=PCP7CRBlmloVtvYPpa5Lo6oauvf1Cq2PWRYLseN9T1M,24323
|
|
3
|
-
astrbot_mcp/config.py,sha256=yRAlOaKuLyM7uMz3zA4EVEDjqK5j5i_uMZ2Ecv6Fs4o,3221
|
|
4
|
-
astrbot_mcp/server.py,sha256=wfSrDHXeX5QUjIJ70WL5niEt4l9klF6JDgNS3HX6G3I,3293
|
|
5
|
-
astrbot_mcp/tools.py,sha256=rBL89W_4B7djcuGwBekLp-6pqi8dbmic8P3LJUIForI,1261
|
|
6
|
-
astrbot_mcp/tools/__init__.py,sha256=EkPBXvdPCCmBOpXuSme755gQC7YqVPxeGq40ogpEW2M,2078
|
|
7
|
-
astrbot_mcp/tools/config_search_tool.py,sha256=qNCeN92dztolFRxsr8x9B1mdKkdnw4c0RrIzxmd2Tvc,5948
|
|
8
|
-
astrbot_mcp/tools/config_tools.py,sha256=-AcXQ76rydpEHBH_RrBE8QCG6nV_xVLSSTQ9XKAt7xs,21614
|
|
9
|
-
astrbot_mcp/tools/control_tools.py,sha256=MaCP20AaQbTbFPB3oExTj7VOVUM8Xz7UyMhklKMhHfw,2309
|
|
10
|
-
astrbot_mcp/tools/helpers.py,sha256=XGsAF1z1dLnGAs9ARyNynAilYgqOuw1hluP9ojYq5rA,3064
|
|
11
|
-
astrbot_mcp/tools/log_tools.py,sha256=iDmBPKrZuu4YLD4o6f7v_s9zGe9sILDXLiYmRaCZWlo,1958
|
|
12
|
-
astrbot_mcp/tools/platform_tools.py,sha256=61dTxf2T6BYh9Z5Wos3MiJLCMNEwV5RdSSCAxapWeNQ,986
|
|
13
|
-
astrbot_mcp/tools/plugin_market_tools.py,sha256=IfTeM7B0X_6gW1QhCPIQn1oa1alPhw1h6mjq1uteSD0,7349
|
|
14
|
-
astrbot_mcp/tools/session_tools.py,sha256=isEAydi3cM9IwdPjqZkD5PoFI5R_-jL_lBYBSCpxk48,21149
|
|
15
|
-
astrbot_mcp/tools/types.py,sha256=rT0izWeUxgO_qoHW1w0xU4iwYciUT2eQ5-vxT26oQWo,1243
|
|
16
|
-
astrbot_mcp/tools/message/__init__.py,sha256=WG5NMj_rjpCTo86RC858lc3bLPTJNFcJeTdA2AIAbkQ,160
|
|
17
|
-
astrbot_mcp/tools/message/cache.py,sha256=IbqQ_efKqHzaArbyVgGwmfct_9Vknn_y6pKk17VJXDQ,776
|
|
18
|
-
astrbot_mcp/tools/message/direct.py,sha256=fEHW3GhI358o5AxQNek8hsEHsSMkTVVzMe_c-RdkZb8,9779
|
|
19
|
-
astrbot_mcp/tools/message/quote.py,sha256=dua9jo0d3dwTzASTpIVm64STZw_lXrpo8KOqWd-oT0Y,2335
|
|
20
|
-
astrbot_mcp/tools/message/utils.py,sha256=2xjq_uq09EucNpIQ_nyEmTGCWBMhYIvKEbZeToWsQMw,1902
|
|
21
|
-
astrbot_mcp/tools/message/webchat.py,sha256=qLQQNGAc9bkH9k2nVaPnG2M3Td0_rtwW3eGHJ-I_2cA,29395
|
|
22
|
-
astrbotmcp-0.3.1.dist-info/licenses/LICENSE.txt,sha256=5AYBumh99nqD7WWRY18ySSOIUKrj3bkAhVAiY-k8ZRo,1061
|
|
23
|
-
astrbotmcp-0.3.1.dist-info/METADATA,sha256=6kcRdToomLBJWw6DSFx30AL6qClIcM2cgCtD_0oKrx4,6847
|
|
24
|
-
astrbotmcp-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
-
astrbotmcp-0.3.1.dist-info/entry_points.txt,sha256=XmfseRwldB3CJKlViESKuZNmw37qV2B57to8EQqvd5Q,56
|
|
26
|
-
astrbotmcp-0.3.1.dist-info/top_level.txt,sha256=yi4CO_u3RImIkeQ562K9EbEc0nnKVgHQupSZ_X1GEO0,12
|
|
27
|
-
astrbotmcp-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|