Skip to content

Commit 88116cc

Browse files
committed
Limit tools available to a user
1 parent 3e3e2bf commit 88116cc

File tree

4 files changed

+33
-101
lines changed

4 files changed

+33
-101
lines changed

README.md

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ The following tools are exposed, derived from the FastAPI application's endpoint
133133
- **Description**: Starts a new sandbox instance for a specified use case.
134134
- **Input**:
135135
- `usecase` (str): The name of the use case for the sandbox (e.g., 'movies', 'blank').
136-
- `cease_emails` (Optional[bool], default: `False`): If true, no emails will be sent regarding this sandbox.
137136
- **Output**: `Dict` (JSON object representing the newly started sandbox)
138137

139138
---
@@ -150,7 +149,6 @@ The following tools are exposed, derived from the FastAPI application's endpoint
150149
- **Description**: Extends the lifetime of a sandbox or all sandboxes for the user.
151150
- **Input**:
152151
- `sandbox_hash_key` (Optional[str]): Specific sandbox to extend. If None, all user's sandboxes are extended.
153-
- `profile_data` (Optional[Dict[str, Any]]): User profile information.
154152
- **Output**: `Dict` (JSON object with status of the extension)
155153
156154
---
@@ -164,23 +162,6 @@ The following tools are exposed, derived from the FastAPI application's endpoint
164162
165163
---
166164
167-
### `invite_sandbox_collaborator`
168-
- **Description**: Invites a collaborator to a specific sandbox.
169-
- **Input**:
170-
- `sandbox_hash_key` (str): The unique hash key identifying the sandbox to share.
171-
- `email` (str): Email address of the user to invite.
172-
- `message` (str): A personal message to include in the invitation.
173-
- **Output**: `Dict` (JSON object with the status of the invitation)
174-
175-
---
176-
177-
### `get_user_information`
178-
- **Description**: Retrieves user information for the authenticated user (from Sandbox API if needed, or use token data).
179-
- **Input**: None
180-
- **Output**: `Dict` (JSON object containing user information)
181-
182-
---
183-
184165
### `request_sandbox_backup`
185166
- **Description**: Requests a backup for a specific sandbox.
186167
- **Input**:

src/sandbox_api_mcp_server/sandbox/models.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
from typing import Annotated, Optional, Dict, Any
1+
from typing import Annotated, Optional, Any
22
from pydantic import BaseModel, Field
33

44

55
class StartSandboxBody(BaseModel):
66
usecase: Annotated[str, Field(description="The name of the use case for the sandbox (e.g., 'movies', 'blank').")]
7-
cease_emails: Annotated[Optional[bool], Field(description="If true, no emails will be sent regarding this sandbox.")] = False
87

98

109
class StopSandboxBody(BaseModel):
@@ -13,7 +12,6 @@ class StopSandboxBody(BaseModel):
1312

1413
class ExtendSandboxBody(BaseModel):
1514
sandbox_hash_key: Annotated[Optional[str], Field(description="Specific sandbox to extend. If None, all user's sandboxes are extended.")] = None
16-
profile_data: Annotated[Optional[Dict[str, Any]], Field(description="User profile information.")] = None
1715

1816

1917
class InviteCollaboratorsBody(BaseModel):

src/sandbox_api_mcp_server/sandbox/routes.py

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fastapi import Query, Path, status
55
from fastapi.responses import PlainTextResponse
66

7-
from .models import StartSandboxBody, StopSandboxBody, ExtendSandboxBody, InviteCollaboratorsBody, AuraUploadBody, BackupDownloadUrlBody, FastApiReadCypherQueryBody, FastApiWriteCypherQueryBody
7+
from .models import StartSandboxBody, StopSandboxBody, ExtendSandboxBody, AuraUploadBody, BackupDownloadUrlBody, FastApiReadCypherQueryBody, FastApiWriteCypherQueryBody
88
from helpers import get_logger
99
from .service import call_sandbox_api, SandboxApiClient, get_sandbox_client
1010

