quickcall-integrations 0.3.5__py3-none-any.whl → 0.3.6__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.
@@ -5,50 +5,14 @@ Resources are automatically available in Claude's context when connected.
5
5
  """
6
6
 
7
7
  import logging
8
- from typing import Any, Dict, Optional
9
8
 
10
- import yaml
11
9
  from fastmcp import FastMCP
12
10
 
13
11
  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
12
 
16
13
  logger = logging.getLogger(__name__)
17
14
 
18
15
 
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
16
  def create_github_resources(mcp: FastMCP) -> None:
53
17
  """Add GitHub resources to the MCP server."""
54
18
 
@@ -107,41 +71,46 @@ def create_github_resources(mcp: FastMCP) -> None:
107
71
  """
108
72
  Available issue templates from project configuration.
109
73
 
74
+ Supports both:
75
+ - GitHub native templates (.github/ISSUE_TEMPLATE/*.yml)
76
+ - Custom templates (ISSUE_TEMPLATE_PATH in .quickcall.env)
77
+
110
78
  Use template names when creating issues with manage_issues.
111
79
  """
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"
80
+ # Import here to avoid circular imports
81
+ from mcp_server.tools.github_tools import _get_all_templates
116
82
 
117
- templates = config.get("templates", {})
118
- defaults = config.get("defaults", {})
83
+ templates = _get_all_templates()
119
84
 
120
85
  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)
86
+ return (
87
+ "No issue templates found.\n\n"
88
+ "Supported sources:\n"
89
+ "1. GitHub native: .github/ISSUE_TEMPLATE/*.yml\n"
90
+ "2. Custom: Add ISSUE_TEMPLATE_PATH to .quickcall.env"
91
+ )
130
92
 
131
93
  lines = ["Available Issue Templates:", ""]
132
94
 
133
- for name, template in templates.items():
95
+ for key, template in templates.items():
96
+ name = template.get("name", key)
97
+ description = template.get("description", "")
134
98
  labels = template.get("labels", [])
135
- body_preview = template.get("body", "")[:80]
136
- lines.append(f"- {name}")
99
+ title_prefix = template.get("title_prefix", "")
100
+
101
+ lines.append(f"- {key}")
102
+ if name != key:
103
+ lines.append(f" Name: {name}")
104
+ if description:
105
+ lines.append(f" Description: {description}")
137
106
  if labels:
138
107
  lines.append(f" Labels: {', '.join(labels)}")
139
- if body_preview:
140
- lines.append(f" Body: {body_preview}...")
108
+ if title_prefix:
109
+ lines.append(f" Title prefix: {title_prefix}")
141
110
 
142
111
  lines.append("")
143
112
  lines.append(
144
- "Usage: manage_issues(action='create', title='...', template='<name>')"
113
+ "Usage: manage_issues(action='create', title='...', template='<key>')"
145
114
  )
146
115
 
147
116
  return "\n".join(lines)
@@ -44,14 +44,85 @@ DEFAULT_ISSUE_TEMPLATE: Dict[str, Any] = {
44
44
  }
45
45
 
46
46
 
47
- def _load_issue_template(template_type: Optional[str] = None) -> Dict[str, Any]:
47
+ def _load_github_native_templates() -> Dict[str, Dict[str, Any]]:
48
48
  """
49
- Load issue template from ISSUE_TEMPLATE_PATH in .quickcall.env.
50
- Returns defaults if not configured.
49
+ Load GitHub native issue templates from .github/ISSUE_TEMPLATE/*.yml.
50
+ Returns dict of template_name -> template_config.
51
51
  """
52
- template_path = os.getenv("ISSUE_TEMPLATE_PATH")
52
+ project_root = _find_project_root()
53
+ if not project_root:
54
+ return {}
55
+
56
+ template_dir = project_root / ".github" / "ISSUE_TEMPLATE"
57
+ if not template_dir.exists():
58
+ return {}
59
+
60
+ templates = {}
61
+ for template_file in template_dir.glob("*.yml"):
62
+ try:
63
+ with open(template_file) as f:
64
+ config = yaml.safe_load(f) or {}
65
+
66
+ # Extract template name (use filename without extension as fallback)
67
+ name = config.get("name", template_file.stem)
68
+ # Use filename stem as key for easier matching
69
+ key = template_file.stem
70
+
71
+ # Convert GitHub template format to our format
72
+ templates[key] = {
73
+ "name": name,
74
+ "description": config.get("description", ""),
75
+ "title_prefix": config.get("title", ""),
76
+ "labels": config.get("labels", []),
77
+ "assignees": config.get("assignees", []),
78
+ "body": _github_template_body_to_markdown(config.get("body", [])),
79
+ }
80
+ except Exception as e:
81
+ logger.warning(f"Failed to load GitHub template {template_file}: {e}")
82
+
83
+ return templates
84
+
85
+
86
+ def _github_template_body_to_markdown(body: List[Dict[str, Any]]) -> str:
87
+ """Convert GitHub issue template body fields to markdown."""
88
+ if not body:
89
+ return ""
90
+
91
+ lines = []
92
+ for field in body:
93
+ field_type = field.get("type", "")
94
+ attrs = field.get("attributes", {})
95
+ label = attrs.get("label", "")
96
+
97
+ if field_type in ("textarea", "input"):
98
+ if label:
99
+ lines.append(f"## {label}")
100
+ lines.append("")
101
+ placeholder = attrs.get("placeholder", "")
102
+ if placeholder:
103
+ lines.append(placeholder)
104
+ lines.append("")
105
+ elif field_type == "markdown":
106
+ value = attrs.get("value", "")
107
+ if value:
108
+ lines.append(value)
109
+ lines.append("")
110
+
111
+ return "\n".join(lines)
112
+
113
+
114
+ def _get_all_templates() -> Dict[str, Dict[str, Any]]:
115
+ """
116
+ Get all available issue templates from both sources.
117
+ Priority: Custom templates (.quickcall.env) > GitHub native templates
118
+ """
119
+ templates = {}
53
120
 
54
- # Check .quickcall.env in project root
121
+ # 1. Load GitHub native templates first (lower priority)
122
+ templates.update(_load_github_native_templates())
123
+
124
+ # 2. Load custom templates (higher priority, can override)
125
+ template_path = os.getenv("ISSUE_TEMPLATE_PATH")
55
126
  if not template_path:
56
127
  project_root = _find_project_root()
57
128
  if project_root:
@@ -63,24 +134,52 @@ def _load_issue_template(template_type: Optional[str] = None) -> Dict[str, Any]:
63
134
  if not Path(template_path).is_absolute():
64
135
  template_path = str(project_root / template_path)
65
136
 
66
- if not template_path:
67
- return DEFAULT_ISSUE_TEMPLATE
137
+ if template_path:
138
+ try:
139
+ with open(template_path) as f:
140
+ config = yaml.safe_load(f) or {}
141
+ custom_templates = config.get("templates", {})
142
+ for key, tpl in custom_templates.items():
143
+ templates[key] = {
144
+ "name": key,
145
+ "description": "",
146
+ "title_prefix": "",
147
+ "labels": tpl.get("labels", []),
148
+ "assignees": tpl.get("assignees", []),
149
+ "body": tpl.get("body", ""),
150
+ }
151
+ except Exception as e:
152
+ logger.warning(f"Failed to load custom templates: {e}")
68
153
 
69
- try:
70
- with open(template_path) as f:
71
- config = yaml.safe_load(f) or {}
154
+ return templates
72
155
 
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
156
 
79
- return config.get("defaults", DEFAULT_ISSUE_TEMPLATE)
80
- except Exception as e:
81
- logger.warning(f"Failed to load issue template: {e}")
157
+ def _load_issue_template(template_type: Optional[str] = None) -> Dict[str, Any]:
158
+ """
159
+ Load issue template from available sources.
160
+
161
+ Sources (in priority order):
162
+ 1. Custom templates from ISSUE_TEMPLATE_PATH in .quickcall.env
163
+ 2. GitHub native templates from .github/ISSUE_TEMPLATE/*.yml
164
+
165
+ Returns defaults if no template found.
166
+ """
167
+ if not template_type:
82
168
  return DEFAULT_ISSUE_TEMPLATE
83
169
 
170
+ all_templates = _get_all_templates()
171
+
172
+ if template_type in all_templates:
173
+ tpl = all_templates[template_type]
174
+ return {
175
+ "labels": tpl.get("labels", []),
176
+ "assignees": tpl.get("assignees", []),
177
+ "body": tpl.get("body", ""),
178
+ "title_prefix": tpl.get("title_prefix", ""),
179
+ }
180
+
181
+ return DEFAULT_ISSUE_TEMPLATE
182
+
84
183
 
85
184
  # Track whether we're using PAT mode for status reporting
86
185
  _using_pat_mode: bool = False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickcall-integrations
3
- Version: 0.3.5
3
+ Version: 0.3.6
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,15 +7,15 @@ mcp_server/auth/__init__.py,sha256=D-JS0Qe7FkeJjYx92u_AqPx8ZRoB3dKMowzzJXlX6cc,7
7
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
+ mcp_server/resources/github_resources.py,sha256=sXE06j9jrSDODxH2832fiCtY9n1lKBQR8QZ8U5wYbJY,4030
11
11
  mcp_server/resources/slack_resources.py,sha256=b_CPxAicwkF3PsBXIat4QoLbDUHM2g_iPzgzvVpwjaw,1687
12
12
  mcp_server/tools/__init__.py,sha256=vIR2ujAaTXm2DgpTsVNz3brI4G34p-Jeg44Qe0uvWc0,405
13
13
  mcp_server/tools/auth_tools.py,sha256=kCPjPC1jrVz0XaRAwPea-ue8ybjLLTxyILplBDJ9Mv4,24477
14
14
  mcp_server/tools/git_tools.py,sha256=jyCTQR2eSzUFXMt0Y8x66758-VY8YCY14DDUJt7GY2U,13957
15
- mcp_server/tools/github_tools.py,sha256=x6aRg03QQd6QV-PePHLUZyOSuKDv06LIVUUCE5qXycU,29951
15
+ mcp_server/tools/github_tools.py,sha256=iQvVuhWwVsKSPI7Ue6Eu0gYJcMKjWBAoo4U2Is9eIUI,33241
16
16
  mcp_server/tools/slack_tools.py,sha256=-HVE_x3Z1KMeYGi1xhyppEwz5ZF-I-ZD0-Up8yBeoYE,11796
17
17
  mcp_server/tools/utility_tools.py,sha256=oxAXpdqtPeB5Ug5dvk54V504r-8v1AO4_px-sO6LFOw,3910
18
- quickcall_integrations-0.3.5.dist-info/METADATA,sha256=T5g_OrKJJtEX_xfntDbj8tlcSWTIDTDyT74uW4xf4Rg,7070
19
- quickcall_integrations-0.3.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
20
- quickcall_integrations-0.3.5.dist-info/entry_points.txt,sha256=kkcunmJUzncYvQ1rOR35V2LPm2HcFTKzdI2l3n7NwiM,66
21
- quickcall_integrations-0.3.5.dist-info/RECORD,,
18
+ quickcall_integrations-0.3.6.dist-info/METADATA,sha256=tt1WubqnaF5Yjq9mE-8HDIsDqCYXO4vaXGSw5ZKKV2Q,7070
19
+ quickcall_integrations-0.3.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
20
+ quickcall_integrations-0.3.6.dist-info/entry_points.txt,sha256=kkcunmJUzncYvQ1rOR35V2LPm2HcFTKzdI2l3n7NwiM,66
21
+ quickcall_integrations-0.3.6.dist-info/RECORD,,