pastcode 0.1.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 +118 -0
- package/index.js +286 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [YEAR] [YOUR NAME]
|
|
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,118 @@
|
|
|
1
|
+
# pastcode
|
|
2
|
+
|
|
3
|
+
A minimal MCP server for git history, git blame, and debugging the moment a bug was introduced.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/pastcode)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
|
|
9
|
+
AI coding agents can read the current snapshot of a repo, but they usually cannot answer the question that matters most when debugging: when did this bug get introduced, who changed it, and what did the file look like before the breakage?
|
|
10
|
+
|
|
11
|
+
pastcode gives any MCP-compatible AI agent read access to your git history so it can investigate bugs the way a senior engineer would: by walking back through time, checking blame, diffing commits, and narrowing the culprit with git bisect.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
Add a server entry like this to your MCP client configuration:
|
|
16
|
+
|
|
17
|
+
### Claude Desktop
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"pastcode": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "pastcode"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This works the same way in Claude Code, Cursor, Windsurf, or any other MCP-compatible client that launches stdio-based MCP servers. There are no environment variables to set up, no repo config to install, and no global state to manage. The repo path is passed per tool call, so one running server can work across unlimited repositories.
|
|
31
|
+
|
|
32
|
+
Not published yet, or testing locally? Use the local path variant above instead.
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"pastcode": {
|
|
38
|
+
"command": "node",
|
|
39
|
+
"args": ["/absolute/path/to/pastcode/index.js"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Available tools
|
|
46
|
+
|
|
47
|
+
| Tool | What it does | Example prompt |
|
|
48
|
+
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
49
|
+
| `get_file_at_commit` | Returns the contents of a file at a specific commit or ref. Useful when an AI agent wants to inspect older code without checking out the repo. | “Show me the version of src/auth.js from commit abc123 and compare it to the current file.” |
|
|
50
|
+
| `list_commits_for_file` | Lists the recent history of a file using `git log --follow`, including commit hashes, authors, dates, and messages. | “Which commits touched src/api/users.ts, and when were they introduced?” |
|
|
51
|
+
| `blame_line_range` | Shows who last changed a line range and which commit introduced it. Great for narrowing suspect edits. | “Who changed the validateEmail function and which commit owns this block?” |
|
|
52
|
+
| `diff_between_commits` | Compares two refs, optionally scoped to a file, to show exactly what changed between them. | “What changed between release-1.2 and release-1.3 in src/checkout.ts?” |
|
|
53
|
+
| `find_introducing_commit` | Runs a git bisect in a temporary worktree using a supplied test command to locate the first bad commit. | “When did the isValidEmail function start returning false for valid emails?” |
|
|
54
|
+
|
|
55
|
+
## How agents actually use this
|
|
56
|
+
|
|
57
|
+
### 1) “When did this bug get introduced?”
|
|
58
|
+
|
|
59
|
+
A user asks: “The login form breaks for valid email addresses after the last deploy. When did this start?”
|
|
60
|
+
|
|
61
|
+
The AI may call:
|
|
62
|
+
|
|
63
|
+
- `list_commits_for_file` on the file that owns email validation
|
|
64
|
+
- `blame_line_range` on the suspicious lines
|
|
65
|
+
- `get_file_at_commit` on historical revisions to inspect behavior before and after the regression
|
|
66
|
+
- `find_introducing_commit` with a minimal test command to confirm the exact first bad commit
|
|
67
|
+
|
|
68
|
+
### 2) “Who changed this logic and why?”
|
|
69
|
+
|
|
70
|
+
A user asks: “I want to know why the pricing calculation changed in the checkout flow.”
|
|
71
|
+
|
|
72
|
+
The AI may call:
|
|
73
|
+
|
|
74
|
+
- `blame_line_range` on the calculation block
|
|
75
|
+
- `diff_between_commits` between the relevant revisions
|
|
76
|
+
- `get_file_at_commit` to inspect the code as it existed before the refactor or hotfix
|
|
77
|
+
|
|
78
|
+
### 3) “What did this file look like before the refactor?”
|
|
79
|
+
|
|
80
|
+
A user asks: “The refactor in the auth service looks risky. Show me the pre-refactor implementation.”
|
|
81
|
+
|
|
82
|
+
The AI may call:
|
|
83
|
+
|
|
84
|
+
- `list_commits_for_file` to locate the refactor commit
|
|
85
|
+
- `get_file_at_commit` at the parent ref or historical tag
|
|
86
|
+
- `diff_between_commits` between the refactor and its parent to highlight the exact behavioral change
|
|
87
|
+
|
|
88
|
+
This is not a CLI tool you run manually. The AI agent drives it as part of a normal debugging workflow.
|
|
89
|
+
|
|
90
|
+
## Security note
|
|
91
|
+
|
|
92
|
+
> ⚠️ `find_introducing_commit` executes an arbitrary shell command (`testCommand`) during bisect. Only use it against trusted repositories and trusted test commands, just as you would with any AI-driven script execution or automated debugging workflow.
|
|
93
|
+
|
|
94
|
+
## Contributing
|
|
95
|
+
|
|
96
|
+
This project is intentionally small and dependency-light. The goal is to stay easy to reason about and easy to wire into AI tools without over-engineering the stack.
|
|
97
|
+
|
|
98
|
+
To work on it locally:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
git clone https://github.com/USERNAME/pastcode.git
|
|
102
|
+
cd pastcode
|
|
103
|
+
npm install
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For manual inspection of the MCP server, you can run it via the official MCP inspector:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx @modelcontextprotocol/inspector node index.js
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Then connect the inspector to the local server entry and inspect tool calls in real time.
|
|
113
|
+
|
|
114
|
+
If you want to propose a new tool, open an issue first to discuss scope and fit before sending a PR. This project keeps the surface area intentionally small, and it’s best to align on the workflow before adding new capabilities.
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
This project is licensed under the [MIT License](LICENSE).
|
package/index.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
|
|
8
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import {
|
|
11
|
+
CallToolRequestSchema,
|
|
12
|
+
ListToolsRequestSchema,
|
|
13
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
14
|
+
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
|
|
17
|
+
function ensureRepo(repoPath) {
|
|
18
|
+
if (!repoPath || typeof repoPath !== "string") {
|
|
19
|
+
throw new Error("repoPath is required");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!fs.existsSync(repoPath) || !fs.existsSync(path.join(repoPath, ".git"))) {
|
|
23
|
+
throw new Error(`Not a git repo: ${repoPath}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function runGit(repoPath, args, stdoutOnly = false) {
|
|
28
|
+
ensureRepo(repoPath);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const { stdout, stderr } = await execFileAsync(
|
|
32
|
+
"git",
|
|
33
|
+
["-C", repoPath, ...args],
|
|
34
|
+
{
|
|
35
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
if (stdoutOnly) {
|
|
39
|
+
return (stdout || "").trim();
|
|
40
|
+
}
|
|
41
|
+
return `${stdout || ""}${stderr ? `\n${stderr}` : ""}`.trim();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
const message = [error.stderr, error.stdout, error.message]
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join("\n");
|
|
46
|
+
if (stdoutOnly) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
(error.stderr || error.message || "git command failed").trim(),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
throw new Error(message.trim() || "git command failed");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function getFileAtCommit(repoPath, filePath, ref) {
|
|
56
|
+
return runGit(repoPath, ["show", `${ref}:${filePath}`], true);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function listCommitsForFile(repoPath, filePath, maxCount) {
|
|
60
|
+
const args = ["log", "--follow"];
|
|
61
|
+
if (maxCount != null && Number.isFinite(Number(maxCount))) {
|
|
62
|
+
args.push("-n", String(Number(maxCount)));
|
|
63
|
+
}
|
|
64
|
+
args.push("--format=%H%x09%an%x09%ad%x09%s", "--date=short", "--", filePath);
|
|
65
|
+
return runGit(repoPath, args);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function blameLineRange(repoPath, filePath, startLine, endLine, ref) {
|
|
69
|
+
const args = ["blame"];
|
|
70
|
+
if (ref) {
|
|
71
|
+
args.push(ref);
|
|
72
|
+
}
|
|
73
|
+
args.push("-L", `${startLine},${endLine}`, "--", filePath);
|
|
74
|
+
return runGit(repoPath, args);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function diffBetweenCommits(repoPath, refA, refB, filePath) {
|
|
78
|
+
const args = ["diff", refA, refB];
|
|
79
|
+
if (filePath) {
|
|
80
|
+
args.push("--", filePath);
|
|
81
|
+
}
|
|
82
|
+
return runGit(repoPath, args);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function findIntroducingCommit(
|
|
86
|
+
repoPath,
|
|
87
|
+
filePath,
|
|
88
|
+
testCommand,
|
|
89
|
+
goodRef,
|
|
90
|
+
badRef,
|
|
91
|
+
) {
|
|
92
|
+
ensureRepo(repoPath);
|
|
93
|
+
|
|
94
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "pastcode-bisect-"));
|
|
95
|
+
const worktreePath = path.join(tempRoot, "repo");
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await runGit(repoPath, [
|
|
99
|
+
"worktree",
|
|
100
|
+
"add",
|
|
101
|
+
"--detach",
|
|
102
|
+
worktreePath,
|
|
103
|
+
goodRef,
|
|
104
|
+
]);
|
|
105
|
+
await runGit(worktreePath, ["bisect", "start"]);
|
|
106
|
+
await runGit(worktreePath, ["bisect", "bad", badRef]);
|
|
107
|
+
await runGit(worktreePath, ["bisect", "good", goodRef]);
|
|
108
|
+
|
|
109
|
+
const bisectOutput = await execFileAsync(
|
|
110
|
+
"git",
|
|
111
|
+
["-C", worktreePath, "bisect", "run", "bash", "-lc", testCommand],
|
|
112
|
+
{ maxBuffer: 20 * 1024 * 1024 },
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const head = await runGit(worktreePath, ["rev-parse", "HEAD"]);
|
|
116
|
+
const summary = [
|
|
117
|
+
`Introduced by commit: ${head}`,
|
|
118
|
+
`File: ${filePath || "(not restricted)"}`,
|
|
119
|
+
"",
|
|
120
|
+
bisectOutput.stdout || bisectOutput.stderr || "",
|
|
121
|
+
].join("\n");
|
|
122
|
+
|
|
123
|
+
return summary.trim();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
const msg = [error.stderr, error.stdout, error.message]
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.join("\n");
|
|
128
|
+
throw new Error(msg.trim() || "bisect failed");
|
|
129
|
+
} finally {
|
|
130
|
+
try {
|
|
131
|
+
if (fs.existsSync(worktreePath)) {
|
|
132
|
+
try {
|
|
133
|
+
await runGit(worktreePath, ["bisect", "reset"]);
|
|
134
|
+
} catch (cleanupError) {
|
|
135
|
+
// ignore bisect reset failures so the worktree can still be removed
|
|
136
|
+
}
|
|
137
|
+
await runGit(repoPath, ["worktree", "remove", "--force", worktreePath]);
|
|
138
|
+
}
|
|
139
|
+
} catch (cleanupError) {
|
|
140
|
+
// ignore cleanup errors during short-lived tooling
|
|
141
|
+
}
|
|
142
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const server = new Server(
|
|
147
|
+
{ name: "pastcode", version: "0.1.0" },
|
|
148
|
+
{ capabilities: { tools: {} } },
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
152
|
+
tools: [
|
|
153
|
+
{
|
|
154
|
+
name: "get_file_at_commit",
|
|
155
|
+
description: "Return the contents of a file at a specific commit or ref.",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
type: "object",
|
|
158
|
+
properties: {
|
|
159
|
+
repoPath: { type: "string" },
|
|
160
|
+
path: { type: "string" },
|
|
161
|
+
ref: { type: "string" },
|
|
162
|
+
},
|
|
163
|
+
required: ["repoPath", "path", "ref"],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: "list_commits_for_file",
|
|
168
|
+
description: "List recent commits affecting a file.",
|
|
169
|
+
inputSchema: {
|
|
170
|
+
type: "object",
|
|
171
|
+
properties: {
|
|
172
|
+
repoPath: { type: "string" },
|
|
173
|
+
path: { type: "string" },
|
|
174
|
+
maxCount: { type: "number" },
|
|
175
|
+
},
|
|
176
|
+
required: ["repoPath", "path"],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "blame_line_range",
|
|
181
|
+
description: "Blame a line range in a file.",
|
|
182
|
+
inputSchema: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: {
|
|
185
|
+
repoPath: { type: "string" },
|
|
186
|
+
path: { type: "string" },
|
|
187
|
+
startLine: { type: "number" },
|
|
188
|
+
endLine: { type: "number" },
|
|
189
|
+
ref: { type: "string" },
|
|
190
|
+
},
|
|
191
|
+
required: ["repoPath", "path", "startLine", "endLine"],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "diff_between_commits",
|
|
196
|
+
description: "Show the diff between two refs for a file or repo.",
|
|
197
|
+
inputSchema: {
|
|
198
|
+
type: "object",
|
|
199
|
+
properties: {
|
|
200
|
+
repoPath: { type: "string" },
|
|
201
|
+
refA: { type: "string" },
|
|
202
|
+
refB: { type: "string" },
|
|
203
|
+
path: { type: "string" },
|
|
204
|
+
},
|
|
205
|
+
required: ["repoPath", "refA", "refB"],
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: "find_introducing_commit",
|
|
210
|
+
description:
|
|
211
|
+
"Use git bisect in a temporary worktree to find the introducing commit for a bug.",
|
|
212
|
+
inputSchema: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {
|
|
215
|
+
repoPath: { type: "string" },
|
|
216
|
+
path: { type: "string" },
|
|
217
|
+
testCommand: { type: "string" },
|
|
218
|
+
goodRef: { type: "string" },
|
|
219
|
+
badRef: { type: "string" },
|
|
220
|
+
},
|
|
221
|
+
required: ["repoPath", "path", "testCommand", "goodRef", "badRef"],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
}));
|
|
226
|
+
|
|
227
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
228
|
+
const { name, arguments: args = {} } = request.params;
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
let result;
|
|
232
|
+
|
|
233
|
+
switch (name) {
|
|
234
|
+
case "get_file_at_commit":
|
|
235
|
+
result = await getFileAtCommit(args.repoPath, args.path, args.ref);
|
|
236
|
+
break;
|
|
237
|
+
case "list_commits_for_file":
|
|
238
|
+
result = await listCommitsForFile(
|
|
239
|
+
args.repoPath,
|
|
240
|
+
args.path,
|
|
241
|
+
args.maxCount,
|
|
242
|
+
);
|
|
243
|
+
break;
|
|
244
|
+
case "blame_line_range":
|
|
245
|
+
result = await blameLineRange(
|
|
246
|
+
args.repoPath,
|
|
247
|
+
args.path,
|
|
248
|
+
args.startLine,
|
|
249
|
+
args.endLine,
|
|
250
|
+
args.ref,
|
|
251
|
+
);
|
|
252
|
+
break;
|
|
253
|
+
case "diff_between_commits":
|
|
254
|
+
result = await diffBetweenCommits(
|
|
255
|
+
args.repoPath,
|
|
256
|
+
args.refA,
|
|
257
|
+
args.refB,
|
|
258
|
+
args.path,
|
|
259
|
+
);
|
|
260
|
+
break;
|
|
261
|
+
case "find_introducing_commit":
|
|
262
|
+
result = await findIntroducingCommit(
|
|
263
|
+
args.repoPath,
|
|
264
|
+
args.path,
|
|
265
|
+
args.testCommand,
|
|
266
|
+
args.goodRef,
|
|
267
|
+
args.badRef,
|
|
268
|
+
);
|
|
269
|
+
break;
|
|
270
|
+
default:
|
|
271
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
content: [{ type: "text", text: String(result) }],
|
|
276
|
+
};
|
|
277
|
+
} catch (error) {
|
|
278
|
+
return {
|
|
279
|
+
content: [{ type: "text", text: String(error.message || error) }],
|
|
280
|
+
isError: true,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const transport = new StdioServerTransport();
|
|
286
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pastcode",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for git history inspection, blame, diffs, and bisect-driven bug investigation for AI coding agents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pastcode": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"git",
|
|
18
|
+
"git-history",
|
|
19
|
+
"git-blame",
|
|
20
|
+
"ai-agent",
|
|
21
|
+
"claude",
|
|
22
|
+
"debugging",
|
|
23
|
+
"bug-tracing",
|
|
24
|
+
"developer-tools"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/USERNAME/pastcode.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/USERNAME/pastcode",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/USERNAME/pastcode/issues"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.17.0"
|
|
40
|
+
}
|
|
41
|
+
}
|