pedrobento988 commited on
Commit
5475a9d
·
verified ·
1 Parent(s): ed8e0af

Add MCP server (#5)

Browse files

- fix: Add missing requirements (7fa7ab92623e1ed9f8941da9c6551f5636a742a1)
- feat: Add mutable list of checkboxes (7e71135a1453a3a309c34163d563904934c89585)
- feat: Get tools from MCP SSE server (c5235b0c2d6b125689b7864597f8987c18b19902)
- feat: Get tools from MCP SSE server (d6af55a4ca2c47860a9f6e7adbf1d4a0fe0270cc)

pyproject.toml CHANGED
@@ -16,6 +16,7 @@ dependencies = [
16
  "gradio[mcp]>=5.32.1",
17
  "huggingface-hub>=0.32.3",
18
  "langchain-aws>=0.2.24",
 
19
  "langgraph>=0.4.7",
20
  ]
21
 
@@ -103,7 +104,17 @@ line-length = 88
103
 
104
  [tool.ruff.lint]
105
  select = ["ALL"]
106
- ignore = ["D100", "D104", "D107", "D401", "EM102", "ERA001", "TRY003"]
 
 
 
 
 
 
 
 
 
 
107
 
108
  [tool.ruff.lint.flake8-quotes]
109
  inline-quotes = "double"
@@ -121,6 +132,9 @@ lines-after-imports = 2
121
  [tool.ruff.lint.mccabe]
122
  max-complexity = 18
123
 
 
 
 
124
  [tool.ruff.lint.pydocstyle]
125
  convention = "google"
126
 
 
16
  "gradio[mcp]>=5.32.1",
17
  "huggingface-hub>=0.32.3",
18
  "langchain-aws>=0.2.24",
19
+ "langchain-mcp-adapters>=0.1.1",
20
  "langgraph>=0.4.7",
21
  ]
22
 
 
104
 
105
  [tool.ruff.lint]
106
  select = ["ALL"]
107
+ ignore = [
108
+ "D100",
109
+ "D104",
110
+ "D107",
111
+ "D401",
112
+ "EM102",
113
+ "ERA001",
114
+ "TC002",
115
+ "TC003",
116
+ "TRY003",
117
+ ]
118
 
119
  [tool.ruff.lint.flake8-quotes]
120
  inline-quotes = "double"
 
132
  [tool.ruff.lint.mccabe]
133
  max-complexity = 18
134
 
135
+ [tool.ruff.lint.pylint]
136
+ max-args = 7
137
+
138
  [tool.ruff.lint.pydocstyle]
139
  convention = "google"
140
 
requirements-dev.txt CHANGED
@@ -41,6 +41,7 @@ jsonpatch==1.33
41
  jsonpointer==3.0.0
42
  langchain-aws==0.2.24
43
  langchain-core==0.3.63
 
44
  langgraph==0.4.7
45
  langgraph-checkpoint==2.0.26
46
  langgraph-prebuilt==0.2.2
 
41
  jsonpointer==3.0.0
42
  langchain-aws==0.2.24
43
  langchain-core==0.3.63
44
+ langchain-mcp-adapters==0.1.1
45
  langgraph==0.4.7
46
  langgraph-checkpoint==2.0.26
47
  langgraph-prebuilt==0.2.2
requirements.txt CHANGED
@@ -34,6 +34,7 @@ jsonpatch==1.33
34
  jsonpointer==3.0.0
35
  langchain-aws==0.2.24
36
  langchain-core==0.3.63
 
37
  langgraph==0.4.7
38
  langgraph-checkpoint==2.0.26
39
  langgraph-prebuilt==0.2.2
 
34
  jsonpointer==3.0.0
35
  langchain-aws==0.2.24
36
  langchain-core==0.3.63
37
+ langchain-mcp-adapters==0.1.1
38
  langgraph==0.4.7
39
  langgraph-checkpoint==2.0.26
40
  langgraph-prebuilt==0.2.2
tdagent/grchat.py CHANGED
@@ -1,7 +1,8 @@
1
  from __future__ import annotations
2
 
3
- from collections.abc import Mapping
4
  from types import MappingProxyType
 
5
 
6
  import boto3
7
  import botocore
@@ -9,9 +10,15 @@ import botocore.exceptions
9
  import gradio as gr
10
  from langchain_aws import ChatBedrock
11
  from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
12
- from langgraph.graph.graph import CompiledGraph
13
  from langgraph.prebuilt import create_react_agent
14
 
 
 
 
 
 
 
15
 
16
  #### Constants ####
17
 
