Skip to content

Commit 1f9afe1

Browse files
Feature genie skills upgrade issue#83 (#233)
* Add serialized space support in Genie API methods - Enhance the `create_or_update_genie` function to accept a `serialized_space` parameter for creating or updating Genie spaces using a full serialized configuration. Introduce new methods in `AgentBricksManager` for exporting and importing Genie spaces with serialized payloads, allowing for full configuration management. Update documentation to reflect these changes and provide usage examples. - new skills: `export_genie` and `import_genie` to support the new functionality including migration skills. - update documentation to reflect these changes and provide usage examples. * Enhance documentation for Genie Space export and import features including migration skills Add detailed instructions for exporting and importing Genie Spaces using `export_genie` and `import_genie` methods. Include information on serialized space structure, migration across workspaces with catalog remapping, and permissions required for operations. Update examples to illustrate cloning and migrating Genie Spaces effectively. * Refactor create_or_update_genie function for improved handling of serialized_space - Streamline logic for updating or creating Genie spaces based on the presence of serialized_space and space_id. - Enhance error handling for non-existent spaces and ensure proper updates are made when serialized_space is provided. - Update documentation to clarify the workflow for creating and updating Genie spaces, including handling sample questions. * Fix formatting issues and clean up whitespace in genie.py - Remove unnecessary blank lines to improve code readability. - Ensure consistent formatting across the file for better maintainability. * Enhance skills documentation and functionality for Genie Spaces - Updated the description in SKILL.md to include additional functionalities related to exporting, importing, and migrating Genie Spaces. - Improved the management and querying capabilities of Genie Spaces in the documentation. - Clarified the usage of `create_or_update_genie` for updating existing spaces, including new examples for metadata updates and full configuration updates. - Added detailed information on the structure of the exported data, including keys for title and description, to facilitate better understanding and usage of the export/import processes. * Tackling PR review feedback: Refactor Genie API methods and update documentation for serialized space handling - Streamlined the `create_or_update_genie` function to improve response structure and error handling. - Updated the `import_genie` method to reflect changes in serialized space versioning. - Enhanced documentation in SKILL.md and spaces.md to clarify usage of serialized space and migration processes. - Fixed minor formatting issues in the documentation for better readability. * Refactor Genie export and import functionality into a single migrate_genie method - Consolidated the export and import processes into a single `migrate_genie` function, allowing for both operations based on a type parameter. - Updated documentation in SKILL.md and spaces.md to reflect the new method and its usage for cloning and migrating Genie Spaces. - Enhanced error handling for required parameters in the migration process. - Improved examples to demonstrate the new workflow for exporting and importing Genie Spaces. * Remove `list_genie` tool from SKILL.md documentation as it is no longer accessible. Update documentation to reflect current available Genie tools and their purposes.
1 parent 16c881c commit 1f9afe1

4 files changed

Lines changed: 555 additions & 60 deletions

File tree

databricks-mcp-server/databricks_mcp_server/tools/genie.py

