Skip to main content
Glama

Shortcut.com MCP Server

by WynnD
utils.py7.34 kB
""" Utility functions for the Shortcut MCP Server. This module contains helper functions and data models for common tasks. """ import json from datetime import datetime from typing import Dict, List, Any, Optional, Literal, Union from enum import Enum from pydantic import BaseModel, Field, validator # Define Pydantic models for type safety and validation # Change from Enum to Literal to avoid $ref generation StoryType = Literal["feature", "bug", "chore"] # Comment out the old enum class # class StoryType(str, Enum): # """Enumeration of story types in Shortcut.""" # FEATURE = "feature" # BUG = "bug" # CHORE = "chore" class StorySummary(BaseModel): """Model for summarized story information.""" id: int name: str story_type: Optional[StoryType] = None workflow_state_id: Optional[int] = None workflow_state_name: Optional[str] = None estimate: Optional[int] = None created_at: Optional[str] = None updated_at: Optional[str] = None class Config: use_enum_values = True class Comment(BaseModel): """Model for story comments.""" id: Optional[int] = None text: str author_id: Optional[str] = None created_at: Optional[str] = None updated_at: Optional[str] = None class StoryDetail(BaseModel): """Model for detailed story information.""" id: int name: str description: Optional[str] = None story_type: Optional[StoryType] = None workflow_state_id: Optional[int] = None workflow_state_name: Optional[str] = None estimate: Optional[int] = None project_id: Optional[int] = None epic_id: Optional[int] = None owner_ids: List[str] = Field(default_factory=list) label_ids: List[int] = Field(default_factory=list) created_at: Optional[str] = None updated_at: Optional[str] = None deadline: Optional[str] = None comments: List[Dict] = Field(default_factory=list) external_links: List[str] = Field(default_factory=list) class Config: use_enum_values = True class WorkflowState(BaseModel): """Model for workflow state information.""" id: int name: str type: str workflow_id: int workflow_name: str class Project(BaseModel): """Model for project information.""" id: int name: str description: Optional[str] = None archived: bool = False class ErrorResponse(BaseModel): """Model for standardized error responses.""" error: str details: Optional[Dict] = None class SuccessResponse(BaseModel): """Model for standardized success responses.""" success: bool = True message: str data: Optional[Any] = None def format_story_for_display(story: Dict[str, Any]) -> Dict[str, Any]: """ Format a story object for display in the MCP. Args: story: Story object from Shortcut API Returns: Formatted story with selected fields """ try: # Convert raw story to validated model object with proper type checking story_detail = StoryDetail( id=story.get("id") or 0, # Provide default for required field name=story.get("name") or "", # Provide default for required field description=story.get("description"), story_type=story.get("story_type"), workflow_state_id=story.get("workflow_state_id"), workflow_state_name=story.get("workflow_state_name"), estimate=story.get("estimate"), project_id=story.get("project_id"), epic_id=story.get("epic_id"), owner_ids=story.get("owner_ids", []), label_ids=story.get("label_ids", []), created_at=story.get("created_at"), updated_at=story.get("updated_at"), deadline=story.get("deadline"), comments=story.get("comments", []), external_links=story.get("external_links", []) ) return story_detail.model_dump() except Exception as e: # Fallback to basic formatting if validation fails return { "id": story.get("id"), "name": story.get("name"), "type": story.get("story_type"), "state": story.get("workflow_state_name", ""), "owner": get_owner_name(story), "url": story.get("app_url", ""), "estimate": story.get("estimate"), "created_at": story.get("created_at"), "updated_at": story.get("updated_at"), } def get_owner_name(story: Dict[str, Any]) -> str: """ Extract the owner name from a story. Args: story: Story object from Shortcut API Returns: Owner name or empty string if no owner """ owner = None owners = story.get("owner_ids", []) if owners and len(owners) > 0: # In a real implementation, you'd want to look up the actual name # from the member ID using the members endpoint return f"Owner ID: {owners[0]}" return "" def format_search_results(stories: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ Format a list of stories for display in search results. Args: stories: List of story objects from Shortcut API Returns: List of formatted stories """ return [format_story_for_display(story) for story in stories] def build_error_response(message: str, status_code: int = 400) -> Dict[str, Any]: """ Build a standardized error response. Args: message: Error message status_code: HTTP status code Returns: Error response dictionary """ return ErrorResponse( error=message, details={"status_code": status_code} ).model_dump() def build_success_response(message: str, data: Any = None) -> Dict[str, Any]: """ Build a standardized success response. Args: message: Success message data: Optional response data Returns: Success response dictionary """ return SuccessResponse( success=True, message=message, data=data ).model_dump() def create_bug_report_template(title: str, steps: str, expected: str, actual: str) -> str: """ Create a template for bug reports. Args: title: Bug title steps: Steps to reproduce expected: Expected behavior actual: Actual behavior Returns: Bug report markdown template """ return f""" # {title} ## Bug Description A bug has been identified that needs to be addressed. ## Steps to Reproduce {steps} ## Expected Behavior {expected} ## Actual Behavior {actual} ## Additional Context Bug reported on {datetime.now().strftime('%Y-%m-%d')} """ def create_feature_request_template(title: str, description: str, user_value: str, acceptance_criteria: str) -> str: """ Create a template for feature requests. Args: title: Feature title description: Description of the feature user_value: Value to users acceptance_criteria: Acceptance criteria Returns: Feature request markdown template """ return f""" # {title} ## Feature Description {description} ## User Value {user_value} ## Acceptance Criteria {acceptance_criteria} ## Additional Notes Feature requested on {datetime.now().strftime('%Y-%m-%d')} """

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/WynnD/mcp-server-shortcut'

If you have feedback or need assistance with the MCP directory API, please join our Discord server