Skip to content

Commit 27398b2

Browse files
committed
enhance mcp config with headers for streamable server
1 parent 6800f84 commit 27398b2

File tree

3 files changed

+152
-24
lines changed

3 files changed

+152
-24
lines changed

AgentCrew/modules/gui/widgets/configs/mcp_config.py

Lines changed: 143 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,7 @@ def init_ui(self):
8686
right_panel = QWidget()
8787
right_layout = QVBoxLayout(right_panel)
8888

89-
# Add toggle button for view mode
90-
toggle_layout = QHBoxLayout()
91-
self.show_code_btn = QPushButton("Show Code")
92-
self.show_code_btn.setStyleSheet(style_provider.get_button_style("primary"))
93-
self.show_code_btn.clicked.connect(self._toggle_view_mode)
94-
self.show_code_btn.setEnabled(False) # Disable until selection
95-
toggle_layout.addWidget(self.show_code_btn)
96-
toggle_layout.addStretch()
97-
98-
right_layout.addLayout(toggle_layout)
89+
right_layout.addLayout(QHBoxLayout()) # Empty layout placeholder
9990

10091
# Create stacked widget for switching between form and code views
10192
self.stacked_widget = QStackedWidget()
@@ -172,6 +163,23 @@ def init_ui(self):
172163
self.env_layout.addLayout(env_btn_layout)
173164
env_group.setLayout(self.env_layout)
174165

166+
# Headers section (for streaming servers)
167+
headers_group = QGroupBox("HTTP Headers")
168+
self.headers_group = headers_group # Store reference
169+
self.headers_layout = QVBoxLayout()
170+
self.header_inputs = []
171+
172+
# Add button for headers
173+
headers_btn_layout = QHBoxLayout()
174+
self.add_header_btn = QPushButton("Add Header")
175+
self.add_header_btn.setStyleSheet(style_provider.get_button_style("primary"))
176+
self.add_header_btn.clicked.connect(lambda: self.add_header_field("", ""))
177+
headers_btn_layout.addWidget(self.add_header_btn)
178+
headers_btn_layout.addStretch()
179+
180+
self.headers_layout.addLayout(headers_btn_layout)
181+
headers_group.setLayout(self.headers_layout)
182+
175183
# Enabled for agents section
176184
enabled_group = QGroupBox("Enabled For Agents")
177185
enabled_layout = QVBoxLayout()
@@ -188,18 +196,12 @@ def init_ui(self):
188196

189197
enabled_group.setLayout(enabled_layout)
190198

191-
# Save button
192-
self.save_btn = QPushButton("Save")
193-
self.save_btn.setStyleSheet(style_provider.get_button_style("primary"))
194-
self.save_btn.clicked.connect(self.save_mcp)
195-
self.save_btn.setEnabled(False) # Disable until selection
196-
197-
# Add all components to editor layout
199+
# Add all components to editor layout (Save button moved to right_layout)
198200
self.editor_layout.addLayout(form_layout)
199201
self.editor_layout.addWidget(args_group)
200202
self.editor_layout.addWidget(env_group)
203+
self.editor_layout.addWidget(headers_group)
201204
self.editor_layout.addWidget(enabled_group)
202-
self.editor_layout.addWidget(self.save_btn)
203205
self.editor_layout.addStretch()
204206

205207
form_scroll.setWidget(self.editor_widget)
@@ -215,6 +217,26 @@ def init_ui(self):
215217

216218
right_layout.addWidget(self.stacked_widget)
217219

220+
# Button layout with Show Code and Save buttons in same row
221+
button_layout = QHBoxLayout()
222+
223+
# Show Code button (secondary color)
224+
self.show_code_btn = QPushButton("Show Code")
225+
self.show_code_btn.setStyleSheet(style_provider.get_button_style("secondary"))
226+
self.show_code_btn.clicked.connect(self._toggle_view_mode)
227+
self.show_code_btn.setEnabled(False) # Disable until selection
228+
229+
# Save button (primary color)
230+
self.save_btn = QPushButton("Save")
231+
self.save_btn.setStyleSheet(style_provider.get_button_style("primary"))
232+
self.save_btn.clicked.connect(self.save_mcp)
233+
self.save_btn.setEnabled(False) # Disable until selection
234+
235+
button_layout.addWidget(self.show_code_btn)
236+
button_layout.addWidget(self.save_btn)
237+
238+
right_layout.addLayout(button_layout)
239+
218240
# Add panels to splitter
219241
splitter = QSplitter(Qt.Orientation.Horizontal)
220242
splitter.addWidget(left_panel)
@@ -305,6 +327,12 @@ def on_mcp_selected(self, current, previous):
305327
for key, value in env.items():
306328
self.add_env_field(key, value, mark_dirty_on_add=False)
307329