Lines changed: 218 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,19 @@ def create_or_update_genie(
4242
description: Optional[str] = None,
4343
sample_questions: Optional[List[str]] = None,
4444
space_id: Optional[str] = None,
45+
serialized_space: Optional[str] = None,
4546
) -> Dict[str, Any]:
4647
"""
4748
Create or update a Genie Space for SQL-based data exploration.
4849
4950
A Genie Space allows users to ask natural language questions about data
5051
and get SQL-generated answers. It connects to tables in Unity Catalog.
5152
53+
When serialized_space is provided, the space is created/updated using the
54+
full serialized configuration via the public /api/2.0/genie/spaces API.
55+
This preserves all instructions, SQL examples, and settings from the source.
56+
Obtain a serialized_space string via export_genie().
57+
5258
Args:
5359
display_name: Display name for the Genie space
5460
table_identifiers: List of tables to include
@@ -58,6 +64,10 @@ def create_or_update_genie(
5864
description: Optional description of what the Genie space does
5965
sample_questions: Optional list of sample questions to help users
6066
space_id: Optional existing space_id to update instead of create
67+
serialized_space: Optional full serialized space config JSON string
68+
(from export_genie). When provided, tables/instructions/SQL examples
69+
from the serialized config are used and the public genie/spaces API
70+
is called instead of data-rooms.
6171
6272
Returns:
6373
Dictionary with:
@@ -75,6 +85,15 @@ def create_or_update_genie(
7585
... sample_questions=["What were total sales last month?"]
7686
... )
7787
{"space_id": "abc123...", "display_name": "Sales Analytics", "operation": "created", ...}
88+
89+
>>> # Update with serialized config (preserves all instructions and SQL examples)
90+
>>> exported = export_genie("abc123...")
91+
>>> create_or_update_genie(
92+
... display_name="Sales Analytics",
93+
... table_identifiers=[],
94+
... space_id="abc123...",
95+
... serialized_space=exported["serialized_space"]
96+
... )
7897
"""
7998
try:
8099
description = with_description_footer(description)
@@ -88,44 +107,84 @@ def create_or_update_genie(
88107

89108
operation = "created"
90109

91-
if space_id:
92-
existing = manager.genie_get(space_id)
93-
if existing:
94-
operation = "updated"
95-
manager.genie_update(
110+
# When serialized_space is provided
111+
if serialized_space:
112+
if space_id:
113+
# Update existing space with serialized config
114+
manager.genie_update_with_serialized_space(
96115
space_id=space_id,
97-
display_name=display_name,
116+
serialized_space=serialized_space,
117+
title=display_name,
98118
description=description,
99119
warehouse_id=warehouse_id,
100-
table_identifiers=table_identifiers,
101-
sample_questions=sample_questions,
102120
)
121+
operation = "updated"
103122
else:
104-
return {"error": f"Genie space {space_id} not found"}
123+
# Check if exists by name, then create or update
124+
existing = manager.genie_find_by_name(display_name)
125+
if existing:
126+
operation = "updated"
127+
space_id = existing.space_id
128+
manager.genie_update_with_serialized_space(
129+
space_id=space_id,
130+
serialized_space=serialized_space,
131+
title=display_name,
132+
description=description,
133+
warehouse_id=warehouse_id,
134+
)
135+
else:
136+
result = manager.genie_import(
137+
warehouse_id=warehouse_id,
138+
serialized_space=serialized_space,
139+
title=display_name,
140+
description=description,
141+
)
142+
space_id = result.get("space_id", "")
143+
144+
# When serialized_space is not provided
105145
else:
106-
existing = manager.genie_find_by_name(display_name)
107-
if existing:
108-
operation = "updated"
109-
manager.genie_update(
110-
space_id=existing.space_id,
111-
display_name=display_name,
112-
description=description,
113-
warehouse_id=warehouse_id,
114-
table_identifiers=table_identifiers,
115-
sample_questions=sample_questions,
116-
)
117-
space_id = existing.space_id
146+
if space_id:
147+
# Update existing space by ID
148+
existing = manager.genie_get(space_id)
149+
if existing:
150+
operation = "updated"
151+
manager.genie_update(
152+
space_id=space_id,
153+
display_name=display_name,
154+
description=description,
155+
warehouse_id=warehouse_id,
156+
table_identifiers=table_identifiers,
157+
sample_questions=sample_questions,
158+
)
159+
else:
160+
return {"error": f"Genie space {space_id} not found"}
118161
else:
119-
result = manager.genie_create(
120-
display_name=display_name,
121-
warehouse_id=warehouse_id,
122-
table_identifiers=table_identifiers,
123-
description=description,
124-
)
125-
space_id = result.get("space_id", "")
126-
127-
if sample_questions and space_id:
128-
manager.genie_add_sample_questions_batch(space_id, sample_questions)
162+
# Check if exists by name first
163+
existing = manager.genie_find_by_name(display_name)
164+
if existing:
165+
operation = "updated"
166+
manager.genie_update(
167+
space_id=existing.space_id,
168+
display_name=display_name,
169+
description=description,
170+
warehouse_id=warehouse_id,
171+
table_identifiers=table_identifiers,
172+
sample_questions=sample_questions,
173+
)
174+
space_id = existing.space_id
175+
else:
176+
# Create new
177+
result = manager.genie_create(
178+
display_name=display_name,
179+
warehouse_id=warehouse_id,
180+
table_identifiers=table_identifiers,
181+
description=description,
182+
)
183+
space_id = result.get("space_id", "")
184+
185+
# Add sample questions if provided
186+
if sample_questions and space_id:
187+
manager.genie_add_sample_questions_batch(space_id, sample_questions)
129188

130189
response = {
131190
"space_id": space_id,
@@ -154,7 +213,7 @@ def create_or_update_genie(
154213

155214

156215
@mcp.tool
157-
def get_genie(space_id: Optional[str] = None) -> Dict[str, Any]:
216+
def get_genie(space_id: Optional[str] = None, include_serialized_space: bool = False) -> Dict[str, Any]:
158217
"""
159218
Get details of a Genie Space, or list all spaces.
160219
@@ -163,14 +222,28 @@ def get_genie(space_id: Optional[str] = None) -> Dict[str, Any]:
163222
164223
Args:
165224
space_id: The Genie space ID. If omitted, lists all spaces.
225+
include_serialized_space: If True, include the full serialized space configuration
226+
in the response (requires at least CAN EDIT permission). Useful when you
227+
want to inspect or export the space config. Default: False.
166228
167229
Returns:
168-
Single space dict (if space_id provided) or {"spaces": [...]}.
230+
Single space dictionary with Genie space details including:
231+
- space_id: The space ID
232+
- display_name: The display name
233+
- description: The description
234+
- warehouse_id: The SQL warehouse ID
235+
- table_identifiers: List of configured tables
236+
- sample_questions: List of sample questions
237+
- serialized_space: Full space config JSON string (only when include_serialized_space=True)
238+
Multiple spaces: List of space dictionaries (only when space_id is omitted)
169239
170240
Example:
171241
>>> get_genie("abc123...")
172242
{"space_id": "abc123...", "display_name": "Sales Analytics", ...}
173243
244+
>>> get_genie("abc123...", include_serialized_space=True)
245+
{"space_id": "abc123...", ..., "serialized_space": "{\"version\":1,...}"}
246+
174247
>>> get_genie()
175248
{"spaces": [{"space_id": "abc123...", "title": "Sales Analytics", ...}, ...]}
176249
"""
@@ -185,14 +258,21 @@ def get_genie(space_id: Optional[str] = None) -> Dict[str, Any]:
185258
questions_response = manager.genie_list_questions(space_id, question_type="SAMPLE_QUESTION")
186259
sample_questions = [q.get("question_text", "") for q in questions_response.get("curated_questions", [])]
187260

188-
return {
261+
response = {
189262
"space_id": result.get("space_id", space_id),
190263
"display_name": result.get("display_name", ""),
191264
"description": result.get("description", ""),
192265
"warehouse_id": result.get("warehouse_id", ""),
193266
"table_identifiers": result.get("table_identifiers", []),
194267
"sample_questions": sample_questions,
195268
}
269+
270+
if include_serialized_space:
271+
exported = manager.genie_export(space_id)
272+
response["serialized_space"] = exported.get("serialized_space", "")
273+
274+
return response
275+
196276
except Exception as e:
197277
return {"error": f"Failed to get Genie space {space_id}: {e}"}
198278

@@ -246,6 +326,110 @@ def delete_genie(space_id: str) -> Dict[str, Any]:
246326
return {"success": False, "space_id": space_id, "error": str(e)}
247327

248328

329+
@mcp.tool
330+
def migrate_genie(
331+
type: str,
332+
space_id: Optional[str] = None,
333+
warehouse_id: Optional[str] = None,
334+
serialized_space: Optional[str] = None,
335+
title: Optional[str] = None,
336+
description: Optional[str] = None,
337+
parent_path: Optional[str] = None,
338+
) -> Dict[str, Any]:
339+
"""
340+
Export or import a Genie Space for cloning and cross-workspace migration.
341+
342+
type="export": Retrieve the full serialized configuration of an existing
343+
Genie Space (tables, instructions, SQL queries, layout). Requires at least
344+
CAN EDIT permission on the space.
345+
346+
type="import": Create a new Genie Space from a serialized payload obtained
347+
via a prior export call.
348+
349+
Args:
350+
type: Operation to perform — "export" or "import"
351+
space_id: (export) The Genie space ID to export
352+
warehouse_id: (import) SQL warehouse ID for the new space.
353+
Use list_warehouses() or get_best_warehouse() to find one.
354+
serialized_space: (import) JSON string from a prior export containing
355+
the full space configuration. Can also be constructed manually:
356+
'{"version":2,"data_sources":{"tables":[{"identifier":"cat.schema.table"}]}}'
357+
title: (import) Optional title override
358+
description: (import) Optional description override
359+
parent_path: (import) Optional workspace folder path for the new space
360+
(e.g., "/Workspace/Users/you@company.com/Genie Spaces")
361+
362+
Returns:
363+
export: Dictionary with space_id, title, description, warehouse_id,
364+
and serialized_space (JSON string with the full config).
365+
import: Dictionary with space_id, title, description, and
366+
operation='imported'.
367+
368+
Example:
369+
>>> exported = migrate_genie(type="export", space_id="abc123...")
370+
>>> migrate_genie(
371+
... type="import",
372+
... warehouse_id=exported["warehouse_id"],
373+
... serialized_space=exported["serialized_space"],
374+
... title="Sales Analytics (Clone)"
375+
... )
376+
{"space_id": "def456...", "title": "Sales Analytics (Clone)", "operation": "imported"}
377+
"""
378+
if type == "export":
379+
if not space_id:
380+
return {"error": "space_id is required for type='export'"}
381+
manager = _get_manager()
382+
try:
383+
result = manager.genie_export(space_id)
384+
return {
385+
"space_id": result.get("space_id", space_id),
386+
"title": result.get("title", ""),
387+
"description": result.get("description", ""),
388+
"warehouse_id": result.get("warehouse_id", ""),
389+
"serialized_space": result.get("serialized_space", ""),
390+
}
391+
except Exception as e:
392+
return {"error": str(e), "space_id": space_id}
393+
394+
elif type == "import":
395+
if not warehouse_id or not serialized_space:
396+
return {"error": "warehouse_id and serialized_space are required for type='import'"}
397+
manager = _get_manager()
398+
try:
399+
result = manager.genie_import(
400+
warehouse_id=warehouse_id,
401+
serialized_space=serialized_space,
402+
title=title,
403+
description=description,
404+
parent_path=parent_path,
405+
)
406+
imported_space_id = result.get("space_id", "")
407+
408+
if imported_space_id:
409+
try:
410+
from ..manifest import track_resource
411+
412+
track_resource(
413+
resource_type="genie_space",
414+
name=title or result.get("title", imported_space_id),
415+
resource_id=imported_space_id,
416+
)
417+
except Exception:
418+
pass
419+
420+
return {
421+
"space_id": imported_space_id,
422+
"title": result.get("title", title or ""),
423+
"description": result.get("description", description or ""),
424+
"operation": "imported",
425+
}
426+
except Exception as e:
427+
return {"error": str(e)}
428+
429+
else:
430+
return {"error": f"Invalid type '{type}'. Must be 'export' or 'import'."}
431+
432+
249433
# ============================================================================
250434
# Genie Conversation API Tools
251435
# ============================================================================

0 commit comments

Comments
 (0)