quickcall-integrations 0.1.0__tar.gz

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(gh secret set:*)"
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,96 @@
1
+ name: Deploy MCP Server
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ workflow_dispatch:
8
+
9
+ env:
10
+ MCP_IMAGE: quickcall-mcp-oss
11
+
12
+ jobs:
13
+ build-and-deploy-mcp:
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: read
17
+ packages: write
18
+
19
+ steps:
20
+ - name: Checkout code
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Log in to Azure Container Registry
24
+ uses: docker/login-action@v3
25
+ with:
26
+ registry: ${{ secrets.AZURE_CONTAINER_REGISTRY }}
27
+ username: ${{ secrets.AZURE_ACR_USERNAME }}
28
+ password: ${{ secrets.AZURE_ACR_PASSWORD }}
29
+
30
+ - name: Set up Docker Buildx
31
+ uses: docker/setup-buildx-action@v3
32
+
33
+ - name: Build and push MCP image
34
+ uses: docker/build-push-action@v5
35
+ with:
36
+ context: .
37
+ file: ./Dockerfile
38
+ push: true
39
+ tags: |
40
+ ${{ secrets.AZURE_CONTAINER_REGISTRY }}/${{ env.MCP_IMAGE }}:latest
41
+ ${{ secrets.AZURE_CONTAINER_REGISTRY }}/${{ env.MCP_IMAGE }}:${{ github.sha }}
42
+ cache-from: type=registry,ref=${{ secrets.AZURE_CONTAINER_REGISTRY }}/${{ env.MCP_IMAGE }}:buildcache
43
+ cache-to: type=registry,ref=${{ secrets.AZURE_CONTAINER_REGISTRY }}/${{ env.MCP_IMAGE }}:buildcache,mode=max
44
+
45
+ - name: Azure Login
46
+ uses: azure/login@v1
47
+ with:
48
+ creds: ${{ secrets.AZURE_CREDENTIALS }}
49
+
50
+ - name: Configure Container Registry
51
+ run: |
52
+ az containerapp registry set \
53
+ --name quickcall-mcp-oss \
54
+ --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \
55
+ --server ${{ secrets.AZURE_CONTAINER_REGISTRY }} \
56
+ --username ${{ secrets.AZURE_ACR_USERNAME }} \
57
+ --password ${{ secrets.AZURE_ACR_PASSWORD }}
58
+
59
+ - name: Deploy MCP to Azure Container Apps
60
+ env:
61
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
62
+ run: |
63
+ az containerapp update \
64
+ --name quickcall-mcp-oss \
65
+ --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \
66
+ --image ${{ secrets.AZURE_CONTAINER_REGISTRY }}/${{ env.MCP_IMAGE }}:${{ github.sha }} \
67
+ --min-replicas 1 \
68
+ --max-replicas 5 \
69
+ --set-env-vars \
70
+ MCP_TRANSPORT=streamable-http \
71
+ MCP_HOST=0.0.0.0 \
72
+ MCP_PORT=8001 \
73
+ OPENAI_API_KEY="$OPENAI_API_KEY"
74
+
75
+ - name: Configure External Ingress
76
+ run: |
77
+ az containerapp ingress enable \
78
+ --name quickcall-mcp-oss \
79
+ --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \
80
+ --type external \
81
+ --target-port 8001 \
82
+ --transport http
83
+
84
+ - name: Get App URL
85
+ run: |
86
+ APP_URL=$(az containerapp show \
87
+ --name quickcall-mcp-oss \
88
+ --resource-group ${{ secrets.AZURE_RESOURCE_GROUP }} \
89
+ --query properties.configuration.ingress.fqdn -o tsv)
90
+
91
+ echo "🚀 MCP Server deployed at: https://$APP_URL"
92
+ echo "📊 Health check: https://$APP_URL/health"
93
+ echo "🔧 MCP endpoint: https://$APP_URL/mcp"
94
+
95
+ - name: Azure Logout
96
+ run: az logout
@@ -0,0 +1,101 @@
1
+ # Secrets and environment
2
+ secrets/
3
+ *.env
4
+ *.env.*
5
+ *.pem
6
+ mcp-inspector-config.json
7
+
8
+ # Byte-compiled / optimized / DLL files
9
+ __pycache__/
10
+ *.py[codz]
11
+ *$py.class
12
+
13
+ # C extensions
14
+ *.so
15
+
16
+ # Distribution / packaging
17
+ .Python
18
+ build/
19
+ develop-eggs/
20
+ dist/
21
+ downloads/
22
+ eggs/
23
+ .eggs/
24
+ lib/
25
+ lib64/
26
+ parts/
27
+ sdist/
28
+ var/
29
+ wheels/
30
+ share/python-wheels/
31
+ *.egg-info/
32
+ .installed.cfg
33
+ *.egg
34
+ MANIFEST
35
+
36
+ # PyInstaller
37
+ *.manifest
38
+ *.spec
39
+
40
+ # Installer logs
41
+ pip-log.txt
42
+ pip-delete-this-directory.txt
43
+
44
+ # Unit test / coverage reports
45
+ htmlcov/
46
+ .tox/
47
+ .nox/
48
+ .coverage
49
+ .coverage.*
50
+ .cache
51
+ nosetests.xml
52
+ coverage.xml
53
+ *.cover
54
+ *.py.cover
55
+ .hypothesis/
56
+ .pytest_cache/
57
+ cover/
58
+
59
+ # Translations
60
+ *.mo
61
+ *.pot
62
+
63
+ # Logs
64
+ *.log
65
+
66
+ # Environments
67
+ .env
68
+ .envrc
69
+ .venv
70
+ env/
71
+ venv/
72
+ ENV/
73
+ env.bak/
74
+ venv.bak/
75
+
76
+ # UV
77
+ .uv/
78
+
79
+ # mypy
80
+ .mypy_cache/
81
+ .dmypy.json
82
+ dmypy.json
83
+
84
+ # Pyre type checker
85
+ .pyre/
86
+
87
+ # pytype static type analyzer
88
+ .pytype/
89
+
90
+ # Ruff
91
+ .ruff_cache/
92
+
93
+ # IDE
94
+ .idea/
95
+ .vscode/
96
+ *.swp
97
+ *.swo
98
+
99
+ # OS
100
+ .DS_Store
101
+ Thumbs.db
@@ -0,0 +1,23 @@
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install uv
6
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
7
+
8
+ # Copy requirements and install dependencies
9
+ COPY requirements.txt .
10
+ RUN uv pip install --system -r requirements.txt
11
+
12
+ # Copy application code
13
+ COPY mcp_server/ ./mcp_server/
14
+
15
+ # Set Python path
16
+ ENV PYTHONPATH=/app/mcp_server
17
+
18
+ # Expose MCP port
19
+ EXPOSE 8001
20
+
21
+ # Run MCP server
22
+ WORKDIR /app/mcp_server
23
+ CMD ["python", "server.py"]
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: quickcall-integrations
3
+ Version: 0.1.0
4
+ Summary: MCP server with developer integrations for Claude Code and Cursor
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: fastmcp>=2.13.0
7
+ Requires-Dist: pydantic>=2.11.7
8
+ Description-Content-Type: text/markdown
9
+
10
+ # QuickCall Integrations
11
+
12
+ Developer integrations for Claude Code and Cursor.
13
+
14
+ **Current integrations:**
15
+ - Git - commits, diffs, code changes
16
+
17
+ **Coming soon:**
18
+ - Calendar
19
+ - Slack
20
+ - GitHub PRs & Issues
21
+
22
+ ## Quick Install
23
+
24
+ Add to Claude Code:
25
+ ```bash
26
+ claude mcp add quickcall -- uvx quickcall-integrations
27
+ ```
28
+
29
+ Or add to your `.mcp.json`:
30
+ ```json
31
+ {
32
+ "mcpServers": {
33
+ "quickcall": {
34
+ "command": "uvx",
35
+ "args": ["quickcall-integrations"]
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ Just ask Claude:
44
+ - "What did I work on today?"
45
+ - "Show me recent commits"
46
+ - "What's changed in the last week?"
47
+
48
+ Or use the plugin command: `/quickcall:daily-updates`
49
+
50
+ ## Development
51
+
52
+ ```bash
53
+ # Clone and install
54
+ git clone https://github.com/quickcall-dev/quickcall-integrations
55
+ cd quickcall-integrations
56
+ uv pip install -e .
57
+
58
+ # Run locally
59
+ quickcall-integrations
60
+
61
+ # Run with SSE (for remote deployment)
62
+ MCP_TRANSPORT=sse quickcall-integrations
63
+ ```
@@ -0,0 +1,54 @@
1
+ # QuickCall Integrations
2
+
3
+ Developer integrations for Claude Code and Cursor.
4
+
5
+ **Current integrations:**
6
+ - Git - commits, diffs, code changes
7
+
8
+ **Coming soon:**
9
+ - Calendar
10
+ - Slack
11
+ - GitHub PRs & Issues
12
+
13
+ ## Quick Install
14
+
15
+ Add to Claude Code:
16
+ ```bash
17
+ claude mcp add quickcall -- uvx quickcall-integrations
18
+ ```
19
+
20
+ Or add to your `.mcp.json`:
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "quickcall": {
25
+ "command": "uvx",
26
+ "args": ["quickcall-integrations"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ Just ask Claude:
35
+ - "What did I work on today?"
36
+ - "Show me recent commits"
37
+ - "What's changed in the last week?"
38
+
39
+ Or use the plugin command: `/quickcall:daily-updates`
40
+
41
+ ## Development
42
+
43
+ ```bash
44
+ # Clone and install
45
+ git clone https://github.com/quickcall-dev/quickcall-integrations
46
+ cd quickcall-integrations
47
+ uv pip install -e .
48
+
49
+ # Run locally
50
+ quickcall-integrations
51
+
52
+ # Run with SSE (for remote deployment)
53
+ MCP_TRANSPORT=sse quickcall-integrations
54
+ ```
@@ -0,0 +1,6 @@
1
+ """
2
+ MCP Server for QuickCall
3
+ GitHub integration tools for AI assistant
4
+ """
5
+
6
+ __version__ = "0.3.4"
@@ -0,0 +1,10 @@
1
+ """
2
+ Configuration for QuickCall Integrations MCP Server.
3
+ """
4
+
5
+
6
+ class Config:
7
+ """Configuration class for MCP server."""
8
+
9
+ SERVER_NAME: str = "QuickCall Integrations (OSS)"
10
+ SERVER_VERSION: str = "1.0.0"
@@ -0,0 +1,42 @@
1
+ """
2
+ QuickCall Integrations MCP Server
3
+
4
+ Git tools for developers - view commits, diffs, and changes.
5
+ """
6
+
7
+ import os
8
+
9
+ from fastmcp import FastMCP
10
+
11
+ from mcp_server.tools.git_tools import create_git_tools
12
+ from mcp_server.tools.utility_tools import create_utility_tools
13
+
14
+
15
+ def create_server() -> FastMCP:
16
+ """Create and configure the MCP server."""
17
+ mcp = FastMCP("quickcall-integrations")
18
+
19
+ create_git_tools(mcp)
20
+ create_utility_tools(mcp)
21
+
22
+ return mcp
23
+
24
+
25
+ mcp = create_server()
26
+
27
+
28
+ def main():
29
+ """Entry point for the CLI."""
30
+ transport = os.getenv("MCP_TRANSPORT", "stdio")
31
+
32
+ if transport == "stdio":
33
+ mcp.run(transport="stdio")
34
+ else:
35
+ host = os.getenv("MCP_HOST", "0.0.0.0")
36
+ port = int(os.getenv("MCP_PORT", "8001"))
37
+ print(f"Starting server: http://{host}:{port}/mcp (transport: {transport})")
38
+ mcp.run(transport=transport, host=host, port=port)
39
+
40
+
41
+ if __name__ == "__main__":
42
+ main()
@@ -0,0 +1 @@
1
+ """MCP tools for external integrations"""
@@ -0,0 +1,194 @@
1
+ """
2
+ Git Tools - Simple tools for viewing repository changes.
3
+ """
4
+
5
+ from typing import Optional, List
6
+ import subprocess
7
+
8
+ from fastmcp import FastMCP
9
+ from fastmcp.exceptions import ToolError
10
+ from pydantic import Field
11
+
12
+
13
+ def _run_git(args: List[str], cwd: Optional[str] = None) -> str:
14
+ """Run a git command and return output."""
15
+ try:
16
+ result = subprocess.run(
17
+ ["git"] + args,
18
+ cwd=cwd,
19
+ capture_output=True,
20
+ text=True,
21
+ timeout=30,
22
+ )
23
+ if result.returncode != 0:
24
+ raise ToolError(f"Git error: {result.stderr.strip()}")
25
+ return result.stdout.strip()
26
+ except subprocess.TimeoutExpired:
27
+ raise ToolError("Git command timed out")
28
+ except FileNotFoundError:
29
+ raise ToolError("Git not found")
30
+
31
+
32
+ def _get_repo_info(cwd: Optional[str] = None) -> dict:
33
+ """Get repository info."""
34
+ try:
35
+ _run_git(["rev-parse", "--git-dir"], cwd)
36
+ except ToolError:
37
+ raise ToolError(f"Not a git repository: {cwd or 'current directory'}")
38
+
39
+ repo_root = _run_git(["rev-parse", "--show-toplevel"], cwd)
40
+
41
+ try:
42
+ remote_url = _run_git(["remote", "get-url", "origin"], cwd)
43
+ if "github.com" in remote_url:
44
+ if remote_url.startswith("git@"):
45
+ path = remote_url.split(":")[-1]
46
+ else:
47
+ path = remote_url.split("github.com/")[-1]
48
+ path = path.rstrip(".git")
49
+ parts = path.split("/")
50
+ owner, repo = (parts[0], parts[1]) if len(parts) >= 2 else (None, None)
51
+ else:
52
+ owner, repo = None, None
53
+ except ToolError:
54
+ owner, repo = None, None
55
+
56
+ try:
57
+ branch = _run_git(["rev-parse", "--abbrev-ref", "HEAD"], cwd)
58
+ except ToolError:
59
+ branch = "unknown"
60
+
61
+ return {"root": repo_root, "owner": owner, "repo": repo, "branch": branch}
62
+
63
+
64
+ def create_git_tools(mcp: FastMCP) -> None:
65
+ """Add git tools to the MCP server."""
66
+
67
+ @mcp.tool(tags={"git", "updates"})
68
+ def get_updates(
69
+ path: str = Field(
70
+ ...,
71
+ description="Path to git repository. Use the user's current working directory.",
72
+ ),
73
+ days: int = Field(
74
+ default=7,
75
+ description="Number of days to look back (default: 7)",
76
+ ),
77
+ author: Optional[str] = Field(
78
+ default=None,
79
+ description="Filter by author name/email",
80
+ ),
81
+ ) -> dict:
82
+ """
83
+ Get updates from a git repository.
84
+
85
+ Returns commits, diff stats, file changes, and actual code diff for the given period.
86
+ """
87
+ try:
88
+ repo_info = _get_repo_info(path)
89
+ repo_name = f"{repo_info['owner']}/{repo_info['repo']}" if repo_info['owner'] else repo_info['root']
90
+
91
+ result = {
92
+ "repository": repo_name,
93
+ "branch": repo_info['branch'],
94
+ "period": f"Last {days} days",
95
+ }
96
+
97
+ # Get commits
98
+ since_date = f"{days} days ago"
99
+ log_format = "--pretty=format:%H|%an|%ad|%s"
100
+ log_args = ["log", log_format, "--date=short", f"--since={since_date}"]
101
+ if author:
102
+ log_args.extend(["--author", author])
103
+
104
+ log_output = _run_git(log_args, path)
105
+
106
+ commits = []
107
+ for line in log_output.split("\n"):
108
+ if not line:
109
+ continue
110
+ parts = line.split("|", 3)
111
+ if len(parts) >= 4:
112
+ commits.append({
113
+ "sha": parts[0][:7],
114
+ "author": parts[1],
115
+ "date": parts[2],
116
+ "message": parts[3],
117
+ })
118
+
119
+ result["commits"] = commits
120
+ result["commit_count"] = len(commits)
121
+
122
+ if not commits:
123
+ result["diff"] = {"files_changed": 0, "additions": 0, "deletions": 0, "patch": ""}
124
+ return result
125
+
126
+ # Get total diff between oldest and newest commit
127
+ oldest_sha = commits[-1]["sha"]
128
+ newest_sha = commits[0]["sha"]
129
+
130
+ try:
131
+ # Get stats
132
+ numstat = _run_git(["diff", "--numstat", f"{oldest_sha}^", newest_sha], path)
133
+
134
+ files = []
135
+ total_add = 0
136
+ total_del = 0
137
+
138
+ for line in numstat.split("\n"):
139
+ if not line:
140
+ continue
141
+ parts = line.split("\t")
142
+ if len(parts) >= 3:
143
+ adds = int(parts[0]) if parts[0] != "-" else 0
144
+ dels = int(parts[1]) if parts[1] != "-" else 0
145
+ files.append({
146
+ "file": parts[2],
147
+ "additions": adds,
148
+ "deletions": dels,
149
+ })
150
+ total_add += adds
151
+ total_del += dels
152
+
153
+ # Get actual diff patch
154
+ diff_patch = _run_git(["diff", f"{oldest_sha}^", newest_sha], path)
155
+
156
+ # Truncate if too large
157
+ if len(diff_patch) > 50000:
158
+ diff_patch = diff_patch[:50000] + "\n\n... (truncated, diff too large)"
159
+
160
+ result["diff"] = {
161
+ "files_changed": len(files),
162
+ "additions": total_add,
163
+ "deletions": total_del,
164
+ "files": files[:30],
165
+ "patch": diff_patch,
166
+ }
167
+ except ToolError:
168
+ result["diff"] = {"files_changed": 0, "additions": 0, "deletions": 0, "patch": ""}
169
+
170
+ # Uncommitted changes
171
+ staged = _run_git(["diff", "--cached", "--name-only"], path)
172
+ unstaged = _run_git(["diff", "--name-only"], path)
173
+
174
+ staged_list = [f for f in staged.split("\n") if f]
175
+ unstaged_list = [f for f in unstaged.split("\n") if f]
176
+
177
+ if staged_list or unstaged_list:
178
+ # Get uncommitted diff patch too
179
+ uncommitted_patch = _run_git(["diff", "HEAD"], path)
180
+ if len(uncommitted_patch) > 20000:
181
+ uncommitted_patch = uncommitted_patch[:20000] + "\n\n... (truncated)"
182
+
183
+ result["uncommitted"] = {
184
+ "staged": staged_list,
185
+ "unstaged": unstaged_list,
186
+ "patch": uncommitted_patch,
187
+ }
188
+
189
+ return result
190
+
191
+ except ToolError:
192
+ raise
193
+ except Exception as e:
194
+ raise ToolError(f"Failed to get updates: {str(e)}")
@@ -0,0 +1,115 @@
1
+ """
2
+ Utility tools for common operations.
3
+
4
+ Provides datetime helpers useful for constructing queries:
5
+ - Get current datetime
6
+ - Calculate date ranges (e.g., "last 7 days")
7
+ - Add/subtract time from dates
8
+ """
9
+
10
+ from datetime import datetime, timezone, timedelta
11
+ from typing import Optional
12
+
13
+ from fastmcp import FastMCP
14
+ from pydantic import Field
15
+
16
+
17
+ def create_utility_tools(mcp: FastMCP) -> None:
18
+ """
19
+ Add utility tools to the MCP server.
20
+
21
+ Args:
22
+ mcp: FastMCP instance to add tools to
23
+ """
24
+
25
+ @mcp.tool(tags={"utility", "datetime"})
26
+ def get_current_datetime(
27
+ format: str = Field(
28
+ default="iso",
29
+ description="Output format: 'iso' for ISO 8601, 'unix' for Unix timestamp",
30
+ ),
31
+ ) -> dict:
32
+ """
33
+ Get the current date and time in UTC.
34
+
35
+ Returns:
36
+ Current datetime in the specified format
37
+ """
38
+ now = datetime.now(timezone.utc)
39
+
40
+ if format == "unix":
41
+ return {
42
+ "datetime": int(now.timestamp()),
43
+ "format": "Unix timestamp",
44
+ }
45
+ else:
46
+ return {
47
+ "datetime": now.isoformat().replace("+00:00", "Z"),
48
+ "format": "ISO 8601",
49
+ }
50
+
51
+ @mcp.tool(tags={"utility", "datetime"})
52
+ def calculate_date_range(
53
+ days_ago: int = Field(
54
+ ...,
55
+ description="Days ago to start. Use 7 for 'last week', 1 for 'yesterday', 0 for 'today'.",
56
+ ),
57
+ ) -> dict:
58
+ """
59
+ Calculate a date range from N days ago until now.
60
+
61
+ Use this to get the 'since' parameter for list_commits.
62
+
63
+ Common mappings:
64
+ - "last week" = days_ago=7
65
+ - "yesterday" = days_ago=1
66
+ - "today" = days_ago=0
67
+
68
+ Returns:
69
+ Dictionary with 'since' ISO datetime string
70
+ """
71
+ now = datetime.now(timezone.utc)
72
+
73
+ # Calculate start date (N days ago at midnight UTC)
74
+ start = now - timedelta(days=days_ago)
75
+ start = start.replace(hour=0, minute=0, second=0, microsecond=0)
76
+
77
+ return {
78
+ "since": start.isoformat().replace("+00:00", "Z"),
79
+ "now": now.isoformat().replace("+00:00", "Z"),
80
+ "days_ago": days_ago,
81
+ }
82
+
83
+ @mcp.tool(tags={"utility", "datetime"})
84
+ def calculate_date_offset(
85
+ days: int = Field(
86
+ default=0,
87
+ description="Number of days to add (negative to subtract)",
88
+ ),
89
+ hours: int = Field(
90
+ default=0,
91
+ description="Number of hours to add (negative to subtract)",
92
+ ),
93
+ base_date: Optional[str] = Field(
94
+ default=None,
95
+ description="Base date in ISO format. If not provided, uses current time.",
96
+ ),
97
+ ) -> dict:
98
+ """
99
+ Calculate a new date by adding/subtracting time.
100
+
101
+ Returns:
102
+ New datetime after applying the offset
103
+ """
104
+ if base_date:
105
+ base = datetime.fromisoformat(base_date.replace("Z", "+00:00"))
106
+ else:
107
+ base = datetime.now(timezone.utc)
108
+
109
+ result = base + timedelta(days=days, hours=hours)
110
+
111
+ return {
112
+ "datetime": result.isoformat().replace("+00:00", "Z"),
113
+ "base_date": base.isoformat().replace("+00:00", "Z"),
114
+ "offset": f"{days} days, {hours} hours",
115
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "quickcall",
3
+ "description": "From a developer to another fellow developer, let's get rid of quickcalls once and for all!",
4
+ "version": "1.0.0",
5
+ "author": {
6
+ "name": "Sagar"
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "quickcall": {
4
+ "command": "uvx",
5
+ "args": ["quickcall-integrations"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,25 @@
1
+ ---
2
+ description: Get git updates for current repository (commits, diffs, changes)
3
+ ---
4
+
5
+ # Daily Updates
6
+
7
+ Get the recent git updates for the user's current working directory.
8
+
9
+ ## Instructions
10
+
11
+ 1. Use the `get_updates` tool from the quickcall MCP server
12
+ 2. Pass the current working directory as the `path` parameter
13
+ 3. Default to 1 day of history (or use the number the user specifies)
14
+ 4. Summarize the changes in a clear format:
15
+ - Number of commits
16
+ - Authors who contributed
17
+ - Key changes made (based on commit messages and diff)
18
+ - Any uncommitted changes
19
+
20
+ ## Output Format
21
+
22
+ Provide a concise summary like:
23
+ - "3 commits today by Alice and Bob"
24
+ - "Main changes: Added auth flow, fixed login bug"
25
+ - "You have 2 uncommitted files"
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "quickcall-integrations"
3
+ version = "0.1.0"
4
+ description = "MCP server with developer integrations for Claude Code and Cursor"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "fastmcp>=2.13.0",
9
+ "pydantic>=2.11.7",
10
+ ]
11
+
12
+ [project.scripts]
13
+ quickcall-integrations = "mcp_server.server:main"
14
+
15
+ [build-system]
16
+ requires = ["hatchling"]
17
+ build-backend = "hatchling.build"
18
+
19
+ [tool.hatch.build.targets.wheel]
20
+ packages = ["mcp_server"]
@@ -0,0 +1,3 @@
1
+ # MCP Server
2
+ fastmcp>=2.13.0
3
+ pydantic>=2.11.7
@@ -0,0 +1,80 @@
1
+ # Testing QuickCall Integrations MCP Server
2
+
3
+ ## Quick Test
4
+
5
+ ### 1. Start the server
6
+
7
+ ```bash
8
+ cd src && python server.py
9
+ ```
10
+
11
+ ### 2. Run the test
12
+
13
+ ```bash
14
+ python tests/test_tools.py
15
+ ```
16
+
17
+ ## Using with Claude Code
18
+
19
+ ### Add to Claude Code config
20
+
21
+ Add to `~/.claude/settings.json`:
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "quickcall": {
27
+ "command": "python",
28
+ "args": ["/path/to/quickcall-mcp-server/src/server.py"],
29
+ "env": {
30
+ "MCP_TRANSPORT": "stdio"
31
+ }
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ Or for HTTP mode (run server separately):
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "quickcall": {
43
+ "url": "http://localhost:8001/mcp"
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### Test in Claude Code
50
+
51
+ Once configured, try these prompts:
52
+
53
+ ```
54
+ "What have I been working on today?"
55
+ "Show me my uncommitted changes"
56
+ "Summarize my work this week"
57
+ ```
58
+
59
+ ## Using MCP Inspector
60
+
61
+ ```bash
62
+ npx @modelcontextprotocol/inspector
63
+ ```
64
+
65
+ Then connect to `http://localhost:8001/mcp` and test the tools:
66
+ - `get_updates` - Shows uncommitted + committed changes
67
+ - `get_diff` - Shows detailed diffs
68
+ - `summarize_updates` - AI summary (needs OPENAI_API_KEY)
69
+
70
+ ## Environment Variables
71
+
72
+ ```bash
73
+ # Optional - for AI summaries
74
+ export OPENAI_API_KEY=your_key
75
+
76
+ # Server config (optional)
77
+ export MCP_HOST=0.0.0.0
78
+ export MCP_PORT=8001
79
+ export MCP_TRANSPORT=streamable-http # or "stdio" for Claude Code
80
+ ```
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test QuickCall Integrations MCP Server.
4
+
5
+ Tests all git tools by connecting to the running server.
6
+ No authentication required - just uses local git commands.
7
+
8
+ Usage:
9
+ 1. Start the server: cd mcp_server && python server.py
10
+ 2. Run this test: python tests/test_tools.py
11
+ """
12
+
13
+ import asyncio
14
+ import json
15
+ import os
16
+ import httpx
17
+ from rich.console import Console
18
+
19
+ console = Console()
20
+
21
+ # MCP server endpoint
22
+ MCP_URL = "http://localhost:8001"
23
+
24
+ # Test repository path (this repo)
25
+ TEST_REPO_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26
+
27
+
28
+ def check_server():
29
+ """Check if server is running."""
30
+ try:
31
+ response = httpx.get(f"{MCP_URL}/mcp", timeout=2)
32
+ # SSE endpoint returns 200 even without proper SSE headers
33
+ console.print(f"[green]✅ Server is running at {MCP_URL}[/green]")
34
+ return True
35
+ except Exception:
36
+ pass
37
+
38
+ console.print("[red]❌ Server is not running![/red]")
39
+ console.print(" Start it with: cd mcp_server && python server.py")
40
+ return False
41
+
42
+
43
+ async def test_with_fastmcp():
44
+ """Test using FastMCP client."""
45
+ from fastmcp import Client
46
+
47
+ console.print("\n[bold]Testing with FastMCP Client[/bold]\n")
48
+
49
+ client = Client(f"{MCP_URL}/mcp")
50
+
51
+ async with client:
52
+ console.print("[green]✅ Connected to MCP server[/green]\n")
53
+
54
+ # List available tools
55
+ console.print("[bold]Available Tools:[/bold]")
56
+ tools = await client.list_tools()
57
+ for tool in tools:
58
+ console.print(f" - {tool.name}: {tool.description[:60]}...")
59
+
60
+ console.print("\n" + "=" * 60 + "\n")
61
+
62
+ # Test 1: get_updates
63
+ console.print("[bold cyan]Test 1: get_updates[/bold cyan]")
64
+ console.print(f"Testing on repo: {TEST_REPO_PATH}\n")
65
+
66
+ try:
67
+ result = await client.call_tool("get_updates", {
68
+ "path": TEST_REPO_PATH,
69
+ "days": 7
70
+ })
71
+
72
+ if isinstance(result, list) and len(result) > 0:
73
+ data = json.loads(result[0].text)
74
+ console.print(f"[green]✅ Repository: {data['repository']}[/green]")
75
+ console.print(f" Branch: {data['branch']}")
76
+ console.print(f" Period: {data['period']}")
77
+ console.print(f" Commits: {data['commit_count']}")
78
+
79
+ if data.get('diff'):
80
+ diff = data['diff']
81
+ console.print(f" Files changed: {diff['files_changed']}")
82
+ console.print(f" Additions: +{diff['additions']}")
83
+ console.print(f" Deletions: -{diff['deletions']}")
84
+
85
+ if diff.get('patch'):
86
+ lines = diff['patch'].split('\n')[:10]
87
+ console.print("\n Diff preview:")
88
+ for line in lines:
89
+ if line.startswith('+') and not line.startswith('+++'):
90
+ console.print(f" [green]{line[:80]}[/green]")
91
+ elif line.startswith('-') and not line.startswith('---'):
92
+ console.print(f" [red]{line[:80]}[/red]")
93
+ else:
94
+ console.print(f" {line[:80]}")
95
+
96
+ if data.get('uncommitted'):
97
+ uncommitted = data['uncommitted']
98
+ console.print(f"\n Uncommitted - Staged: {len(uncommitted.get('staged', []))}")
99
+ console.print(f" Uncommitted - Unstaged: {len(uncommitted.get('unstaged', []))}")
100
+
101
+ if data.get('commits'):
102
+ console.print("\n Recent commits:")
103
+ for commit in data['commits'][:3]:
104
+ msg = commit['message'][:50] + "..." if len(commit['message']) > 50 else commit['message']
105
+ console.print(f" - {commit['sha']} {msg}")
106
+
107
+ except Exception as e:
108
+ console.print(f"[red]❌ Error: {e}[/red]")
109
+
110
+ console.print("\n" + "=" * 60 + "\n")
111
+
112
+ # Test 2: Utility tools
113
+ console.print("[bold cyan]Test 2: Utility Tools[/bold cyan]")
114
+
115
+ try:
116
+ result = await client.call_tool("get_current_datetime", {})
117
+ if isinstance(result, list) and len(result) > 0:
118
+ data = json.loads(result[0].text)
119
+ console.print(f"[green]✅ Current time: {data['datetime']}[/green]")
120
+
121
+ result = await client.call_tool("calculate_date_range", {"days_ago": 7})
122
+ if isinstance(result, list) and len(result) > 0:
123
+ data = json.loads(result[0].text)
124
+ console.print(f"[green]✅ Last 7 days: since {data['since']}[/green]")
125
+
126
+ except Exception as e:
127
+ console.print(f"[red]❌ Error: {e}[/red]")
128
+
129
+ console.print("\n" + "=" * 60 + "\n")
130
+ console.print("[green]✅ All tests completed![/green]")
131
+
132
+
133
+ async def main():
134
+ """Run tests."""
135
+ console.print("\n[bold]QuickCall Integrations - MCP Server Test[/bold]\n")
136
+
137
+ if not check_server():
138
+ return
139
+
140
+ await test_with_fastmcp()
141
+
142
+
143
+ if __name__ == "__main__":
144
+ asyncio.run(main())