quickcall-integrations 0.3.3__py3-none-any.whl → 0.3.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mcp_server/__init__.py CHANGED
@@ -3,4 +3,10 @@ MCP Server for QuickCall
3
3
  GitHub integration tools for AI assistant
4
4
  """
5
5
 
6
- __version__ = "0.1.8"
6
+ from importlib.metadata import version, PackageNotFoundError
7
+
8
+ try:
9
+ __version__ = version("quickcall-integrations")
10
+ except PackageNotFoundError:
11
+ # Package not installed (development mode)
12
+ __version__ = "0.0.0-dev"
@@ -578,6 +578,114 @@ class GitHubClient:
578
578
 
579
579
  return branches
580
580
 
581
+ # ========================================================================
582
+ # Issue Operations
583
+ # ========================================================================
584
+
585
+ def _issue_to_dict(self, issue) -> Dict[str, Any]:
586
+ """Convert PyGithub Issue to dict."""
587
+ return {
588
+ "number": issue.number,
589
+ "title": issue.title,
590
+ "body": issue.body,
591
+ "state": issue.state,
592
+ "html_url": issue.html_url,
593
+ "labels": [label.name for label in issue.labels],
594
+ "assignees": [a.login for a in issue.assignees],
595
+ "created_at": issue.created_at.isoformat(),
596
+ }
597
+
598
+ def create_issue(
599
+ self,
600
+ title: str,
601
+ body: Optional[str] = None,
602
+ labels: Optional[List[str]] = None,
603
+ assignees: Optional[List[str]] = None,
604
+ owner: Optional[str] = None,
605
+ repo: Optional[str] = None,
606
+ ) -> Dict[str, Any]:
607
+ """Create a GitHub issue."""
608
+ gh_repo = self._get_repo(owner, repo)
609
+ issue = gh_repo.create_issue(
610
+ title=title,
611
+ body=body or "",
612
+ labels=labels or [],
613
+ assignees=assignees or [],
614
+ )
615
+ return self._issue_to_dict(issue)
616
+
617
+ def update_issue(
618
+ self,
619
+ issue_number: int,
620
+ title: Optional[str] = None,
621
+ body: Optional[str] = None,
622
+ labels: Optional[List[str]] = None,
623
+ assignees: Optional[List[str]] = None,
624
+ owner: Optional[str] = None,
625
+ repo: Optional[str] = None,
626
+ ) -> Dict[str, Any]:
627
+ """Update a GitHub issue."""
628
+ gh_repo = self._get_repo(owner, repo)
629
+ issue = gh_repo.get_issue(issue_number)
630
+
631
+ kwargs = {}
632
+ if title is not None:
633
+ kwargs["title"] = title
634
+ if body is not None:
635
+ kwargs["body"] = body
636
+ if labels is not None:
637
+ kwargs["labels"] = labels
638
+ if assignees is not None:
639
+ kwargs["assignees"] = assignees
640
+
641
+ if kwargs:
642
+ issue.edit(**kwargs)
643
+
644
+ return self._issue_to_dict(issue)
645
+
646
+ def close_issue(
647
+ self,
648
+ issue_number: int,
649
+ owner: Optional[str] = None,
650
+ repo: Optional[str] = None,
651
+ ) -> Dict[str, Any]:
652
+ """Close a GitHub issue."""
653
+ gh_repo = self._get_repo(owner, repo)
654
+ issue = gh_repo.get_issue(issue_number)
655
+ issue.edit(state="closed")
656
+ return self._issue_to_dict(issue)
657
+
658
+ def reopen_issue(
659
+ self,
660
+ issue_number: int,
661
+ owner: Optional[str] = None,
662
+ repo: Optional[str] = None,
663
+ ) -> Dict[str, Any]:
664
+ """Reopen a GitHub issue."""
665
+ gh_repo = self._get_repo(owner, repo)
666
+ issue = gh_repo.get_issue(issue_number)
667
+ issue.edit(state="open")
668
+ return self._issue_to_dict(issue)
669
+
670
+ def comment_on_issue(
671
+ self,
672
+ issue_number: int,
673
+ body: str,
674
+ owner: Optional[str] = None,
675
+ repo: Optional[str] = None,
676
+ ) -> Dict[str, Any]:
677
+ """Add a comment to a GitHub issue."""
678
+ gh_repo = self._get_repo(owner, repo)
679
+ issue = gh_repo.get_issue(issue_number)
680
+ comment = issue.create_comment(body)
681
+ return {
682
+ "id": comment.id,
683
+ "body": comment.body,
684
+ "html_url": comment.html_url,
685
+ "created_at": comment.created_at.isoformat(),
686
+ "issue_number": issue_number,
687
+ }
688
+
581
689
  # ========================================================================
582
690
  # Search Operations (for Appraisals)
583
691
  # ========================================================================
@@ -353,9 +353,9 @@ class CredentialStore:
353
353
  api_creds = self.get_api_credentials(force_refresh=True)
354
354
  result["github"] = {
355
355
  "connected": api_creds.github_connected if api_creds else False,
356
- "mode": "github_app"
357
- if (api_creds and api_creds.github_connected)
358
- else None,
356
+ "mode": (
357
+ "github_app" if (api_creds and api_creds.github_connected) else None
358
+ ),
359
359
  "username": api_creds.github_username if api_creds else None,
360
360
  }
361
361
  result["slack"] = {
@@ -0,0 +1,147 @@
1
+ """
2
+ GitHub MCP Resources - Exposes GitHub data for Claude's context.
3
+
4
+ Resources are automatically available in Claude's context when connected.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any, Dict, Optional
9
+
10
+ import yaml
11
+ from fastmcp import FastMCP
12
+
13
+ from mcp_server.auth import get_credential_store, get_github_pat
14
+ from mcp_server.auth.credentials import _find_project_root, _parse_env_file
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def _load_issue_templates_config() -> Optional[Dict[str, Any]]:
20
+ """
21
+ Load issue templates from ISSUE_TEMPLATE_PATH in .quickcall.env.
22
+ Returns None if not configured or file doesn't exist.
23
+ """
24
+ import os
25
+ from pathlib import Path
26
+
27
+ template_path = os.getenv("ISSUE_TEMPLATE_PATH")
28
+
29
+ # Check .quickcall.env in project root
30
+ if not template_path:
31
+ project_root = _find_project_root()
32
+ if project_root:
33
+ config_path = project_root / ".quickcall.env"
34
+ if config_path.exists():
35
+ env_vars = _parse_env_file(config_path)
36
+ if "ISSUE_TEMPLATE_PATH" in env_vars:
37
+ template_path = env_vars["ISSUE_TEMPLATE_PATH"]
38
+ if not Path(template_path).is_absolute():
39
+ template_path = str(project_root / template_path)
40
+
41
+ if not template_path:
42
+ return None
43
+
44
+ try:
45
+ with open(template_path) as f:
46
+ return yaml.safe_load(f) or {}
47
+ except Exception as e:
48
+ logger.warning(f"Failed to load issue templates: {e}")
49
+ return None
50
+
51
+
52
+ def create_github_resources(mcp: FastMCP) -> None:
53
+ """Add GitHub resources to the MCP server."""
54
+
55
+ @mcp.resource("github://repositories")
56
+ def get_github_repositories() -> str:
57
+ """
58
+ List of GitHub repositories the user has access to.
59
+
60
+ Use these when working with GitHub operations.
61
+ """
62
+ store = get_credential_store()
63
+
64
+ # Check if authenticated via PAT or QuickCall
65
+ pat_token, pat_source = get_github_pat()
66
+ has_pat = pat_token is not None
67
+
68
+ if not has_pat and not store.is_authenticated():
69
+ return "GitHub not connected. Options:\n- Run connect_github_via_pat with a Personal Access Token\n- Run connect_quickcall to use QuickCall"
70
+
71
+ # Check QuickCall GitHub App connection
72
+ has_app = False
73
+ if store.is_authenticated():
74
+ creds = store.get_api_credentials()
75
+ if creds and creds.github_connected and creds.github_token:
76
+ has_app = True
77
+
78
+ if not has_pat and not has_app:
79
+ return "GitHub not connected. Connect at quickcall.dev/assistant or use connect_github_via_pat."
80
+
81
+ try:
82
+ # Import here to avoid circular imports
83
+ from mcp_server.tools.github_tools import _get_client
84
+
85
+ client = _get_client()
86
+ repos = client.list_repos(limit=50)
87
+
88
+ # Determine auth mode for display
89
+ auth_mode = "PAT" if has_pat else "GitHub App"
90
+
91
+ lines = [f"GitHub Repositories (via {auth_mode}):", ""]
92
+ for repo in repos:
93
+ visibility = "private" if repo.private else "public"
94
+ lines.append(f"- {repo.full_name} ({visibility})")
95
+
96
+ if len(repos) >= 50:
97
+ lines.append("")
98
+ lines.append("(Showing first 50 repos)")
99
+
100
+ return "\n".join(lines)
101
+ except Exception as e:
102
+ logger.error(f"Failed to fetch GitHub repositories: {e}")
103
+ return f"Error fetching repositories: {str(e)}"
104
+
105
+ @mcp.resource("github://issue-templates")
106
+ def get_issue_templates() -> str:
107
+ """
108
+ Available issue templates from project configuration.
109
+
110
+ Use template names when creating issues with manage_issues.
111
+ """
112
+ config = _load_issue_templates_config()
113
+
114
+ if not config:
115
+ return "No issue templates configured.\n\nTo configure:\n1. Create a YAML file with your templates\n2. Add ISSUE_TEMPLATE_PATH=/path/to/templates.yaml to .quickcall.env"
116
+
117
+ templates = config.get("templates", {})
118
+ defaults = config.get("defaults", {})
119
+
120
+ if not templates:
121
+ lines = ["Issue Templates:", ""]
122
+ if defaults:
123
+ labels = defaults.get("labels", [])
124
+ lines.append("Default template:")
125
+ if labels:
126
+ lines.append(f" Labels: {', '.join(labels)}")
127
+ if defaults.get("body"):
128
+ lines.append(f" Body template: {defaults['body'][:100]}...")
129
+ return "\n".join(lines)
130
+
131
+ lines = ["Available Issue Templates:", ""]
132
+
133
+ for name, template in templates.items():
134
+ labels = template.get("labels", [])
135
+ body_preview = template.get("body", "")[:80]
136
+ lines.append(f"- {name}")
137
+ if labels:
138
+ lines.append(f" Labels: {', '.join(labels)}")
139
+ if body_preview:
140
+ lines.append(f" Body: {body_preview}...")
141
+
142
+ lines.append("")
143
+ lines.append(
144
+ "Usage: manage_issues(action='create', title='...', template='<name>')"
145
+ )
146
+
147
+ return "\n".join(lines)
mcp_server/server.py CHANGED
@@ -24,6 +24,7 @@ from mcp_server.tools.github_tools import create_github_tools
24
24
  from mcp_server.tools.slack_tools import create_slack_tools
