"""System tools: permissions, feedback, verifications."""
from typing import Optional
from mcp.server.fastmcp import FastMCP
from .. import client
from ..types import ClinkApiError, FeedbackCategory
def register(mcp: FastMCP) -> None:
"""Register system tools with the MCP server."""
@mcp.tool()
async def submit_feedback(
category: str,
content: str,
tool: Optional[str] = None,
) -> str:
"""Submit feedback to help improve Clink. This allows you to report bugs, request features, or suggest improvements directly to Voxos.
**Important:** Requires an API key with feedback permission enabled. If you get a permission error, ask the user to create a new API key with feedback permission enabled in the Clink dashboard.
Categories:
- bug: Report something that isn't working correctly
- feature: Request a new capability or feature
- improvement: Suggest enhancements to existing features
- other: General feedback that doesn't fit other categories
Args:
category: The type of feedback: bug, feature, improvement, or other
content: The feedback content. Be specific and include relevant context (max 8192 characters)
tool: Optional: The tool or feature this feedback relates to
"""
try:
if not category:
return "Please provide a category: bug, feature, improvement, or other."
if category not in ("bug", "feature", "improvement", "other"):
return f"Invalid category '{category}'. Use: bug, feature, improvement, or other."
if not content:
return "Please provide feedback content."
if len(content) > 8192:
return f"Feedback too long ({len(content)} chars). Maximum is 8192 characters."
result = await client.submit_feedback(
category=category, # type: ignore
content=content,
tool=tool,
)
output = "Feedback submitted successfully!\n\n"
output += f"Feedback ID: {result.feedback_id}\n"
output += f"Category: {result.category}\n"
output += f"Submitted: {result.submitted_at}\n"
output += "\nThank you for helping improve Clink!"
return output
except ClinkApiError as e:
if e.status_code == 403:
return "Permission denied. Your API key doesn't have feedback permission. Create a new API key with feedback permission in the Clink dashboard."
return f"Error: {e}"
@mcp.tool()
async def get_my_permissions() -> str:
"""Get the permissions granted to your API key. Use this to check what operations you're allowed to perform.
Returns:
- A list of permissions showing which are granted ([x]) or denied ([ ])
- Your API key scope (user or group)
- Whether the key belongs to a user or agent profile
"""
try:
result = await client.get_my_permissions()
output = f"API Key Permissions\n"
output += f"{'=' * 40}\n\n"
output += f"Scope: {result.scope_type} ({result.scope_id})\n"
output += f"Owner: {result.owner_type}\n\n"
output += "Permissions:\n"
for perm_name, perm_info in result.permissions_info.items():
granted = "[x]" if perm_info.granted else "[ ]"
output += f" {granted} {perm_name}: {perm_info.description}\n"
return output
except ClinkApiError as e:
return f"Error: {e}"
@mcp.tool()
async def list_pending_verifications(
group: str,
limit: Optional[int] = None,
) -> str:
"""List pending Human-in-the-Loop (HIL) verifications for a group. Shows checkpoints and votes awaiting human approval.
Args:
group: The group slug (e.g., "backend-team") or group ID
limit: Maximum verifications to return (default: 50)
"""
try:
if not group:
groups = await client.list_groups()
if groups:
group_list = "\n".join(f'* {g.slug} - "{g.name}"' for g in groups)
return f"Please provide a group slug. Your groups:\n\n{group_list}"
return "Please provide a group slug."
group_id = await client.resolve_group(group)
verifications = await client.list_pending_verifications(
group_id,
limit=limit,
)
if not verifications:
return f"No pending verifications in group '{group}'."
output = f"Pending verifications in {group} ({len(verifications)}):\n\n"
for v in verifications:
output += f"* {v.target_type.upper()}: "
if v.target_type == "checkpoint":
output += f"{v.milestone_title} / {v.checkpoint_title}\n"
output += f" Milestone ID: {v.milestone_id}\n"
output += f" Checkpoint: #{v.checkpoint_order}\n"
else: # vote
output += f"{v.proposal_title}\n"
output += f" Proposal ID: {v.proposal_id}\n"
output += f" Verification ID: {v.verification_id}\n"
output += f" Requested by: {v.requester_name or v.requested_by}\n"
output += f" Expires: {v.expires_at}\n"
output += f" Status: {v.status}\n\n"
return output.strip()
except ClinkApiError as e:
return f"Error: {e}"