idun-agent-engine 0.4.0__py3-none-any.whl → 0.4.2__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.
Files changed (51) hide show
  1. idun_agent_engine/_version.py +1 -1
  2. idun_agent_engine/agent/adk/adk.py +7 -4
  3. idun_agent_engine/agent/haystack/__init__.py +0 -2
  4. idun_agent_engine/agent/haystack/haystack.py +9 -5
  5. idun_agent_engine/agent/langgraph/langgraph.py +10 -13
  6. idun_agent_engine/core/config_builder.py +33 -13
  7. idun_agent_engine/guardrails/guardrails_hub/guardrails_hub.py +52 -9
  8. idun_agent_engine/mcp/__init__.py +2 -2
  9. idun_agent_engine/mcp/helpers.py +53 -15
  10. idun_agent_engine/mcp/registry.py +5 -5
  11. idun_agent_engine/observability/base.py +11 -2
  12. idun_agent_engine/observability/gcp_trace/gcp_trace_handler.py +3 -1
  13. idun_agent_engine/observability/langfuse/langfuse_handler.py +1 -3
  14. idun_agent_engine/server/dependencies.py +7 -2
  15. idun_agent_engine/server/lifespan.py +2 -7
  16. idun_agent_engine/server/routers/agent.py +2 -1
  17. idun_agent_engine/server/routers/base.py +7 -5
  18. idun_agent_engine/telemetry/__init__.py +0 -1
  19. idun_agent_engine/telemetry/config.py +0 -1
  20. idun_agent_engine/telemetry/telemetry.py +3 -4
  21. idun_agent_engine/templates/correction.py +4 -7
  22. idun_agent_engine/templates/deep_research.py +1 -0
  23. idun_agent_engine/templates/translation.py +4 -4
  24. {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/METADATA +2 -2
  25. idun_agent_engine-0.4.2.dist-info/RECORD +86 -0
  26. idun_platform_cli/groups/agent/package.py +4 -1
  27. idun_platform_cli/groups/agent/serve.py +2 -0
  28. idun_platform_cli/groups/init.py +2 -0
  29. idun_platform_cli/telemetry.py +55 -0
  30. idun_platform_cli/tui/css/create_agent.py +137 -14
  31. idun_platform_cli/tui/css/main.py +7 -10
  32. idun_platform_cli/tui/main.py +3 -3
  33. idun_platform_cli/tui/schemas/create_agent.py +8 -4
  34. idun_platform_cli/tui/screens/create_agent.py +186 -20
  35. idun_platform_cli/tui/utils/config.py +23 -2
  36. idun_platform_cli/tui/validators/guardrails.py +20 -6
  37. idun_platform_cli/tui/validators/mcps.py +9 -6
  38. idun_platform_cli/tui/widgets/__init__.py +8 -4
  39. idun_platform_cli/tui/widgets/chat_widget.py +155 -0
  40. idun_platform_cli/tui/widgets/guardrails_widget.py +12 -4
  41. idun_platform_cli/tui/widgets/identity_widget.py +28 -10
  42. idun_platform_cli/tui/widgets/mcps_widget.py +113 -25
  43. idun_platform_cli/tui/widgets/memory_widget.py +194 -0
  44. idun_platform_cli/tui/widgets/observability_widget.py +12 -14
  45. idun_platform_cli/tui/widgets/serve_widget.py +50 -47
  46. idun_agent_engine/agent/haystack/haystack_model.py +0 -13
  47. idun_agent_engine/guardrails/guardrails_hub/utils.py +0 -1
  48. idun_agent_engine/server/routers/agui.py +0 -47
  49. idun_agent_engine-0.4.0.dist-info/RECORD +0 -86
  50. {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/WHEEL +0 -0
  51. {idun_agent_engine-0.4.0.dist-info → idun_agent_engine-0.4.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,155 @@
1
+ """Chat widget for interacting with running agent."""
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.containers import Horizontal, Vertical
5
+ from textual.reactive import reactive
6
+ from textual.widget import Widget
7
+ from textual.widgets import Button, Input, Label, LoadingIndicator, RichLog
8
+
9
+
10
+ class ChatWidget(Widget):
11
+ server_running = reactive(False)
12
+
13
+ def __init__(self, *args, **kwargs):
14
+ super().__init__(*args, **kwargs)
15
+ self.config_data = {}
16
+ self.server_port = None
17
+ self.agent_name = ""
18
+
19
+ def compose(self) -> ComposeResult:
20
+ chat_container = Vertical(classes="chat-history-container")
21
+ chat_container.border_title = "Conversation"
22
+ with chat_container:
23
+ yield RichLog(id="chat_history", highlight=True, markup=True, wrap=True)
24
+
25
+ thinking_container = Horizontal(
26
+ classes="chat-thinking-container", id="chat_thinking"
27
+ )
28
+ thinking_container.display = False
29
+ with thinking_container:
30
+ yield LoadingIndicator(id="chat_spinner")
31
+ yield Label("Thinking...", id="thinking_label")
32
+
33
+ input_container = Horizontal(classes="chat-input-container")
34
+ with input_container:
35
+ yield Input(
36
+ placeholder="Type your message...",
37
+ id="chat_input",
38
+ classes="chat-input",
39
+ )
40
+ yield Button("Send", id="send_button", classes="send-btn")
41
+
42
+ def load_config(self, config: dict) -> None:
43
+ self.config_data = config
44
+ server_config = config.get("server", {})
45
+ api_config = server_config.get("api", {})
46
+ self.server_port = api_config.get("port", 8008)
47
+
48
+ agent_config = config.get("agent", {}).get("config", {})
49
+ self.agent_name = agent_config.get("name", "Agent")
50
+
51
+ self.run_worker(self._check_server_status())
52
+
53
+ def on_mount(self) -> None:
54
+ chat_log = self.query_one("#chat_history", RichLog)
55
+ chat_log.write("[dim]Start chatting with your agent...[/dim]")
56
+ chat_log.write(
57
+ "[dim]Make sure the agent server is running from the Serve page.[/dim]"
58
+ )
59
+
60
+ def on_button_pressed(self, event: Button.Pressed) -> None:
61
+ if event.button.id == "send_button":
62
+ self._handle_send()
63
+
64
+ def on_input_submitted(self, event: Input.Submitted) -> None:
65
+ if event.input.id == "chat_input":
66
+ self._handle_send()
67
+
68
+ def _handle_send(self) -> None:
69
+ input_widget = self.query_one("#chat_input", Input)
70
+ message = input_widget.value.strip()
71
+
72
+ if not message:
73
+ return
74
+
75
+ if not self.server_port:
76
+ self.app.notify("Server not configured", severity="error")
77
+ return
78
+
79
+ input_widget.value = ""
80
+
81
+ chat_log = self.query_one("#chat_history", RichLog)
82
+ chat_log.write(f"[cyan]You:[/cyan] {message}")
83
+
84
+ thinking_container = self.query_one("#chat_thinking")
85
+ thinking_container.display = True
86
+
87
+ self.run_worker(self._send_message(message))
88
+
89
+ async def _send_message(self, message: str) -> None:
90
+ import httpx
91
+
92
+ chat_log = self.query_one("#chat_history", RichLog)
93
+ thinking_container = self.query_one("#chat_thinking")
94
+
95
+ try:
96
+ url = f"http://localhost:{self.server_port}/agent/invoke"
97
+ async with httpx.AsyncClient(timeout=60.0) as client:
98
+ response = await client.post(
99
+ url, json={"session_id": "123", "query": message}
100
+ )
101
+ result = response.json()
102
+
103
+ agent_response = result.get(
104
+ "output", result.get("response", "No response")
105
+ )
106
+ thinking_container.display = False
107
+ chat_log.write(f"[green]{self.agent_name}:[/green] {agent_response}")
108
+
109
+ except httpx.ConnectError:
110
+ thinking_container.display = False
111
+ chat_log.write("[red]Error:[/red] Cannot connect to server. Is it running?")
112
+ self.app.notify(
113
+ "Server not reachable. Start it from the Serve page.", severity="error"
114
+ )
115
+ except httpx.TimeoutException:
116
+ thinking_container.display = False
117
+ chat_log.write("[red]Error:[/red] Request timed out")
118
+ self.app.notify("Request timed out", severity="error")
119
+ except Exception as e:
120
+ thinking_container.display = False
121
+ chat_log.write(f"[red]Error:[/red] Failed to send message: {e}")
122
+ self.app.notify(
123
+ "Failed to send message. Check server connection.", severity="error"
124
+ )
125
+
126
+ async def _check_server_status(self) -> None:
127
+ import httpx
128
+
129
+ if not self.server_port:
130
+ return
131
+
132
+ try:
133
+ url = f"http://localhost:{self.server_port}/health"
134
+ async with httpx.AsyncClient(timeout=2.0) as client:
135
+ response = await client.get(url)
136
+ self.server_running = response.status_code == 200
137
+
138
+ if self.server_running:
139
+ chat_log = self.query_one("#chat_history", RichLog)
140
+ chat_log.write(
141
+ f"[green]✓ Connected to server on port {self.server_port}[/green]"
142
+ )
143
+ except Exception:
144
+ self.server_running = False
145
+
146
+ def watch_server_running(self, is_running: bool) -> None:
147
+ input_widget = self.query_one("#chat_input", Input)
148
+ send_button = self.query_one("#send_button", Button)
149
+
150
+ if is_running:
151
+ input_widget.disabled = False
152
+ send_button.disabled = False
153
+ else:
154
+ input_widget.disabled = True
155
+ send_button.disabled = True
@@ -1,11 +1,11 @@
1
1
  """Guardrails configuration widget."""
2
2
 
3
+ from idun_agent_schema.engine.guardrails_v2 import GuardrailsV2
3
4
  from textual.app import ComposeResult
4
- from textual.containers import Horizontal, Vertical, Grid
5
- from textual.widgets import Static, Input, Switch, RadioSet, RadioButton, TextArea
5
+ from textual.containers import Grid, Horizontal, Vertical
6
6
  from textual.widget import Widget
7
+ from textual.widgets import Input, RadioButton, RadioSet, Static, Switch, TextArea
7
8
 
8
- from idun_agent_schema.engine.guardrails_v2 import GuardrailsV2
9
9
  from idun_platform_cli.tui.validators.guardrails import validate_guardrail
10
10
 
11
11
 
@@ -304,7 +304,15 @@ class GuardrailsWidget(Widget):
304
304
  if applies_to in ["output", "both"]:
305
305
  output_guardrails.append(validated_config)
306
306
 
307
- return GuardrailsV2(input=input_guardrails, output=output_guardrails)
307
+ try:
308
+ return GuardrailsV2(input=input_guardrails, output=output_guardrails)
309
+ except Exception:
310
+ self.app.notify(
311
+ "Error validating Guardrails: make sure all fields are correct.",
312
+ severity="error",
313
+ timeout=10,
314
+ )
315
+ return None
308
316
 
309
317
  def _extract_config(self, guardrail_id: str) -> dict:
310
318
  config = {}
@@ -25,6 +25,7 @@ class IdentityWidget(Widget):
25
25
  def __init__(self, *args, **kwargs):
26
26
  super().__init__(*args, **kwargs)
27
27
  self.selected_file_path = ""
28
+ self.selected_variable = ""
28
29
 
29
30
  def compose(self) -> ComposeResult:
30
31
  agent_info_section = Horizontal(
@@ -115,7 +116,7 @@ class IdentityWidget(Widget):
115
116
  import ast
116
117
 
117
118
  try:
118
- with open(file_path, "r") as f:
119
+ with open(file_path) as f:
119
120
  tree = ast.parse(f.read())
120
121
 
121
122
  variables = []
@@ -127,21 +128,28 @@ class IdentityWidget(Widget):
127
128
 
128
129
  var_list = self.query_one("#variable_list", OptionList)
129
130
  var_list.clear_options()
131
+ self.selected_variable = ""
130
132
 
131
133
  if variables:
132
134
  for var in variables:
133
135
  var_list.add_option(Option(var, id=var))
134
- var_list.highlighted = 0
135
136
  else:
136
137
  var_list.add_option(Option("No variables found", id="none"))
137
138
 
138
- except Exception as e:
139
- self.app.notify(f"Error parsing file: {str(e)}", severity="error")
139
+ except Exception:
140
+ self.app.notify(
141
+ "Error parsing file. Make sure it's a valid Python file.",
142
+ severity="error",
143
+ )
140
144
 
141
145
  def on_option_list_option_highlighted(
142
146
  self, event: OptionList.OptionHighlighted
143
147
  ) -> None:
144
148
  if event.option_list.id == "variable_list":
149
+ var_list = self.query_one("#variable_list", OptionList)
150
+ if var_list.highlighted is not None:
151
+ variable_option = var_list.get_option_at_index(var_list.highlighted)
152
+ self.selected_variable = str(variable_option.id)
145
153
  self._update_full_definition()
146
154
  elif event.option_list.id == "framework_select":
147
155
  self._update_section_labels()
@@ -149,7 +157,9 @@ class IdentityWidget(Widget):
149
157
  def _update_section_labels(self) -> None:
150
158
  framework_select = self.query_one("#framework_select", OptionList)
151
159
  if framework_select.highlighted is not None:
152
- framework_option = framework_select.get_option_at_index(framework_select.highlighted)
160
+ framework_option = framework_select.get_option_at_index(
161
+ framework_select.highlighted
162
+ )
153
163
  framework = str(framework_option.id)
154
164
 
155
165
  graph_section = self.query_one(".graph-definition-section", Vertical)
@@ -192,14 +202,22 @@ class IdentityWidget(Widget):
192
202
  self.app.notify("Agent name and port are required!", severity="error")
193
203
  return False
194
204
 
195
- var_list = self.query_one("#variable_list", OptionList)
196
- var_index = var_list.highlighted
205
+ if not self.selected_file_path:
206
+ self.query_one("#identity_error", Static).update(
207
+ "Please select a Python file"
208
+ )
209
+ self.app.notify(
210
+ "Graph definition incomplete! Select a file.", severity="error"
211
+ )
212
+ return False
197
213
 
198
- if not self.selected_file_path or var_index is None:
214
+ if not self.selected_variable or self.selected_variable == "none":
199
215
  self.query_one("#identity_error", Static).update(
200
- "Please select a file and variable"
216
+ "Please select a variable from the list"
217
+ )
218
+ self.app.notify(
219
+ "Graph definition incomplete! Select a variable.", severity="error"
201
220
  )
202
- self.app.notify("Graph definition incomplete!", severity="error")
203
221
  return False
204
222
 
205
223
  return True
@@ -2,11 +2,18 @@
2
2
 
3
3
  from textual.app import ComposeResult
4
4
  from textual.containers import Horizontal, Vertical
5
- from textual.widgets import Static, Input, Button, RadioSet, RadioButton, TextArea, OptionList
6
5
  from textual.widget import Widget
6
+ from textual.widgets import (
7
+ Button,
8
+ Input,
9
+ OptionList,
10
+ RadioButton,
11
+ RadioSet,
12
+ Static,
13
+ TextArea,
14
+ )
7
15
  from textual.widgets.option_list import Option
8
16
 
9
-
10
17
  MCP_TEMPLATES = {
11
18
  "time": {
12
19
  "name": "time-reference",
@@ -34,14 +41,24 @@ class MCPsWidget(Widget):
34
41
  templates_row = Horizontal(classes="templates-row")
35
42
  templates_row.compose_add_child(Static("Select template:", classes="mcp-label"))
36
43
  option_list = OptionList(id="template_selector", classes="template-selector")
37
- for template_name in MCP_TEMPLATES.keys():
44
+ for template_name in MCP_TEMPLATES:
38
45
  option_list.add_option(Option(template_name.title(), id=template_name))
39
46
  templates_row.compose_add_child(option_list)
40
- templates_row.compose_add_child(Button("Add from Template", id="add_from_template_button", classes="add-template-btn"))
47
+ templates_row.compose_add_child(
48
+ Button(
49
+ "Add from Template",
50
+ id="add_from_template_button",
51
+ classes="add-template-btn",
52
+ )
53
+ )
41
54
  templates_section.compose_add_child(templates_row)
42
55
  yield templates_section
43
56
 
44
- yield Button("+ Add Custom MCP Server", id="add_custom_mcp_button", classes="add-custom-btn")
57
+ yield Button(
58
+ "+ Add Custom MCP Server",
59
+ id="add_custom_mcp_button",
60
+ classes="add-custom-btn",
61
+ )
45
62
 
46
63
  yield Vertical(id="mcps_container", classes="mcps-container")
47
64
 
@@ -67,7 +84,9 @@ class MCPsWidget(Widget):
67
84
  def _add_from_template(self) -> None:
68
85
  template_selector = self.query_one("#template_selector", OptionList)
69
86
  if template_selector.highlighted is not None:
70
- option_id = template_selector.get_option_at_index(template_selector.highlighted).id
87
+ option_id = template_selector.get_option_at_index(
88
+ template_selector.highlighted
89
+ ).id
71
90
  if option_id and str(option_id) in MCP_TEMPLATES:
72
91
  template_data = MCP_TEMPLATES[str(option_id)].copy()
73
92
  self._add_mcp_server(template_data)
@@ -95,52 +114,109 @@ class MCPsWidget(Widget):
95
114
 
96
115
  header = Horizontal(classes="mcp-header")
97
116
  name_value = template_data.get("name", "") if template_data else ""
98
- header.compose_add_child(Static(name_value or f"Server {index + 1}", id=f"mcp_name_display_{index}", classes="mcp-name-display"))
99
- header.compose_add_child(Button("Remove", id=f"remove_mcp_{index}", classes="remove-mcp-btn"))
117
+ header.compose_add_child(
118
+ Static(
119
+ name_value or f"Server {index + 1}",
120
+ id=f"mcp_name_display_{index}",
121
+ classes="mcp-name-display",
122
+ )
123
+ )
124
+ header.compose_add_child(
125
+ Button("Remove", id=f"remove_mcp_{index}", classes="remove-mcp-btn")
126
+ )
100
127
  card.compose_add_child(header)
101
128
 
102
129
  name_row = Horizontal(classes="mcp-field-row")
103
130
  name_row.compose_add_child(Static("Name:", classes="mcp-label"))
104
- name_row.compose_add_child(Input(value=name_value, placeholder="server-name", id=f"mcp_name_{index}", classes="mcp-input"))
131
+ name_row.compose_add_child(
132
+ Input(
133
+ value=name_value,
134
+ placeholder="server-name",
135
+ id=f"mcp_name_{index}",
136
+ classes="mcp-input",
137
+ )
138
+ )
105
139
  card.compose_add_child(name_row)
106
140
 
107
141
  transport_row = Horizontal(classes="mcp-field-row")
108
142
  transport_row.compose_add_child(Static("Transport:", classes="mcp-label"))
109
143
  radio_set = RadioSet(id=f"mcp_transport_{index}")
110
144
 
111
- transport_value = template_data.get("transport", "streamable_http") if template_data else "streamable_http"
112
- radio_set.compose_add_child(RadioButton("stdio", id="stdio", value=(transport_value == "stdio")))
113
- radio_set.compose_add_child(RadioButton("sse", id="sse", value=(transport_value == "sse")))
114
- radio_set.compose_add_child(RadioButton("streamable_http", id="streamable_http", value=(transport_value == "streamable_http")))
115
- radio_set.compose_add_child(RadioButton("websocket", id="websocket", value=(transport_value == "websocket")))
145
+ transport_value = (
146
+ template_data.get("transport", "streamable_http")
147
+ if template_data
148
+ else "streamable_http"
149
+ )
150
+ radio_set.compose_add_child(
151
+ RadioButton("stdio", id="stdio", value=(transport_value == "stdio"))
152
+ )
153
+ radio_set.compose_add_child(
154
+ RadioButton("sse", id="sse", value=(transport_value == "sse"))
155
+ )
156
+ radio_set.compose_add_child(
157
+ RadioButton(
158
+ "streamable_http",
159
+ id="streamable_http",
160
+ value=(transport_value == "streamable_http"),
161
+ )
162
+ )
163
+ radio_set.compose_add_child(
164
+ RadioButton(
165
+ "websocket", id="websocket", value=(transport_value == "websocket")
166
+ )
167
+ )
116
168
  transport_row.compose_add_child(radio_set)
117
169
  card.compose_add_child(transport_row)
118
170
 
119
- http_fields = Vertical(id=f"mcp_http_fields_{index}", classes="http-fields-container")
171
+ http_fields = Vertical(
172
+ id=f"mcp_http_fields_{index}", classes="http-fields-container"
173
+ )
120
174
  http_fields.border_title = "HTTP Configuration"
121
175
 
122
176
  url_row = Horizontal(classes="mcp-field-row")
123
177
  url_row.compose_add_child(Static("URL:", classes="mcp-label"))
124
178
  url_value = template_data.get("url", "") if template_data else ""
125
- url_row.compose_add_child(Input(value=url_value, placeholder="https://api.example.com/mcp", id=f"mcp_url_{index}", classes="mcp-input"))
179
+ url_row.compose_add_child(
180
+ Input(
181
+ value=url_value,
182
+ placeholder="https://api.example.com/mcp",
183
+ id=f"mcp_url_{index}",
184
+ classes="mcp-input",
185
+ )
186
+ )
126
187
  http_fields.compose_add_child(url_row)
127
188
 
128
189
  headers_row = Horizontal(classes="mcp-field-row")
129
190
  headers_row.compose_add_child(Static("Headers (JSON):", classes="mcp-label"))
130
191
  headers_value = template_data.get("headers", "") if template_data else ""
131
- headers_row.compose_add_child(TextArea(text=str(headers_value) if headers_value else "", id=f"mcp_headers_{index}", classes="mcp-textarea"))
192
+ headers_row.compose_add_child(
193
+ TextArea(
194
+ text=str(headers_value) if headers_value else "",
195
+ id=f"mcp_headers_{index}",
196
+ classes="mcp-textarea",
197
+ )
198
+ )
132
199
  http_fields.compose_add_child(headers_row)
133
200
 
134
201
  http_fields.display = transport_value in ["sse", "streamable_http", "websocket"]
135
202
  card.compose_add_child(http_fields)
136
203
 
137
- stdio_fields = Vertical(id=f"mcp_stdio_fields_{index}", classes="stdio-fields-container")
204
+ stdio_fields = Vertical(
205
+ id=f"mcp_stdio_fields_{index}", classes="stdio-fields-container"
206
+ )
138
207
  stdio_fields.border_title = "Stdio Configuration"
139
208
 
140
209
  command_row = Horizontal(classes="mcp-field-row")
141
210
  command_row.compose_add_child(Static("Command:", classes="mcp-label"))
142
211
  command_value = template_data.get("command", "") if template_data else ""
143
- command_row.compose_add_child(Input(value=command_value, placeholder="npx", id=f"mcp_command_{index}", classes="mcp-input"))
212
+ command_row.compose_add_child(
213
+ Input(
214
+ value=command_value,
215
+ placeholder="npx",
216
+ id=f"mcp_command_{index}",
217
+ classes="mcp-input",
218
+ )
219
+ )
144
220
  stdio_fields.compose_add_child(command_row)
145
221
 
146
222
  args_row = Horizontal(classes="mcp-field-row")
@@ -150,7 +226,9 @@ class MCPsWidget(Widget):
150
226
  args_list = template_data["args"]
151
227
  if isinstance(args_list, list):
152
228
  args_value = "\n".join(args_list)
153
- args_textarea = TextArea(text=args_value, id=f"mcp_args_{index}", classes="mcp-textarea")
229
+ args_textarea = TextArea(
230
+ text=args_value, id=f"mcp_args_{index}", classes="mcp-textarea"
231
+ )
154
232
  args_textarea.placeholder = "run\n-i\n--rm"
155
233
  args_row.compose_add_child(args_textarea)
156
234
  stdio_fields.compose_add_child(args_row)
@@ -160,8 +238,11 @@ class MCPsWidget(Widget):
160
238
  env_value = ""
161
239
  if template_data and "env" in template_data:
162
240
  import json
241
+
163
242
  env_value = json.dumps(template_data["env"], indent=2)
164
- env_row.compose_add_child(TextArea(text=env_value, id=f"mcp_env_{index}", classes="mcp-textarea"))
243
+ env_row.compose_add_child(
244
+ TextArea(text=env_value, id=f"mcp_env_{index}", classes="mcp-textarea")
245
+ )
165
246
  stdio_fields.compose_add_child(env_row)
166
247
 
167
248
  stdio_fields.display = transport_value == "stdio"
@@ -189,7 +270,9 @@ class MCPsWidget(Widget):
189
270
  name = name_input.value
190
271
 
191
272
  if not name:
192
- self.app.notify(f"Server {server_id + 1}: Name is required", severity="error")
273
+ self.app.notify(
274
+ f"Server {server_id + 1}: Name is required", severity="error"
275
+ )
193
276
  return None
194
277
 
195
278
  radio_set = self.query_one(f"#mcp_transport_{server_id}", RadioSet)
@@ -206,7 +289,9 @@ class MCPsWidget(Widget):
206
289
  url_input = self.query_one(f"#mcp_url_{server_id}", Input)
207
290
  server_config["url"] = url_input.value
208
291
 
209
- headers_textarea = self.query_one(f"#mcp_headers_{server_id}", TextArea)
292
+ headers_textarea = self.query_one(
293
+ f"#mcp_headers_{server_id}", TextArea
294
+ )
210
295
  if headers_textarea.text.strip():
211
296
  server_config["headers"] = headers_textarea.text.strip()
212
297
 
@@ -223,8 +308,11 @@ class MCPsWidget(Widget):
223
308
 
224
309
  servers_data.append(server_config)
225
310
 
226
- except Exception as e:
227
- self.app.notify(f"Error reading server {server_id + 1}: {str(e)}", severity="error")
311
+ except Exception:
312
+ self.app.notify(
313
+ f"Error reading MCP server {server_id + 1}: check your configuration.",
314
+ severity="error",
315
+ )
228
316
  return None
229
317
 
230
318
  return servers_data