330+
self.clear_header_fields()
331+
332+
headers = server_config.get("headers", {})
333+
for key, value in headers.items():
334+
self.add_header_field(key, value, mark_dirty_on_add=False)
335+
308336
# Set agent checkboxes
309337
enabled_agents = server_config.get("enabledForAgents", [])
310338
for agent, checkbox in self.agent_checkboxes.items():
@@ -323,6 +351,13 @@ def on_mcp_selected(self, current, previous):
323351
def _set_sse_fields_visisble(self, visible: bool):
324352
self.url_input.setVisible(visible)
325353
self.url_label.setVisible(visible)
354+
self.add_header_btn.setVisible(visible)
355+
for header_input in self.header_inputs:
356+
header_input["key_input"].setVisible(visible)
357+
header_input["value_input"].setVisible(visible)
358+
header_input["remove_btn"].setVisible(visible)
359+
if hasattr(self, "headers_group"):
360+
self.headers_group.setVisible(visible)
326361

327362
def _set_stdio_fields_visible(self, visible: bool):
328363
self.command_input.setVisible(visible)
@@ -369,12 +404,15 @@ def set_editor_enabled(self, enabled: bool):
369404
self.args_group.setVisible(False)
370405
if hasattr(self, "env_group"):
371406
self.env_group.setVisible(False)
407+
if hasattr(self, "headers_group"):
408+
self.headers_group.setVisible(False)
372409

373410
# Always enable/disable these regardless of visibility
374411
self.url_input.setEnabled(enabled)
375412
self.command_input.setEnabled(enabled)
376413
self.add_arg_btn.setEnabled(enabled)
377414
self.add_env_btn.setEnabled(enabled)
415+
self.add_header_btn.setEnabled(enabled)
378416

379417
for checkbox in self.agent_checkboxes.values():
380418
checkbox.setEnabled(enabled)
@@ -397,7 +435,16 @@ def set_editor_enabled(self, enabled: bool):
397435
env_input["value_input"].setVisible(not is_streaming)
398436
env_input["remove_btn"].setVisible(not is_streaming)
399437

400-
# Enable/disable JSON editor
438+
for header_input in self.header_inputs:
439+
header_input["key_input"].setEnabled(enabled)
440+
header_input["value_input"].setEnabled(enabled)
441+
header_input["remove_btn"].setEnabled(enabled)
442+
if enabled:
443+
is_streaming = self.streaming_server_checkbox.isChecked()
444+
header_input["key_input"].setVisible(is_streaming)
445+
header_input["value_input"].setVisible(is_streaming)
446+
header_input["remove_btn"].setVisible(is_streaming)
447+
401448
self.json_editor.set_read_only(not enabled)
402449

403450
if not enabled:
@@ -518,6 +565,68 @@ def clear_env_fields(self):
518565
while self.env_inputs:
519566
self.remove_env_field(self.env_inputs[0])
520567

568+
def add_header_field(self, key="", value="", mark_dirty_on_add=True):
569+
"""Add a field for an HTTP header."""
570+
header_layout = QHBoxLayout()
571+
572+
key_input = QLineEdit()
573+
key_input.setText(str(key))
574+
key_input.setPlaceholderText("Header Name (e.g., Authorization)")
575+
key_input.textChanged.connect(self._mark_dirty)
576+
577+
value_input = QLineEdit()
578+
value_input.setText(str(value))
579+
value_input.setPlaceholderText("Header Value (e.g., Bearer token)")
580+
value_input.textChanged.connect(self._mark_dirty)
581+
582+
remove_btn = QPushButton("Remove")
583+
remove_btn.setMaximumWidth(80)
584+
585+
style_provider = StyleProvider()
586+
remove_btn.setStyleSheet(style_provider.get_button_style("red"))
587+
588+
header_layout.addWidget(key_input)
589+
header_layout.addWidget(value_input)
590+
header_layout.addWidget(remove_btn)
591+
592+
# Insert before the add button
593+
self.headers_layout.insertLayout(len(self.header_inputs), header_layout)
594+
595+
# Store references
596+
header_data = {
597+
"layout": header_layout,
598+
"key_input": key_input,
599+
"value_input": value_input,
600+
"remove_btn": remove_btn,
601+
}
602+
self.header_inputs.append(header_data)
603+
604+
# Connect remove button
605+
remove_btn.clicked.connect(lambda: self.remove_header_field(header_data))
606+
607+
if mark_dirty_on_add:
608+
self._mark_dirty()
609+
return header_data
610+
611+
def remove_header_field(self, header_data):
612+
"""Remove an HTTP header field."""
613+
# Remove from layout
614+
self.headers_layout.removeItem(header_data["layout"])
615+
616+
# Delete widgets
617+
header_data["key_input"].deleteLater()
618+
header_data["value_input"].deleteLater()
619+
header_data["remove_btn"].deleteLater()
620+
621+
# Remove from list
622+
self.header_inputs.remove(header_data)
623+
self._mark_dirty()
624+
625+
def clear_header_fields(self):
626+
"""Clear all HTTP header fields."""
627+
while self.header_inputs:
628+
self.remove_header_field(self.header_inputs[0])
629+
521630
def add_new_mcp(self):
522631
"""Add a new MCP server to the configuration."""
523632
# Create a new server with default values
@@ -530,6 +639,7 @@ def add_new_mcp(self):
530639
"enabledForAgents": [],
531640
"streaming_server": False,
532641
"url": "",
642+
"headers": {},
533643
}
534644

