Skip to content

Commit 0ad046d

Browse files
authored
Feature/helios onboarding prompt (#71)
* enable terminate virtual workspace tool * fix terminate virtual workspace tool * adapt terminate virtual workspace if elicitation is not supported by client * new onboarding prompt * add onboarding prompt
1 parent 8b398ec commit 0ad046d

File tree

7 files changed

+203
-78
lines changed

7 files changed

+203
-78
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
With MCP, you can use Claude Desktop, Cursor, or any compatible MCP client to interact with SingleStore using natural language, making it easier to perform complex operations effortlessly.
88

9+
💡 **Pro Tip**: Not sure what the MCP server can do? Just type `/help` in your chat! The MCP server exposes its capabilities through tools that the LLM understands, so you can get a complete overview of available features or ask specific questions like "How do I work with SingleStore workspaces?" The LLM will guide you through the available features and help you accomplish your tasks.
10+
911
## Requirements
1012

1113
- Python >= v3.11.0

changelog/0.5.0.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# [0.5.0] - xxxx-xx-xx
2+
3+
## Added
4+
5+
- Enabled `terminate_virtual_workspace` tool with safety confirmation ellicitation
6+
- New `onboarding_user` prompt to guide users through the initial helios setup process
7+
- New `help` prompt to provide an overview of the SingleStore MCP server capabilities, available tools, resources, and prompts

src/api/common.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def build_request_endpoint(endpoint: str, params: dict = None):
216216
if org_id is not None:
217217
params["organizationID"] = org_id
218218

219-
if params and type == "GET": # Only add query params for GET requests
219+
if params:
220220
url += "?"
221221
for key, value in params.items():
222222
url += f"{key}={value}&"
@@ -260,10 +260,7 @@ def build_request_endpoint(endpoint: str, params: dict = None):
260260
if request.status_code != 200:
261261
raise HTTPException(request.status_code, request.text)
262262

263-
try:
264-
return request.json()
265-
except ValueError:
266-
raise ValueError(f"Invalid JSON response: {request.text}")
263+
return request.json()
267264

268265

269266
def __find_workspace_group(workspace_group_identifier: str):

src/api/prompts/prompts.py

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,107 @@
11
from src.api.prompts.types import Prompt
2+
from mcp.server.fastmcp.prompts.base import AssistantMessage, UserMessage
23

3-
prompts_definitions = []
4+
5+
def onboarding_helios_user() -> list:
6+
"""
7+
Guide new users through SingleStore setup process:
8+
1. Account setup and organization selection
9+
2. Workspace creation with sample data
10+
3. Basic SQL tutorial
11+
"""
12+
return [
13+
AssistantMessage("Welcome to SingleStore! Let's get you started."),
14+
AssistantMessage(
15+
"Step 1: Make sure you are authenticated and have access to an organization. If you haven't, please run the login command or use the authentication flow in your client."
16+
),
17+
AssistantMessage(
18+
"Step 2: Create a starter workspace and database. You can do this by running the following command or using the UI:"
19+
),
20+
UserMessage(
21+
"Let's create a starter workspace if I don't have one yet. Then, populate the database with sample data.",
22+
),
23+
AssistantMessage(
24+
"Step 3: Load sample data into your new database. Try running this SQL in your virtual workspace:"
25+
),
26+
AssistantMessage(
27+
"""\
28+
CREATE TABLE IF NOT EXISTS sample_customers (
29+
id INT NOT NULL,
30+
name VARCHAR(100),
31+
email VARCHAR(100),
32+
country VARCHAR(50),
33+
PRIMARY KEY (id)
34+
);
35+
36+
INSERT INTO sample_customers VALUES
37+
(1, 'John Doe', 'john@example.com', 'USA'),
38+
(2, 'Jane Smith', 'jane@example.com', 'Canada'),
39+
(3, 'Alice Johnson', 'alice@example.com', 'UK');
40+
"""
41+
),
42+
AssistantMessage("Step 4: Try your first query!"),
43+
UserMessage("Let's run a simple select query to see our data."),
44+
AssistantMessage(
45+
"""Here are some ways to explore your SingleStore database:
46+
47+
1. Performance Monitoring & Analysis:
48+
- View query history and performance metrics in SingleStore Helios Portal
49+
- Try EXPLAIN before queries to analyze execution plans
50+
51+
2. Data Management:
52+
- Experiment with different table types (rowstore vs. columnstore)
53+
- Try bulk data loading with LOAD DATA
54+
- Explore JSON and geospatial data types
55+
56+
3. Optimization Features:
57+
- Create indexes for better query performance
58+
- Use window functions for analytics
59+
- Try full-text search capabilities
60+
61+
4. Development Integration:
62+
- Connect using various client libraries (Python, Drizzle ORM)
63+
- Explore SingleStore Notebooks for interactive analysis
64+
65+
Which area would you like to explore first?"""
66+
),
67+
]
68+
69+
70+
def help() -> list:
71+
"""
72+
Provides an overview of the SingleStore MCP server capabilities,
73+
available tools, resources, and prompts.
74+
"""
75+
return [
76+
AssistantMessage(
77+
"Welcome to the SingleStore Model Context Protocol (MCP) Server! 👋\nDo not call any tools! The user only wnat's to list them, not perform any action"
78+
),
79+
AssistantMessage(
80+
"Let me show you what tools , resources, and prompts are available to you."
81+
),
82+
AssistantMessage(
83+
"Organize the response in the following format:\n"
84+
"- **Tools**: List of available tools with brief descriptions\n"
85+
"- **Resources**: Links to documentation, tutorials, and guides\n"
86+
"- **Prompts**: List of available prompts with their titles and descriptions"
87+
),
88+
UserMessage("List all the MCP tools, resources, and prompts available to me."),
89+
AssistantMessage(
90+
"Need help with something specific? Just ask and I'll guide you through it!"
91+
),
92+
]
93+
94+
95+
prompts_definitions = [
96+
{
97+
"title": "Helios User Onboarding",
98+
"func": onboarding_helios_user,
99+
},
100+
{
101+
"title": "Help",
102+
"func": help,
103+
},
104+
]
4105

5106
# Export the prompts using create_from_dict for consistency
6107
prompts = [Prompt.create_from_dict(prompt) for prompt in prompts_definitions]

src/api/prompts/register.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from functools import wraps
2-
from typing import Callable, List
1+
from typing import List
32
from mcp.server.fastmcp import FastMCP
43

54
from src.api.common import filter_mcp_concepts
@@ -8,22 +7,11 @@
87
from .prompts import prompts as prompts_list
98

109

11-
def create_prompts_wrapper(func: Callable, name: str, description: str):
12-
@wraps(func)
13-
async def wrapper(*args, **kwargs):
14-
return func(*args, **kwargs)
15-
16-
wrapper.__name__ = name
17-
wrapper.__doc__ = description
18-
return wrapper
19-
20-
2110
def register_prompts(mcp: FastMCP) -> None:
2211
filtered_prompts: List[Prompt] = filter_mcp_concepts(prompts_list)
2312

2413
for prompt in filtered_prompts:
2514
func = prompt.func
26-
# Add context support for MCP
27-
wrapper = create_prompts_wrapper(func, func.__name__, func.__doc__ or "")
28-
29-
mcp.prompt(name=func.__name__, description=func.__doc__ or "")(wrapper)
15+
mcp.prompt(
16+
name=func.__name__, description=func.__doc__ or "", title=prompt.title
17+
)(func)

src/api/tools/tools.py

Lines changed: 84 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2040,95 +2040,123 @@ async def terminate_virtual_workspace(
20402040
workspace_id: str,
20412041
) -> Dict[str, Any]:
20422042
"""
2043-
Terminate a virtual (starter) workspace using the SingleStore SDK.
2043+
Permanently delete a virtual workspace in SingleStore with safety confirmations.
20442044
2045-
This tool provides a safe and reliable way to terminate virtual workspaces,
2046-
with proper error handling and confirmation of the termination status.
2045+
⚠️ WARNING: This action CANNOT be undone. All workspace data will be permanently lost.
2046+
Make sure to backup important data before proceeding.
20472047
2048-
⚠️ WARNING: This action is permanent and cannot be undone. All data in the
2049-
workspace will be lost. Make sure to backup any important data before terminating.
2048+
Safety Features:
2049+
- Requires explicit user confirmation (if elicitation is supported)
2050+
- Validates workspace existence
2051+
- Provides warning messages
2052+
- Includes error handling
20502053
20512054
Args:
2052-
workspace_id: Unique identifier of the virtual workspace to terminate
2055+
ctx: Context for user interaction and logging
2056+
workspace_id: Workspace identifier (format: "ws-" followed by alphanumeric chars)
20532057
20542058
Returns:
2055-
Dictionary with termination status and details including:
2056-
- status: "success" or "error"
2057-
- message: Human-readable description of the result
2058-
- workspace_id: ID of the terminated workspace
2059-
- workspace_name: Name of the terminated workspace (if available)
2060-
- termination_time: Timestamp when termination was initiated
2061-
2062-
Benefits over direct API calls:
2063-
- Automatic retry logic and error handling
2064-
- Proper validation of workspace existence
2065-
- Detailed error messages and status reporting
2066-
- Built-in authentication handling
2059+
{
2060+
"status": "success" | "error" | "cancelled",
2061+
"message": str, # Human-readable result
2062+
"workspace_id": str,
2063+
"workspace_name": str, # If available
2064+
"termination_time": str, # ISO 8601 (if successful)
2065+
"error": str # If status="error"
2066+
}
20672067
2068-
Example Usage:
2068+
Example:
20692069
```python
2070-
result = terminate_virtual_workspace(
2071-
ctx=ctx,
2072-
workspace_id="ws-abc123def456"
2073-
)
2070+
result = await terminate_virtual_workspace(ctx, "ws-abc123")
20742071
if result["status"] == "success":
2075-
print(f"Workspace {result['workspace_name']} terminated successfully")
2076-
else:
2077-
print(f"Failed to terminate workspace: {result['message']}")
2072+
print(f"Workspace {result['workspace_name']} terminated")
20782073
```
2074+
2075+
Related:
2076+
- list_virtual_workspaces()
2077+
- create_starter_workspace()
20792078
"""
20802079
# Validate workspace ID format
20812080
validated_workspace_id = validate_workspace_id(workspace_id)
20822081

2083-
await ctx.info(f"Terminating virtual workspace with ID: {validated_workspace_id}")
2084-
20852082
settings = config.get_settings()
20862083
user_id = config.get_user_id()
20872084

2088-
# Track analytics event
2089-
settings.analytics_manager.track_event(
2090-
user_id,
2091-
"tool_calling",
2092-
{
2093-
"name": "terminate_virtual_workspace",
2094-
"workspace_id": validated_workspace_id,
2095-
},
2096-
)
2097-
20982085
try:
2099-
# First, try to get the workspace details before termination
2100-
workspace_name = None
2101-
try:
2102-
starter_workspace_data = build_request(
2103-
"GET", f"sharedtier/virtualWorkspaces/{validated_workspace_id}"
2086+
starter_workspace_data = build_request(
2087+
"GET", f"sharedtier/virtualWorkspaces/{validated_workspace_id}"
2088+
)
2089+
workspace_name = starter_workspace_data.get("name")
2090+
await ctx.info(
2091+
f"Found virtual workspace '{workspace_name}' (ID: {validated_workspace_id})"
2092+
)
2093+
2094+
class TerminationConfirmation(BaseModel):
2095+
"""Schema for collecting organization selection."""
2096+
2097+
confirm: bool = Field(
2098+
description="Do you really want to terminate this virtual workspace?",
2099+
default=False,
21042100
)
2105-
workspace_name = starter_workspace_data.get("name")
2101+
2102+
# Check if elicitation is supported
2103+
elicit_result, error = await try_elicitation(
2104+
ctx=ctx,
2105+
message=f"⚠️ **WARNING**: You are about to terminate the virtual workspace '{workspace_name}'.\n\n"
2106+
"This action is permanent and cannot be undone. All data in the workspace will be lost.\n\n"
2107+
"Do you want to proceed with the termination?",
2108+
schema=TerminationConfirmation,
2109+
)
2110+
2111+
# Skip confirmation if elicitation is not supported
2112+
if error == ElicitationError.NOT_SUPPORTED:
21062113
await ctx.info(
2107-
f"Found virtual workspace '{workspace_name}' (ID: {validated_workspace_id})"
2108-
)
2109-
except Exception as e:
2110-
# If we can't get the workspace, it might not exist or already be terminated
2111-
ctx.warning(f"Could not retrieve workspace details: {str(e)}")
2112-
raise ValueError(
2113-
f"Virtual workspace '{validated_workspace_id}' does not exist or has already been terminated."
2114+
"Proceeding with termination without confirmation since interactive confirmation is not supported."
21142115
)
2116+
else:
2117+
# Only check confirmation if elicitation was supported
2118+
if not (
2119+
elicit_result.status == "success"
2120+
and elicit_result.data
2121+
and elicit_result.data.confirm
2122+
):
2123+
return {
2124+
"status": "cancelled",
2125+
"message": "Workspace termination was cancelled by the user",
2126+
"workspace_id": validated_workspace_id,
2127+
"workspace_name": workspace_name,
2128+
}
2129+
2130+
# Track analytics event
2131+
settings.analytics_manager.track_event(
2132+
user_id,
2133+
"tool_calling",
2134+
{
2135+
"name": "terminate_virtual_workspace",
2136+
"workspace_id": validated_workspace_id,
2137+
},
2138+
)
2139+
2140+
await ctx.info(
2141+
f"Proceeding with termination of virtual workspace: {validated_workspace_id}"
2142+
)
21152143

21162144
# Terminate the virtual workspace
21172145
build_request(
21182146
"DELETE", f"sharedtier/virtualWorkspaces/{validated_workspace_id}"
21192147
)
21202148

2121-
termination_time = datetime.now().isoformat()
2122-
2123-
success_message = f"Virtual workspace '{workspace_name or validated_workspace_id}' terminated successfully"
2149+
success_message = (
2150+
f"Virtual workspace '{workspace_name}' terminated successfully"
2151+
)
21242152
await ctx.info(success_message)
21252153

21262154
return {
21272155
"status": "success",
21282156
"message": success_message,
21292157
"workspace_id": validated_workspace_id,
21302158
"workspace_name": workspace_name,
2131-
"termination_time": termination_time,
2159+
"termination_time": datetime.now().isoformat(),
21322160
}
21332161

21342162
except Exception as e:
@@ -2252,7 +2280,7 @@ async def set_organization(ctx: Context, organization_id: str) -> dict:
22522280
{"func": complete_database_migration, "internal": True},
22532281
# This tool is under development and not yet available for public use
22542282
{"func": create_starter_workspace, "internal": True},
2255-
{"func": terminate_virtual_workspace, "internal": True},
2283+
{"func": terminate_virtual_workspace},
22562284
]
22572285

22582286
# Export the tools

src/commands/start.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from mcp.server.fastmcp import FastMCP
33
from mcp.server.auth.settings import AuthSettings, ClientRegistrationOptions
44

5+
from src.api.prompts.register import register_prompts
56
from src.auth.callback import make_auth_callback_handler
67
from src.api.tools import register_tools
78
from src.auth.provider import SingleStoreOAuthProvider
@@ -65,6 +66,7 @@ def start_command(transport: str, host: str):
6566

6667
register_tools(mcp)
6768
register_resources(mcp)
69+
register_prompts(mcp)
6870

6971
if settings.is_remote:
7072
# Register the callback handler with the captured oauth_provider

0 commit comments

Comments
 (0)