File size: 7,160 Bytes
7476735
 
 
 
 
3a7c6fc
7476735
3a7c6fc
7476735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a7c6fc
 
 
 
 
 
 
 
 
 
 
 
 
7476735
 
 
 
 
3a7c6fc
7476735
 
 
 
 
 
 
 
 
9b7940f
7476735
3754af6
7476735
 
 
0e3672c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a7c6fc
7476735
 
 
 
 
3a7c6fc
7476735
 
 
 
 
 
 
 
 
3a7c6fc
 
7476735
 
9b7940f
7476735
3a7c6fc
7476735
 
 
 
 
 
 
3a7c6fc
7476735
 
 
0e3672c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a7c6fc
9b7940f
7476735
 
 
 
 
 
0e3672c
7476735
9b7940f
 
 
 
3754af6
 
 
 
7476735
 
3a7c6fc
7476735
 
 
9b7940f
0e3672c
 
7476735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0e3672c
 
 
7476735
 
 
3a7c6fc
 
7476735
 
3a7c6fc
7476735
 
 
 
3a7c6fc
7476735
 
 
 
 
 
 
 
 
 
 
 
 
0e3672c
7476735
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import gradio as gr
import httpx
import json
from huggingface_hub import HfApi
import random
import re

def find_endpoints(openapi_spec_url, api_base_url, paths, methods):
    print(f"Finding endpoints for {openapi_spec_url} with methods {methods}")
    if openapi_spec_url.startswith(("http://", "https://")):
        response = httpx.get(openapi_spec_url)
        response.raise_for_status()
        content = response.text
    else:
        raise gr.Error("Invalid URL for OpenAPI spec")

    try:
        spec = json.loads(content)
    except json.JSONDecodeError as e:
        raise gr.Error("Invalid JSON for OpenAPI spec")

    api_paths = spec.get("paths", {})
    if not api_paths:
        raise gr.Error("No valid paths found in the OpenAPI specification")

    valid_api_paths = []
    for path, path_item in api_paths.items():
        for method, operation in path_item.items():
            if methods and method.lower() not in [m.lower() for m in methods]:
                continue
            if not paths:
                valid_api_paths.append({
                    "path": path,
                    "method": method.upper(),
                })
            else:
                for path_regex in paths.split(","):
                    if re.match(path_regex, path):
                        valid_api_paths.append({
                            "path": path,
                            "method": method.upper(),
                        })
                        break

    return gr.JSON(valid_api_paths, label=f"πŸ” {len(valid_api_paths)} endpoints found")

def update_bottom(oauth_token: gr.OAuthToken | None):
    if oauth_token:
        return "Click the πŸš€ Create button to create a new MCP Space", gr.Button(interactive=True)
    else:
        return gr.skip()

gradio_app_code = """
import gradio as gr

gr.load_openapi(
    openapi_spec=\"{}\",
    base_url=\"{}\",
    paths={},
    methods={},
    auth_token={}
).launch(mcp_server=True)
"""

readme_code = """
---
title: OpenAPI MCP Server
sdk: gradio
sdk_version: 5.38.2
app_file: app.py
pinned: false
tags:
 - openapi2mcp
---

Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
"""

requirements_code = """
https://gradio-pypi-previews.s3.amazonaws.com/86331cf36187d17a6d3ce26b01f28ea8dd5cbe35/gradio-5.38.2-py3-none-any.whl
"""