@@ -59,7 +66,7 @@ def create_bedrock_llm(
59
  try:
60
  bedrock_client = boto3.client("bedrock-runtime", **boto3_config)
61
  llm = ChatBedrock(
62
- model=bedrock_model_id,
63
  client=bedrock_client,
64
  model_kwargs={"temperature": 0.7},
65
  )
@@ -78,6 +85,7 @@ async def gr_connect_to_bedrock(
78
  secret_key: str,
79
  session_token: str,
80
  region: str,
 
81
  ) -> str:
82
  """Initialize Bedrock agent."""
83
  global llm_agent # noqa: PLW0603
@@ -96,9 +104,33 @@ async def gr_connect_to_bedrock(
96
  if llm is None:
97
  return f"❌ Connection failed: {error}"
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  llm_agent = create_react_agent(
100
  model=llm,
101
- tools=[],
102
  prompt=SYSTEM_MESSAGE,
103
  )
104
 
@@ -134,6 +166,18 @@ async def gr_chat_function( # noqa: D103
134
  with gr.Blocks() as gr_app:
135
  gr.Markdown("# 🔐 Secure Bedrock Chatbot")
136
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  # Credentials section (collapsible)
138
  with gr.Accordion("🔑 Bedrock Configuration", open=True):
139
  gr.Markdown(
@@ -185,6 +229,7 @@ with gr.Blocks() as gr_app:
185
  aws_secret_key_textbox,
186
  aws_session_token_textbox,
187
  aws_region_dropdown,
 
188
  ],
189
  outputs=[status_textbox],
190
  )
 
1
  from __future__ import annotations
2
 
3
+ from collections.abc import Mapping, Sequence
4
  from types import MappingProxyType
5
+ from typing import TYPE_CHECKING
6
 
7
  import boto3
8
  import botocore
 
10
  import gradio as gr
11
  from langchain_aws import ChatBedrock
12
  from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
13
+ from langchain_mcp_adapters.client import MultiServerMCPClient
14
  from langgraph.prebuilt import create_react_agent
15
 
16
+ from tdagent.grcomponents import MutableCheckBoxGroup, MutableCheckBoxGroupEntry
17
+
18
+
19
+ if TYPE_CHECKING:
20
+ from langgraph.graph.graph import CompiledGraph
21
+
22
 
23
  #### Constants ####
24
 
 
66
  try:
67
  bedrock_client = boto3.client("bedrock-runtime", **boto3_config)
68
  llm = ChatBedrock(
69
+ model_id=bedrock_model_id,
70
  client=bedrock_client,
71
  model_kwargs={"temperature": 0.7},
72
  )
 
85
  secret_key: str,
86
  session_token: str,
87
  region: str,
88
+ mcp_servers: Sequence[MutableCheckBoxGroupEntry] | None,
89
  ) -> str:
90
  """Initialize Bedrock agent."""
91
  global llm_agent # noqa: PLW0603
 
104
  if llm is None:
105
  return f"❌ Connection failed: {error}"
106
 
107
+ # client = MultiServerMCPClient(
108
+ # {
109
+ # "toolkit": {
110
+ # "url": "https://agents-mcp-hackathon-tdagenttools.hf.space/gradio_api/mcp/sse",
111
+ # "transport": "sse",
112
+ # },
113
+ # }
114
+ # )
115
+ # tools = await client.get_tools()
116
+
117
+ if mcp_servers:
118
+ client = MultiServerMCPClient(
119
+ {
120
+ server.name.replace(" ", "-"): {
121
+ "url": server.value,
122
+ "transport": "sse",
123
+ }
124
+ for server in mcp_servers
125
+ },
126
+ )
127
+ tools = await client.get_tools()
128
+ else:
129
+ tools = []
130
+
131
  llm_agent = create_react_agent(
132
  model=llm,
133
+ tools=tools,
134
  prompt=SYSTEM_MESSAGE,
135
  )
136
 
 
166
  with gr.Blocks() as gr_app:
167
  gr.Markdown("# 🔐 Secure Bedrock Chatbot")
168
 
169
+ ### MCP Servers ###
170
+ with gr.Accordion():
171
+ mcp_list = MutableCheckBoxGroup(
172
+ values=[
173
+ MutableCheckBoxGroupEntry(
174
+ name="TDAgent tools",
175
+ value="https://agents-mcp-hackathon-tdagenttools.hf.space/gradio_api/mcp/sse",
176
+ ),
177
+ ],
178
+ label="MCP Servers",
179
+ )
180
+
181
  # Credentials section (collapsible)
182
  with gr.Accordion("🔑 Bedrock Configuration", open=True):
183
  gr.Markdown(
 
229
  aws_secret_key_textbox,
230
  aws_session_token_textbox,
231
  aws_region_dropdown,
232
+ mcp_list.state,
233
  ],
234
  outputs=[status_textbox],
235
  )
