File size: 4,364 Bytes
0d1138e
 
42c6967
0d1138e
 
 
 
 
 
 
8aac38c
 
 
 
 
0d1138e
59ba69c
0d1138e
8aac38c
0d1138e
 
 
8aac38c
59ba69c
0d1138e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8aac38c
0d1138e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59ba69c
0d1138e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8aac38c
0d1138e
59ba69c
0d1138e
 
d78f12e
0d1138e
 
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
import gradio as gr

import config
import utils


# Import obb (don't reload it)
if gr.NO_RELOAD:
    from openbb import obb

# A set of submodules to include when searching for tools, registering
# them to the MCP server, and dynamically building the Gradio interface
# List of valid values: https://docs.openbb.co/platform/reference
INCLUDE = {"currency", "equity", "news"}

# Get tool names and guides
print("Getting tool names and guides...")
ARGS = "obb", obb # type: ignore
tool_names = utils.get_callable_names(*ARGS, include=INCLUDE)
tool_guides = utils.generate_callable_guides(*ARGS, tool_names)


# Build the demo interface
print("Building the demo...")
with gr.Blocks() as demo:
    gr.Markdown("# OpenBB MCP")

    # Reference guide
    with gr.Accordion("Tool Reference Guide 📃", open=False):
        for tool_name, tool_guide in zip(tool_names, tool_guides):
            
            # Create a collapsible for each tool
            with gr.Accordion(f"{tool_name}", open=False):
                gr.Markdown(f"```\n{tool_guide}```")

                # Dynamically generate tool test UI
                tool_params = utils.get_callable_params(*ARGS, tool_name)
                tool_param_inputs = []
                tool_param_kinds = []
                with gr.Row():
                    for param in tool_params:
                        tool_param_inputs.append(
                            gr.Textbox(
                              label=param["name"],
                              value=str(param["default"]) if param["default"] else ""
                            )
                        )
                        tool_param_kinds.append(param["kind"])
                output = gr.Textbox(label="Output", lines=10)
                test_btn = gr.Button("Run")

                # Format vars for dynamic function generation
                t = tool_name
                tool_name = tool_name.replace(".", "_")
                tool_guide = tool_guide.replace(t, tool_name)
                tool_param_names = [
                    param["name"] for param in tool_params
                ]
                csv_tool_names = ", ".join(tool_param_names)

                # Dynamically generate function
                # TODO: Fragile. Refactor if possible
                namespace = { # Create a local namespace
                    "utils": utils,
                    "ARGS": ARGS,
                    "tool_param_kinds": tool_param_kinds
                }
                exec(f"""\
import inspect
import json               

def {tool_name}({csv_tool_names}) -> str:
    \"\"\"
    {tool_guide}
    \"\"\"
    print("{tool_name}:", locals())
    args = []
    try:
        kwargs = json.loads(kwargs)
    except Exception:
        kwargs = {{}}
    for kind, name, value in zip(
        tool_param_kinds,
        {tool_param_names},
        [{csv_tool_names}],
    ):
        if kind in {{
            inspect.Parameter.POSITIONAL_ONLY,
            inspect.Parameter.POSITIONAL_OR_KEYWORD,
        }}:
            args.append(value if value else None)
    return utils.test_callable(*ARGS, "{tool_name}", *args, **kwargs)
""", namespace)

                # Extract tool from namespace and register to the MCP server
                test_btn.click(
                    fn=namespace[tool_name], # type: ignore
                    inputs=tool_param_inputs,
                    outputs=output,
                )

    # Usage instructions
    with gr.Accordion("Use via MCP 🛠️", open=False):
        gr.Markdown("""\
**SSE support**: To add this MCP to clients that support SSE (e.g. Cursor, Windsurf, Cline), simply add the following configuration to your MCP config:
 
```
{
  "mcpServers": {
    "OpenBB-MCP": {
      "url": "http://xarical-openbb-mcp.hf.space/gradio_api/mcp/sse"
    }
  }
}
```

**Stdio support**: For clients that only support stdio, first install Node.js. Then, you can use the following command:
 
```
{
  "mcpServers": {
    "OpenBB-MCP": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "http://xarical-openbb-mcp.hf.space/gradio_api/mcp/sse",
        "--transport",
        "sse-only"
      ]
    }
  }
}
```
""")


# Launch the demo
if __name__ == "__main__":
    print("Starting the demo...")
    demo.launch(
        server_port=7860,
        show_api=False,
        mcp_server=True,
    )