def create_hf_space(token, space_name, app_code):
    """
    Create a new Hugging Face Space with optional app.py file
    
    Args:
        token (str): Your Hugging Face API token
        space_name (str): The name of the space to create
        app_code (str): String content for the app.py file
    
    Returns:
        SpaceInfo: Information about the created space
    """
    
    api = HfApi(token=token)
    user_info = api.whoami()
    username = user_info["name"]
    space_name = space_name or f"my-mcp-space-{random.randint(100000, 999999)}" 
    space_id = f"{username}/{space_name}"
    
    try:
        gr.Info(f"Creating space {space_id}...", duration=20)
        space_info = api.create_repo(
            repo_id=space_id,
            repo_type="space",
            private=False,
            space_sdk="gradio"
        )
        api.upload_file(
            path_or_fileobj=app_code.encode('utf-8'),
            path_in_repo="app.py",
            repo_id=space_id,
            repo_type="space",
            commit_message="Add app.py"
        )
        api.upload_file(
            path_or_fileobj=readme_code.encode('utf-8'),
            path_in_repo="README.md",
            repo_id=space_id,
            repo_type="space",
            commit_message="Add README.md"
        )
        api.upload_file(
            path_or_fileobj=requirements_code.encode('utf-8'),
            path_in_repo="requirements.txt",
            repo_id=space_id,
            repo_type="space",
            commit_message="Add requirements.txt"
        )

        space_url = f"https://huggingface.co/spaces/{space_id}"
        gr.Success(f"πŸš€ Your space will be available at: <a href='{space_url}' target='_blank'>{space_url} ‴</a>", duration=None)        
        return space_info
        
    except Exception as e:
        gr.Error(f"❌ Error creating space: {str(e)}")


def launch_mcp_server(openapi_spec_url, api_base_url, paths, methods, auth_token, space_name_box, oauth_token: gr.OAuthToken | None):
    if oauth_token:
        if not paths:
            paths = None
        else:
            paths = f"[\"{paths}\"]"
        if not auth_token:
            auth_token = None
        else:
            auth_token = f"[\"{auth_token}\"]"
        create_hf_space(
            oauth_token.token, 
            space_name_box,
            gradio_app_code.format(
                openapi_spec_url,
                api_base_url,
                paths,
                methods,
                auth_token
            )
        )
    else:
        pass

with gr.Blocks(theme="ocean") as demo:
    gr.Markdown("## OpenAPI βžͺ MCP")
    gr.Markdown("""
    This is a tool that converts an OpenAPI spec to a MCP server that you can launch as a Space and then use with any MCP Client (e.g. ChatGPT, Claude, Cursor, Cline).
    """)
    with gr.Row():
        with gr.Column():
            openapi_spec_url = gr.Textbox(label="OpenAPI Spec URL", value="https://petstore3.swagger.io/api/v3/openapi.json")
            api_base_url = gr.Textbox(label="API Base URL", value="https://petstore3.swagger.io/api/v3/")
            methods = gr.CheckboxGroup(label="Methods", choices=["GET", "POST", "PUT", "DELETE"], value=["GET", "POST", "PUT", "DELETE"])
            with gr.Accordion("Optional Settings", open=False):
                paths = gr.Textbox(label="Regex to filter paths by", placeholder=".*user.*", info="Only include API endpoints that match this regex")
                auth_token = gr.Textbox(label="Auth token to include in API requests", info="This will be sent to the API endpoints as a Bearer token")
            find_endpoints_button = gr.Button("πŸ” Find Endpoints")
        with gr.Column():
            endpoints = gr.JSON(label="πŸ” endpoints found", value=[], max_height=400)
            message = gr.Markdown("_Note:_ you must be signed in through your Hugging Face account to create the MCP Space")
            space_name_box = gr.Textbox(show_label=False, placeholder="Optional space name (e.g. my-mcp-space)")
            with gr.Row():
                login_button = gr.LoginButton()
                launch_button = gr.Button("πŸš€ Create MCP Space", variant="primary", interactive=False)

        gr.on(
            [demo.load, find_endpoints_button.click],
            find_endpoints,
            inputs=[openapi_spec_url, api_base_url, paths, methods],
            outputs=endpoints,
        )

        gr.on(
            [demo.load],
            update_bottom,
            inputs=None,
            outputs=[message, launch_button]
        )

        gr.on(
            [launch_button.click],
            launch_mcp_server,
            inputs=[openapi_spec_url, api_base_url, paths, methods, auth_token, space_name_box],
            outputs=None
        )

demo.launch()