OpenBB-MCP / app.py
xarical's picture
Import config
42c6967
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,
)