535645
# Add to list
@@ -574,6 +684,7 @@ def remove_mcp(self):
574684
self.command_input.clear()
575685
self.clear_argument_fields()
576686
self.clear_env_fields()
687+
self.clear_header_fields()
577688
for checkbox in self.agent_checkboxes.values():
578689
checkbox.setChecked(False)
579690
self.save_all_mcps()
@@ -672,9 +783,7 @@ def _toggle_view_mode(self):
672783
server_id, server_config = current_item.data(Qt.ItemDataRole.UserRole)
673784

674785
if self.is_code_view:
675-
# Switching from code view to form view
676786
try:
677-
# Get JSON data from editor and update form
678787
json_data = self.json_editor.get_json()
679788
self._update_form_from_json(json_data, server_id)
680789
self.stacked_widget.setCurrentIndex(0) # Form view
@@ -688,8 +797,6 @@ def _toggle_view_mode(self):
688797
)
689798
return
690799
else:
691-
# Switching from form view to code view
692-
# Update server data from form and set JSON
693800
server_data = self._get_form_data()
694801
if server_data: # Only proceed if form data is valid
695802
self.json_editor.set_json(server_data)
@@ -720,6 +827,14 @@ def _get_form_data(self) -> dict:
720827
if key:
721828
env[key] = value
722829

830+
# Get headers
831+
headers = {}
832+
for header_data in self.header_inputs:
833+
key = header_data["key_input"].text().strip()
834+
value = header_data["value_input"].text().strip()
835+
if key:
836+
headers[key] = value
837+
723838
# Get enabled agents
724839
enabled_agents = [
725840
agent
@@ -735,6 +850,7 @@ def _get_form_data(self) -> dict:
735850
"enabledForAgents": enabled_agents,
736851
"streaming_server": streaming_server,
737852
"url": url,
853+
"headers": headers,
738854
}
739855

740856
def _update_form_from_json(self, json_data: dict, server_id: str):
@@ -760,6 +876,10 @@ def _update_form_from_json(self, json_data: dict, server_id: str):
760876
for key, value in json_data.get("env", {}).items():
761877
self.add_env_field(key, value, mark_dirty_on_add=False)
762878

879+
self.clear_header_fields()
880+
for key, value in json_data.get("headers", {}).items():
881+
self.add_header_field(key, value, mark_dirty_on_add=False)
882+
763883
enabled_agents = json_data.get("enabledForAgents", [])
764884
for agent, checkbox in self.agent_checkboxes.items():
765885
checkbox.setChecked(agent in enabled_agents)

AgentCrew/modules/mcpclient/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class MCPServerConfig:
1616
env: Optional[Dict[str, str]] = None
1717
streaming_server: bool = False
1818
url: str = ""
19+
headers: Optional[Dict[str, str]] = None
1920

2021

2122
class MCPConfigManager:
@@ -68,6 +69,7 @@ def load_config(self) -> Dict[str, MCPServerConfig]:
6869
enabledForAgents=config.get("enabledForAgents", []),
6970
streaming_server=config.get("streaming_server", False),
7071
url=config.get("url", ""),
72+
headers=config.get("headers"),
7173
)
7274

7375
return self.configs

AgentCrew/modules/mcpclient/service.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ async def _manage_single_connection(self, server_config: MCPServerConfig):
3636
from mcp.client.streamable_http import streamablehttp_client
3737

3838
logger.info(f"MCPService: Using streaming HTTP client for {server_id}")
39-
async with streamablehttp_client(server_config.url) as (
39+
40+
# Prepare headers for the streamable HTTP client
41+
headers = server_config.headers if server_config.headers else {}
42+
43+
async with streamablehttp_client(
44+
server_config.url, headers=headers
45+
) as (
4046
read_stream,
4147
write_stream,
4248
_,

0 commit comments

Comments
 (0)