openbb-mcp-server 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openbb_mcp_server-0.0.1/PKG-INFO +142 -0
- openbb_mcp_server-0.0.1/README.md +122 -0
- openbb_mcp_server-0.0.1/openbb_mcp_server/__init__.py +1 -0
- openbb_mcp_server-0.0.1/openbb_mcp_server/main.py +264 -0
- openbb_mcp_server-0.0.1/openbb_mcp_server/registry.py +87 -0
- openbb_mcp_server-0.0.1/openbb_mcp_server/tool_models.py +37 -0
- openbb_mcp_server-0.0.1/openbb_mcp_server/utils/__init__.py +1 -0
- openbb_mcp_server-0.0.1/openbb_mcp_server/utils/config.py +147 -0
- openbb_mcp_server-0.0.1/openbb_mcp_server/utils/route_filtering.py +49 -0
- openbb_mcp_server-0.0.1/openbb_mcp_server/utils/settings.py +70 -0
- openbb_mcp_server-0.0.1/pyproject.toml +23 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: openbb-mcp-server
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: OpenBB Platform MCP Server
|
|
5
|
+
License: AGPL-3.0-only
|
|
6
|
+
Author: OpenBB
|
|
7
|
+
Author-email: hello@openbb.co
|
|
8
|
+
Requires-Python: >=3.10,<3.13
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: fastmcp (>=2.8.0,<3.0.0)
|
|
15
|
+
Requires-Dist: openbb-core (>=1.4.3,<2.0.0)
|
|
16
|
+
Project-URL: Documentation, https://docs.openbb.co
|
|
17
|
+
Project-URL: Homepage, https://openbb.co
|
|
18
|
+
Project-URL: Repository, https://github.com/openbb-finance/openbb
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# OpenBB MCP Server
|
|
22
|
+
|
|
23
|
+
Model Context Protocol (MCP) server extension for OpenBB Platform. This extension enables LLM agents to interact with OpenBB Platform's REST API endpoints through the MCP protocol.
|
|
24
|
+
|
|
25
|
+
The server provides management tools that allow agents to explore different options and dynamically adjust their active toolset. This prevents agents from being overwhelmed with too many tools while allowing them to discover and activate only the tools they need for specific tasks.
|
|
26
|
+
|
|
27
|
+
Using these dynamic tool discovery, has one major drawback, it makes the server a single-user server. The tool updates are global, so if one user updates a tool, it will be updated for all users. If you plan to server multiple users, you should disable tool discovery, and instead use the `allowed_tool_categories` and `default_tool_categories` settings to control the tools that are available to the users.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install openbb-mcp-server
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
Start the OpenBB MCP server with default settings:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
openbb-mcp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Command Line Options
|
|
44
|
+
|
|
45
|
+
- `--host`: Host to bind to (default: 127.0.0.1)
|
|
46
|
+
- `--port`: Port to listen on (default: 8001)
|
|
47
|
+
- `--allowed-categories`: Comma-separated list of allowed tool categories
|
|
48
|
+
- `--default-categories`: Comma-separated list of categories enabled at startup (default: all)
|
|
49
|
+
- `--transport`: Transport protocol (default: streamable-http)
|
|
50
|
+
- `--no-tool-discovery`: Disable tool discovery for multi-client deployments
|
|
51
|
+
|
|
52
|
+
### Examples
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Start with default settings
|
|
56
|
+
openbb-mcp
|
|
57
|
+
|
|
58
|
+
# Start with specific categories and custom host/port
|
|
59
|
+
openbb-mcp --default-categories equity,news --host 0.0.0.0 --port 8080
|
|
60
|
+
|
|
61
|
+
# Start with allowed categories restriction
|
|
62
|
+
openbb-mcp --allowed-categories equity,crypto,news
|
|
63
|
+
|
|
64
|
+
# Disable tool discovery for multi-client usage
|
|
65
|
+
openbb-mcp --no-tool-discovery
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
The server can be configured through multiple methods:
|
|
71
|
+
|
|
72
|
+
### 1. Configuration File (Recommended)
|
|
73
|
+
|
|
74
|
+
The server automatically creates and uses `~/.openbb_platform/mcp_settings.json`:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"name": "OpenBB MCP",
|
|
79
|
+
"description": "All OpenBB REST endpoints exposed as MCP tools...",
|
|
80
|
+
"default_tool_categories": ["all"],
|
|
81
|
+
"allowed_tool_categories": null,
|
|
82
|
+
"enable_tool_discovery": true,
|
|
83
|
+
"describe_responses": true
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. Environment Variables
|
|
88
|
+
|
|
89
|
+
Override settings using environment variables:
|
|
90
|
+
- `OPENBB_MCP_NAME`: Server name
|
|
91
|
+
- `OPENBB_MCP_DESCRIPTION`: Server description
|
|
92
|
+
- `OPENBB_MCP_DEFAULT_TOOL_CATEGORIES`: Comma-separated list of default categories
|
|
93
|
+
- `OPENBB_MCP_ALLOWED_TOOL_CATEGORIES`: Comma-separated list of allowed categories
|
|
94
|
+
- `OPENBB_MCP_ENABLE_TOOL_DISCOVERY`: true/false - Enable tool discovery features
|
|
95
|
+
- `OPENBB_MCP_DESCRIBE_ALL_RESPONSES`: true/false - Include response details in descriptions
|
|
96
|
+
- `OPENBB_MCP_DESCRIBE_FULL_RESPONSE_SCHEMA`: true/false - Include full response schemas
|
|
97
|
+
|
|
98
|
+
### 3. Command Line Arguments
|
|
99
|
+
|
|
100
|
+
Command line arguments override both configuration file and environment variables.
|
|
101
|
+
|
|
102
|
+
## Settings Reference
|
|
103
|
+
|
|
104
|
+
| Setting | Type | Default | Description |
|
|
105
|
+
|---------|------|---------|-------------|
|
|
106
|
+
| name | string | "OpenBB MCP" | Server name displayed to MCP clients |
|
|
107
|
+
| description | string | "All OpenBB REST endpoints..." | Server description |
|
|
108
|
+
| default_tool_categories | list[string] | ["all"] | Categories enabled at startup. Use "all" to enable all categories, or specify individual categories |
|
|
109
|
+
| allowed_tool_categories | list[string] | null | If set, restricts available categories to this list |
|
|
110
|
+
| enable_tool_discovery | boolean | true | Enable discovery and management tools |
|
|
111
|
+
| describe_responses | boolean | true | Include response information in tool descriptions |
|
|
112
|
+
|
|
113
|
+
## Tool Categories
|
|
114
|
+
|
|
115
|
+
The server organizes OpenBB tools into categories based on the REST API structure:
|
|
116
|
+
|
|
117
|
+
- **`equity`** - Stock data, fundamentals, price history, estimates
|
|
118
|
+
- **`crypto`** - Cryptocurrency data and analysis
|
|
119
|
+
- **`economy`** - Economic indicators, GDP, employment data
|
|
120
|
+
- **`news`** - Financial news from various sources
|
|
121
|
+
- **`fixedincome`** - Bond data, rates, government securities
|
|
122
|
+
- **`derivatives`** - Options and futures data
|
|
123
|
+
- **`etf`** - ETF information and holdings
|
|
124
|
+
- **`currency`** - Foreign exchange data
|
|
125
|
+
- **`commodity`** - Commodity prices and data
|
|
126
|
+
- **`index`** - Market indices data
|
|
127
|
+
- **`regulators`** - SEC, CFTC regulatory data
|
|
128
|
+
|
|
129
|
+
Each category contains subcategories that group related functionality (e.g., `equity_price`, `equity_fundamental`, etc.).
|
|
130
|
+
|
|
131
|
+
## Tool Discovery
|
|
132
|
+
|
|
133
|
+
When `enable_tool_discovery` is enabled (default), the server provides management tools that allow agents to:
|
|
134
|
+
|
|
135
|
+
- Discover available tool categories and subcategories
|
|
136
|
+
- See tool counts and descriptions before activating
|
|
137
|
+
- Enable/disable specific tools dynamically during a session
|
|
138
|
+
- Start with minimal tools and progressively add more as needed
|
|
139
|
+
|
|
140
|
+
To take full advantage of minimal startup tools, you should set the `--default-categories` argument to `admin` this will enable only the discovery tools at startup.
|
|
141
|
+
|
|
142
|
+
For multi-client deployments or scenarios where you want a fixed toolset, disable tool discovery with `--no-tool-discovery`.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# OpenBB MCP Server
|
|
2
|
+
|
|
3
|
+
Model Context Protocol (MCP) server extension for OpenBB Platform. This extension enables LLM agents to interact with OpenBB Platform's REST API endpoints through the MCP protocol.
|
|
4
|
+
|
|
5
|
+
The server provides management tools that allow agents to explore different options and dynamically adjust their active toolset. This prevents agents from being overwhelmed with too many tools while allowing them to discover and activate only the tools they need for specific tasks.
|
|
6
|
+
|
|
7
|
+
Using these dynamic tool discovery, has one major drawback, it makes the server a single-user server. The tool updates are global, so if one user updates a tool, it will be updated for all users. If you plan to server multiple users, you should disable tool discovery, and instead use the `allowed_tool_categories` and `default_tool_categories` settings to control the tools that are available to the users.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install openbb-mcp-server
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Start the OpenBB MCP server with default settings:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
openbb-mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Command Line Options
|
|
24
|
+
|
|
25
|
+
- `--host`: Host to bind to (default: 127.0.0.1)
|
|
26
|
+
- `--port`: Port to listen on (default: 8001)
|
|
27
|
+
- `--allowed-categories`: Comma-separated list of allowed tool categories
|
|
28
|
+
- `--default-categories`: Comma-separated list of categories enabled at startup (default: all)
|
|
29
|
+
- `--transport`: Transport protocol (default: streamable-http)
|
|
30
|
+
- `--no-tool-discovery`: Disable tool discovery for multi-client deployments
|
|
31
|
+
|
|
32
|
+
### Examples
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Start with default settings
|
|
36
|
+
openbb-mcp
|
|
37
|
+
|
|
38
|
+
# Start with specific categories and custom host/port
|
|
39
|
+
openbb-mcp --default-categories equity,news --host 0.0.0.0 --port 8080
|
|
40
|
+
|
|
41
|
+
# Start with allowed categories restriction
|
|
42
|
+
openbb-mcp --allowed-categories equity,crypto,news
|
|
43
|
+
|
|
44
|
+
# Disable tool discovery for multi-client usage
|
|
45
|
+
openbb-mcp --no-tool-discovery
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
The server can be configured through multiple methods:
|
|
51
|
+
|
|
52
|
+
### 1. Configuration File (Recommended)
|
|
53
|
+
|
|
54
|
+
The server automatically creates and uses `~/.openbb_platform/mcp_settings.json`:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"name": "OpenBB MCP",
|
|
59
|
+
"description": "All OpenBB REST endpoints exposed as MCP tools...",
|
|
60
|
+
"default_tool_categories": ["all"],
|
|
61
|
+
"allowed_tool_categories": null,
|
|
62
|
+
"enable_tool_discovery": true,
|
|
63
|
+
"describe_responses": true
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Environment Variables
|
|
68
|
+
|
|
69
|
+
Override settings using environment variables:
|
|
70
|
+
- `OPENBB_MCP_NAME`: Server name
|
|
71
|
+
- `OPENBB_MCP_DESCRIPTION`: Server description
|
|
72
|
+
- `OPENBB_MCP_DEFAULT_TOOL_CATEGORIES`: Comma-separated list of default categories
|
|
73
|
+
- `OPENBB_MCP_ALLOWED_TOOL_CATEGORIES`: Comma-separated list of allowed categories
|
|
74
|
+
- `OPENBB_MCP_ENABLE_TOOL_DISCOVERY`: true/false - Enable tool discovery features
|
|
75
|
+
- `OPENBB_MCP_DESCRIBE_ALL_RESPONSES`: true/false - Include response details in descriptions
|
|
76
|
+
- `OPENBB_MCP_DESCRIBE_FULL_RESPONSE_SCHEMA`: true/false - Include full response schemas
|
|
77
|
+
|
|
78
|
+
### 3. Command Line Arguments
|
|
79
|
+
|
|
80
|
+
Command line arguments override both configuration file and environment variables.
|
|
81
|
+
|
|
82
|
+
## Settings Reference
|
|
83
|
+
|
|
84
|
+
| Setting | Type | Default | Description |
|
|
85
|
+
|---------|------|---------|-------------|
|
|
86
|
+
| name | string | "OpenBB MCP" | Server name displayed to MCP clients |
|
|
87
|
+
| description | string | "All OpenBB REST endpoints..." | Server description |
|
|
88
|
+
| default_tool_categories | list[string] | ["all"] | Categories enabled at startup. Use "all" to enable all categories, or specify individual categories |
|
|
89
|
+
| allowed_tool_categories | list[string] | null | If set, restricts available categories to this list |
|
|
90
|
+
| enable_tool_discovery | boolean | true | Enable discovery and management tools |
|
|
91
|
+
| describe_responses | boolean | true | Include response information in tool descriptions |
|
|
92
|
+
|
|
93
|
+
## Tool Categories
|
|
94
|
+
|
|
95
|
+
The server organizes OpenBB tools into categories based on the REST API structure:
|
|
96
|
+
|
|
97
|
+
- **`equity`** - Stock data, fundamentals, price history, estimates
|
|
98
|
+
- **`crypto`** - Cryptocurrency data and analysis
|
|
99
|
+
- **`economy`** - Economic indicators, GDP, employment data
|
|
100
|
+
- **`news`** - Financial news from various sources
|
|
101
|
+
- **`fixedincome`** - Bond data, rates, government securities
|
|
102
|
+
- **`derivatives`** - Options and futures data
|
|
103
|
+
- **`etf`** - ETF information and holdings
|
|
104
|
+
- **`currency`** - Foreign exchange data
|
|
105
|
+
- **`commodity`** - Commodity prices and data
|
|
106
|
+
- **`index`** - Market indices data
|
|
107
|
+
- **`regulators`** - SEC, CFTC regulatory data
|
|
108
|
+
|
|
109
|
+
Each category contains subcategories that group related functionality (e.g., `equity_price`, `equity_fundamental`, etc.).
|
|
110
|
+
|
|
111
|
+
## Tool Discovery
|
|
112
|
+
|
|
113
|
+
When `enable_tool_discovery` is enabled (default), the server provides management tools that allow agents to:
|
|
114
|
+
|
|
115
|
+
- Discover available tool categories and subcategories
|
|
116
|
+
- See tool counts and descriptions before activating
|
|
117
|
+
- Enable/disable specific tools dynamically during a session
|
|
118
|
+
- Start with minimal tools and progressively add more as needed
|
|
119
|
+
|
|
120
|
+
To take full advantage of minimal startup tools, you should set the `--default-categories` argument to `admin` this will enable only the discovery tools at startup.
|
|
121
|
+
|
|
122
|
+
For multi-client deployments or scenarios where you want a fixed toolset, disable tool discovery with `--no-tool-discovery`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""OpenBB MCP Server package."""
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""OpenBB MCP Server."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import uvicorn
|
|
9
|
+
from fastapi import FastAPI
|
|
10
|
+
from fastmcp import FastMCP
|
|
11
|
+
from fastmcp.server.openapi import (
|
|
12
|
+
FastMCPOpenAPI,
|
|
13
|
+
OpenAPIResource,
|
|
14
|
+
OpenAPIResourceTemplate,
|
|
15
|
+
OpenAPITool,
|
|
16
|
+
)
|
|
17
|
+
from fastmcp.utilities.openapi import HTTPRoute
|
|
18
|
+
from openbb_core.api.rest_api import app
|
|
19
|
+
from openbb_core.app.service.system_service import SystemService
|
|
20
|
+
from pydantic import Field
|
|
21
|
+
from starlette.middleware import Middleware
|
|
22
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
23
|
+
|
|
24
|
+
from .registry import ToolRegistry
|
|
25
|
+
from .tool_models import CategoryInfo, SubcategoryInfo, ToolInfo
|
|
26
|
+
from .utils.config import load_mcp_settings_with_overrides, parse_args
|
|
27
|
+
from .utils.route_filtering import create_route_maps_from_settings
|
|
28
|
+
from .utils.settings import MCPSettings
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger("openbb_mcp_server")
|
|
31
|
+
logger.setLevel(logging.INFO)
|
|
32
|
+
handler = logging.StreamHandler()
|
|
33
|
+
handler.setLevel(logging.INFO)
|
|
34
|
+
formatter = logging.Formatter("\n%(message)s\n")
|
|
35
|
+
handler.setFormatter(formatter)
|
|
36
|
+
logger.addHandler(handler)
|
|
37
|
+
|
|
38
|
+
PREFIX_SEGMENTS = ("api",)
|
|
39
|
+
VERSION_SEGMENT_RE = re.compile(r"v\d+\Z")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _extract_brief_description(full_description: str) -> str:
|
|
43
|
+
"""Extract only the brief description before the detailed API documentation."""
|
|
44
|
+
if not full_description:
|
|
45
|
+
return "No description available"
|
|
46
|
+
|
|
47
|
+
brief, *_ = re.split(
|
|
48
|
+
r"\n{2,}\*\*(?:Query Parameters|Responses):", full_description, maxsplit=1
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return brief.strip() or "No description available"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def create_mcp_server(settings: MCPSettings, fastapi_app: FastAPI) -> FastMCPOpenAPI:
|
|
55
|
+
"""Create and configure the MCP server."""
|
|
56
|
+
tool_registry = ToolRegistry()
|
|
57
|
+
|
|
58
|
+
def customize_components(
|
|
59
|
+
route: HTTPRoute,
|
|
60
|
+
component: OpenAPITool | OpenAPIResource | OpenAPIResourceTemplate,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Attach category/tool tags & disable tools that are not admin."""
|
|
63
|
+
if not isinstance(component, OpenAPITool):
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
segments = [seg for seg in route.path.lstrip("/").split("/") if "{" not in seg]
|
|
67
|
+
|
|
68
|
+
while segments and segments[0] in PREFIX_SEGMENTS:
|
|
69
|
+
segments.pop(0)
|
|
70
|
+
if segments and VERSION_SEGMENT_RE.match(segments[0]):
|
|
71
|
+
segments.pop(0)
|
|
72
|
+
|
|
73
|
+
if len(segments) < 2:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
# Use hierarchical structure: category/subcategory/tool
|
|
77
|
+
category = segments[0]
|
|
78
|
+
if len(segments) == 2:
|
|
79
|
+
subcategory = "general"
|
|
80
|
+
tool = segments[1]
|
|
81
|
+
else:
|
|
82
|
+
subcategory = segments[1]
|
|
83
|
+
tool_parts = segments[2:]
|
|
84
|
+
tool = "_".join(tool_parts)
|
|
85
|
+
|
|
86
|
+
component.name = (
|
|
87
|
+
f"{category}_{subcategory}_{tool}"
|
|
88
|
+
if subcategory != "general"
|
|
89
|
+
else f"{category}_{tool}"
|
|
90
|
+
)
|
|
91
|
+
component.tags.add(category)
|
|
92
|
+
|
|
93
|
+
if not settings.describe_responses:
|
|
94
|
+
component.description = _extract_brief_description(
|
|
95
|
+
component.description or ""
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if "all" in settings.default_tool_categories or any(
|
|
99
|
+
tag in settings.default_tool_categories for tag in component.tags
|
|
100
|
+
):
|
|
101
|
+
component.enable()
|
|
102
|
+
else:
|
|
103
|
+
component.disable()
|
|
104
|
+
|
|
105
|
+
tool_registry.register_tool(
|
|
106
|
+
category=category,
|
|
107
|
+
subcategory=subcategory,
|
|
108
|
+
tool_name=component.name,
|
|
109
|
+
tool=component,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
mcp = FastMCP.from_fastapi(
|
|
115
|
+
app=fastapi_app,
|
|
116
|
+
mcp_component_fn=customize_components,
|
|
117
|
+
name=settings.name,
|
|
118
|
+
route_maps=create_route_maps_from_settings(settings),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Add discovery tools if enabled
|
|
122
|
+
if settings.enable_tool_discovery:
|
|
123
|
+
|
|
124
|
+
@mcp.tool(tags={"admin"})
|
|
125
|
+
def available_categories() -> list[CategoryInfo]:
|
|
126
|
+
"""List all categories with their subcategories and tool counts.
|
|
127
|
+
|
|
128
|
+
This gives you a complete overview of what types of tasks you can solve.
|
|
129
|
+
"""
|
|
130
|
+
categories = tool_registry.get_categories()
|
|
131
|
+
return [
|
|
132
|
+
CategoryInfo(
|
|
133
|
+
name=category_name,
|
|
134
|
+
subcategories=[
|
|
135
|
+
SubcategoryInfo(name=subcat_name, tool_count=len(tools))
|
|
136
|
+
for subcat_name, tools in sorted(subcategories.items())
|
|
137
|
+
],
|
|
138
|
+
total_tools=sum(len(tools) for tools in subcategories.values()),
|
|
139
|
+
)
|
|
140
|
+
for category_name, subcategories in sorted(categories.items())
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
@mcp.tool(tags={"admin"})
|
|
144
|
+
def available_tools(
|
|
145
|
+
category: Annotated[
|
|
146
|
+
str, Field(description="The category of tools to list")
|
|
147
|
+
],
|
|
148
|
+
subcategory: Annotated[
|
|
149
|
+
str | None,
|
|
150
|
+
Field(
|
|
151
|
+
description="Optional subcategory to filter by. Use 'general' for tools directly under the category."
|
|
152
|
+
),
|
|
153
|
+
] = None,
|
|
154
|
+
) -> list[ToolInfo]:
|
|
155
|
+
"""For a category, list tools and whether they're currently enabled. Optionally filter by subcategory."""
|
|
156
|
+
category_data = tool_registry.get_category_subcategories(category)
|
|
157
|
+
if not category_data:
|
|
158
|
+
available_categories = list(tool_registry.get_categories().keys())
|
|
159
|
+
categories_str = ", ".join(sorted(available_categories))
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"Category '{category}' not found. Available categories: {categories_str}"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if subcategory:
|
|
165
|
+
# Single subcategory
|
|
166
|
+
tools_dict = tool_registry.get_category_tools(category, subcategory)
|
|
167
|
+
if not tools_dict:
|
|
168
|
+
available_subcategories = list(category_data.keys())
|
|
169
|
+
subcategories_str = ", ".join(sorted(available_subcategories))
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"Subcategory '{subcategory}' not found in category '{category}'. "
|
|
172
|
+
f"Available subcategories: {subcategories_str}"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return [
|
|
176
|
+
ToolInfo(
|
|
177
|
+
name=name,
|
|
178
|
+
active=tool.enabled,
|
|
179
|
+
description=_extract_brief_description(tool.description or ""),
|
|
180
|
+
)
|
|
181
|
+
for name, tool in sorted(tools_dict.items())
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
# All subcategories - flatten them
|
|
185
|
+
tools_dict = tool_registry.get_category_tools(category)
|
|
186
|
+
return [
|
|
187
|
+
ToolInfo(
|
|
188
|
+
name=name,
|
|
189
|
+
active=tool.enabled,
|
|
190
|
+
description=_extract_brief_description(tool.description or ""),
|
|
191
|
+
)
|
|
192
|
+
for name, tool in sorted(tools_dict.items())
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
@mcp.tool(tags={"admin"})
|
|
196
|
+
def activate_tools(
|
|
197
|
+
tool_names: Annotated[
|
|
198
|
+
list[str], Field(description="The name of the tool to activate")
|
|
199
|
+
],
|
|
200
|
+
) -> str:
|
|
201
|
+
"""Activate a tool or a list of tools."""
|
|
202
|
+
return tool_registry.toggle_tools(tool_names, enable=True).message
|
|
203
|
+
|
|
204
|
+
@mcp.tool(tags={"admin"})
|
|
205
|
+
def deactivate_tools(
|
|
206
|
+
tool_names: Annotated[
|
|
207
|
+
list[str], Field(description="The name of the tool to deactivate")
|
|
208
|
+
],
|
|
209
|
+
) -> str:
|
|
210
|
+
"""Deactivate a tool or a list of tools."""
|
|
211
|
+
return tool_registry.toggle_tools(tool_names, enable=False).message
|
|
212
|
+
|
|
213
|
+
return mcp
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def main():
|
|
217
|
+
"""Start the OpenBB MCP server."""
|
|
218
|
+
args = parse_args()
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
overrides = {}
|
|
222
|
+
if args.allowed_categories:
|
|
223
|
+
overrides["allowed_tool_categories"] = args.allowed_categories.split(",")
|
|
224
|
+
if args.default_categories:
|
|
225
|
+
overrides["default_tool_categories"] = args.default_categories.split(",")
|
|
226
|
+
if args.no_tool_discovery:
|
|
227
|
+
overrides["enable_tool_discovery"] = False
|
|
228
|
+
|
|
229
|
+
settings = load_mcp_settings_with_overrides(**overrides)
|
|
230
|
+
mcp = create_mcp_server(settings, app)
|
|
231
|
+
|
|
232
|
+
if args.transport == "stdio":
|
|
233
|
+
mcp.run(transport=args.transport, host=args.host, port=args.port)
|
|
234
|
+
|
|
235
|
+
else:
|
|
236
|
+
# Get CORS settings from system configuration
|
|
237
|
+
system_service = SystemService()
|
|
238
|
+
cors_settings = system_service.system_settings.api_settings.cors
|
|
239
|
+
|
|
240
|
+
cors_middleware = [
|
|
241
|
+
Middleware(
|
|
242
|
+
CORSMiddleware,
|
|
243
|
+
allow_origins=cors_settings.allow_origins,
|
|
244
|
+
allow_methods=cors_settings.allow_methods,
|
|
245
|
+
allow_headers=cors_settings.allow_headers,
|
|
246
|
+
),
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
# Create ASGI app with cors middleware
|
|
250
|
+
http_app = mcp.http_app(
|
|
251
|
+
middleware=cors_middleware,
|
|
252
|
+
transport=args.transport,
|
|
253
|
+
stateless_http=True,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
uvicorn.run(http_app, host=args.host, port=args.port)
|
|
257
|
+
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error("Failed to start MCP server: %s", e)
|
|
260
|
+
sys.exit(1)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
if __name__ == "__main__":
|
|
264
|
+
main()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Tool registry for managing MCP tools and tool discovery."""
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Mapping
|
|
6
|
+
|
|
7
|
+
from fastmcp.server.openapi import OpenAPITool
|
|
8
|
+
|
|
9
|
+
from .tool_models import ToggleResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class ToolRegistry:
|
|
14
|
+
"""Keeps track of categories, subcategories and tool instances."""
|
|
15
|
+
|
|
16
|
+
_by_category: dict[str, dict[str, dict[str, OpenAPITool]]] = field(
|
|
17
|
+
default_factory=lambda: defaultdict(lambda: defaultdict(dict))
|
|
18
|
+
)
|
|
19
|
+
_by_name: dict[str, OpenAPITool] = field(default_factory=dict)
|
|
20
|
+
|
|
21
|
+
def register_tool(
|
|
22
|
+
self, *, category: str, subcategory: str, tool_name: str, tool: OpenAPITool
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Register a tool in the registry."""
|
|
25
|
+
self._by_category[category][subcategory][tool_name] = tool
|
|
26
|
+
self._by_name[tool_name] = tool
|
|
27
|
+
|
|
28
|
+
def get_categories(self) -> Mapping[str, Mapping[str, Mapping[str, OpenAPITool]]]:
|
|
29
|
+
"""Get immutable view of all categories and their tools."""
|
|
30
|
+
return self._by_category
|
|
31
|
+
|
|
32
|
+
def get_category_tools(
|
|
33
|
+
self, category: str, subcategory: str | None = None
|
|
34
|
+
) -> dict[str, OpenAPITool]:
|
|
35
|
+
"""Get tools in a category, optionally filtered by subcategory."""
|
|
36
|
+
if subcategory is None:
|
|
37
|
+
# flatten all subcategories
|
|
38
|
+
return {
|
|
39
|
+
name: tool
|
|
40
|
+
for subcat_tools in self._by_category.get(category, {}).values()
|
|
41
|
+
for name, tool in subcat_tools.items()
|
|
42
|
+
}
|
|
43
|
+
return self._by_category.get(category, {}).get(subcategory, {})
|
|
44
|
+
|
|
45
|
+
def get_tool(self, tool_name: str) -> OpenAPITool | None:
|
|
46
|
+
"""Get a tool by name."""
|
|
47
|
+
return self._by_name.get(tool_name)
|
|
48
|
+
|
|
49
|
+
def get_category_subcategories(
|
|
50
|
+
self, category: str
|
|
51
|
+
) -> dict[str, dict[str, OpenAPITool]] | None:
|
|
52
|
+
"""Get all subcategories for a specific category."""
|
|
53
|
+
return self._by_category.get(category)
|
|
54
|
+
|
|
55
|
+
def toggle_tools(self, tool_names: list[str], enable: bool) -> ToggleResult:
|
|
56
|
+
"""Enable or disable a list of tools, returning a status message."""
|
|
57
|
+
successful, failed = [], []
|
|
58
|
+
|
|
59
|
+
for name in tool_names:
|
|
60
|
+
tool = self._by_name.get(name)
|
|
61
|
+
if tool:
|
|
62
|
+
(tool.enable if enable else tool.disable)()
|
|
63
|
+
successful.append(name)
|
|
64
|
+
else:
|
|
65
|
+
failed.append(name)
|
|
66
|
+
|
|
67
|
+
action = "activated" if enable else "deactivated"
|
|
68
|
+
parts: list[str] = []
|
|
69
|
+
|
|
70
|
+
if successful:
|
|
71
|
+
parts.append(f"{action.capitalize()}: {', '.join(successful)}")
|
|
72
|
+
if failed:
|
|
73
|
+
parts.append(f"Not found: {', '.join(failed)}")
|
|
74
|
+
|
|
75
|
+
message = " ".join(parts) if parts else "No tools processed."
|
|
76
|
+
|
|
77
|
+
return ToggleResult(
|
|
78
|
+
action=action,
|
|
79
|
+
successful=successful,
|
|
80
|
+
failed=failed,
|
|
81
|
+
message=message,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def clear(self) -> None:
|
|
85
|
+
"""Clear the registry."""
|
|
86
|
+
self._by_category.clear()
|
|
87
|
+
self._by_name.clear()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Tool models for MCP server."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ToolInfo(BaseModel):
|
|
9
|
+
"""Information about a single tool."""
|
|
10
|
+
|
|
11
|
+
name: str
|
|
12
|
+
active: bool
|
|
13
|
+
description: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SubcategoryInfo(BaseModel):
|
|
17
|
+
"""Metadata for a tool subcategory."""
|
|
18
|
+
|
|
19
|
+
name: str
|
|
20
|
+
tool_count: int
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CategoryInfo(BaseModel):
|
|
24
|
+
"""Metadata for a category of tools."""
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
subcategories: List[SubcategoryInfo]
|
|
28
|
+
total_tools: int
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ToggleResult(BaseModel):
|
|
32
|
+
"""Result of a request to activate or deactivate one or more tools."""
|
|
33
|
+
|
|
34
|
+
action: Literal["activated", "deactivated"]
|
|
35
|
+
successful: List[str]
|
|
36
|
+
failed: List[str]
|
|
37
|
+
message: str
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Utility functions for MCP server."""
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Configuration loader for MCP Server."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List, Union
|
|
9
|
+
|
|
10
|
+
from openbb_core.app.constants import OPENBB_DIRECTORY
|
|
11
|
+
|
|
12
|
+
from .settings import MCPSettings
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_mcp_config_path() -> Path:
|
|
18
|
+
"""Get the path to the MCP configuration file."""
|
|
19
|
+
return OPENBB_DIRECTORY / "mcp_settings.json"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_mcp_settings() -> MCPSettings:
|
|
23
|
+
"""Load MCP settings from configuration file or create defaults."""
|
|
24
|
+
config_path = get_mcp_config_path()
|
|
25
|
+
|
|
26
|
+
if config_path.exists():
|
|
27
|
+
try:
|
|
28
|
+
with open(config_path, encoding="utf-8") as f:
|
|
29
|
+
config_data = json.load(f)
|
|
30
|
+
return MCPSettings(**config_data)
|
|
31
|
+
except (json.JSONDecodeError, TypeError, ValueError) as e:
|
|
32
|
+
logger.warning(
|
|
33
|
+
"Error loading MCP configuration from %s: %s. Using default settings.",
|
|
34
|
+
config_path,
|
|
35
|
+
e,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Create default settings
|
|
39
|
+
settings = MCPSettings()
|
|
40
|
+
|
|
41
|
+
# Create the directory if it doesn't exist
|
|
42
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
# Save default settings to file
|
|
45
|
+
save_mcp_settings(settings)
|
|
46
|
+
|
|
47
|
+
return settings
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def save_mcp_settings(settings: MCPSettings) -> None:
|
|
51
|
+
"""Save MCP settings to configuration file."""
|
|
52
|
+
config_path = get_mcp_config_path()
|
|
53
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
|
|
55
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
56
|
+
json.dump(settings.model_dump(), f, indent=2)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_settings_from_env() -> Dict[str, Union[str, List[str], bool]]:
|
|
60
|
+
"""Load MCP settings from environment variables."""
|
|
61
|
+
env_vars = {
|
|
62
|
+
f.alias: os.environ[f.alias]
|
|
63
|
+
for f in MCPSettings.model_fields.values()
|
|
64
|
+
if f.alias and f.alias in os.environ
|
|
65
|
+
}
|
|
66
|
+
if not env_vars:
|
|
67
|
+
return {}
|
|
68
|
+
|
|
69
|
+
env_settings = MCPSettings.model_validate(env_vars, from_attributes=False)
|
|
70
|
+
return env_settings.model_dump(exclude_unset=True)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def load_mcp_settings_with_overrides(**overrides) -> MCPSettings:
|
|
74
|
+
"""Load MCP settings with optional overrides."""
|
|
75
|
+
settings = load_mcp_settings()
|
|
76
|
+
|
|
77
|
+
# Apply environment variable overrides
|
|
78
|
+
env_settings = load_settings_from_env()
|
|
79
|
+
if env_settings:
|
|
80
|
+
settings_dict = settings.model_dump()
|
|
81
|
+
settings_dict.update(env_settings)
|
|
82
|
+
settings = MCPSettings(**settings_dict)
|
|
83
|
+
|
|
84
|
+
# Apply function argument overrides
|
|
85
|
+
if overrides:
|
|
86
|
+
settings_dict = settings.model_dump()
|
|
87
|
+
settings_dict.update(overrides)
|
|
88
|
+
settings = MCPSettings(**settings_dict)
|
|
89
|
+
|
|
90
|
+
return settings
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def parse_args():
|
|
94
|
+
"""Parse command line arguments."""
|
|
95
|
+
parser = argparse.ArgumentParser(
|
|
96
|
+
description="Start the OpenBB MCP server",
|
|
97
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
98
|
+
epilog="""
|
|
99
|
+
Examples:
|
|
100
|
+
openbb-mcp # Start with default settings
|
|
101
|
+
openbb-mcp --host 0.0.0.0 --port 8001 # Custom host and port
|
|
102
|
+
openbb-mcp --allowed-categories equity,crypto # Allowed categories
|
|
103
|
+
openbb-mcp --default-categories equity,crypto # Override default (all categories enabled by default)
|
|
104
|
+
openbb-mcp --transport stdio # Use stdio transport
|
|
105
|
+
openbb-mcp --no-tool-discovery # Disable tool discovery (multi-client safe)
|
|
106
|
+
|
|
107
|
+
The server can also be configured via:
|
|
108
|
+
- Configuration file: ~/.openbb_platform/mcp_settings.json
|
|
109
|
+
- Environment variables: OPENBB_MCP_HOST, OPENBB_MCP_PORT, etc.
|
|
110
|
+
|
|
111
|
+
Command line arguments override configuration file and environment variables.
|
|
112
|
+
""",
|
|
113
|
+
)
|
|
114
|
+
parser.add_argument(
|
|
115
|
+
"--host",
|
|
116
|
+
type=str,
|
|
117
|
+
default="127.0.0.1",
|
|
118
|
+
help="Host IP address to bind the server to",
|
|
119
|
+
)
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"--port", type=int, default=8001, help="Port number to bind the server to"
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"--allowed-categories",
|
|
125
|
+
type=str,
|
|
126
|
+
default=None,
|
|
127
|
+
help="Comma-separated list of tool categories allowed to be used",
|
|
128
|
+
)
|
|
129
|
+
parser.add_argument(
|
|
130
|
+
"--default-categories",
|
|
131
|
+
type=str,
|
|
132
|
+
default="all",
|
|
133
|
+
help="Comma-separated list of tool categories enabled at startup",
|
|
134
|
+
)
|
|
135
|
+
parser.add_argument(
|
|
136
|
+
"--transport",
|
|
137
|
+
type=str,
|
|
138
|
+
default="streamable-http",
|
|
139
|
+
help="MCP transport protocol to use",
|
|
140
|
+
choices=["streamable-http", "stdio", "sse"],
|
|
141
|
+
)
|
|
142
|
+
parser.add_argument(
|
|
143
|
+
"--no-tool-discovery",
|
|
144
|
+
action="store_true",
|
|
145
|
+
help="Disable tool discovery (multi-client safe)",
|
|
146
|
+
)
|
|
147
|
+
return parser.parse_args()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Route filtering utilities for MCP server using FastMCP RouteMap."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from fastmcp.server.openapi import MCPType, RouteMap
|
|
7
|
+
|
|
8
|
+
from .settings import MCPSettings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_route_maps_from_settings(settings: MCPSettings) -> List[RouteMap]:
|
|
12
|
+
"""
|
|
13
|
+
Create RouteMap objects based on MCPSettings for filtering routes.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
settings : MCPSettings
|
|
18
|
+
MCPSettings instance with filtering configuration
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
List[RouteMap]
|
|
23
|
+
List of RouteMap objects that FastMCP will use for filtering
|
|
24
|
+
"""
|
|
25
|
+
route_maps = []
|
|
26
|
+
|
|
27
|
+
if settings.allowed_tool_categories:
|
|
28
|
+
# Create patterns for allowed categories
|
|
29
|
+
allowed_pattern = "|".join(
|
|
30
|
+
re.escape(cat) for cat in settings.allowed_tool_categories
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Include only allowed categories
|
|
34
|
+
route_maps.append(
|
|
35
|
+
RouteMap(
|
|
36
|
+
pattern=rf"^/api/v\d+/({allowed_pattern})/.*",
|
|
37
|
+
mcp_type=MCPType.TOOL,
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Exclude everything else
|
|
42
|
+
route_maps.append(
|
|
43
|
+
RouteMap(
|
|
44
|
+
pattern=rf"^/api/v\d+/(?!({allowed_pattern})/).*",
|
|
45
|
+
mcp_type=MCPType.EXCLUDE,
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return route_maps
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""MCP Server Settings model."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MCPSettings(BaseModel):
|
|
9
|
+
"""MCP Server settings model."""
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(
|
|
12
|
+
populate_by_name=True,
|
|
13
|
+
extra="ignore",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Basic server configuration
|
|
17
|
+
name: str = Field(
|
|
18
|
+
default="OpenBB MCP",
|
|
19
|
+
alias="OPENBB_MCP_NAME",
|
|
20
|
+
)
|
|
21
|
+
description: str = Field(
|
|
22
|
+
default="""All OpenBB REST endpoints exposed as MCP tools. Enables LLM agents
|
|
23
|
+
to query financial data, run screeners, and build workflows using
|
|
24
|
+
the exact same operations available to REST clients.""",
|
|
25
|
+
alias="OPENBB_MCP_DESCRIPTION",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Tool category filtering
|
|
29
|
+
default_tool_categories: List[str] = Field(
|
|
30
|
+
default_factory=lambda: ["all"],
|
|
31
|
+
description="Default active tool categories on startup",
|
|
32
|
+
alias="OPENBB_MCP_DEFAULT_TOOL_CATEGORIES",
|
|
33
|
+
)
|
|
34
|
+
allowed_tool_categories: Optional[List[str]] = Field(
|
|
35
|
+
default=None,
|
|
36
|
+
description="If set, restricts available tool categories to this list",
|
|
37
|
+
alias="OPENBB_MCP_ALLOWED_TOOL_CATEGORIES",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Tool discovery configuration
|
|
41
|
+
enable_tool_discovery: bool = Field(
|
|
42
|
+
default=True,
|
|
43
|
+
description="""
|
|
44
|
+
Enable tool discovery, allowing the agent to hot-swap tools at runtime.
|
|
45
|
+
Disable for multi-client or fixed toolset deployments.
|
|
46
|
+
""",
|
|
47
|
+
alias="OPENBB_MCP_ENABLE_TOOL_DISCOVERY",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Response configuration
|
|
51
|
+
describe_responses: bool = Field(
|
|
52
|
+
default=True,
|
|
53
|
+
description="Include response types in tool descriptions",
|
|
54
|
+
alias="OPENBB_MCP_DESCRIBE_RESPONSES",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@field_validator(
|
|
58
|
+
"default_tool_categories", "allowed_tool_categories", mode="before"
|
|
59
|
+
)
|
|
60
|
+
@classmethod
|
|
61
|
+
def _split_list(cls, v):
|
|
62
|
+
if isinstance(v, str):
|
|
63
|
+
return [part.strip() for part in v.split(",") if part.strip()]
|
|
64
|
+
return v
|
|
65
|
+
|
|
66
|
+
def __repr__(self) -> str:
|
|
67
|
+
"""Return string representation."""
|
|
68
|
+
return f"{self.__class__.__name__}\n\n" + "\n".join(
|
|
69
|
+
f"{k}: {v}" for k, v in self.model_dump().items()
|
|
70
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "openbb-mcp-server"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "OpenBB Platform MCP Server"
|
|
5
|
+
authors = ["OpenBB <hello@openbb.co>"]
|
|
6
|
+
license = "AGPL-3.0-only"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
homepage = "https://openbb.co"
|
|
9
|
+
repository = "https://github.com/openbb-finance/openbb"
|
|
10
|
+
documentation = "https://docs.openbb.co"
|
|
11
|
+
packages = [{ include = "openbb_mcp_server" }]
|
|
12
|
+
|
|
13
|
+
[tool.poetry.scripts]
|
|
14
|
+
openbb-mcp = "openbb_mcp_server.main:main"
|
|
15
|
+
|
|
16
|
+
[tool.poetry.dependencies]
|
|
17
|
+
python = ">=3.10,<3.13"
|
|
18
|
+
openbb-core = "^1.4.3"
|
|
19
|
+
fastmcp = "^2.8.0"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["poetry-core>=1.0.0"]
|
|
23
|
+
build-backend = "poetry.core.masonry.api"
|