fiftyone-mcp-server 0.1.0__py3-none-any.whl
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.
- fiftyone_mcp/__init__.py +11 -0
- fiftyone_mcp/config/settings.json +15 -0
- fiftyone_mcp/server.py +127 -0
- fiftyone_mcp/tools/__init__.py +7 -0
- fiftyone_mcp/tools/datasets.py +210 -0
- fiftyone_mcp/tools/operators.py +575 -0
- fiftyone_mcp/tools/plugins.py +289 -0
- fiftyone_mcp/tools/session.py +352 -0
- fiftyone_mcp/tools/utils.py +112 -0
- fiftyone_mcp_server-0.1.0.dist-info/METADATA +174 -0
- fiftyone_mcp_server-0.1.0.dist-info/RECORD +13 -0
- fiftyone_mcp_server-0.1.0.dist-info/WHEEL +4 -0
- fiftyone_mcp_server-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plugin management tools for FiftyOne MCP server.
|
|
3
|
+
|
|
4
|
+
| Copyright 2017-2025, Voxel51, Inc.
|
|
5
|
+
| `voxel51.com <https://voxel51.com/>`_
|
|
6
|
+
|
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
import fiftyone.plugins as fop
|
|
13
|
+
from mcp.types import Tool, TextContent
|
|
14
|
+
|
|
15
|
+
from .utils import format_response, safe_serialize
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def list_plugins(enabled=None):
|
|
22
|
+
"""Lists available FiftyOne plugins.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
enabled (None): whether to list only enabled plugins. If None,
|
|
26
|
+
lists all downloaded plugins
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
a dict with success status and plugin data
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
if enabled is None:
|
|
33
|
+
plugins = fop.list_downloaded_plugins()
|
|
34
|
+
else:
|
|
35
|
+
plugins = fop.list_plugins(enabled=enabled)
|
|
36
|
+
|
|
37
|
+
plugin_list = []
|
|
38
|
+
for item in plugins:
|
|
39
|
+
plugin_name = item if isinstance(item, str) else item.name
|
|
40
|
+
try:
|
|
41
|
+
plugin = fop.get_plugin(plugin_name)
|
|
42
|
+
plugin_list.append(
|
|
43
|
+
{
|
|
44
|
+
"name": plugin.name,
|
|
45
|
+
"version": plugin.version,
|
|
46
|
+
"description": plugin.description,
|
|
47
|
+
"operators": plugin.operators or [],
|
|
48
|
+
"author": getattr(plugin, "author", None),
|
|
49
|
+
"license": getattr(plugin, "license", None),
|
|
50
|
+
"builtin": plugin.builtin,
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logger.warning(f"Error getting plugin {plugin_name}: {e}")
|
|
55
|
+
plugin_list.append({"name": str(plugin_name), "error": str(e)})
|
|
56
|
+
|
|
57
|
+
return format_response(
|
|
58
|
+
{"plugins": plugin_list, "count": len(plugin_list)}, success=True
|
|
59
|
+
)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.error(f"Error listing plugins: {e}")
|
|
62
|
+
return format_response(None, success=False, error=str(e))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_plugin_info(plugin_name):
|
|
66
|
+
"""Gets detailed information about a specific plugin.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
plugin_name: the name of the plugin (e.g., "@voxel51/brain")
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
a dict with success status and plugin info
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
plugin = fop.get_plugin(plugin_name)
|
|
76
|
+
|
|
77
|
+
info = {
|
|
78
|
+
"name": plugin.name,
|
|
79
|
+
"version": plugin.version,
|
|
80
|
+
"description": plugin.description,
|
|
81
|
+
"operators": plugin.operators or [],
|
|
82
|
+
"author": getattr(plugin, "author", None),
|
|
83
|
+
"license": getattr(plugin, "license", None),
|
|
84
|
+
"builtin": plugin.builtin,
|
|
85
|
+
"directory": str(plugin.directory),
|
|
86
|
+
"has_python": plugin.has_py,
|
|
87
|
+
"has_javascript": plugin.has_js,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return format_response({"plugin": info}, success=True)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logger.error(f"Error getting plugin info for {plugin_name}: {e}")
|
|
93
|
+
return format_response(None, success=False, error=str(e))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def download_plugin(url_or_repo, plugin_names=None, overwrite=False):
|
|
97
|
+
"""Downloads and installs a FiftyOne plugin.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
url_or_repo: a GitHub repository URL or "user/repo" string
|
|
101
|
+
plugin_names (None): optional list of specific plugin names to
|
|
102
|
+
download from the repo. If None, downloads all plugins
|
|
103
|
+
overwrite (False): whether to overwrite existing plugins
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
a dict with success status and installation result
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
fop.download_plugin(
|
|
110
|
+
url_or_repo, plugin_names=plugin_names, overwrite=overwrite
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
downloaded = fop.list_downloaded_plugins()
|
|
114
|
+
return format_response(
|
|
115
|
+
{
|
|
116
|
+
"message": "Plugin(s) downloaded successfully",
|
|
117
|
+
"url": url_or_repo,
|
|
118
|
+
"plugin_names": plugin_names,
|
|
119
|
+
"total_downloaded": len(downloaded),
|
|
120
|
+
},
|
|
121
|
+
success=True,
|
|
122
|
+
)
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error(f"Error downloading plugin from {url_or_repo}: {e}")
|
|
125
|
+
return format_response(None, success=False, error=str(e))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def enable_plugin(plugin_name):
|
|
129
|
+
"""Enables a downloaded plugin.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
plugin_name: the name of the plugin to enable
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
a dict with success status
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
fop.enable_plugin(plugin_name)
|
|
139
|
+
return format_response(
|
|
140
|
+
{"message": f"Plugin {plugin_name} enabled successfully"},
|
|
141
|
+
success=True,
|
|
142
|
+
)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(f"Error enabling plugin {plugin_name}: {e}")
|
|
145
|
+
return format_response(None, success=False, error=str(e))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def disable_plugin(plugin_name):
|
|
149
|
+
"""Disables a plugin.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
plugin_name: the name of the plugin to disable
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
a dict with success status
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
fop.disable_plugin(plugin_name)
|
|
159
|
+
return format_response(
|
|
160
|
+
{"message": f"Plugin {plugin_name} disabled successfully"},
|
|
161
|
+
success=True,
|
|
162
|
+
)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.error(f"Error disabling plugin {plugin_name}: {e}")
|
|
165
|
+
return format_response(None, success=False, error=str(e))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_plugin_tools():
|
|
169
|
+
"""Returns the list of plugin management MCP tools.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
list of :class:`mcp.types.Tool`
|
|
173
|
+
"""
|
|
174
|
+
return [
|
|
175
|
+
Tool(
|
|
176
|
+
name="list_plugins",
|
|
177
|
+
description="Lists available FiftyOne plugins and their operators. Plugins extend functionality by providing additional operators. Key plugins: @voxel51/brain (16 operators for similarity/duplicates/visualization), @voxel51/utils (12 operators for dataset CRUD), @voxel51/io (5 operators for import/export), @voxel51/evaluation (5 operators), @voxel51/annotation (6 operators), @voxel51/zoo (2 operators). Use this to discover what plugins are installed and what operators they provide.",
|
|
178
|
+
inputSchema={
|
|
179
|
+
"type": "object",
|
|
180
|
+
"properties": {
|
|
181
|
+
"enabled": {
|
|
182
|
+
"type": "boolean",
|
|
183
|
+
"description": "If true, list only enabled plugins. If false, list only disabled plugins. If not specified, lists all downloaded plugins",
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
),
|
|
188
|
+
Tool(
|
|
189
|
+
name="get_plugin_info",
|
|
190
|
+
description="Gets detailed information about a specific FiftyOne plugin including its operators, version, and metadata.",
|
|
191
|
+
inputSchema={
|
|
192
|
+
"type": "object",
|
|
193
|
+
"properties": {
|
|
194
|
+
"plugin_name": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"description": "The name of the plugin (e.g., '@voxel51/brain')",
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
"required": ["plugin_name"],
|
|
200
|
+
},
|
|
201
|
+
),
|
|
202
|
+
Tool(
|
|
203
|
+
name="download_plugin",
|
|
204
|
+
description="Downloads and installs a FiftyOne plugin from GitHub. Plugins immediately add new operators to the system (accessible via list_operators and execute_operator). Common repo: 'voxel51/fiftyone-plugins' contains all official plugins. After installation, use enable_plugin to activate the plugin's operators.",
|
|
205
|
+
inputSchema={
|
|
206
|
+
"type": "object",
|
|
207
|
+
"properties": {
|
|
208
|
+
"url_or_repo": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"description": "GitHub repository URL or 'user/repo' string (e.g., 'voxel51/fiftyone-plugins' or 'https://github.com/voxel51/fiftyone-plugins')",
|
|
211
|
+
},
|
|
212
|
+
"plugin_names": {
|
|
213
|
+
"type": "array",
|
|
214
|
+
"items": {"type": "string"},
|
|
215
|
+
"description": "Optional list of specific plugin names to download from the repo. If not specified, downloads all plugins in the repo",
|
|
216
|
+
},
|
|
217
|
+
"overwrite": {
|
|
218
|
+
"type": "boolean",
|
|
219
|
+
"description": "Whether to overwrite existing plugins. Default is false",
|
|
220
|
+
"default": False,
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
"required": ["url_or_repo"],
|
|
224
|
+
},
|
|
225
|
+
),
|
|
226
|
+
Tool(
|
|
227
|
+
name="enable_plugin",
|
|
228
|
+
description="Enables a downloaded FiftyOne plugin, making its operators immediately available through list_operators and execute_operator. Required after download_plugin to activate the plugin's functionality.",
|
|
229
|
+
inputSchema={
|
|
230
|
+
"type": "object",
|
|
231
|
+
"properties": {
|
|
232
|
+
"plugin_name": {
|
|
233
|
+
"type": "string",
|
|
234
|
+
"description": "The name of the plugin to enable",
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
"required": ["plugin_name"],
|
|
238
|
+
},
|
|
239
|
+
),
|
|
240
|
+
Tool(
|
|
241
|
+
name="disable_plugin",
|
|
242
|
+
description="Disables a FiftyOne plugin, removing its operators from availability.",
|
|
243
|
+
inputSchema={
|
|
244
|
+
"type": "object",
|
|
245
|
+
"properties": {
|
|
246
|
+
"plugin_name": {
|
|
247
|
+
"type": "string",
|
|
248
|
+
"description": "The name of the plugin to disable",
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
"required": ["plugin_name"],
|
|
252
|
+
},
|
|
253
|
+
),
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
async def handle_plugin_tool(name, arguments):
|
|
258
|
+
"""Handles plugin management tool calls.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
name: the tool name
|
|
262
|
+
arguments: dict of arguments for the tool
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
list of TextContent with the result
|
|
266
|
+
"""
|
|
267
|
+
if name == "list_plugins":
|
|
268
|
+
enabled = arguments.get("enabled")
|
|
269
|
+
result = list_plugins(enabled=enabled)
|
|
270
|
+
elif name == "get_plugin_info":
|
|
271
|
+
plugin_name = arguments["plugin_name"]
|
|
272
|
+
result = get_plugin_info(plugin_name)
|
|
273
|
+
elif name == "download_plugin":
|
|
274
|
+
url_or_repo = arguments["url_or_repo"]
|
|
275
|
+
plugin_names = arguments.get("plugin_names")
|
|
276
|
+
overwrite = arguments.get("overwrite", False)
|
|
277
|
+
result = download_plugin(url_or_repo, plugin_names, overwrite)
|
|
278
|
+
elif name == "enable_plugin":
|
|
279
|
+
plugin_name = arguments["plugin_name"]
|
|
280
|
+
result = enable_plugin(plugin_name)
|
|
281
|
+
elif name == "disable_plugin":
|
|
282
|
+
plugin_name = arguments["plugin_name"]
|
|
283
|
+
result = disable_plugin(plugin_name)
|
|
284
|
+
else:
|
|
285
|
+
result = format_response(
|
|
286
|
+
None, success=False, error=f"Unknown tool: {name}"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session management tools for FiftyOne MCP server.
|
|
3
|
+
|
|
4
|
+
| Copyright 2017-2025, Voxel51, Inc.
|
|
5
|
+
| `voxel51.com <https://voxel51.com/>`_
|
|
6
|
+
|
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
import fiftyone as fo
|
|
13
|
+
from fiftyone import ViewField as F
|
|
14
|
+
from mcp.types import Tool, TextContent
|
|
15
|
+
|
|
16
|
+
from .utils import format_response
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
_active_session = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def launch_app(dataset_name=None, port=None, remote=False):
|
|
25
|
+
"""Launches the FiftyOne App.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
dataset_name (None): optional dataset name to load in the app
|
|
29
|
+
port (None): optional port number for the app server
|
|
30
|
+
remote (False): whether to launch in remote mode
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
a dict with success status and session info
|
|
34
|
+
"""
|
|
35
|
+
global _active_session
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
dataset = None
|
|
39
|
+
if dataset_name:
|
|
40
|
+
dataset = fo.load_dataset(dataset_name)
|
|
41
|
+
|
|
42
|
+
session = fo.launch_app(dataset=dataset, port=port, remote=remote)
|
|
43
|
+
|
|
44
|
+
_active_session = session
|
|
45
|
+
|
|
46
|
+
session_info = {
|
|
47
|
+
"url": session.url if hasattr(session, "url") else None,
|
|
48
|
+
"dataset": dataset_name,
|
|
49
|
+
"port": port,
|
|
50
|
+
"remote": remote,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return format_response(
|
|
54
|
+
{"message": "FiftyOne App launched successfully", **session_info},
|
|
55
|
+
success=True,
|
|
56
|
+
)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.error(f"Error launching FiftyOne App: {e}")
|
|
59
|
+
return format_response(None, success=False, error=str(e))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def close_app():
|
|
63
|
+
"""Closes the active FiftyOne App session.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
a dict with success status
|
|
67
|
+
"""
|
|
68
|
+
global _active_session
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
fo.close_app()
|
|
72
|
+
_active_session = None
|
|
73
|
+
|
|
74
|
+
return format_response(
|
|
75
|
+
{"message": "FiftyOne App closed successfully"}, success=True
|
|
76
|
+
)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error(f"Error closing FiftyOne App: {e}")
|
|
79
|
+
return format_response(None, success=False, error=str(e))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_session_info():
|
|
83
|
+
"""Gets information about the active FiftyOne App session.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
a dict with success status and session details
|
|
87
|
+
"""
|
|
88
|
+
global _active_session
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
if _active_session is None:
|
|
92
|
+
return format_response(
|
|
93
|
+
{"active": False, "message": "No active session"}, success=True
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
view = _active_session.view
|
|
97
|
+
info = {
|
|
98
|
+
"active": True,
|
|
99
|
+
"url": (
|
|
100
|
+
_active_session.url
|
|
101
|
+
if hasattr(_active_session, "url")
|
|
102
|
+
else None
|
|
103
|
+
),
|
|
104
|
+
"dataset": (
|
|
105
|
+
_active_session.dataset.name
|
|
106
|
+
if _active_session.dataset
|
|
107
|
+
else None
|
|
108
|
+
),
|
|
109
|
+
"view": {
|
|
110
|
+
"name": view.name if view else None,
|
|
111
|
+
"num_samples": len(view) if view else None,
|
|
112
|
+
"stages": len(view._stages) if view else 0,
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return format_response(info, success=True)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.error(f"Error getting session info: {e}")
|
|
119
|
+
return format_response(None, success=False, error=str(e))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def set_view(
|
|
123
|
+
filters=None,
|
|
124
|
+
match=None,
|
|
125
|
+
exists=None,
|
|
126
|
+
tags=None,
|
|
127
|
+
sample_ids=None,
|
|
128
|
+
view_name=None,
|
|
129
|
+
):
|
|
130
|
+
"""Sets a filtered view in the active FiftyOne App session.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
filters (None): a dict mapping field names to values to match
|
|
134
|
+
match (None): a raw match expression dict for complex queries
|
|
135
|
+
exists (None): a field name or list of field names that must exist
|
|
136
|
+
tags (None): a tag or list of tags to match
|
|
137
|
+
sample_ids (None): a list of sample IDs to select
|
|
138
|
+
view_name (None): the name of a saved view to load
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
a dict with view info
|
|
142
|
+
"""
|
|
143
|
+
global _active_session
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
if _active_session is None:
|
|
147
|
+
return format_response(
|
|
148
|
+
None,
|
|
149
|
+
success=False,
|
|
150
|
+
error="No active session. Use launch_app first.",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
dataset = _active_session.dataset
|
|
154
|
+
if dataset is None:
|
|
155
|
+
return format_response(
|
|
156
|
+
None,
|
|
157
|
+
success=False,
|
|
158
|
+
error="No dataset loaded in session.",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if view_name:
|
|
162
|
+
if view_name not in dataset.list_saved_views():
|
|
163
|
+
return format_response(
|
|
164
|
+
None,
|
|
165
|
+
success=False,
|
|
166
|
+
error=f"Saved view '{view_name}' not found.",
|
|
167
|
+
)
|
|
168
|
+
view = dataset.load_saved_view(view_name)
|
|
169
|
+
_active_session.view = view
|
|
170
|
+
return format_response(
|
|
171
|
+
{
|
|
172
|
+
"view_name": view_name,
|
|
173
|
+
"num_samples": len(view),
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
view = dataset.view()
|
|
178
|
+
|
|
179
|
+
if filters:
|
|
180
|
+
for field, value in filters.items():
|
|
181
|
+
view = view.match(F(field) == value)
|
|
182
|
+
|
|
183
|
+
if match:
|
|
184
|
+
view = view.match(match)
|
|
185
|
+
|
|
186
|
+
if exists:
|
|
187
|
+
if isinstance(exists, str):
|
|
188
|
+
exists = [exists]
|
|
189
|
+
for field in exists:
|
|
190
|
+
view = view.exists(field)
|
|
191
|
+
|
|
192
|
+
if tags:
|
|
193
|
+
view = view.match_tags(tags)
|
|
194
|
+
|
|
195
|
+
if sample_ids:
|
|
196
|
+
view = view.select(sample_ids)
|
|
197
|
+
|
|
198
|
+
_active_session.view = view
|
|
199
|
+
|
|
200
|
+
return format_response(
|
|
201
|
+
{
|
|
202
|
+
"num_samples": len(view),
|
|
203
|
+
"stages": len(view._stages),
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.error(f"Error setting view: {e}")
|
|
209
|
+
return format_response(None, success=False, error=str(e))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def clear_view():
|
|
213
|
+
"""Clears the current view from the session.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
a dict with success status
|
|
217
|
+
"""
|
|
218
|
+
global _active_session
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
if _active_session is None:
|
|
222
|
+
return format_response(
|
|
223
|
+
None,
|
|
224
|
+
success=False,
|
|
225
|
+
error="No active session. Use launch_app first.",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
_active_session.clear_view()
|
|
229
|
+
|
|
230
|
+
return format_response({"message": "View cleared"})
|
|
231
|
+
|
|
232
|
+
except Exception as e:
|
|
233
|
+
logger.error(f"Error clearing view: {e}")
|
|
234
|
+
return format_response(None, success=False, error=str(e))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def get_session_tools():
|
|
238
|
+
"""Returns the list of session management MCP tools.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
list of :class:`mcp.types.Tool`
|
|
242
|
+
"""
|
|
243
|
+
return [
|
|
244
|
+
Tool(
|
|
245
|
+
name="launch_app",
|
|
246
|
+
description="Launches the FiftyOne App server. Required for executing delegated operators that need background execution (e.g., brain operators like find_near_duplicates).",
|
|
247
|
+
inputSchema={
|
|
248
|
+
"type": "object",
|
|
249
|
+
"properties": {
|
|
250
|
+
"dataset_name": {
|
|
251
|
+
"type": "string",
|
|
252
|
+
"description": "Optional dataset name to load in the app",
|
|
253
|
+
},
|
|
254
|
+
"port": {
|
|
255
|
+
"type": "integer",
|
|
256
|
+
"description": "Optional port number for the app server. If not specified, uses default port",
|
|
257
|
+
},
|
|
258
|
+
"remote": {
|
|
259
|
+
"type": "boolean",
|
|
260
|
+
"description": "Whether to launch in remote mode. Default is false",
|
|
261
|
+
"default": False,
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
),
|
|
266
|
+
Tool(
|
|
267
|
+
name="close_app",
|
|
268
|
+
description="Closes the active FiftyOne App session and stops the server.",
|
|
269
|
+
inputSchema={"type": "object", "properties": {}},
|
|
270
|
+
),
|
|
271
|
+
Tool(
|
|
272
|
+
name="get_session_info",
|
|
273
|
+
description="Gets information about the current FiftyOne App session, including whether it's active and what dataset is loaded.",
|
|
274
|
+
inputSchema={"type": "object", "properties": {}},
|
|
275
|
+
),
|
|
276
|
+
Tool(
|
|
277
|
+
name="set_view",
|
|
278
|
+
description="Sets a filtered view in the FiftyOne App. Use this to filter samples by field values, tags, existence of fields, or load saved views. The view updates immediately in the App UI.",
|
|
279
|
+
inputSchema={
|
|
280
|
+
"type": "object",
|
|
281
|
+
"properties": {
|
|
282
|
+
"filters": {
|
|
283
|
+
"type": "object",
|
|
284
|
+
"description": 'Dict mapping field names to values to match exactly (e.g., {"near_dup_id": 1})',
|
|
285
|
+
},
|
|
286
|
+
"exists": {
|
|
287
|
+
"type": "array",
|
|
288
|
+
"items": {"type": "string"},
|
|
289
|
+
"description": "Field name(s) that must have a non-None value",
|
|
290
|
+
},
|
|
291
|
+
"tags": {
|
|
292
|
+
"type": "array",
|
|
293
|
+
"items": {"type": "string"},
|
|
294
|
+
"description": "Sample tag(s) to match",
|
|
295
|
+
},
|
|
296
|
+
"sample_ids": {
|
|
297
|
+
"type": "array",
|
|
298
|
+
"items": {"type": "string"},
|
|
299
|
+
"description": "Specific sample IDs to select",
|
|
300
|
+
},
|
|
301
|
+
"view_name": {
|
|
302
|
+
"type": "string",
|
|
303
|
+
"description": "Name of a saved view to load",
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
),
|
|
308
|
+
Tool(
|
|
309
|
+
name="clear_view",
|
|
310
|
+
description="Clears the current view from the session, showing all samples.",
|
|
311
|
+
inputSchema={"type": "object", "properties": {}},
|
|
312
|
+
),
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
async def handle_session_tool(name, arguments):
|
|
317
|
+
"""Handles session management tool calls.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
name: the tool name
|
|
321
|
+
arguments: dict of arguments for the tool
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
list of TextContent with the result
|
|
325
|
+
"""
|
|
326
|
+
if name == "launch_app":
|
|
327
|
+
result = launch_app(
|
|
328
|
+
dataset_name=arguments.get("dataset_name"),
|
|
329
|
+
port=arguments.get("port"),
|
|
330
|
+
remote=arguments.get("remote", False),
|
|
331
|
+
)
|
|
332
|
+
elif name == "close_app":
|
|
333
|
+
result = close_app()
|
|
334
|
+
elif name == "get_session_info":
|
|
335
|
+
result = get_session_info()
|
|
336
|
+
elif name == "set_view":
|
|
337
|
+
result = set_view(
|
|
338
|
+
filters=arguments.get("filters"),
|
|
339
|
+
match=arguments.get("match"),
|
|
340
|
+
exists=arguments.get("exists"),
|
|
341
|
+
tags=arguments.get("tags"),
|
|
342
|
+
sample_ids=arguments.get("sample_ids"),
|
|
343
|
+
view_name=arguments.get("view_name"),
|
|
344
|
+
)
|
|
345
|
+
elif name == "clear_view":
|
|
346
|
+
result = clear_view()
|
|
347
|
+
else:
|
|
348
|
+
result = format_response(
|
|
349
|
+
None, success=False, error=f"Unknown tool: {name}"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|