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.
@@ -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"