gitstandup-mcp 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/build/config.js +34 -0
- package/build/git.js +134 -0
- package/build/index.js +185 -0
- package/package.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 GitStandup Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# GitStandup MCP Server
|
|
2
|
+
|
|
3
|
+
> Generate daily standup notes from your git commits using AI
|
|
4
|
+
|
|
5
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that automatically collects your git commits from multiple repositories and helps AI assistants generate natural, comprehensive standup summaries.
|
|
6
|
+
|
|
7
|
+
## โจ Features
|
|
8
|
+
|
|
9
|
+
- ๐ฆ **Multi-repo support** - Track commits across all your projects
|
|
10
|
+
- ๐ค **User-specific** - Only shows your commits (filtered by git user.email)
|
|
11
|
+
- โฐ **Time-based** - Configurable lookback period (default: last 24 hours)
|
|
12
|
+
- ๐ฏ **Smart diff analysis** - Includes code changes with intelligent truncation
|
|
13
|
+
- ๐พ **Persistent config** - Remembers your repos in `~/.gitstandup/config.json`
|
|
14
|
+
- ๐งน **Clean output** - Skips generated files (lock files, minified code)
|
|
15
|
+
|
|
16
|
+
## ๐ Quick Start
|
|
17
|
+
|
|
18
|
+
### Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Using npx (no installation needed)
|
|
22
|
+
npx -y gitstandup-mcp
|
|
23
|
+
|
|
24
|
+
# Or install globally
|
|
25
|
+
npm install -g gitstandup-mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Setup with Claude Desktop
|
|
29
|
+
|
|
30
|
+
Add to your Claude Desktop config at `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"gitstandup": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "gitstandup-mcp"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Setup with VS Code (GitHub Copilot)
|
|
44
|
+
|
|
45
|
+
Add to your VS Code MCP settings:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"gitstandup": {
|
|
50
|
+
"type": "stdio",
|
|
51
|
+
"command": "npx",
|
|
52
|
+
"args": ["-y", "gitstandup-mcp"]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## ๐ Usage
|
|
58
|
+
|
|
59
|
+
Once configured, you can use natural language with your AI assistant:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
"Generate my standup notes"
|
|
63
|
+
"What did I work on yesterday?"
|
|
64
|
+
"Show my commits from the last 2 days"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### First Time Setup
|
|
68
|
+
|
|
69
|
+
1. **Add your repositories:**
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
"Add /path/to/my/project to GitStandup"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
2. **Generate standup notes:**
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
"Generate my standup notes"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
3. **The AI will create a summary like:**
|
|
82
|
+
> Yesterday I:
|
|
83
|
+
>
|
|
84
|
+
> - Implemented OAuth authentication flow in the api-server
|
|
85
|
+
> - Fixed critical bug in payment processing
|
|
86
|
+
> - Added integration tests for user registration
|
|
87
|
+
|
|
88
|
+
## ๐ ๏ธ Available Tools
|
|
89
|
+
|
|
90
|
+
The server exposes four MCP tools that AI assistants can use:
|
|
91
|
+
|
|
92
|
+
### `generate_standup`
|
|
93
|
+
|
|
94
|
+
Generate standup notes from configured repositories.
|
|
95
|
+
|
|
96
|
+
**Parameters:**
|
|
97
|
+
|
|
98
|
+
- `hours` (optional): Number of hours to look back (default: 24)
|
|
99
|
+
- `repos` (optional): Array of specific repo paths to use
|
|
100
|
+
|
|
101
|
+
**Example:**
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
{
|
|
105
|
+
"hours": 48, // Last 2 days
|
|
106
|
+
"repos": ["/path/to/repo1", "/path/to/repo2"] // Optional
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `add_repos`
|
|
111
|
+
|
|
112
|
+
Add repository paths to the configuration.
|
|
113
|
+
|
|
114
|
+
**Parameters:**
|
|
115
|
+
|
|
116
|
+
- `paths`: Array of absolute paths to git repositories
|
|
117
|
+
|
|
118
|
+
**Example:**
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
{
|
|
122
|
+
"paths": ["/Users/you/projects/my-app", "/Users/you/projects/api"]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `list_repos`
|
|
127
|
+
|
|
128
|
+
List currently configured repositories.
|
|
129
|
+
|
|
130
|
+
**Returns:** Array of configured repository paths
|
|
131
|
+
|
|
132
|
+
### `remove_repos`
|
|
133
|
+
|
|
134
|
+
Remove repository paths from the configuration.
|
|
135
|
+
|
|
136
|
+
**Parameters:**
|
|
137
|
+
|
|
138
|
+
- `paths`: Array of repository paths to remove
|
|
139
|
+
|
|
140
|
+
## ๐ง Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Clone the repository
|
|
144
|
+
git clone https://github.com/muba00/gitstandup.git
|
|
145
|
+
cd gitstandup
|
|
146
|
+
|
|
147
|
+
# Install dependencies
|
|
148
|
+
npm install
|
|
149
|
+
|
|
150
|
+
# Build
|
|
151
|
+
npm run build
|
|
152
|
+
|
|
153
|
+
# Test locally
|
|
154
|
+
node build/index.js
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Project Structure
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
gitstandup/
|
|
161
|
+
โโโ src/
|
|
162
|
+
โ โโโ index.ts # MCP server setup and tool definitions
|
|
163
|
+
โ โโโ git.ts # Git operations and commit collection
|
|
164
|
+
โ โโโ config.ts # Configuration management
|
|
165
|
+
โโโ build/ # Compiled JavaScript (generated)
|
|
166
|
+
โโโ package.json
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## ๐ Configuration
|
|
170
|
+
|
|
171
|
+
Repository paths are stored in `~/.gitstandup/config.json`:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"repos": ["/Users/you/projects/project1", "/Users/you/projects/project2"]
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
You can edit this file manually or use the `add_repos` and `remove_repos` tools.
|
|
180
|
+
|
|
181
|
+
## ๐ค Contributing
|
|
182
|
+
|
|
183
|
+
Contributions are welcome! Feel free to:
|
|
184
|
+
|
|
185
|
+
- ๐ Report bugs
|
|
186
|
+
- ๐ก Suggest new features
|
|
187
|
+
- ๐ง Submit pull requests
|
|
188
|
+
|
|
189
|
+
See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for details.
|
|
190
|
+
|
|
191
|
+
## ๐ License
|
|
192
|
+
|
|
193
|
+
MIT License - see [LICENSE](LICENSE) for details
|
|
194
|
+
|
|
195
|
+
## ๐ Acknowledgments
|
|
196
|
+
|
|
197
|
+
Built with:
|
|
198
|
+
|
|
199
|
+
- [Model Context Protocol SDK](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
200
|
+
- [simple-git](https://github.com/steveukx/git-js)
|
|
201
|
+
- [Zod](https://github.com/colinhacks/zod)
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
**Note:** This tool only reads git commit history and does not modify your repositories.
|
package/build/config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), ".gitstandup");
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
7
|
+
/**
|
|
8
|
+
* Ensure the config directory exists
|
|
9
|
+
*/
|
|
10
|
+
export async function ensureConfigDir() {
|
|
11
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
12
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Load configuration from ~/.gitstandup/config.json
|
|
17
|
+
*/
|
|
18
|
+
export async function loadConfig() {
|
|
19
|
+
try {
|
|
20
|
+
const data = await readFile(CONFIG_FILE, "utf-8");
|
|
21
|
+
return JSON.parse(data);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
// If file doesn't exist or is invalid, return empty config
|
|
25
|
+
return { repos: [] };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Save configuration to ~/.gitstandup/config.json
|
|
30
|
+
*/
|
|
31
|
+
export async function saveConfig(config) {
|
|
32
|
+
await ensureConfigDir();
|
|
33
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
34
|
+
}
|
package/build/git.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { simpleGit } from "simple-git";
|
|
2
|
+
import path from "path";
|
|
3
|
+
const MAX_DIFF_LINES_PER_FILE = 500;
|
|
4
|
+
const MAX_DIFF_LINES_PER_COMMIT = 2000;
|
|
5
|
+
/**
|
|
6
|
+
* Truncate a diff to stay within limits
|
|
7
|
+
*/
|
|
8
|
+
function truncateDiff(diff, maxLines) {
|
|
9
|
+
const lines = diff.split("\n");
|
|
10
|
+
if (lines.length <= maxLines) {
|
|
11
|
+
return diff;
|
|
12
|
+
}
|
|
13
|
+
const halfLines = Math.floor(maxLines / 2);
|
|
14
|
+
const start = lines.slice(0, halfLines);
|
|
15
|
+
const end = lines.slice(-halfLines);
|
|
16
|
+
return [
|
|
17
|
+
...start,
|
|
18
|
+
`\n... (${lines.length - maxLines} lines omitted) ...\n`,
|
|
19
|
+
...end,
|
|
20
|
+
].join("\n");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get the current git user email for a repository
|
|
24
|
+
*/
|
|
25
|
+
async function getCurrentUserEmail(git) {
|
|
26
|
+
try {
|
|
27
|
+
const email = await git.raw(["config", "user.email"]);
|
|
28
|
+
return email.trim();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Collect commits from a single repository
|
|
36
|
+
*/
|
|
37
|
+
async function collectRepoCommits(repoPath, hoursAgo) {
|
|
38
|
+
const repoName = path.basename(repoPath);
|
|
39
|
+
try {
|
|
40
|
+
const git = simpleGit(repoPath);
|
|
41
|
+
// Check if it's a valid git repository
|
|
42
|
+
const isRepo = await git.checkIsRepo();
|
|
43
|
+
if (!isRepo) {
|
|
44
|
+
return {
|
|
45
|
+
name: repoName,
|
|
46
|
+
path: repoPath,
|
|
47
|
+
error: "Not a git repository",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Get current user email
|
|
51
|
+
const userEmail = await getCurrentUserEmail(git);
|
|
52
|
+
if (!userEmail) {
|
|
53
|
+
return {
|
|
54
|
+
name: repoName,
|
|
55
|
+
path: repoPath,
|
|
56
|
+
error: "Could not determine git user.email",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Calculate time threshold
|
|
60
|
+
const since = new Date(Date.now() - hoursAgo * 60 * 60 * 1000);
|
|
61
|
+
const sinceStr = since.toISOString();
|
|
62
|
+
// Get commits by current user since the time threshold
|
|
63
|
+
const log = await git.log({
|
|
64
|
+
"--since": sinceStr,
|
|
65
|
+
"--author": userEmail,
|
|
66
|
+
"--all": null,
|
|
67
|
+
});
|
|
68
|
+
if (log.all.length === 0) {
|
|
69
|
+
return {
|
|
70
|
+
name: repoName,
|
|
71
|
+
path: repoPath,
|
|
72
|
+
commits: [],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Collect commit details with diffs
|
|
76
|
+
const commits = [];
|
|
77
|
+
for (const commit of log.all) {
|
|
78
|
+
try {
|
|
79
|
+
// Get the diff for this commit
|
|
80
|
+
const diffSummary = await git.diffSummary([
|
|
81
|
+
`${commit.hash}^`,
|
|
82
|
+
commit.hash,
|
|
83
|
+
]);
|
|
84
|
+
// Get full diff, but limit to avoid huge output
|
|
85
|
+
let fullDiff = await git.diff([`${commit.hash}^`, commit.hash]);
|
|
86
|
+
// Truncate diff if too large
|
|
87
|
+
fullDiff = truncateDiff(fullDiff, MAX_DIFF_LINES_PER_COMMIT);
|
|
88
|
+
// Skip generated files and lock files
|
|
89
|
+
const relevantFiles = diffSummary.files.filter((f) => {
|
|
90
|
+
const fileName = f.file.toLowerCase();
|
|
91
|
+
return (!fileName.includes("package-lock.json") &&
|
|
92
|
+
!fileName.includes("yarn.lock") &&
|
|
93
|
+
!fileName.includes(".min.") &&
|
|
94
|
+
!fileName.endsWith(".lock"));
|
|
95
|
+
});
|
|
96
|
+
commits.push({
|
|
97
|
+
hash: commit.hash.substring(0, 7),
|
|
98
|
+
message: commit.message,
|
|
99
|
+
author: userEmail,
|
|
100
|
+
timestamp: commit.date,
|
|
101
|
+
files: relevantFiles.map((f) => f.file),
|
|
102
|
+
diff: fullDiff,
|
|
103
|
+
stats: {
|
|
104
|
+
additions: diffSummary.insertions,
|
|
105
|
+
deletions: diffSummary.deletions,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
// If we can't get diff for a commit, skip it
|
|
111
|
+
console.error(`Error getting diff for commit ${commit.hash}:`, error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
name: repoName,
|
|
116
|
+
path: repoPath,
|
|
117
|
+
commits,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
name: repoName,
|
|
123
|
+
path: repoPath,
|
|
124
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Collect commits from multiple repositories
|
|
130
|
+
*/
|
|
131
|
+
export async function collectCommits(repoPaths, hoursAgo) {
|
|
132
|
+
const results = await Promise.all(repoPaths.map((path) => collectRepoCommits(path, hoursAgo)));
|
|
133
|
+
return results;
|
|
134
|
+
}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { collectCommits } from "./git.js";
|
|
6
|
+
import { loadConfig, saveConfig, ensureConfigDir } from "./config.js";
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: "gitstandup-mcp",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
});
|
|
11
|
+
// Tool: generate_standup
|
|
12
|
+
server.registerTool("generate_standup", {
|
|
13
|
+
title: "Generate Standup Notes",
|
|
14
|
+
description: "Generate standup notes from git commits in configured repositories",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
hours: z.number().optional().describe("Hours to look back (default: 24)"),
|
|
17
|
+
repos: z
|
|
18
|
+
.array(z.string())
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Specific repo paths to use instead of configured ones"),
|
|
21
|
+
},
|
|
22
|
+
outputSchema: {
|
|
23
|
+
repos: z.array(z.object({
|
|
24
|
+
name: z.string(),
|
|
25
|
+
path: z.string(),
|
|
26
|
+
commits: z.array(z.object({
|
|
27
|
+
hash: z.string(),
|
|
28
|
+
message: z.string(),
|
|
29
|
+
author: z.string(),
|
|
30
|
+
timestamp: z.string(),
|
|
31
|
+
files: z.array(z.string()),
|
|
32
|
+
diff: z.string(),
|
|
33
|
+
stats: z.object({
|
|
34
|
+
additions: z.number(),
|
|
35
|
+
deletions: z.number(),
|
|
36
|
+
}),
|
|
37
|
+
})),
|
|
38
|
+
error: z.string().optional(),
|
|
39
|
+
})),
|
|
40
|
+
summary: z.object({
|
|
41
|
+
totalCommits: z.number(),
|
|
42
|
+
totalRepos: z.number(),
|
|
43
|
+
timeRange: z.string(),
|
|
44
|
+
user: z.string().optional(),
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
}, async ({ hours = 24, repos }) => {
|
|
48
|
+
const config = await loadConfig();
|
|
49
|
+
const repoPaths = repos || config.repos;
|
|
50
|
+
if (repoPaths.length === 0) {
|
|
51
|
+
const output = {
|
|
52
|
+
repos: [],
|
|
53
|
+
summary: {
|
|
54
|
+
totalCommits: 0,
|
|
55
|
+
totalRepos: 0,
|
|
56
|
+
timeRange: `last ${hours} hours`,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: "No repositories configured. Use add_repos tool to add repositories.",
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
structuredContent: output,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const results = await collectCommits(repoPaths, hours);
|
|
70
|
+
const totalCommits = results.reduce((sum, r) => sum + (r.commits?.length || 0), 0);
|
|
71
|
+
const output = {
|
|
72
|
+
repos: results,
|
|
73
|
+
summary: {
|
|
74
|
+
totalCommits,
|
|
75
|
+
totalRepos: results.length,
|
|
76
|
+
timeRange: `last ${hours} hours`,
|
|
77
|
+
user: results[0]?.commits?.[0]?.author,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: "text",
|
|
84
|
+
text: JSON.stringify(output, null, 2),
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
structuredContent: output,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
// Tool: add_repos
|
|
91
|
+
server.registerTool("add_repos", {
|
|
92
|
+
title: "Add Repositories",
|
|
93
|
+
description: "Add repository paths to the configuration",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
paths: z.array(z.string()).describe("Absolute paths to git repositories"),
|
|
96
|
+
},
|
|
97
|
+
outputSchema: {
|
|
98
|
+
added: z.array(z.string()),
|
|
99
|
+
repos: z.array(z.string()),
|
|
100
|
+
},
|
|
101
|
+
}, async ({ paths }) => {
|
|
102
|
+
await ensureConfigDir();
|
|
103
|
+
const config = await loadConfig();
|
|
104
|
+
const newPaths = paths.filter((p) => !config.repos.includes(p));
|
|
105
|
+
config.repos.push(...newPaths);
|
|
106
|
+
await saveConfig(config);
|
|
107
|
+
const output = {
|
|
108
|
+
added: newPaths,
|
|
109
|
+
repos: config.repos,
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: `Added ${newPaths.length} repository(ies). Total: ${config.repos.length}`,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
structuredContent: output,
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
// Tool: list_repos
|
|
122
|
+
server.registerTool("list_repos", {
|
|
123
|
+
title: "List Repositories",
|
|
124
|
+
description: "List currently configured repositories",
|
|
125
|
+
inputSchema: {},
|
|
126
|
+
outputSchema: {
|
|
127
|
+
repos: z.array(z.string()),
|
|
128
|
+
count: z.number(),
|
|
129
|
+
},
|
|
130
|
+
}, async () => {
|
|
131
|
+
const config = await loadConfig();
|
|
132
|
+
const output = {
|
|
133
|
+
repos: config.repos,
|
|
134
|
+
count: config.repos.length,
|
|
135
|
+
};
|
|
136
|
+
return {
|
|
137
|
+
content: [
|
|
138
|
+
{
|
|
139
|
+
type: "text",
|
|
140
|
+
text: JSON.stringify(output, null, 2),
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
structuredContent: output,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
// Tool: remove_repos
|
|
147
|
+
server.registerTool("remove_repos", {
|
|
148
|
+
title: "Remove Repositories",
|
|
149
|
+
description: "Remove repository paths from the configuration",
|
|
150
|
+
inputSchema: {
|
|
151
|
+
paths: z.array(z.string()).describe("Repository paths to remove"),
|
|
152
|
+
},
|
|
153
|
+
outputSchema: {
|
|
154
|
+
removed: z.array(z.string()),
|
|
155
|
+
repos: z.array(z.string()),
|
|
156
|
+
},
|
|
157
|
+
}, async ({ paths }) => {
|
|
158
|
+
const config = await loadConfig();
|
|
159
|
+
const removed = paths.filter((p) => config.repos.includes(p));
|
|
160
|
+
config.repos = config.repos.filter((p) => !paths.includes(p));
|
|
161
|
+
await saveConfig(config);
|
|
162
|
+
const output = {
|
|
163
|
+
removed,
|
|
164
|
+
repos: config.repos,
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: `Removed ${removed.length} repository(ies). Remaining: ${config.repos.length}`,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
structuredContent: output,
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
// Start the server
|
|
177
|
+
async function main() {
|
|
178
|
+
const transport = new StdioServerTransport();
|
|
179
|
+
await server.connect(transport);
|
|
180
|
+
console.error("GitStandup MCP Server running on stdio");
|
|
181
|
+
}
|
|
182
|
+
main().catch((error) => {
|
|
183
|
+
console.error("Fatal error:", error);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gitstandup-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for generating daily standup notes from git commits",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gitstandup-mcp": "./build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc && chmod +x build/index.js",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"prepare": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"git",
|
|
17
|
+
"standup",
|
|
18
|
+
"commits"
|
|
19
|
+
],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
24
|
+
"simple-git": "^3.25.0",
|
|
25
|
+
"zod": "^3.23.8"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"typescript": "^5.3.0"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"build"
|
|
33
|
+
]
|
|
34
|
+
}
|