mcp polishing
frdel committed
Jun 4, 2025 at 17:03 UTC
08f69c10126f958a03cf6608ed6bb6b2b8a4d282
8 files changed
+319
-65
docker/run/fs/ins/post_install.sh
+1
-1
@@ -2,4 +2,4 @@
2
3
# Cleanup package list
4
rm -rf /var/lib/apt/lists/*
5
-# apt-get clean
\ No newline at end of file
5
+apt-get clean
\ No newline at end of file
python/api/mcp_server_get_detail.py
new
+20
@@ -0,0 +1,20 @@
1
+from math import log
2
+from python.helpers.api import ApiHandler
3
+from flask import Request, Response
4
+
5
+from typing import Any
6
+
7
+from python.helpers.mcp_handler import MCPConfig
8
+
9
+
10
+class McpServerGetDetail(ApiHandler):
11
+ async def process(self, input: dict[Any, Any], request: Request) -> dict[Any, Any] | Response:
12
+
13
+ # try:
14
+ server_name = input.get("server_name")
15
+ if not server_name:
16
+ return {"success": False, "error": "Missing server_name"}
17
+ detail = MCPConfig.get_instance().get_server_detail(server_name)
18
+ return {"success": True, "detail": detail}
19
+ # except Exception as e:
20
+ # return {"success": False, "error": str(e)}
python/helpers/mcp_handler.py
+29
-8
@@ -5,6 +5,7 @@ from typing import (
5
Dict,
6
Optional,
7
Any,
8
+ TextIO,
9
Union,
10
Literal,
11
Annotated,
@@ -623,6 +624,8 @@ class MCPConfig(BaseModel):
624
connected = True # tool_count > 0
625
# get error message if any
626
error = server.get_error()
627
+ # get log bool
628
+ has_log = server.get_log() != ""
629
630
# add server status to result
631
result.append(
@@ -631,6 +634,7 @@ class MCPConfig(BaseModel):
634
"connected": connected,
635
"error": error,
636
"tool_count": tool_count,
637
+ "has_log": has_log,
638
}
639
)
640
@@ -642,11 +646,27 @@ class MCPConfig(BaseModel):
646
"connected": False,
647
"error": disconnected["error"],
648
"tool_count": 0,
649
+ "has_log": False,
650
}
651
)
652
653
return result
654
655
+ def get_server_detail(self, server_name: str) -> dict[str, Any]:
656
+ with self.__lock:
657
+ for server in self.servers:
658
+ if server.name == server_name:
659
+ try:
660
+ tools = server.get_tools()
661
+ except Exception as e:
662
+ tools = []
663
+ return {
664
+ "name": server.name,
665
+ "description": server.description,
666
+ "tools": tools,
667
+ }
668
+ return {}
669
+
670
def is_initialized(self) -> bool:
671
"""Check if the client is initialized"""
672
with self.__lock:
@@ -677,13 +697,16 @@ class MCPConfig(BaseModel):
697
for server in self.servers:
698
if server.name in server_names:
699
server_name = server.name
700
+ prompt += f"### {server_name}\n"
701
+ prompt += f"{server.description}\n\n"
702
+
703
for tool in server.get_tools():
704
prompt += (
705
f"### {server_name}.{tool['name']}:\n"
706
f"{tool['description']}\n\n"
684
- f"#### Categories:\n"
685
- f"* kind: MCP Server Tool\n"
686
- f'* server: "{server_name}" ({server.description})\n\n'
707
+ # f"#### Categories:\n"
708
+ # f"* kind: MCP Server Tool\n"
709
+ # f'* server: "{server_name}" ({server.description})\n\n'
710
f"#### Arguments:\n"
711
)
712
@@ -783,6 +806,7 @@ class MCPClientBase(ABC):
806
self.tools: List[dict[str, Any]] = [] # Tools are cached on the client instance
807
self.error: str = ""
808
self.log: List[str] = []
809
+ self.log_file: Optional[TextIO] = None
810
811
# Protected method
812
@abstractmethod
@@ -946,8 +970,6 @@ class MCPClientBase(ABC):
970
f"MCPClientBase::Failed to call tool '{tool_name}' on server '{self.server.name}'. Original error: {type(e).__name__}: {e}"
971
)
972
949
-
950
-class MCPClientLocal(MCPClientBase):
973
def get_log(self):
974
# read and return lines from self.log_file, do not close it
975
if not hasattr(self, 'log_file') or self.log_file is None:
@@ -959,6 +981,8 @@ class MCPClientLocal(MCPClientBase):
981
log = ""
982
return log
983
984
+
985
+class MCPClientLocal(MCPClientBase):
986
def __del__(self):
987
# close the log file if it exists
988
if hasattr(self, 'log_file') and self.log_file is not None:
@@ -1006,9 +1030,6 @@ class MCPClientLocal(MCPClientBase):
1030
1031
class MCPClientRemote(MCPClientBase):
1032
1009
- def get_log(self):
1010
- return "Logging not implemented for remote servers yet"
1011
-
1033
async def _create_stdio_transport(
1034
self, current_exit_stack: AsyncExitStack
1035
) -> tuple[
run_ui.py
+11
-2
@@ -186,8 +186,9 @@ def run():
186
PrintStyle().error(f"Failed to start Cloudflare tunnel: {e}")
187
PrintStyle().print("Continuing without tunnel...")
188
189
- # initialize contexts from persisted chats
190
- persist_chat.load_tmp_chats()
189
+ # # initialize contexts from persisted chats - moved to async task
190
+ # persist_chat.load_tmp_chats()
191
+
192
# # reload scheduler
193
# scheduler = TaskScheduler.get()
194
# asyncio.run(scheduler.reload())
@@ -301,12 +302,20 @@ def run():
302
303
process.set_server(server)
304
server.log_startup()
305
+
306
+ # Start init_a0 in a background thread when server starts
307
+ import threading
308
+ threading.Thread(target=init_a0, daemon=True).start()
309
+
310
server.serve_forever()
311
finally:
312
# Clean up tunnel if it was started
313
if tunnel:
314
tunnel.stop()
315
316
+def init_a0():
317
+ # initialize contexts from persisted chats
318
+ persist_chat.load_tmp_chats()
319
320
# run the internal server
321
if __name__ == "__main__":
webui/components/settings/mcp/client/mcp-server-descripton.html
new
+24
@@ -0,0 +1,24 @@
1
+<html>
2
+
3
+<head>
4
+ <title>MCP Server Detail</title>
5
+
6
+ <script type="module">
7
+ import { store } from "/components/settings/mcp/client/mcp-servers-store.js";
8
+ </script>
9
+</head>
10
+
11
+<body>
12
+ <div x-data>
13
+ <template x-if="$store.mcpServersStore">
14
+ <h3 x-text="$store.mcpServersStore.serverDetail.name"></h3>
15
+ <p x-text="$store.mcpServersStore.serverDetail.description"></p>
16
+ </template>
17
+ </div>
18
+
19
+ <style>
20
+ </style>
21
+
22
+</body>
23
+
24
+</html>
\ No newline at end of file
webui/components/settings/mcp/client/mcp-server-tools.html
new
+113
@@ -0,0 +1,113 @@
1
+<html>
2
+
3
+<head>
4
+ <title>MCP Server Detail</title>
5
+
6
+ <script type="module">
7
+ import { store } from "/components/settings/mcp/client/mcp-servers-store.js";
8
+ </script>
9
+</head>
10
+
11
+<body>
12
+ <div x-data>
13
+ <template x-if="$store.mcpServersStore">
14
+ <div>
15
+ <h3 x-text="$store.mcpServersStore.serverDetail.name"></h3>
16
+ <p x-text="$store.mcpServersStore.serverDetail.description"></p>
17
+
18
+ <div class="tools-container">
19
+ <template x-for="tool in $store.mcpServersStore.serverDetail.tools" :key="tool.name">
20
+ <div class="tool-item">
21
+ <h4 x-text="tool.name"></h4>
22
+ <p class="tool-description" x-text="tool.description"></p>
23
+
24
+ <template x-if="tool.input_schema?.properties">
25
+ <div class="tool-properties">
26
+ <p class="properties-title">Properties:</p>
27
+ <ul>
28
+ <template x-for="(prop, propName) in tool.input_schema.properties"
29
+ :key="propName">
30
+ <li>
31
+ <span class="prop-name" x-text="propName"></span>:
32
+ <span class="prop-type" x-text="prop.type || 'any'"></span>
33
+ <template x-if="prop.description">
34
+ <span class="prop-desc" x-text="' - ' + prop.description"></span>
35
+ </template>
36
+ </li>
37
+ </template>
38
+ </ul>
39
+ </div>
40
+ </template>
41
+ </div>
42
+ </template>
43
+ </div>
44
+ </div>
45
+ </template>
46
+ </div>
47
+
48
+ <style>
49
+ .tools-container {
50
+ margin-top: 1.5em;
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 1.2em;
54
+ }
55
+
56
+ .tool-item {
57
+ padding: 1em;
58
+ border: 1px solid rgba(192, 192, 192, 0.16);
59
+ border-radius: 4px;
60
+ }
61
+
62
+ .tool-item h4 {
63
+ margin-top: 0;
64
+ margin-bottom: 0.5em;
65
+ font-size: 1.1em;
66
+ }
67
+
68
+ .tool-description {
69
+ margin-bottom: 1em;
70
+ color: var(--c-fg);
71
+ line-height: 1.4;
72
+ }
73
+
74
+ .tool-properties {
75
+ margin-top: 0.8em;
76
+ padding: 0.8em;
77
+ background-color: rgba(0, 0, 0, 0.04);
78
+ border-radius: 3px;
79
+ }
80
+
81
+ .properties-title {
82
+ font-weight: 600;
83
+ margin-top: 0;
84
+ margin-bottom: 0.5em;
85
+ }
86
+
87
+ .tool-properties ul {
88
+ margin: 0;
89
+ padding-left: 1.5em;
90
+ }
91
+
92
+ .tool-properties li {
93
+ margin-bottom: 0.3em;
94
+ }
95
+
96
+ .prop-name {
97
+ font-weight: 600;
98
+ color: var(--c-accent);
99
+ }
100
+
101
+ .prop-type {
102
+ color: var(--c-fg2);
103
+ font-style: italic;
104
+ }
105
+
106
+ .prop-desc {
107
+ color: var(--c-fg);
108
+ }
109
+ </style>
110
+
111
+</body>
112
+
113
+</html>
\ No newline at end of file
webui/components/settings/mcp/client/mcp-servers-store.js
+21
-1
@@ -115,7 +115,27 @@ const model = {
115
});
116
if (resp.success) {
117
this.serverLog = resp.log;
118
- openModal("settings/mcp/client/mcp-servers-log.html")
118
+ openModal("settings/mcp/client/mcp-servers-log.html");
119
+ }
120
+ },
121
+
122
+ async onServerClick(serverName) {
123
+ const resp = await API.callJsonApi("mcp_server_get_detail", {
124
+ server_name: serverName,
125
+ });
126
+ if (resp.success) {
127
+ this.serverDetail = resp.detail;
128
+ openModal("settings/mcp/client/mcp-server-description.html");
129
+ }
130
+ },
131
+
132
+ async onToolCountClick(serverName) {
133
+ const resp = await API.callJsonApi("mcp_server_get_detail", {
134
+ server_name: serverName,
135
+ });
136
+ if (resp.success) {
137
+ this.serverDetail = resp.detail;
138
+ openModal("settings/mcp/client/mcp-server-tools.html");
139
}
140
},
141
};
webui/components/settings/mcp/client/mcp-servers.html
+100
-53
@@ -27,24 +27,41 @@
27
<div class="server-list">
28
<template x-for="server in $store.mcpServersStore.servers" :key="server.name">
29
<div class="server-item">
30
- <div class="status-icon" style="margin-right: 0.5em;"
31
- x-data="{ connected: server.connected }">
32
- <svg viewBox="0 0 30 30">
33
- <!-- Connected State (filled circle, green or orange) -->
34
- <circle x-bind:class="server.error ? 'disconnected-circle' : 'connected-circle'" cx="15" cy="15" r="8" x-bind:fill="server.connected
35
- ? (server.error ? '#e40138' : (server.tool_count === 0 ? '#e40138' : '#00c340'))
36
- : 'none'" x-bind:opacity="server.connected ? 1 : 0" />
37
-
38
- <!-- Disconnected State (outline circle) -->
39
- <circle x-bind:class="server.error ? 'disconnected-circle' : 'connected-circle'" cx="15" cy="15" r="9" fill="none"
40
- stroke="#e40138" stroke-width="3" x-bind:opacity="server.connected ? 0 : 1" />
41
- </svg>
30
+ <div class="server-main-row">
31
+ <!-- Status indicator -->
32
+ <div class="status-dot" x-data="{ connected: server.connected }">
33
+ <svg viewBox="0 0 16 16" width="12" height="12">
34
+ <circle cx="8" cy="8" r="6"
35
+ x-bind:fill="server.connected
36
+ ? (server.error ? '#e40138' : (server.tool_count > 0 ? '#00c340' : '#e40138'))
37
+ : 'none'"
38
+ x-bind:opacity="server.connected ? 1 : 0" />
39
+ <circle cx="8" cy="8" r="6" fill="none"
40
+ stroke="#e40138" stroke-width="2"
41
+ x-bind:opacity="server.connected ? 0 : 1" />
42
+ </svg>
43
+ </div>
44
+
45
+ <!-- Server name -->
46
+ <span class="server-name" x-text="server.name"></span>
47
+
48
+ <!-- Tool count (clickable if > 0, only for connected servers without errors) -->
49
+ <span class="tool-count"
50
+ x-show="server.tool_count > 0"
51
+ @click="$store.mcpServersStore.onToolCountClick && $store.mcpServersStore.onToolCountClick(server.name)"
52
+ x-text="server.tool_count + ' tools'"></span>
53
+
54
+ <!-- Disabled in config message -->
55
+ <span class="config-status" x-show="server.disabled_in_config">Disabled in config</span>
56
+
57
+ <!-- Log button (only shown if has_log is true) -->
58
+ <span class="log-btn" x-show="server.has_log" @click="$store.mcpServersStore.getServerLog(server.name)">Log</span>
59
+ </div>
60
+
61
+ <!-- Error message (if any) -->
62
+ <div class="server-error-row" x-show="server.error">
63
+ <span class="server-error" x-text="server.error"></span>
64
</div>
43
- <span class="server-name" x-text="server.name"></span>
44
- <span class="server-tools"
45
- x-text="'- ' + (server.tool_count ? server.tool_count : 0) + ' tools'"></span>
46
- <span class="server-error" x-show="server.error" x-text="server.error"></span>
47
- <span class="server-links"><a href="#" @click="$store.mcpServersStore.getServerLog(server.name)">View Log</a></span>
65
</div>
66
</template>
67
<div x-show="$store.mcpServersStore.servers.length === 0" class="no-servers">
@@ -70,56 +87,86 @@
87
88
.server-item {
89
display: flex;
73
- align-items: center;
74
- flex-wrap: wrap;
75
- padding: 0.5em;
76
- border-bottom: 1px solid var(--c-border);
90
+ flex-direction: column;
91
+ padding: 0.5em 0.7em;
92
+ margin-bottom: 0.4em;
93
+ min-height: 2.2em; /* Ensure consistent height even without errors */
94
+ border: 1px solid rgba(192, 192, 192, 0.161); /* Silver with 30% opacity */
95
+ border-radius: 4px;
96
}
97
79
- .server-item:last-child {
80
- border-bottom: none;
81
- }
82
-
83
- .status-indicator {
84
- display: inline-block;
85
- width: 0.8em;
86
- height: 0.8em;
87
- border-radius: 50%;
88
- margin-right: 0.8em;
98
+ .server-list {
99
+ margin-top: 0.5em;
100
+ margin-bottom: 1em;
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: 0.2em;
104
}
90
-
91
- .status-indicator.connected {
92
- background-color: #4CAF50;
105
+
106
+ .server-main-row {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 0.8em;
110
+ width: 100%;
111
}
112
95
- .status-indicator.disconnected {
96
- background-color: #F44336;
113
+ .status-dot {
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
}
98
-
118
+
119
.server-name {
100
- font-weight: 500;
101
- margin-right: 0.5em;
120
+ font-weight: 600;
121
+ min-width: 12em;
122
}
103
-
104
- .server-tools {
123
+
124
+ .tool-count {
125
color: var(--c-fg2);
126
font-size: 0.9em;
127
+ user-select: none;
128
}
108
-
109
- .no-servers {
110
- padding: 1em;
111
- color: var(--c-fg2);
112
- font-style: italic;
129
+
130
+ .tool-count {
131
+ cursor: default;
132
}
114
-
115
- .server-error {
116
- color: #F44336;
133
+
134
+ .tool-count:hover {
135
+ opacity: 0.8;
136
+ cursor: pointer;
137
+ }
138
+
139
+ .config-status {
140
+ color: #e40138;
141
+ font-size: 0.85em;
142
+ opacity: 0.8;
143
+ }
144
+
145
+ .log-btn {
146
+ margin-left: auto;
147
font-size: 0.9em;
118
- margin-left: 0.8em;
148
+ cursor: pointer;
149
+ text-decoration: none;
150
+ opacity: 0.85;
151
}
120
-
121
- .server-links {
122
- margin-left: 0.8em;
152
+
153
+ .log-btn:hover {
154
+ opacity: 1;
155
+ }
156
+
157
+ .server-error-row {
158
+ margin-left: 1.8em;
159
+ margin-top: 0.1em;
160
+ font-size: 0.8em;
161
+ color: #F44336;
162
+ opacity: 0.85;
163
+ line-height: 1.2;
164
+ }
165
+
166
+ .no-servers {
167
+ padding: 0.5em;
168
+ color: var(--c-fg2);
169
+ font-style: italic;
170
}
171
</style>
172