Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions changelog/0.5.0.md → changelog/0.4.6.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# [0.5.0] - xxxx-xx-xx
# [0.4.6] - 2025-07-30

## Added

- Enabled `terminate_virtual_workspace` tool with safety confirmation ellicitation
- Enabled `terminate_virtual_workspace` and `create_starter_workspace` tools with safety confirmation ellicitation
- New `list_sharedtier_regions` tool to list regions available for shared tier workspaces
- New `database_onboarding` prompt to guide users through the process of setting up a new database
- New `help` prompt to provide an overview of the SingleStore MCP server capabilities, available tools, resources, and prompts
3 changes: 2 additions & 1 deletion scripts/mark-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ if [ ! -f "$CHANGELOG_FILE" ]; then

## Fixed
-
- " > "$CHANGELOG_FILE"
-
" > "$CHANGELOG_FILE"

# Add changelog to git
git add "$CHANGELOG_FILE"
Expand Down
4 changes: 2 additions & 2 deletions src/api/tools/regions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Regions tools for SingleStore MCP server."""

from .regions import list_regions
from .regions import list_regions, list_sharedtier_regions

__all__ = ["list_regions"]
__all__ = ["list_regions", "list_sharedtier_regions"]
53 changes: 53 additions & 0 deletions src/api/tools/regions/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

import time
from datetime import datetime, timezone
from typing import Any, Dict

from mcp.server.fastmcp import Context

from src.api.common import build_request
from src.api.tools.regions.utils import fetch_shared_tier_regions
from src.logger import get_logger

# Set up logger for this module
Expand Down Expand Up @@ -49,3 +53,52 @@ def list_regions() -> dict:
"timestamp": datetime.now(timezone.utc).isoformat(),
},
}


async def list_sharedtier_regions(ctx: Context) -> Dict[str, Any]:
"""
List all regions where shared tier workspaces can be created.

This tool provides information about available regions for creating starter workspaces,
including region names and cloud providers.

Args:
ctx: Context for user interaction and logging

Returns:
Dictionary with region information including:
- regionName: Name of the region (e.g., "us-west-2", "europe-west1")
- provider: Cloud provider (AWS, GCP, or Azure)
- name: Human-readable region name (e.g., Europe West 2 (London), US West 2 (Oregon))

Example Usage:
```python
result = await list_shared_tier_regions(ctx)
regions = result["data"]["result"]
```
"""
await ctx.info("Listing available shared tier regions...")

try:
regions_data = fetch_shared_tier_regions()

return {
"status": "success",
"message": f"Retrieved {len(regions_data)} shared tier regions",
"data": {"result": regions_data},
"metadata": {
"execution_time_ms": 100, # Placeholder for actual execution time
"count": len(regions_data),
"timestamp": datetime.now().isoformat(),
},
}

except Exception as e:
error_msg = str(e)
await ctx.error(error_msg)

return {
"status": "error",
"message": error_msg,
"error": str(e),
}
21 changes: 21 additions & 0 deletions src/api/tools/regions/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Utility functions for regions operations."""

from typing import List, Dict, Any
from src.api.common import build_request


def fetch_shared_tier_regions() -> List[Dict[str, Any]]:
"""
Fetch shared tier regions data from the API.

Returns:
List of region dictionaries containing region information

Raises:
Exception: If the API request fails or returns an error
"""
try:
regions_data = build_request("GET", "regions/sharedtier")
return regions_data
except Exception as e:
raise Exception(f"Failed to list shared tier regions: {str(e)}")
108 changes: 103 additions & 5 deletions src/api/tools/starter_workspaces/starter_workspaces.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"""Starter workspaces tools for SingleStore MCP server."""

from datetime import datetime
from typing import Dict, Any
from typing import Dict, Any, Optional
from pydantic import BaseModel, Field

from mcp.server.fastmcp import Context

from src.config import config
from src.api.common import build_request
from src.api.tools.regions.utils import fetch_shared_tier_regions
from src.utils.uuid_validation import validate_workspace_id
from src.utils.elicitation import try_elicitation, ElicitationError
from src.logger import get_logger
Expand Down Expand Up @@ -49,7 +50,11 @@ def list_virtual_workspaces() -> Dict[str, Any]:


