alihmaou commited on
Commit
cb42665
·
1 Parent(s): 193a503

First commit as a hackathon submission, will improve it if I have time :)

Browse files
Files changed (6) hide show
  1. .gitignore +196 -0
  2. README.md +64 -1
  3. app.py +145 -0
  4. data/hackathon_mcp_tools.parquet +3 -0
  5. requirements.txt +8 -0
  6. src/bonus.py +71 -0
.gitignore ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .env
2
+ # Byte-compiled / optimized / DLL files
3
+ __pycache__/
4
+ *.py[cod]
5
+ *$py.class
6
+
7
+ # C extensions
8
+ *.so
9
+
10
+ # Distribution / packaging
11
+ .Python
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ wheels/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ # For a library or package, you might want to ignore these files since the code is
88
+ # intended to run in multiple environments; otherwise, check them in:
89
+ # .python-version
90
+
91
+ # pipenv
92
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
94
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
95
+ # install all needed dependencies.
96
+ #Pipfile.lock
97
+
98
+ # UV
99
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
100
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
101
+ # commonly ignored for libraries.
102
+ #uv.lock
103
+
104
+ # poetry
105
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
106
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
107
+ # commonly ignored for libraries.
108
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
109
+ #poetry.lock
110
+ #poetry.toml
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ #pdm.lock
115
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
116
+ # in version control.
117
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
118
+ .pdm.toml
119
+ .pdm-python
120
+ .pdm-build/
121
+
122
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
123
+ __pypackages__/
124
+
125
+ # Celery stuff
126
+ celerybeat-schedule
127
+ celerybeat.pid
128
+
129
+ # SageMath parsed files
130
+ *.sage.py
131
+
132
+ # Environments
133
+ .env
134
+ .venv
135
+ env/
136
+ venv/
137
+ ENV/
138
+ env.bak/
139
+ venv.bak/
140
+
141
+ # Spyder project settings
142
+ .spyderproject
143
+ .spyproject
144
+
145
+ # Rope project settings
146
+ .ropeproject
147
+
148
+ # mkdocs documentation
149
+ /site
150
+
151
+ # mypy
152
+ .mypy_cache/
153
+ .dmypy.json
154
+ dmypy.json
155
+
156
+ # Pyre type checker
157
+ .pyre/
158
+
159
+ # pytype static type analyzer
160
+ .pytype/
161
+
162
+ # Cython debug symbols
163
+ cython_debug/
164
+
165
+ # PyCharm
166
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
167
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
168
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
169
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
170
+ #.idea/
171
+
172
+ # Abstra
173
+ # Abstra is an AI-powered process automation framework.
174
+ # Ignore directories containing user credentials, local state, and settings.
175
+ # Learn more at https://abstra.io/docs
176
+ .abstra/
177
+
178
+ # Visual Studio Code
179
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
180
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
181
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
182
+ # you could uncomment the following to ignore the entire vscode folder
183
+ # .vscode/
184
+
185
+ # Ruff stuff:
186
+ .ruff_cache/
187
+
188
+ # PyPI configuration file
189
+ .pypirc
190
+
191
+ # Cursor
192
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
193
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
194
+ # refer to https://docs.cursor.com/context/ignore-files
195
+ .cursorignore
196
+ .cursorindexingignore
README.md CHANGED
@@ -8,6 +8,69 @@ sdk_version: 5.33.0
8
  app_file: app.py
9
  pinned: false
10
  short_description: A little app with a smolagent to explore and test MCP Tools
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  app_file: app.py
9
  pinned: false
10
  short_description: A little app with a smolagent to explore and test MCP Tools
11
+ tags:
12
+ - agent-demo-track
13
  ---
14
 
