374 lines
12 KiB
Python
374 lines
12 KiB
Python
"""Session tools for MCP."""
|
|
|
|
from typing import Optional
|
|
|
|
from core.mcp.auth import MCPContext, Role, check_entity_access, execute_graphql
|
|
from core.mcp.base import mcp, json_response, error_response
|
|
|
|
|
|
@mcp.tool()
|
|
async def get_active_session(entity_type: str, entity_id: str) -> str:
|
|
"""
|
|
Get the active session for a service or project.
|
|
|
|
Args:
|
|
entity_type: Either 'service' or 'project'
|
|
entity_id: UUID of the service or project
|
|
|
|
Returns:
|
|
JSON object with session details or null if no active session
|
|
"""
|
|
profile = MCPContext.get_profile()
|
|
if not profile:
|
|
return error_response("No active profile. Call set_active_profile first.")
|
|
|
|
try:
|
|
await check_entity_access(entity_type, entity_id)
|
|
except PermissionError as e:
|
|
return error_response(str(e))
|
|
|
|
if entity_type == "service":
|
|
query = """
|
|
query GetActiveSession($serviceId: UUID!) {
|
|
activeServiceSession(serviceId: $serviceId) {
|
|
id
|
|
start
|
|
end
|
|
createdBy { id firstName lastName }
|
|
}
|
|
}
|
|
"""
|
|
variables = {"serviceId": entity_id}
|
|
result_key = "activeServiceSession"
|
|
elif entity_type == "project":
|
|
query = """
|
|
query GetActiveSession($projectId: UUID!) {
|
|
activeProjectSession(projectId: $projectId) {
|
|
id
|
|
start
|
|
end
|
|
createdBy { id firstName lastName }
|
|
}
|
|
}
|
|
"""
|
|
variables = {"projectId": entity_id}
|
|
result_key = "activeProjectSession"
|
|
else:
|
|
return error_response("entity_type must be 'service' or 'project'")
|
|
|
|
result = await execute_graphql(query, variables)
|
|
|
|
if "errors" in result:
|
|
return json_response(result)
|
|
|
|
session = result["data"].get(result_key)
|
|
return json_response({"active": session is not None, "session": session})
|
|
|
|
|
|
@mcp.tool()
|
|
async def open_session(entity_type: str, entity_id: str) -> str:
|
|
"""
|
|
Start a work session for a service or project.
|
|
- ADMIN: Any service/project
|
|
- TEAM_MEMBER: Only if assigned
|
|
|
|
Args:
|
|
entity_type: Either 'service' or 'project'
|
|
entity_id: UUID of the service or project
|
|
|
|
Returns:
|
|
JSON object with opened session details
|
|
"""
|
|
profile = MCPContext.get_profile()
|
|
if not profile:
|
|
return error_response("No active profile. Call set_active_profile first.")
|
|
|
|
if profile.role == Role.TEAM_LEADER.value:
|
|
return error_response("Access denied. TEAM_LEADER role is view-only.")
|
|
|
|
try:
|
|
await check_entity_access(entity_type, entity_id)
|
|
except PermissionError as e:
|
|
return error_response(str(e))
|
|
|
|
if entity_type == "service":
|
|
mutation = """
|
|
mutation OpenSession($input: OpenServiceSessionInput!) {
|
|
openServiceSession(input: $input) {
|
|
id
|
|
start
|
|
service { id status }
|
|
}
|
|
}
|
|
"""
|
|
variables = {"input": {"serviceId": entity_id}}
|
|
result_key = "openServiceSession"
|
|
elif entity_type == "project":
|
|
mutation = """
|
|
mutation OpenSession($input: ProjectSessionStartInput!) {
|
|
openProjectSession(input: $input) {
|
|
id
|
|
start
|
|
project { id status }
|
|
}
|
|
}
|
|
"""
|
|
variables = {"input": {"projectId": entity_id}}
|
|
result_key = "openProjectSession"
|
|
else:
|
|
return error_response("entity_type must be 'service' or 'project'")
|
|
|
|
result = await execute_graphql(mutation, variables)
|
|
|
|
if "errors" in result:
|
|
return json_response(result)
|
|
|
|
return json_response({"success": True, "session": result["data"][result_key]})
|
|
|
|
|
|
@mcp.tool()
|
|
async def close_session(
|
|
entity_type: str,
|
|
entity_id: str,
|
|
completed_task_ids: Optional[str] = None
|
|
) -> str:
|
|
"""
|
|
Complete a work session and mark tasks as done.
|
|
- ADMIN: Any session
|
|
- TEAM_MEMBER: Only their own sessions
|
|
|
|
Args:
|
|
entity_type: Either 'service' or 'project'
|
|
entity_id: UUID of the service or project
|
|
completed_task_ids: Comma-separated UUIDs of completed tasks
|
|
|
|
Returns:
|
|
JSON object with closed session details
|
|
"""
|
|
profile = MCPContext.get_profile()
|
|
if not profile:
|
|
return error_response("No active profile. Call set_active_profile first.")
|
|
|
|
if profile.role == Role.TEAM_LEADER.value:
|
|
return error_response("Access denied. TEAM_LEADER role is view-only.")
|
|
|
|
try:
|
|
await check_entity_access(entity_type, entity_id)
|
|
except PermissionError as e:
|
|
return error_response(str(e))
|
|
|
|
task_ids = []
|
|
if completed_task_ids:
|
|
task_ids = [tid.strip() for tid in completed_task_ids.split(",")]
|
|
|
|
if entity_type == "service":
|
|
mutation = """
|
|
mutation CloseSession($input: CloseServiceSessionInput!) {
|
|
closeServiceSession(input: $input) {
|
|
id
|
|
start
|
|
end
|
|
service { id status }
|
|
}
|
|
}
|
|
"""
|
|
variables = {"input": {"serviceId": entity_id, "taskIds": task_ids}}
|
|
result_key = "closeServiceSession"
|
|
elif entity_type == "project":
|
|
mutation = """
|
|
mutation CloseSession($input: ProjectSessionCloseInput!) {
|
|
closeProjectSession(input: $input) {
|
|
id
|
|
start
|
|
end
|
|
project { id status }
|
|
}
|
|
}
|
|
"""
|
|
variables = {"input": {"projectId": entity_id, "taskIds": task_ids}}
|
|
result_key = "closeProjectSession"
|
|
else:
|
|
return error_response("entity_type must be 'service' or 'project'")
|
|
|
|
result = await execute_graphql(mutation, variables)
|
|
|
|
if "errors" in result:
|
|
return json_response(result)
|
|
|
|
return json_response({"success": True, "session": result["data"][result_key]})
|
|
|
|
|
|
@mcp.tool()
|
|
async def revert_session(entity_type: str, entity_id: str) -> str:
|
|
"""
|
|
Cancel an active session and revert status to SCHEDULED.
|
|
- ADMIN: Any session
|
|
- TEAM_MEMBER: Only their own sessions
|
|
|
|
Args:
|
|
entity_type: Either 'service' or 'project'
|
|
entity_id: UUID of the service or project
|
|
|
|
Returns:
|
|
JSON object confirming reversion
|
|
"""
|
|
profile = MCPContext.get_profile()
|
|
if not profile:
|
|
return error_response("No active profile. Call set_active_profile first.")
|
|
|
|
if profile.role == Role.TEAM_LEADER.value:
|
|
return error_response("Access denied. TEAM_LEADER role is view-only.")
|
|
|
|
try:
|
|
await check_entity_access(entity_type, entity_id)
|
|
except PermissionError as e:
|
|
return error_response(str(e))
|
|
|
|
if entity_type == "service":
|
|
mutation = """
|
|
mutation RevertSession($input: RevertServiceSessionInput!) {
|
|
revertServiceSession(input: $input)
|
|
}
|
|
"""
|
|
variables = {"input": {"serviceId": entity_id}}
|
|
result_key = "revertServiceSession"
|
|
elif entity_type == "project":
|
|
mutation = """
|
|
mutation RevertSession($input: ProjectSessionRevertInput!) {
|
|
revertProjectSession(input: $input)
|
|
}
|
|
"""
|
|
variables = {"input": {"projectId": entity_id}}
|
|
result_key = "revertProjectSession"
|
|
else:
|
|
return error_response("entity_type must be 'service' or 'project'")
|
|
|
|
result = await execute_graphql(mutation, variables)
|
|
|
|
if "errors" in result:
|
|
return json_response(result)
|
|
|
|
return json_response({"success": True, "reverted": result["data"][result_key]})
|
|
|
|
|
|
@mcp.tool()
|
|
async def add_task_completion(
|
|
entity_type: str,
|
|
entity_id: str,
|
|
task_id: str,
|
|
notes: Optional[str] = None
|
|
) -> str:
|
|
"""
|
|
Mark a task as completed during an active session.
|
|
|
|
Args:
|
|
entity_type: Either 'service' or 'project'
|
|
entity_id: UUID of the service or project
|
|
task_id: UUID of the task to mark complete
|
|
notes: Optional notes about task completion
|
|
|
|
Returns:
|
|
JSON object confirming task completion
|
|
"""
|
|
profile = MCPContext.get_profile()
|
|
if not profile:
|
|
return error_response("No active profile. Call set_active_profile first.")
|
|
|
|
if profile.role == Role.TEAM_LEADER.value:
|
|
return error_response("Access denied. TEAM_LEADER role is view-only.")
|
|
|
|
try:
|
|
await check_entity_access(entity_type, entity_id)
|
|
except PermissionError as e:
|
|
return error_response(str(e))
|
|
|
|
if entity_type == "service":
|
|
mutation = """
|
|
mutation AddTaskCompletion($serviceId: ID!, $taskId: ID!, $notes: String) {
|
|
addTaskCompletion(serviceId: $serviceId, taskId: $taskId, notes: $notes) {
|
|
id
|
|
}
|
|
}
|
|
"""
|
|
variables = {"serviceId": entity_id, "taskId": task_id, "notes": notes}
|
|
result_key = "addTaskCompletion"
|
|
elif entity_type == "project":
|
|
mutation = """
|
|
mutation AddTaskCompletion($projectId: ID!, $taskId: ID!, $notes: String) {
|
|
addProjectTaskCompletion(projectId: $projectId, taskId: $taskId, notes: $notes) {
|
|
id
|
|
}
|
|
}
|
|
"""
|
|
variables = {"projectId": entity_id, "taskId": task_id, "notes": notes}
|
|
result_key = "addProjectTaskCompletion"
|
|
else:
|
|
return error_response("entity_type must be 'service' or 'project'")
|
|
|
|
result = await execute_graphql(mutation, variables)
|
|
|
|
if "errors" in result:
|
|
return json_response(result)
|
|
|
|
return json_response({"success": True, "completion": result["data"][result_key]})
|
|
|
|
|
|
@mcp.tool()
|
|
async def remove_task_completion(
|
|
entity_type: str,
|
|
entity_id: str,
|
|
task_id: str
|
|
) -> str:
|
|
"""
|
|
Unmark a task completion from an active session.
|
|
|
|
Args:
|
|
entity_type: Either 'service' or 'project'
|
|
entity_id: UUID of the service or project
|
|
task_id: UUID of the task to unmark
|
|
|
|
Returns:
|
|
JSON object confirming removal
|
|
"""
|
|
profile = MCPContext.get_profile()
|
|
if not profile:
|
|
return error_response("No active profile. Call set_active_profile first.")
|
|
|
|
if profile.role == Role.TEAM_LEADER.value:
|
|
return error_response("Access denied. TEAM_LEADER role is view-only.")
|
|
|
|
try:
|
|
await check_entity_access(entity_type, entity_id)
|
|
except PermissionError as e:
|
|
return error_response(str(e))
|
|
|
|
if entity_type == "service":
|
|
mutation = """
|
|
mutation RemoveTaskCompletion($serviceId: ID!, $taskId: ID!) {
|
|
removeTaskCompletion(serviceId: $serviceId, taskId: $taskId) {
|
|
id
|
|
}
|
|
}
|
|
"""
|
|
variables = {"serviceId": entity_id, "taskId": task_id}
|
|
result_key = "removeTaskCompletion"
|
|
elif entity_type == "project":
|
|
mutation = """
|
|
mutation RemoveTaskCompletion($projectId: ID!, $taskId: ID!) {
|
|
removeProjectTaskCompletion(projectId: $projectId, taskId: $taskId) {
|
|
id
|
|
}
|
|
}
|
|
"""
|
|
variables = {"projectId": entity_id, "taskId": task_id}
|
|
result_key = "removeProjectTaskCompletion"
|
|
else:
|
|
return error_response("entity_type must be 'service' or 'project'")
|
|
|
|
result = await execute_graphql(mutation, variables)
|
|
|
|
if "errors" in result:
|
|
return json_response(result)
|
|
|
|
return json_response({"success": True, "removed": result["data"][result_key]})
|