async def create_starter_workspace(
ctx: Context, name: str, database_name: str
ctx: Context,
name: str,
database_name: str,
provider: Optional[str] = None,
region_name: Optional[str] = None,
) -> Dict[str, Any]:
"""
Create a new starter workspace using the SingleStore SDK.
Expand All @@ -60,6 +65,8 @@ async def create_starter_workspace(
Args:
name: Unique name for the new starter workspace
database_name: Name of the database to create in the starter workspace
provider: Cloud provider for the workspace (e.g., "AWS", "GCP", "Azure")
region_name: Region where the workspace should be deployed (e.g., "us-west-2", "europe-west1")

Returns:
Dictionary with starter workspace creation details including:
Expand Down Expand Up @@ -98,12 +105,66 @@ async def create_starter_workspace(
)

try:
# If provider or region_name are not provided, fetch available regions and ask user to pick
if provider is None or region_name is None:
await ctx.info("Fetching available shared tier regions...")
regions_data = fetch_shared_tier_regions()

# Group regions by provider
provider_regions = {}
for region in regions_data:
prov = region.get("provider")
if prov not in provider_regions:
provider_regions[prov] = []
provider_regions[prov].append(region.get("regionName"))

# Create region selection schema
class RegionSelection(BaseModel):
provider: str = Field(
description=f"Choose a cloud provider from: {', '.join(provider_regions.keys())}"
)
region_name: str = Field(
description="Choose a region name from the available regions for the selected provider"
)

# Format region information for user
region_info = "\n".join(
[
f"**{prov}**: {', '.join([r for r in regions])}"
for prov, regions in provider_regions.items()
]
)

elicit_result, error = await try_elicitation(
ctx=ctx,
message=f"Please select a provider and region for your starter workspace:\n\n{region_info}",
schema=RegionSelection,
)

if error == ElicitationError.NOT_SUPPORTED:
# Use first available region if elicitation not supported
first_region = regions_data[0]
provider = first_region.get("provider")
region_name = first_region.get("regionName")
await ctx.info(f"Using default region: {provider} - {region_name}")
elif elicit_result.status == "success" and elicit_result.data:
provider = elicit_result.data.provider
region_name = elicit_result.data.region_name
await ctx.info(f"Selected region: {provider} - {region_name}")
else:
return {
"status": "cancelled",
"message": "Workspace creation cancelled - no region selected",
"workspace_name": name,
"database_name": database_name,
}

# Create the starter workspace using the API
payload = {
"name": name,
"databaseName": database_name,
# TODO: Dinamically set region_id if needed
"workspaceGroup": {"cellID": "3482219c-a389-4079-b18b-d50662524e8a"},
"provider": provider, # e.g., "AWS", "GCP", "Azure"
"regionName": region_name, # e.g., "us-west-2", "europe-west1"
}

starter_workspace_data = build_request(
Expand All @@ -125,7 +186,7 @@ async def create_starter_workspace(

except Exception as e:
error_msg = f"Failed to create starter workspace '{name}': {str(e)}"
ctx.error(error_msg)
await ctx.error(error_msg)

return {
"status": "error",
Expand Down Expand Up @@ -242,6 +303,43 @@ class TerminationConfirmation(BaseModel):
f"Proceeding with termination of virtual workspace: {validated_workspace_id}"
)

class TerminationConfirmation(BaseModel):
"""Schema for collecting organization selection."""

confirm: bool = Field(
description="Confirm that you want to permanently terminate this virtual workspace",
default=False,
)

result = await ctx.elicit(
message=f"⚠️ **WARNING**: You are about to terminate the virtual workspace '{workspace_name or validated_workspace_id}'.\n\n"
"This action is permanent and cannot be undone. All data in the workspace will be lost.\n\n"
"Do you want to proceed with the termination?",
schema=TerminationConfirmation,
)

if not (result.action == "accept" and result.data and result.data.confirm):
return {
"status": "cancelled",
"message": "Workspace termination was cancelled by the user",
"workspace_id": validated_workspace_id,
"workspace_name": workspace_name,
}

# Track analytics event
settings.analytics_manager.track_event(
user_id,
"tool_calling",
{
"name": "terminate_virtual_workspace",
"workspace_id": validated_workspace_id,
},
)

await ctx.info(
f"Proceeding with termination of virtual workspace: {validated_workspace_id}"
)

# Terminate the virtual workspace
build_request(
"DELETE", f"sharedtier/virtualWorkspaces/{validated_workspace_id}"
Expand Down
5 changes: 3 additions & 2 deletions src/api/tools/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
create_starter_workspace,
terminate_virtual_workspace,
)
from src.api.tools.regions import list_regions
from src.api.tools.regions import list_regions, list_sharedtier_regions
from src.api.tools.database import run_sql
from src.api.tools.user import get_user_id
from src.api.tools.notebooks import (
Expand All @@ -33,9 +33,10 @@
{"func": workspace_groups_info},
{"func": workspaces_info},
{"func": list_virtual_workspaces},
{"func": create_starter_workspace, "internal": True},
{"func": create_starter_workspace},
{"func": terminate_virtual_workspace},
{"func": list_regions},
{"func": list_sharedtier_regions},
{"func": run_sql},
{"func": create_notebook_file},
{"func": upload_notebook_file},
Expand Down
2 changes: 1 addition & 1 deletion src/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.4.5"
__version__ = "0.4.6"