@@ -35,7 +35,7 @@ async def start_sandbox(
3535
):
3636
"""Starts a new sandbox instance for a specified use case."""
3737
try:
38-
return await call_sandbox_api("start_sandbox", client, usecase=body.usecase, cease_emails=body.cease_emails)
38+
return await call_sandbox_api("start_sandbox", client, usecase=body.usecase)
3939
except Exception as e:
4040
logger.error(f"Error starting sandbox: {e}")
4141
raise e
@@ -59,7 +59,7 @@ async def extend_sandbox(
5959
):
6060
"""Extends the lifetime of a sandbox or all sandboxes for the user."""
6161
try:
62-
return await call_sandbox_api("extend_sandbox", client, sandbox_hash_key=body.sandbox_hash_key, profile_data=body.profile_data)
62+
return await call_sandbox_api("extend_sandbox", client, sandbox_hash_key=body.sandbox_hash_key)
6363
except Exception as e:
6464
logger.error(f"Error extending sandbox: {e}")
6565
raise e
@@ -78,31 +78,6 @@ async def get_sandbox_details(
7878
logger.error(f"Error getting sandbox details: {e}")
7979
raise e
8080

81-
@router.post("/invite-collaborator", operation_id="invite_sandbox_collaborator", tags=["Sandbox"], response_model=Dict)
82-
async def invite_collaborator(
83-
body: InviteCollaboratorsBody,
84-
client: Annotated[SandboxApiClient, Depends(get_sandbox_client)],
85-
):
86-
"""Invites a collaborator to a specific sandbox."""
87-
try:
88-
return await call_sandbox_api("invite_collaborator", client, sandbox_hash_key=body.sandbox_hash_key, email=body.email, message=body.message)
89-
except Exception as e:
90-
logger.error(f"Error inviting collaborator: {e}")
91-
raise e
92-
93-
@router.get("/user-info", operation_id="get_user_information", tags=["User"], response_model=Dict)
94-
async def get_user_info_ep(
95-
client: Annotated[SandboxApiClient, Depends(get_sandbox_client)],
96-
):
97-
"""Retrieves user information for the authenticated user (from Sandbox API if needed, or use token data)."""
98-
# This could either return user info from the token (user param) or call the Sandbox API
99-
# return user # Example: return Auth0 user profile
100-
try:
101-
return await call_sandbox_api("get_user_info", client)
102-
except Exception as e:
103-
logger.error(f"Error getting user info: {e}")
104-
raise e
105-
10681
# --- Backup Related Endpoints ---
10782
@router.post("/request-backup/{sandbox_hash_key}", operation_id="request_sandbox_backup", tags=["Backup"], response_model=Dict)
10883
async def request_backup_ep(
@@ -161,7 +136,14 @@ async def upload_to_aura_ep(
161136
):
162137
"""Uploads a sandbox backup to an Aura instance."""
163138
try:
164-
return await call_sandbox_api("upload_to_aura", client, sandbox_hash_key=body.sandbox_hash_key, aura_uri=body.aura_uri, aura_password=body.aura_password, aura_username=body.aura_username)
139+
return await call_sandbox_api(
140+
"upload_to_aura",
141+
client,
142+
sandbox_hash_key=body.sandbox_hash_key,
143+
aura_uri=body.aura_uri,
144+
aura_password=body.aura_password,
145+
aura_username=body.aura_username,
146+
)
165147
except Exception as e:
166148
logger.error(f"Error uploading to Aura: {e}")
167149
raise e

src/sandbox_api_mcp_server/sandbox/service.py

Lines changed: 21 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -79,29 +79,23 @@ async def list_sandboxes_for_user(self, timezone: Optional[str] = None) -> Dict[
7979
"sandboxes": await self._request("GET", "/SandboxGetRunningInstancesForUser", params=params),
8080
}
8181

82-
async def start_sandbox(self, usecase: str, cease_emails: Optional[bool] = None, draft_only: Optional[bool] = None, auth_token: Optional[str] = None) -> Dict[str, Any]:
82+
async def start_sandbox(self, usecase: str) -> Dict[str, Any]:
8383
"""
8484
Creates and deploys a new sandbox instance or returns an existing one if duplicates are not allowed and one exists.
8585
Corresponds to POST /SandboxRunInstance in swagger.
8686
8787
Args:
88-
usecase (str): The name of the use case for the sandbox.
89-
cease_emails (Optional[bool]): If true, no emails will be sent regarding this sandbox. (default: false)
90-
draft_only (Optional[bool]): If true, only a draft record is created, and the instance is not deployed. (default: false)
91-
auth_token (Optional[str]): Optional authentication token for certain flows.
88+
usecase (str): The name of the use case for the sandbox. Possible values are:
89+
blank-sandbox,bloom,citations,contact-tracing,cybersecurity,entity-resolution,fincen,
90+
fraud-detection,graph-data-science,graph-data-science-blank-sandbox,healthcare-analytics,
91+
icij-offshoreleaks,icij-paradise-papers,legis-graph,movies,network-management,
92+
openstreetmap,pole,recommendations,twitch,twitter-trolls,wwc2019,yelp,twitter-v2
9293
9394
Returns:
9495
Dict[str, Any]: Sandbox instance details or draft confirmation.
9596
See #/components/schemas/RunInstanceResponse in swagger.
9697
"""
97-
json_data: Dict[str, Any] = {"usecase": usecase}
98-
if cease_emails is not None:
99-
json_data["cease_emails"] = cease_emails
100-
if draft_only is not None:
101-
json_data["draft_only"] = draft_only
102-
if auth_token is not None:
103-
json_data["auth_token"] = auth_token
104-
return await self._request("POST", "/SandboxRunInstance", json_data=json_data)
98+
return await self._request("POST", "/SandboxRunInstance", json_data={"usecase": usecase})
10599

106100
async def stop_sandbox(self, sandbox_hash_key: str) -> Optional[Dict[str, Any]]:
107101
"""
@@ -118,24 +112,21 @@ async def stop_sandbox(self, sandbox_hash_key: str) -> Optional[Dict[str, Any]]:
118112
json_data = {"sandboxHashKey": sandbox_hash_key}
119113
return await self._request("POST", "/SandboxStopInstance", json_data=json_data)
120114

121-
async def extend_sandbox(self, sandbox_hash_key: Optional[str] = None, profile_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
115+
async def extend_sandbox(self, sandbox_hash_key: Optional[str] = None) -> Dict[str, Any]:
122116
"""
123117
Extends the lifetime of a user's sandbox(es). User profile details can be submitted with this request.
124118
Corresponds to POST /SandboxExtend in swagger.
125119
126120
Args:
127121
sandbox_hash_key (Optional[str]): Specific sandbox to extend. If not provided, all user's sandboxes are extended.
128-
profile_data (Optional[Dict[str, Any]]): User profile information to submit.
129-
Expected keys can include 'email', 'company', 'country', 'industry', 'telephone', 'jobrole', 'whyneo4j'.
130-
See #/components/schemas/ExtendRequest in swagger.
131122
132123
Returns:
133124
Dict[str, Any]: Sandbox lifetime extension status. See #/components/schemas/ExtendResponse.
134125
"""
135-
json_data = profile_data if profile_data is not None else {}
126+
json_data: Dict[str, Any] = {}
136127
if sandbox_hash_key:
137128
json_data["sandboxHashKey"] = sandbox_hash_key
138-
return await self._request("POST", "/SandboxExtend", json_data=json_data if json_data else {})
129+
return await self._request("POST", "/SandboxExtend", json_data=json_data)
139130

140131
async def get_sandbox_details(self, sandbox_hash_key: str, verify_connect: Optional[bool] = False) -> Dict[str, Any]:
141132
"""
@@ -155,26 +146,6 @@ async def get_sandbox_details(self, sandbox_hash_key: str, verify_connect: Optio
155146
params["verifyConnect"] = verify_connect
156147
return await self._request("GET", "/SandboxAuthdGetInstanceByHashKey", params=params)
157148

158-
async def invite_collaborator(self, sandbox_hash_key: str, email: str, message: str) -> Dict[str, Any]:
159-
"""
160-
Invites another user to share one of the authenticated user's sandboxes.
161-
Corresponds to POST /SandboxShare in swagger.
162-
163-
Args:
164-
sandbox_hash_key (str): The unique hash key identifying the sandbox to share.
165-
email (str): Email address of the user to invite.
166-
message (str): A personal message to include in the invitation.
167-
168-
Returns:
169-
Dict[str, Any]: Invitation creation status. See #/components/schemas/ShareResponse.
170-
"""
171-
json_data = {
172-
"sandboxHashKey": sandbox_hash_key,
173-
"email": email,
174-
"message": message
175-
}
176-
return await self._request("POST", "/SandboxShare", json_data=json_data)
177-
178149
async def request_backup(self, sandbox_hash_key: str) -> Dict[str, Any]:
179150
"""
180151
Initiates a backup process for a specific sandbox.
@@ -272,22 +243,22 @@ async def get_aura_upload_result(self, result_id: str) -> Dict[str, Any]:
272243
endpoint = f"/SandboxAuraUpload/result/{result_id}"
273244
return await self._request("GET", endpoint)
274245

275-
async def get_user_info(self) -> Dict[str, Any]:
276-
"""
277-
Retrieves user information for the authenticated user.
278-
Corresponds to GET /SandboxGetUserInfo in swagger.
279-
280-
Returns:
281-
Dict[str, Any]: User information. See #/components/schemas/UserInfoResponse.
282-
"""
283-
return await self._request("GET", "/SandboxGetUserInfo")
284-
285246
async def get_schema(self, hash_key: str) -> FastApiReadCypherQueryResponse:
286247
"""
287248
Retrieves the schema of the Neo4j database.
288249
Corresponds to POST /SandboxQuery in swagger.
289250
"""
290-
return await self.read_query(hash_key, "call apoc.meta.data() yield label, property, type, other, unique, index, elementType where elementType = 'node' and not label starts with '_' with label, collect(case when type <> 'RELATIONSHIP' then [property, type + case when unique then ' unique' else '' end + case when index then ' indexed' else '' end] end) as attributes, collect(case when type = 'RELATIONSHIP' then [property, head(other)] end) as relationships return label, apoc.map.fromPairs(attributes) as attributes, apoc.map.fromPairs(relationships) as relationships")
251+
schema_query = (
252+
"call apoc.meta.data() yield label, property, type, other, unique, index, elementType "
253+
"where elementType = 'node' and not label starts with '_' "
254+
"with label, collect(case when type <> 'RELATIONSHIP' "
255+
"then [property, type + case when unique then ' unique' else '' end + "
256+
"case when index then ' indexed' else '' end] end) as attributes, "
257+
"collect(case when type = 'RELATIONSHIP' then [property, head(other)] end) as relationships "
258+
"return label, apoc.map.fromPairs(attributes) as attributes, "
259+
"apoc.map.fromPairs(relationships) as relationships"
260+
)
261+
return await self.read_query(hash_key, schema_query)
291262

292263
async def read_query(self, hash_key: str, query: str, params: Optional[Dict[str, Any]] = None) -> FastApiReadCypherQueryResponse:
293264
"""

0 commit comments

Comments
 (0)