15
+ # 🚀 MCP Explorer
16
+
17
+ **Project submitted for the [Agents-MCP-Hackathon – June 2025](https://huggingface.co/Agents-MCP-Hackathon)**
18
+ Explore, test, and debug any MCP-compatible server — in one simple interface.
19
+
20
+ ---
21
+
22
+ ## 🧩 What is MCP Explorer?
23
+
24
+ **MCP Explorer** is a Gradio-based tool built to help participants of the MCP Hackathon (and beyond) interact with the Model Context Protocol ecosystem.
25
+
26
+ It allows you to:
27
+
28
+ - 🔍 Discover and visualize tools exposed by any MCP server
29
+ - 🧪 Test real-time prompts using a `smolagent` powered by `Qwen 32B`
30
+ - 🔄 Automatically refresh the list of tools from any endpoint
31
+ - 🧠 Browse available tools in a clean interactive table
32
+ - 🌍 Quickly switch between hackathon candidates and custom endpoints
33
+
34
+ ---
35
+
36
+ ## 🎯 Features
37
+
38
+ | Feature | Description |
39
+ |----------------------------------|-----------------------------------------------------------------------------|
40
+ | 🔧 **Live MCP querying** | Send a prompt and let the agent reason using the selected MCP tools |
41
+ | 📊 **Structured tool viewer** | Display each tool's name, description, and input schema in a readable way |
42
+ | 🔀 **Endpoint selector** | Toggle between hackathon submissions or custom MCP server URLs |
43
+ | ⚡ **Auto-refresh on load** | Automatically pulls tools on app startup |
44
+ | 🗃 **Tool index builder (bonus)** | Aggregates all MCP tools from the hackathon into a single `parquet` file |
45
+
46
+ ---
47
+
48
+ ## 🧪 How to use it
49
+
50
+ 1. Clone or open the Space.
51
+ 2. Select a known MCP server or paste a custom one.
52
+ 3. Click “🔄 Refresh MCP tools list” to load the available tools.
53
+ 4. Type a natural language prompt — the agent will use the tools if applicable.
54
+
55
+ ---
56
+
57
+ ## 📦 Bonus: MCP Tool Index
58
+
59
+ In the `src/bonus.py` module, you'll find `extracthackathontools()` —
60
+ a utility that scans all `mcp-server` tagged Spaces from the hackathon and builds a global index of available tools in `hackathon_mcp_tools.parquet`.
61
+
62
+ ---
63
+
64
+ ## 🧠 Credits
65
+
66
+ Built by [Ali Hmaou](https://huggingface.co/alihmaou)
67
+ With gratitude to the Hugging Face team for the amazing infrastructure, API access, and documentation.
68
+
69
+
70
+ ---
71
+
72
+ ## 🛠 Tech stack
73
+
74
+ - Python, Gradio
75
+ - `smolagents`, `Qwen 32B`, `pandas`
76
+ - MCP Protocol (streamable-http)
app.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ from mcp import StdioServerParameters
4
+ from smolagents import InferenceClientModel, CodeAgent, MCPClient
5
+ import pandas as pd
6
+ import requests
7
+
8
+ DEFAULT_MCP_URL = "https://alihmaou-mcp-tools.hf.space/gradio_api/mcp/sse"
9
+ HF_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN")
10
+
11
+ def get_mcp_space_endpoints():
12
+ """Fetch MCP-compatible Spaces from the HF Hackathon org."""
13
+ try:
14
+ resp = requests.get("https://huggingface.co/api/spaces?author=Agents-MCP-Hackathon")
15
+ resp.raise_for_status()
16
+ spaces = resp.json()
17
+ except Exception as e:
18
+ print(f"[Warning] Failed to fetch MCP spaces: {e}")
19
+ return [DEFAULT_MCP_URL]
20
+
21
+ endpoints = [DEFAULT_MCP_URL]
22
+ for space in spaces:
23
+ if "mcp-server" in space.get("tags", []):
24
+ full_id = space["id"]
25
+ slug = full_id.replace("/", "-").replace("_", "-").lower()
26
+ endpoint = f"https://{slug}.hf.space/gradio_api/mcp/sse"
27
+ if endpoint != DEFAULT_MCP_URL:
28
+ endpoints.append(endpoint)
29
+ return endpoints
30
+
31
+ def reload_tools_from_url(mcp_url_input):
32
+ global tools, agent, mcp_client, mcp_url
33
+
34
+ mcp_url=mcp_url_input
35
+
36
+ try:
37
+ mcp_client = MCPClient({"url": mcp_url,"transport": "sse"}) # Might be deprecated soon but didnt find out the clean way
38
+ tools = mcp_client.get_tools()
39
+
40
+ model = InferenceClientModel(token=os.getenv("HUGGINGFACE_API_TOKEN"))
41
+ agent = CodeAgent(tools=tools, model=model,)
42
+ except Exception as e:
43
+ print(f"[Warning] Failed to reach MCP server: {e}")
44
+
45
+ # Tableau structuré : nom, description, inputs attendus
46
+ rows = []
47
+ for tool in tools:
48
+ input_fields = ", ".join(param for param in tool.inputs)
49
+ rows.append({
50
+ "Tool name": tool.name,
51
+ "Description": tool.description,
52
+ "Params": input_fields
53
+ })
54
+ df = pd.DataFrame(rows)
55
+ return gr.DataFrame(value=df)
56
+
57
+ with gr.Blocks() as demo:
58
+ gr.Markdown("""
59
+ <div align="center">
60
+ <h1>🚀 MCP Tools Explorer – Agents-MCP-Hackathon (June 2025)</h1>
61
+ <p>
62
+ 🔍 Query any MCP-compatible endpoint, 🛠️ browse available tools in a clean table view, and 🤖 test real-time interactions using a `smolagent` powered by `HuggingFace`. <br/>
63
+ Perfect for 🧪 exploring fellow participants’ tools or 🧰 debugging your own MCP server during the event! </br>
64
+ 🍒 As a cherry on the cake, the list of the tools developed for the hackathon will be updated here from time to time (see src/bonus.py) : <a href="https://huggingface.co/datasets/alihmaou/Agents_MCP_Hackathon_Tools_List/blob/main/hackathon_mcp_tools.parquet">Ready to use hackathon MCP tools list</a>
65
+ </p>
66
+ </div>
67
+ """,)
68
+
69
+ with gr.Row():
70
+
71
+ with gr.Column(scale=1):
72
+ gr.Markdown("""
73
+ <div align="center">
74
+ <h2>🛠️ Set an MCP server and discover its tools</h2>
75
+ </div>
76
+ """)
77
+ source_selector = gr.Radio(
78
+ choices=["Hackathon candidates", "Custom MCP URL"],
79
+ label="🔀 Source",
80
+ value="Hackathon candidates"
81
+ )
82
+
83
+ hackathon_dropdown = gr.Dropdown(
84
+ label="Select a MCP server from the hackathon organisation",
85
+ choices=get_mcp_space_endpoints(),
86
+ value=DEFAULT_MCP_URL,
87
+ interactive=True,
88
+ visible=True
89
+ )
90
+
91
+ custom_url_input = gr.Textbox(
92
+ label="Enter custom MCP server URL",
93
+ value="https://agents-mcp-hackathon-mcp-server-demo.hf.space/gradio_api/mcp/sse",
94
+ visible=False
95
+ )
96
+
97
+ def toggle_url_input(source):
98
+ return (
99
+ gr.update(visible=(source == "Hackathon candidates")),
100
+ gr.update(visible=(source == "Custom MCP URL"))
101
+ )
102
+
103
+ def reload_tools_router(source, dropdown_val, custom_val):
104
+ selected_url = dropdown_val if source == "Hackathon candidates" else custom_val
105
+ return reload_tools_from_url(selected_url)
106
+
107
+ source_selector.change(fn=toggle_url_input, inputs=source_selector, outputs=[hackathon_dropdown, custom_url_input])
108
+
109
+ #print("[MCP] Endpoints loaded:", get_mcp_space_endpoints())
110
+ tool_table = gr.DataFrame(headers=["Tool name", "Description", "Params"], interactive=False, label="🔧 MCP Tools availables", wrap=True)
111
+ reload_btn = gr.Button("🔄 Refresh and set MCP tools list")
112
+ reload_btn.click(
113
+ fn=reload_tools_router,
114
+ inputs=[source_selector, hackathon_dropdown, custom_url_input],
115
+ outputs=tool_table
116
+ )
117
+ hackathon_dropdown.change(
118
+ fn=reload_tools_router,
119
+ inputs=[source_selector, hackathon_dropdown, custom_url_input],
120
+ outputs=tool_table
121
+ )
122
+ custom_url_input.submit(
123
+ fn=reload_tools_router,
124
+ inputs=[source_selector, hackathon_dropdown, custom_url_input],
125
+ outputs=tool_table
126
+ )
127
+
128
+ with gr.Column(scale=2):
129
+ gr.Markdown("""
130
+ <div align="center">
131
+ <h2>🔎 Test them with smolagents</h2>
132
+ </div>
133
+ """)
134
+ chatbot = gr.ChatInterface(
135
+ fn=lambda message, history: str(agent.run(message)),
136
+ type="messages",
137
+ )
138
+
139
+ demo.launch()
140
+
141
+ try:
142
+ if mcp_client:
143
+ mcp_client.disconnect()
144
+ except Exception as e:
145
+ print(f"[Warning] Failed to disconnect MCP client: {e}")
data/hackathon_mcp_tools.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:68e00574e01656199f29d490355f2adc9486ff07a97105ab0b44c823ba59e0d9
3
+ size 40653
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ smolagents[mcp]
2
+ gradio [mcp]
3
+ mcp
4
+ fastmcp
5
+ python-dotenv
6
+ pandas
7
+ pyarrow
8
+ fastparquet
src/bonus.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import pandas as pd
3
+ from smolagents import MCPClient
4
+
5
+
6
+
7
+ def extracthackathontools():
8
+ """Scrape all MCP tools from hackathon Spaces and return as a pandas DataFrame to allow download as a bonus feature of the MCP Explorer."""
9
+ HF_API = "https://huggingface.co/api/spaces?author=Agents-MCP-Hackathon"
10
+
11
+ try:
12
+ resp = requests.get(HF_API)
13
+ resp.raise_for_status()
14
+ except Exception as e:
15
+ raise RuntimeError(f"Failed to fetch Spaces list: {e}")
16
+
17
+ spaces = resp.json()
18
+ rows = []
19
+
20
+ for space in spaces:
21
+ tags = space.get("tags", [])
22
+ if "mcp-server" not in tags:
23
+ continue
24
+
25
+ space_id = space["id"]
26
+ space_slug = space_id.replace("/", "-").replace("_", "-").lower()
27
+ mcp_base_url = f"https://{space_slug}.hf.space/gradio_api/mcp/sse"
28
+
29
+
30
+ try:
31
+ print(mcp_base_url)
32
+ #mcp_status = requests.get(mcp_base_url, timeout=5)
33
+ #print(mcp_status.status_code)
34
+ #if mcp_status.status_code != 200:
35
+ # print(f"[Skip] Space in error: {mcp_base_url}")
36
+ # continue
37
+
38
+ mcp_client = MCPClient({"url": mcp_base_url,"transport": "sse"}) # Might be deprecated soon but didnt find out the clean way
39
+ tools = mcp_client.get_tools()
40
+ print(len(tools))
41
+ mcp_client.disconnect()
42
+ except Exception as e:
43
+ print(f"[Warning] Could not fetch tools from {mcp_base_url}: {e}")
44
+ continue
45
+
46
+ # Infos générales du Space
47
+ author, name = space_id.split("/")
48
+ hf_url = f"https://huggingface.co/spaces/{space_id}"
49
+ created_at = space.get("createdAt", "")
50
+ n_likes = space.get("likes", 0)
51
+
52
+ for tool in tools:
53
+ input_fields = ", ".join(param for param in tool.inputs)
54
+ rows.append({
55
+ "Gradio MCP endpoint": f"{mcp_base_url}",
56
+ "Tool name": tool.name,
57
+ "Tool description": tool.description,
58
+ "Tool inputs": input_fields,
59
+ "Space name": name,
60
+ "HF Space URL": hf_url,
61
+ "Likes": n_likes,
62
+ "Created at": created_at,
63
+ "Tags": ", ".join(tags)
64
+ })
65
+
66
+ df = pd.DataFrame(rows)
67
+ return df
68
+
69
+ df=extracthackathontools()
70
+
71
+ df.to_parquet("./data/hackathon_mcp_tools.parquet", index=False)