tdagent/grcomponents/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .mcbgroup import MutableCheckBoxGroup, MutableCheckBoxGroupEntry
tdagent/grcomponents/mcbgroup.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, NamedTuple
4
+
5
+ import gradio as gr
6
+
7
+
8
+ if TYPE_CHECKING:
9
+ from collections.abc import Callable, Sequence
10
+
11
+
12
+ class MutableCheckBoxGroupEntry(NamedTuple):
13
+ """Entry of the mutable checkbox group."""
14
+
15
+ name: str
16
+ value: str
17
+
18
+
19
+ class MutableCheckBoxGroup(gr.Blocks):
20
+ """Check box group with controls to add or remove values."""
21
+
22
+ def __init__( # noqa: PLR0913
23
+ self,
24
+ values: list[MutableCheckBoxGroupEntry] | None = None,
25
+ label: str = "Extendable List",
26
+ new_value_label: str = "New Item Value",
27
+ new_name_label: str = "New Item Name",
28
+ new_value_placeholder: str = "New item value ...",
29
+ new_name_placeholder: str = "New item name, if not given value will be used...",
30
+ on_change: Callable[[Sequence[MutableCheckBoxGroupEntry]], None] | None = None,
31
+ ) -> None:
32
+ super().__init__()
33
+ self.values = values or []
34
+
35
+ self.label = label
36
+ self.new_value_label = new_value_label
37
+ self.new_name_label = new_name_label
38
+
39
+ self.new_value_placeholder = new_value_placeholder
40
+ self.new_name_placeholder = new_name_placeholder
41
+
42
+ self.on_change = on_change
43
+ self._build_interface()
44
+
45
+ def _build_interface(self) -> None:
46
+ # Custom CSS for vertical checkbox layout
47
+ self.style = """
48
+ #vertical-container .wrap {
49
+ display: flex;
50
+ flex-direction: column;
51
+ gap: 8px;
52
+ }
53
+ #vertical-container .wrap label {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: 8px;
57
+ }
58
+ """
59
+
60
+ with self:
61
+ gr.Markdown(f"### {self.label}")
62
+
63
+ # Store items in state
64
+ self.state = gr.State(self.values)
65
+
66
+ # Input row
67
+ with gr.Row():
68
+ self.input_value = gr.Textbox(
69
+ label=self.new_value_label,
70
+ placeholder=self.new_value_placeholder,
71
+ scale=4,
72
+ )
73
+ self.input_name = gr.Textbox(
74
+ label=self.new_name_label,
75
+ placeholder=self.new_name_placeholder,
76
+ scale=2,
77
+ )
78
+ with gr.Column():
79
+ self.add_btn = gr.Button("Add", variant="primary", scale=1)
80
+ self.delete_btn = gr.Button("Delete Selected", variant="stop")
81
+
82
+ # Vertical checkbox group
83
+ self.items_group = gr.CheckboxGroup(
84
+ choices=self.values,
85
+ label="Items",
86
+ elem_id="vertical-container",
87
+ container=True,
88
+ )
89
+
90
+ # Set up event handlers
91
+ self.add_btn.click(
92
+ self._add_item,
93
+ inputs=[self.state, self.input_value, self.input_name],
94
+ outputs=[
95
+ self.state,
96
+ self.items_group,
97
+ self.input_value,
98
+ self.input_name,
99
+ ],
100
+ )
101
+
102
+ self.delete_btn.click(
103
+ self._delete_selected,
104
+ inputs=[self.state, self.items_group],
105
+ outputs=[self.state, self.items_group],
106
+ )
107
+
108
+ def get_values(self) -> Sequence[str]:
109
+ """Get check box values."""
110
+ return self.state.value
111
+
112
+ def _add_item(
113
+ self,
114
+ items: list[MutableCheckBoxGroupEntry],
115
+ new_value: str,
116
+ new_name: str,
117
+ ) -> tuple[list[MutableCheckBoxGroupEntry], dict[str, Any], str, str]:
118
+ if not new_name:
119
+ new_name = new_value
120
+
121
+ if new_value:
122
+ if any(entry.name == new_name for entry in items):
123
+ raise gr.Error(
124
+ f"Entry with name '{new_name}' already exists",
125
+ duration=10,
126
+ )
127
+ if any(entry.value == new_value for entry in items):
128
+ raise gr.Error(
129
+ f"Entry with value '{new_value}' already exists",
130
+ duration=10,
131
+ )
132
+
133
+ items = [*items, MutableCheckBoxGroupEntry(new_name, new_value)]
134
+ if self.on_change:
135
+ self.on_change(items)
136
+
137
+ # State, checkbox, input_value, input_name
138
+ return items, gr.update(choices=items), "", ""
139
+
140
+ # State, checkbox, input_value, input_name
141
+ return items, gr.update(), "", ""
142
+
143
+ def _delete_selected(
144
+ self,
145
+ items: list[MutableCheckBoxGroupEntry],
146
+ selected: list[str],
147
+ ) -> tuple[list[MutableCheckBoxGroupEntry], dict[str, Any]]:
148
+ updated_items = [item for item in items if item.value not in selected]
149
+ if self.on_change:
150
+ self.on_change(updated_items)
151
+ return updated_items, gr.update(choices=updated_items, value=[])
uv.lock CHANGED
@@ -705,6 +705,19 @@ wheels = [
705
  { url = "https://files.pythonhosted.org/packages/5c/71/a748861e6a69ab6ef50ab8e65120422a1f36245c71a0dd0f02de49c208e1/langchain_core-0.3.63-py3-none-any.whl", hash = "sha256:f91db8221b1bc6808f70b2e72fded1a94d50ee3f1dff1636fb5a5a514c64b7f5", size = 438468 },
706
  ]
