File size: 5,829 Bytes
7476735
 
 
 
 
3a7c6fc
7476735
3a7c6fc
7476735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a7c6fc
 
 
 
 
 
 
 
 
 
 
 
 
7476735
 
 
 
 
3a7c6fc
7476735
 
 
 
 
 
 
 
 
9b7940f
7476735
 
 
 
3a7c6fc
7476735
 
 
 
 
3a7c6fc
7476735
 
 
 
 
 
 
 
 
3a7c6fc
 
7476735
 
9b7940f
7476735
3a7c6fc
7476735
 
 
 
 
 
 
3a7c6fc
7476735
 
 
3a7c6fc
9b7940f
7476735
 
 
 
 
 
3a7c6fc
7476735
9b7940f
 
 
 
7476735
 
3a7c6fc
7476735
 
 
9b7940f
7476735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9b7940f
7476735
 
 
 
3a7c6fc
 
7476735
 
3a7c6fc
7476735
 
 
 
3a7c6fc
7476735
 
 
 
 
 
 
 
 
 
 
 
 
3a7c6fc
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
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={},
).launch(mcp_server=True)
"""

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"
        )
        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, space_name_box, oauth_token: gr.OAuthToken | None):
    if oauth_token:
        if not paths:
            paths = None
        else:
            paths = f"[\"{paths}\"]"
        create_hf_space(
            oauth_token.token, 
            space_name_box,
            gradio_app_code.format(
                openapi_spec_url,
                api_base_url,
                paths,
                methods
            )
        )
    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/")
            paths = gr.Textbox(label="Optional regex to filter paths by", placeholder=".*user.*")
            methods = gr.CheckboxGroup(label="Methods", choices=["GET", "POST", "PUT", "DELETE"], value=["GET", "POST", "PUT", "DELETE"])
            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, space_name_box],
            outputs=None
        )

demo.launch()