zendesk_ticket_to_gitlab_context
Formats a Zendesk ticket as a GitLab issue draft. Includes ticket metadata, full comment conversation, and a placeholder for steps to reproduce.
Instructions
Format a Zendesk ticket and its comments as an issue draft. Returns Markdown with ticket metadata, full conversation, and an empty Steps to Reproduce section. Use this output as the description when creating an issue in your tracker.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| ticket_id | Yes |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- The _get_gitlab_context helper function that fetches a Zendesk ticket and its comments, formats them as a Markdown issue draft including metadata (subject, URL, requester, status, priority, type, tags), description, full conversation, and an empty 'Steps to Reproduce' section.
def _get_gitlab_context(ticket_id: int) -> str: try: client = get_client() ticket = client.tickets(id=ticket_id) comments = list(client.tickets.comments(ticket_id)) subdomain = load_config().get("subdomain", "") ticket_url = f"https://{subdomain}.zendesk.com/agent/tickets/{ticket_id}" comment_lines = [] for comment in comments: try: author = client.users(id=comment.author_id) author_name = author.name except Exception: author_name = "Unknown" visibility = "Internal Note" if not comment.public else "Public Reply" timestamp = str(comment.created_at)[:10] comment_lines.append( f"**[{visibility}] {author_name} ({timestamp}):**\n{comment.body}" ) conversation = "\n\n---\n\n".join(comment_lines) if comment_lines else "_No comments._" return f"""## [Ticket #{ticket_id}] {ticket.subject} **Zendesk:** {ticket_url} **Requester:** {ticket.requester.name} <{ticket.requester.email}> **Status:** {ticket.status} | **Priority:** {ticket.priority} | **Type:** {ticket.type} **Tags:** {", ".join(ticket.tags) if ticket.tags else "none"} --- ## Description {ticket.description} --- ## Conversation {conversation} --- ## Steps to Reproduce <!-- Fill in from ticket context or leave for engineer --> 1. 2. 3. --- _Source: Zendesk ticket #{ticket_id} — {ticket_url}_ """ except ConfigError as e: return str(e) except Exception as e: if "RecordNotFound" in str(e) or "404" in str(e): return f"Ticket #{ticket_id} not found or not accessible with current credentials." return f"Zendesk API error: {e}" - The actual tool handler function 'zendesk_ticket_to_gitlab_context' decorated with @mcp.tool(), which calls _get_gitlab_context.
def register_gitlab_context_tools(mcp) -> None: @mcp.tool() def zendesk_ticket_to_gitlab_context(ticket_id: int) -> str: """Format a Zendesk ticket and its comments as an issue draft. Returns Markdown with ticket metadata, full conversation, and an empty Steps to Reproduce section. Use this output as the description when creating an issue in your tracker.""" return _get_gitlab_context(ticket_id) - src/zendesk_mcp/tools/gitlab_context.py:70-74 (registration)The register_gitlab_context_tools function that registers the 'zendesk_ticket_to_gitlab_context' tool via the @mcp.tool() decorator.
def register_gitlab_context_tools(mcp) -> None: @mcp.tool() def zendesk_ticket_to_gitlab_context(ticket_id: int) -> str: """Format a Zendesk ticket and its comments as an issue draft. Returns Markdown with ticket metadata, full conversation, and an empty Steps to Reproduce section. Use this output as the description when creating an issue in your tracker.""" return _get_gitlab_context(ticket_id) - src/zendesk_mcp/server.py:36-36 (registration)Where register_gitlab_context_tools(mcp) is called in the main server function to register the tool.
register_gitlab_context_tools(mcp) - src/zendesk_mcp/config.py:9-28 (helper)load_config helper used by _get_gitlab_context to retrieve the subdomain for ticket URL construction.
def load_config(path: Path | None = None) -> dict: resolved = path or config_path() try: return json.loads(resolved.read_text()) except (FileNotFoundError, json.JSONDecodeError): return {} def save_config(data: dict, path: Path | None = None) -> None: resolved = path or config_path() resolved.parent.mkdir(parents=True, exist_ok=True) resolved.write_text(json.dumps(data, indent=2) + "\n") resolved.chmod(0o600) def attachment_cache_dir(ticket_id: int, config_file: Path | None = None) -> Path: cfg = load_config(config_file) base = cfg.get("attachment_cache_dir", "~/.cache/zendesk-mcp/attachments") return Path(base).expanduser() / str(ticket_id)