707
 
 
 
 
 
 
 
 
 
 
 
 
 
 
708
  [[package]]
709
  name = "langgraph"
710
  version = "0.4.7"
@@ -1905,6 +1918,7 @@ dependencies = [
1905
  { name = "gradio", extra = ["mcp"] },
1906
  { name = "huggingface-hub" },
1907
  { name = "langchain-aws" },
 
1908
  { name = "langgraph" },
1909
  ]
1910
 
@@ -1927,6 +1941,7 @@ requires-dist = [
1927
  { name = "gradio", extras = ["mcp"], specifier = ">=5.32.1" },
1928
  { name = "huggingface-hub", specifier = ">=0.32.3" },
1929
  { name = "langchain-aws", specifier = ">=0.2.24" },
 
1930
  { name = "langgraph", specifier = ">=0.4.7" },
1931
  ]
1932
 
 
705
  { url = "https://files.pythonhosted.org/packages/5c/71/a748861e6a69ab6ef50ab8e65120422a1f36245c71a0dd0f02de49c208e1/langchain_core-0.3.63-py3-none-any.whl", hash = "sha256:f91db8221b1bc6808f70b2e72fded1a94d50ee3f1dff1636fb5a5a514c64b7f5", size = 438468 },
706
  ]
707
 
708
+ [[package]]
709
+ name = "langchain-mcp-adapters"
710
+ version = "0.1.1"
711
+ source = { registry = "https://pypi.org/simple" }
712
+ dependencies = [
713
+ { name = "langchain-core" },
714
+ { name = "mcp" },
715
+ ]
716
+ sdist = { url = "https://files.pythonhosted.org/packages/b0/de/62a6ee2c21f74eed961773e75a4e3170f8abc79fd5fd7a1b4e2ea07f4c04/langchain_mcp_adapters-0.1.1.tar.gz", hash = "sha256:e43ddf06e4ce237ad80f5c91d0efa7fd7c845b274a5a07e7e8a7f9d7239331d7", size = 17359 }
717
+ wheels = [
718
+ { url = "https://files.pythonhosted.org/packages/30/68/13405f252b38a8e1bd7ef345907a4e0eda535c2ca36fe4d6821fc7e9f5de/langchain_mcp_adapters-0.1.1-py3-none-any.whl", hash = "sha256:81594b265d824012040ebd24056fbdb5aabf0b46f780e369ed132421e3411e4d", size = 12100 },
719
+ ]
720
+
721
  [[package]]
722
  name = "langgraph"
723
  version = "0.4.7"
 
1918
  { name = "gradio", extra = ["mcp"] },
1919
  { name = "huggingface-hub" },
1920
  { name = "langchain-aws" },
1921
+ { name = "langchain-mcp-adapters" },
1922
  { name = "langgraph" },
1923
  ]
1924
 
 
1941
  { name = "gradio", extras = ["mcp"], specifier = ">=5.32.1" },
1942
  { name = "huggingface-hub", specifier = ">=0.32.3" },
1943
  { name = "langchain-aws", specifier = ">=0.2.24" },
1944
+ { name = "langchain-mcp-adapters", specifier = ">=0.1.1" },
1945
  { name = "langgraph", specifier = ">=0.4.7" },
1946
  ]
1947