25
25
  from mcp_server.tools.auth_tools import create_auth_tools
26
26
  from mcp_server.resources.slack_resources import create_slack_resources
27
+ from mcp_server.resources.github_resources import create_github_resources
27
28
 
28
29
  # Configure logging
29
30
  logging.basicConfig(
@@ -57,6 +58,7 @@ def create_server() -> FastMCP:
57
58
 
58
59
  # Register resources (available in Claude's context)
59
60
  create_slack_resources(mcp)
61
+ create_github_resources(mcp)
60
62
 
61
63
  # Log current status
62
64
  if is_authenticated:
@@ -2,95 +2,173 @@
2
2
  GitHub Tools - Pull requests and commits via GitHub API.
3
3
 
4
4
  Authentication (in priority order):
5
- 1. QuickCall GitHub App (preferred) - connect via connect_quickcall
6
- 2. Personal Access Token (PAT) - set GITHUB_TOKEN env var or use .quickcall.env file
7
-
8
- PAT fallback is useful for:
9
- - Users at organizations that can't install the GitHub App
10
- - Personal repositories without app installation
11
- - Testing and development
5
+ 1. Personal Access Token (PAT) - more permissions, user's default choice
6
+ - Set via connect_github_via_pat command
7
+ - Or GITHUB_TOKEN env var / .quickcall.env file
8
+ 2. QuickCall GitHub App - fallback if no PAT configured
9
+
10
+ PAT is preferred because:
11
+ - Users have direct control over permissions
12
+ - Works with any repository the user has access to
13
+ - No GitHub App installation required
12
14
  """
13
15
 
14
- from typing import List, Optional, Tuple
15
16
  import logging
17
+ import os
18
+ from pathlib import Path
19
+ from typing import Any, Dict, List, Optional, Tuple
16
20
 
21
+ import yaml
17
22
  from fastmcp import FastMCP
18
23
  from fastmcp.exceptions import ToolError
19
24
  from pydantic import Field
20
25
 
26
+ from mcp_server.api_clients.github_client import GitHubClient
21
27
  from mcp_server.auth import (
22
28
  get_credential_store,
23
29
  get_github_pat,
24
30
  get_github_pat_username,
25
31
  )
26
- from mcp_server.api_clients.github_client import GitHubClient
32
+ from mcp_server.auth.credentials import _find_project_root, _parse_env_file
27
33
 
28
34
  logger = logging.getLogger(__name__)
29
35
 
30
36
 
37
+ # ============================================================================
38
+ # Issue Template Support
39
+ # ============================================================================
40
+
41
+ DEFAULT_ISSUE_TEMPLATE: Dict[str, Any] = {
42
+ "labels": [],
43
+ "body": "## Description\n\n## Details\n",
44
+ }
45
+
46
+
47
+ def _load_issue_template(template_type: Optional[str] = None) -> Dict[str, Any]:
48
+ """
49
+ Load issue template from ISSUE_TEMPLATE_PATH in .quickcall.env.
50
+ Returns defaults if not configured.
51
+ """
52
+ template_path = os.getenv("ISSUE_TEMPLATE_PATH")
53
+
54
+ # Check .quickcall.env in project root
55
+ if not template_path:
56
+ project_root = _find_project_root()
57
+ if project_root:
58
+ config_path = project_root / ".quickcall.env"
59
+ if config_path.exists():
60
+ env_vars = _parse_env_file(config_path)
61
+ if "ISSUE_TEMPLATE_PATH" in env_vars:
62
+ template_path = env_vars["ISSUE_TEMPLATE_PATH"]
63
+ if not Path(template_path).is_absolute():
64
+ template_path = str(project_root / template_path)
65
+
66
+ if not template_path:
67
+ return DEFAULT_ISSUE_TEMPLATE
68
+
69
+ try:
70
+ with open(template_path) as f:
71
+ config = yaml.safe_load(f) or {}
72
+
73
+ # If template_type specified, look for it in templates section
74
+ if template_type and "templates" in config:
75
+ return config["templates"].get(
76
+ template_type, config.get("defaults", DEFAULT_ISSUE_TEMPLATE)
77
+ )
78
+
79
+ return config.get("defaults", DEFAULT_ISSUE_TEMPLATE)
80
+ except Exception as e:
81
+ logger.warning(f"Failed to load issue template: {e}")
82
+ return DEFAULT_ISSUE_TEMPLATE
83
+
84
+
31
85
  # Track whether we're using PAT mode for status reporting
32
86
  _using_pat_mode: bool = False
33
87
  _pat_source: Optional[str] = None
34
88
 
89
+ # Module-level client cache (keyed by token hash for security)
90
+ _client_cache: Optional[Tuple[int, GitHubClient]] = None
91
+
35
92
 
36
93
  def _get_client() -> GitHubClient:
37
94
  """
38
95
  Get the GitHub client using the best available authentication method.
39
96
 
40
97
  Authentication priority:
41
- 1. QuickCall GitHub App (if connected and working)
42
- 2. Personal Access Token from environment/config file
98
+ 1. Personal Access Token (PAT) - preferred, more permissions
99
+ 2. QuickCall GitHub App - fallback if no PAT
100
+
101
+ Uses cached client if token hasn't changed.
43
102
 
44
103
  Raises:
45
104
  ToolError: If no authentication method is available
46
105
  """
47
- global _using_pat_mode, _pat_source
106
+ global _using_pat_mode, _pat_source, _client_cache
48
107
 
49
108
  store = get_credential_store()
50
109
 
51
- # Try QuickCall GitHub App first (preferred)
110
+ # Try PAT first (preferred - user has more control)
111
+ pat_token, pat_source_str = get_github_pat()
112
+ if pat_token:
113
+ token_hash = hash(pat_token)
114
+
115
+ # Return cached client if token matches
116
+ if _client_cache and _client_cache[0] == token_hash:
117
+ return _client_cache[1]
118
+
119
+ _using_pat_mode = True
120
+ _pat_source = pat_source_str
121
+ pat_username = get_github_pat_username()
122
+ logger.info(f"Using GitHub PAT from {pat_source_str}")
123
+
124
+ client = GitHubClient(
125
+ token=pat_token,
126
+ default_owner=pat_username,
127
+ installation_id=None, # No installation ID for PAT
128
+ )
129
+ _client_cache = (token_hash, client)
130
+ return client
131
+
132
+ # Fall back to QuickCall GitHub App
52
133
  if store.is_authenticated():
53
134
  creds = store.get_api_credentials()
54
135
  if creds and creds.github_connected and creds.github_token:
136
+ token_hash = hash(creds.github_token)
137
+
138
+ # Return cached client if token matches
139
+ if _client_cache and _client_cache[0] == token_hash:
140
+ return _client_cache[1]
141
+
55
142
  _using_pat_mode = False
56
143
  _pat_source = None
57
- return GitHubClient(
144
+
145
+ client = GitHubClient(
58
146
  token=creds.github_token,
59
147
  default_owner=creds.github_username,
60
148
  installation_id=creds.github_installation_id,
61
149
  )
62
-
63
- # Try PAT fallback
64
- pat_token, pat_source = get_github_pat()
65
- if pat_token:
66
- _using_pat_mode = True
67
- _pat_source = pat_source
68
- pat_username = get_github_pat_username()
69
- logger.info(f"Using GitHub PAT from {pat_source}")
70
- return GitHubClient(
71
- token=pat_token,
72
- default_owner=pat_username,
73
- installation_id=None, # No installation ID for PAT
74
- )
150
+ _client_cache = (token_hash, client)
151
+ return client
75
152
 
76
153
  # No authentication available - provide helpful error message
77
154
  _using_pat_mode = False
78
155
  _pat_source = None
156
+ _client_cache = None
79
157
 
80
158
  if store.is_authenticated():
81
159
  # Connected to QuickCall but GitHub not connected
82
160
  raise ToolError(
83
161
  "GitHub not connected. Options:\n"
84
- "1. Connect GitHub App at quickcall.dev/assistant (recommended)\n"
85
- "2. Run connect_github_via_pat with your Personal Access Token\n"
162
+ "1. Run connect_github_via_pat with your Personal Access Token (recommended)\n"
163
+ "2. Connect GitHub App at quickcall.dev/assistant\n"
86
164
  "3. Set GITHUB_TOKEN environment variable"
87
165
  )
88
166
  else:
89
167
  # Not connected to QuickCall at all
90
168
  raise ToolError(
91
169
  "GitHub authentication required. Options:\n"
92
- "1. Run connect_quickcall to use QuickCall (full access to GitHub + Slack)\n"
93
- "2. Run connect_github_via_pat with a Personal Access Token (GitHub only)\n"
170
+ "1. Run connect_github_via_pat with a Personal Access Token (recommended)\n"
171
+ "2. Run connect_quickcall to use QuickCall (GitHub App + Slack)\n"
94
172
  "3. Set GITHUB_TOKEN environment variable\n\n"
95
173
  "For PAT: Create token at https://github.com/settings/tokens\n"
96
174
  "Required scopes: repo (private) or public_repo (public only)"
@@ -389,6 +467,128 @@ def create_github_tools(mcp: FastMCP) -> None:
389
467
  except Exception as e:
390
468
  raise ToolError(f"Failed to list branches: {str(e)}")
391
469
 
470
+ @mcp.tool(tags={"github", "issues"})
471
+ def manage_issues(
472
+ action: str = Field(
473
+ ...,
474
+ description="Action: 'create', 'update', 'close', 'reopen', or 'comment'",
475
+ ),
476
+ issue_numbers: Optional[List[int]] = Field(
477
+ default=None,
478
+ description="Issue number(s). Required for update/close/reopen/comment. Supports bulk operations.",
479
+ ),
480
+ title: Optional[str] = Field(
481
+ default=None,
482
+ description="Issue title (for 'create' or 'update')",
483
+ ),
484
+ body: Optional[str] = Field(
485
+ default=None,
486
+ description="Issue body (for 'create'/'update') or comment text (for 'comment')",
487
+ ),
488
+ labels: Optional[List[str]] = Field(
489
+ default=None,
490
+ description="Labels (for 'create' or 'update')",
491
+ ),
492
+ assignees: Optional[List[str]] = Field(
493
+ default=None,
494
+ description="GitHub usernames to assign",
495
+ ),
496
+ template: Optional[str] = Field(
497
+ default=None,
498
+ description="Template name for 'create' (e.g., 'bug', 'feature')",
499
+ ),
500
+ owner: Optional[str] = Field(
501
+ default=None,
502
+ description="Repository owner",
503
+ ),
504
+ repo: Optional[str] = Field(
505
+ default=None,
506
+ description="Repository name. Required.",
507
+ ),
508
+ ) -> dict:
509
+ """
510
+ Manage GitHub issues: create, update, close, reopen, or comment.
511
+
512
+ Supports bulk operations for close/reopen/comment via issue_numbers list.
513
+
514
+ Examples:
515
+ - create: manage_issues(action="create", title="Bug", template="bug")
516
+ - close multiple: manage_issues(action="close", issue_numbers=[1, 2, 3])
517
+ - comment: manage_issues(action="comment", issue_numbers=[42], body="Fixed!")
518
+ """
519
+ try:
520
+ client = _get_client()
521
+
522
+ if action == "create":
523
+ if not title:
524
+ raise ToolError("'title' is required for 'create' action")
525
+
526
+ tpl = _load_issue_template(template)
527
+ final_body = body if body is not None else tpl.get("body", "")
528
+ final_labels = labels if labels is not None else tpl.get("labels", [])
529
+
530
+ issue = client.create_issue(
531
+ title=title,
532
+ body=final_body,
533
+ labels=final_labels,
534
+ assignees=assignees,
535
+ owner=owner,
536
+ repo=repo,
537
+ )
538
+ return {"action": "created", "issue": issue}
539
+
540
+ # All other actions require issue_numbers
541
+ if not issue_numbers:
542
+ raise ToolError(f"'issue_numbers' required for '{action}' action")
543
+
544
+ results = []
545
+ for issue_number in issue_numbers:
546
+ if action == "update":
547
+ client.update_issue(
548
+ issue_number=issue_number,
549
+ title=title,
550
+ body=body,
551
+ labels=labels,
552
+ assignees=assignees,
553
+ owner=owner,
554
+ repo=repo,
555
+ )
556
+ results.append({"number": issue_number, "status": "updated"})
557
+
558
+ elif action == "close":
559
+ client.close_issue(issue_number, owner=owner, repo=repo)
560
+ results.append({"number": issue_number, "status": "closed"})
561
+
562
+ elif action == "reopen":
563
+ client.reopen_issue(issue_number, owner=owner, repo=repo)
564
+ results.append({"number": issue_number, "status": "reopened"})
565
+
566
+ elif action == "comment":
567
+ if not body:
568
+ raise ToolError("'body' is required for 'comment' action")
569
+ comment = client.comment_on_issue(
570
+ issue_number, body=body, owner=owner, repo=repo
571
+ )
572
+ results.append(
573
+ {
574
+ "number": issue_number,
575
+ "status": "commented",
576
+ "comment_url": comment["html_url"],
577
+ }
578
+ )
579
+
580
+ else:
581
+ raise ToolError(f"Invalid action: {action}")
582
+
583
+ return {"action": action, "count": len(results), "results": results}
584
+
585
+ except ToolError:
586
+ raise
587
+ except ValueError as e:
588
+ raise ToolError(f"Repository not specified: {str(e)}")
589
+ except Exception as e:
590
+ raise ToolError(f"Failed to {action} issue(s): {str(e)}")
591
+
392
592
  @mcp.tool(tags={"github", "prs", "appraisal"})
393
593
  def prepare_appraisal_data(
394
594
  author: Optional[str] = Field(
@@ -5,6 +5,7 @@ Provides datetime helpers useful for constructing queries:
5
5
  - Get current datetime
6
6
  - Calculate date ranges (e.g., "last 7 days")
7
7
  - Add/subtract time from dates
8
+ - Get MCP server version
8
9
  """
9
10
 
10
11
  from datetime import datetime, timezone, timedelta
@@ -13,6 +14,8 @@ from typing import Optional
13
14
  from fastmcp import FastMCP
14
15
  from pydantic import Field
15
16
 
17
+ from mcp_server import __version__
18
+
16
19
 
17
20
  def create_utility_tools(mcp: FastMCP) -> None:
18
21
  """
@@ -113,3 +116,19 @@ def create_utility_tools(mcp: FastMCP) -> None:
113
116
  "base_date": base.isoformat().replace("+00:00", "Z"),
114
117
  "offset": f"{days} days, {hours} hours",
115
118
  }
119
+
120
+ @mcp.tool(tags={"utility", "version"})
121
+ def get_mcp_version() -> dict:
122
+ """
123
+ Get the QuickCall MCP server version.
124
+
125
+ Returns the version from pyproject.toml (single source of truth).
126
+ Useful for debugging and verifying which version is running.
127
+
128
+ Returns:
129
+ Version info including version string and package name
130
+ """
131
+ return {
132
+ "package": "quickcall-integrations",
133
+ "version": __version__,
134
+ }
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickcall-integrations
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: MCP server with developer integrations for Claude Code and Cursor
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: fastmcp>=2.13.0
7
7
  Requires-Dist: httpx>=0.28.0
8
8
  Requires-Dist: pydantic>=2.11.7
9
9
  Requires-Dist: pygithub>=2.8.1
10
+ Requires-Dist: pyyaml>=6.0
10
11
  Requires-Dist: rapidfuzz>=3.0.0
11
12
  Description-Content-Type: text/markdown
12
13
 
@@ -1,20 +1,21 @@
1
- mcp_server/__init__.py,sha256=UJBr5BLG_aU2S4s2fEbRBZYd7GUWDVejxBpqezNBo8Q,98
2
- mcp_server/server.py,sha256=zGrrYwp7H24pJAAGAVkHDk7Y6IydOR_wo5hIL-e6_50,3001
1
+ mcp_server/__init__.py,sha256=6KGzjSPyVB6vQh150DwBjINM_CsZNDhOzwSQFWpXz0U,301
2
+ mcp_server/server.py,sha256=kv5hh0J-M7yENUBBNI1bkq1y7MB0zn5R_-R1tib6_sk,3108
3
3
  mcp_server/api_clients/__init__.py,sha256=kOG5_sxIVpAx_tvf1nq_P0QCkqojAVidRE-wenLS-Wc,207
4
- mcp_server/api_clients/github_client.py,sha256=shaBgAtTXUM-LZ7e5obuIRWylkOqBrim3_DapIN4rYI,25861
4
+ mcp_server/api_clients/github_client.py,sha256=HBLKgwZpbbdhroladE0l4mGjmuoa8uKCTf3oEL6S_P4,29298
5
5
  mcp_server/api_clients/slack_client.py,sha256=w3rcGghttfYw8Ird2beNo2LEYLc3rCTbUKMH4X7QQuQ,16447
6
6
  mcp_server/auth/__init__.py,sha256=D-JS0Qe7FkeJjYx92u_AqPx8ZRoB3dKMowzzJXlX6cc,780
7
- mcp_server/auth/credentials.py,sha256=1e1FpiaPPVc5hVdLlvIU2JbhbdHbXH9pzYBDPsUr0hI,20003
7
+ mcp_server/auth/credentials.py,sha256=sDS0W5c16i_UGvhG8Sh1RO93FxRn-hHVAdI9hlWuhx0,20011
8
8
  mcp_server/auth/device_flow.py,sha256=NXNWHzd-CA4dlhEVCgUhwfpe9TpMKpLSJuyFCh70xKs,8371
9
9
  mcp_server/resources/__init__.py,sha256=JrMa3Kf-DmeCB4GwVNfmfw9OGnxF9pJJxCw9Y7u7ujQ,35
10
+ mcp_server/resources/github_resources.py,sha256=gS0mTe7UHySAKiD4AqsFZ6pf7ua8Cq9dDrUqRu72_QM,5180
10
11
  mcp_server/resources/slack_resources.py,sha256=b_CPxAicwkF3PsBXIat4QoLbDUHM2g_iPzgzvVpwjaw,1687
11
12
  mcp_server/tools/__init__.py,sha256=vIR2ujAaTXm2DgpTsVNz3brI4G34p-Jeg44Qe0uvWc0,405
12
13
  mcp_server/tools/auth_tools.py,sha256=kCPjPC1jrVz0XaRAwPea-ue8ybjLLTxyILplBDJ9Mv4,24477
13
14
  mcp_server/tools/git_tools.py,sha256=jyCTQR2eSzUFXMt0Y8x66758-VY8YCY14DDUJt7GY2U,13957
14
- mcp_server/tools/github_tools.py,sha256=HEp0YoihI3te4l-6bWb-trtQySuCo73-2ZWLc70Of6Y,22531
15
+ mcp_server/tools/github_tools.py,sha256=x6aRg03QQd6QV-PePHLUZyOSuKDv06LIVUUCE5qXycU,29951
15
16
  mcp_server/tools/slack_tools.py,sha256=-HVE_x3Z1KMeYGi1xhyppEwz5ZF-I-ZD0-Up8yBeoYE,11796
16
- mcp_server/tools/utility_tools.py,sha256=1WiOpJivu6Ug9OLajm77lzsmFfBPgWHs8e1hNCEX_Aw,3359
17
- quickcall_integrations-0.3.3.dist-info/METADATA,sha256=fd1nVZVj0hMuDBgCERVn-DQxkWv43Yr3otNZ2oJQ4PE,7043
18
- quickcall_integrations-0.3.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
19
- quickcall_integrations-0.3.3.dist-info/entry_points.txt,sha256=kkcunmJUzncYvQ1rOR35V2LPm2HcFTKzdI2l3n7NwiM,66
20
- quickcall_integrations-0.3.3.dist-info/RECORD,,
17
+ mcp_server/tools/utility_tools.py,sha256=oxAXpdqtPeB5Ug5dvk54V504r-8v1AO4_px-sO6LFOw,3910
18
+ quickcall_integrations-0.3.4.dist-info/METADATA,sha256=JRc32RU12a6wnPLT9pQBWdkMCwSx7bS2ycNaPchQvc8,7070
19
+ quickcall_integrations-0.3.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
20
+ quickcall_integrations-0.3.4.dist-info/entry_points.txt,sha256=kkcunmJUzncYvQ1rOR35V2LPm2HcFTKzdI2l3n7NwiM,66
21
+ quickcall_integrations-0.3.4.dist-info/RECORD,,