@ebowwa/tooling-mcp 0.1.0 → 0.1.2
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/bin/tooling-mcp.js +16 -0
- package/dist/index.js +158 -0
- package/dist/stdio/stdio.js +284 -0
- package/index.js +294 -0
- package/index.ts +4 -4
- package/lmdb.db +0 -0
- package/lmdb.db-lock +0 -0
- package/package.json +17 -10
- package/stdio.js +427 -0
- package/stdio.ts +343 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Wrapper script to run tooling-mcp with bun
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const mainScript = join(__dirname, "..", "dist", "index.js");
|
|
9
|
+
|
|
10
|
+
// Run the main script with bun
|
|
11
|
+
const proc = spawn("bun", ["run", mainScript, ...process.argv.slice(2)], {
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
env: process.env
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
proc.on("exit", (code) => process.exit(code ?? 0));
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// index.ts
|
|
5
|
+
import { StateManager } from "@ebowwa/tooling/src/state";
|
|
6
|
+
import { SyncManager } from "@ebowwa/tooling/src/sync";
|
|
7
|
+
import { getGitStatus } from "@ebowwa/tooling/src/git";
|
|
8
|
+
var state = new StateManager;
|
|
9
|
+
var sync = new SyncManager(state);
|
|
10
|
+
async function statusTool() {
|
|
11
|
+
const statuses = await sync.getStatus();
|
|
12
|
+
const lines = [
|
|
13
|
+
"\uD83D\uDCCA Codespaces Monorepo Status",
|
|
14
|
+
"=".repeat(40),
|
|
15
|
+
"",
|
|
16
|
+
`Phase: ${state.phase}`,
|
|
17
|
+
`Current repo: ${state.currentRepo || "none"}`,
|
|
18
|
+
"",
|
|
19
|
+
"Repos:"
|
|
20
|
+
];
|
|
21
|
+
for (const [repo, status] of Object.entries(statuses)) {
|
|
22
|
+
const dirty = status.dirty ? " \u26A0\uFE0F dirty" : "";
|
|
23
|
+
const behind = status.commitsBehind ? ` \u2193${status.commitsBehind}` : "";
|
|
24
|
+
const ahead = status.commitsAhead ? ` \u2191${status.commitsAhead}` : "";
|
|
25
|
+
lines.push(` ${repo}:`);
|
|
26
|
+
lines.push(` branch: ${status.branch}${dirty}${behind}${ahead}`);
|
|
27
|
+
lines.push(` commit: ${status.lastCommit?.slice(0, 8) || "unknown"}`);
|
|
28
|
+
}
|
|
29
|
+
return lines.join(`
|
|
30
|
+
`);
|
|
31
|
+
}
|
|
32
|
+
async function listReposTool() {
|
|
33
|
+
const lines = [
|
|
34
|
+
"\uD83D\uDCC1 Discovered Repositories",
|
|
35
|
+
"=".repeat(40),
|
|
36
|
+
""
|
|
37
|
+
];
|
|
38
|
+
for (const [name, remote] of Object.entries(state.remotes)) {
|
|
39
|
+
lines.push(` ${name}:`);
|
|
40
|
+
lines.push(` path: ${remote.path}`);
|
|
41
|
+
lines.push(` url: ${remote.url}`);
|
|
42
|
+
lines.push(` main branch: ${remote.mainBranch}`);
|
|
43
|
+
lines.push("");
|
|
44
|
+
}
|
|
45
|
+
return lines.join(`
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
async function getRepoPathTool(repoName) {
|
|
49
|
+
try {
|
|
50
|
+
const path = state.getRepoPath(repoName);
|
|
51
|
+
return `Path to '${repoName}': ${path}`;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(`Unknown repo: ${repoName}. Available repos: ${Object.keys(state.remotes).join(", ")}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function syncTool(repo) {
|
|
57
|
+
try {
|
|
58
|
+
let result;
|
|
59
|
+
if (repo) {
|
|
60
|
+
result = await sync.syncRepo(repo);
|
|
61
|
+
return `Synced ${repo}: ${result.synced ? "\u2713 success" : "\u2717 failed"}${result.error ? ` - ${result.error}` : ""}`;
|
|
62
|
+
} else {
|
|
63
|
+
result = await sync.syncAll();
|
|
64
|
+
const lines = ["\uD83D\uDD04 Sync Results", ""];
|
|
65
|
+
for (const [name, r] of Object.entries(result.repos)) {
|
|
66
|
+
lines.push(` ${r.synced ? "\u2713" : "\u2717"} ${name}`);
|
|
67
|
+
}
|
|
68
|
+
if (result.errors.length > 0) {
|
|
69
|
+
lines.push("", "Errors:");
|
|
70
|
+
result.errors.forEach((e) => lines.push(` - ${e}`));
|
|
71
|
+
}
|
|
72
|
+
return lines.join(`
|
|
73
|
+
`);
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error(`Sync failed: ${error}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function gitStatusTool(repoName) {
|
|
80
|
+
try {
|
|
81
|
+
const path = state.getRepoPath(repoName);
|
|
82
|
+
const status = await getGitStatus(path);
|
|
83
|
+
const lines = [
|
|
84
|
+
`\uD83D\uDCCB Git Status: ${repoName}`,
|
|
85
|
+
"=".repeat(40),
|
|
86
|
+
"",
|
|
87
|
+
`Branch: ${status.branch}`,
|
|
88
|
+
`Dirty: ${status.dirty ? "Yes" : "No"}`,
|
|
89
|
+
`Commit: ${status.lastCommit || "unknown"}`
|
|
90
|
+
];
|
|
91
|
+
if (status.commitsBehind) {
|
|
92
|
+
lines.push(`Behind: ${status.commitsBehind} commits`);
|
|
93
|
+
}
|
|
94
|
+
if (status.commitsAhead) {
|
|
95
|
+
lines.push(`Ahead: ${status.commitsAhead} commits`);
|
|
96
|
+
}
|
|
97
|
+
return lines.join(`
|
|
98
|
+
`);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
throw new Error(`Failed to get git status for '${repoName}': ${error}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
var MCP_PORT = parseInt(process.env.MCP_PORT || "8912");
|
|
104
|
+
Bun.serve({
|
|
105
|
+
port: MCP_PORT,
|
|
106
|
+
fetch: async (req) => {
|
|
107
|
+
const url = new URL(req.url);
|
|
108
|
+
if (url.pathname === "/health") {
|
|
109
|
+
return Response.json({ status: "ok", port: MCP_PORT });
|
|
110
|
+
}
|
|
111
|
+
if (url.pathname === "/mcp") {
|
|
112
|
+
if (req.method === "POST") {
|
|
113
|
+
const body = await req.json();
|
|
114
|
+
const { tool, args } = body;
|
|
115
|
+
try {
|
|
116
|
+
let result;
|
|
117
|
+
switch (tool) {
|
|
118
|
+
case "status":
|
|
119
|
+
result = await statusTool();
|
|
120
|
+
break;
|
|
121
|
+
case "list_repos":
|
|
122
|
+
result = await listReposTool();
|
|
123
|
+
break;
|
|
124
|
+
case "get_repo_path":
|
|
125
|
+
result = await getRepoPathTool(args?.repo);
|
|
126
|
+
break;
|
|
127
|
+
case "sync":
|
|
128
|
+
result = await syncTool(args?.repo);
|
|
129
|
+
break;
|
|
130
|
+
case "git_status":
|
|
131
|
+
result = await gitStatusTool(args?.repo);
|
|
132
|
+
break;
|
|
133
|
+
default:
|
|
134
|
+
return Response.json({ error: `Unknown tool: ${tool}` }, { status: 400 });
|
|
135
|
+
}
|
|
136
|
+
return Response.json({ result });
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return Response.json({ error: String(error) }, { status: 500 });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Response.json({
|
|
142
|
+
name: "tooling-mcp",
|
|
143
|
+
version: "0.1.0",
|
|
144
|
+
tools: [
|
|
145
|
+
{ name: "status", description: "Get status of all repos in the monorepo" },
|
|
146
|
+
{ name: "list_repos", description: "List all discovered repos" },
|
|
147
|
+
{ name: "get_repo_path", description: "Get filesystem path to a repo", args: ["repo"] },
|
|
148
|
+
{ name: "sync", description: "Sync all repos or a specific repo", args: ["repo?"] },
|
|
149
|
+
{ name: "git_status", description: "Get detailed git status for a repo", args: ["repo"] }
|
|
150
|
+
]
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return Response.json({ error: "Not found" }, { status: 404 });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
console.log(`\uD83D\uDE80 Tooling MCP Server running on port ${MCP_PORT}`);
|
|
157
|
+
console.log(` Health: http://localhost:${MCP_PORT}/health`);
|
|
158
|
+
console.log(` MCP: http://localhost:${MCP_PORT}/mcp`);
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __require = import.meta.require;
|
|
20
|
+
|
|
21
|
+
// stdio.ts
|
|
22
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
23
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
24
|
+
import {
|
|
25
|
+
CallToolRequestSchema,
|
|
26
|
+
ListToolsRequestSchema
|
|
27
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
28
|
+
var toolingModule = null;
|
|
29
|
+
async function loadTooling() {
|
|
30
|
+
if (toolingModule === null) {
|
|
31
|
+
try {
|
|
32
|
+
const tooling = await import("@ebowwa/tooling");
|
|
33
|
+
toolingModule = {
|
|
34
|
+
StateManager: tooling.StateManager,
|
|
35
|
+
SyncManager: tooling.SyncManager,
|
|
36
|
+
getGitStatus: tooling.getGitStatus
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(`Failed to load @ebowwa/tooling: ${error}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return toolingModule;
|
|
43
|
+
}
|
|
44
|
+
async function statusTool(StateManager, SyncManager) {
|
|
45
|
+
const state = new StateManager;
|
|
46
|
+
const sync = new SyncManager(state);
|
|
47
|
+
const statuses = await sync.getStatus();
|
|
48
|
+
const lines = [
|
|
49
|
+
"\uD83D\uDCCA Codespaces Monorepo Status",
|
|
50
|
+
"=".repeat(40),
|
|
51
|
+
"",
|
|
52
|
+
`Phase: ${state.phase}`,
|
|
53
|
+
`Current repo: ${state.currentRepo || "none"}`,
|
|
54
|
+
"",
|
|
55
|
+
"Repos:"
|
|
56
|
+
];
|
|
57
|
+
for (const [repo, status] of Object.entries(statuses)) {
|
|
58
|
+
const dirty = status.dirty ? " \u26A0\uFE0F dirty" : "";
|
|
59
|
+
const behind = status.commitsBehind ? ` \u2193${status.commitsBehind}` : "";
|
|
60
|
+
const ahead = status.commitsAhead ? ` \u2191${status.commitsAhead}` : "";
|
|
61
|
+
lines.push(` ${repo}:`);
|
|
62
|
+
lines.push(` branch: ${status.branch}${dirty}${behind}${ahead}`);
|
|
63
|
+
lines.push(` commit: ${status.lastCommit?.slice(0, 8) || "unknown"}`);
|
|
64
|
+
}
|
|
65
|
+
return lines.join(`
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
async function listReposTool(StateManager) {
|
|
69
|
+
const state = new StateManager;
|
|
70
|
+
const lines = [
|
|
71
|
+
"\uD83D\uDCC1 Discovered Repositories",
|
|
72
|
+
"=".repeat(40),
|
|
73
|
+
""
|
|
74
|
+
];
|
|
75
|
+
for (const [name, remote] of Object.entries(state.remotes)) {
|
|
76
|
+
lines.push(` ${name}:`);
|
|
77
|
+
lines.push(` path: ${remote.path}`);
|
|
78
|
+
lines.push(` url: ${remote.url}`);
|
|
79
|
+
lines.push(` main branch: ${remote.mainBranch}`);
|
|
80
|
+
lines.push("");
|
|
81
|
+
}
|
|
82
|
+
return lines.join(`
|
|
83
|
+
`);
|
|
84
|
+
}
|
|
85
|
+
async function getRepoPathTool(StateManager, repoName) {
|
|
86
|
+
const state = new StateManager;
|
|
87
|
+
try {
|
|
88
|
+
const path = state.getRepoPath(repoName);
|
|
89
|
+
return `Path to '${repoName}': ${path}`;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw new Error(`Unknown repo: ${repoName}. Available repos: ${Object.keys(state.remotes).join(", ")}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function syncTool(StateManager, SyncManager, repo) {
|
|
95
|
+
const state = new StateManager;
|
|
96
|
+
const sync = new SyncManager(state);
|
|
97
|
+
try {
|
|
98
|
+
let result;
|
|
99
|
+
if (repo) {
|
|
100
|
+
result = await sync.syncRepo(repo);
|
|
101
|
+
return `Synced ${repo}: ${result.synced ? "\u2713 success" : "\u2717 failed"}${result.error ? ` - ${result.error}` : ""}`;
|
|
102
|
+
} else {
|
|
103
|
+
result = await sync.syncAll();
|
|
104
|
+
const lines = ["\uD83D\uDD04 Sync Results", ""];
|
|
105
|
+
for (const [name, r] of Object.entries(result.repos)) {
|
|
106
|
+
lines.push(` ${r.synced ? "\u2713" : "\u2717"} ${name}`);
|
|
107
|
+
}
|
|
108
|
+
if (result.errors.length > 0) {
|
|
109
|
+
lines.push("", "Errors:");
|
|
110
|
+
result.errors.forEach((e) => lines.push(` - ${e}`));
|
|
111
|
+
}
|
|
112
|
+
return lines.join(`
|
|
113
|
+
`);
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
throw new Error(`Sync failed: ${error}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function gitStatusTool(StateManager, getGitStatus, repoName) {
|
|
120
|
+
const state = new StateManager;
|
|
121
|
+
try {
|
|
122
|
+
const path = state.getRepoPath(repoName);
|
|
123
|
+
const status = await getGitStatus(path);
|
|
124
|
+
const lines = [
|
|
125
|
+
`\uD83D\uDCCB Git Status: ${repoName}`,
|
|
126
|
+
"=".repeat(40),
|
|
127
|
+
"",
|
|
128
|
+
`Branch: ${status.branch}`,
|
|
129
|
+
`Dirty: ${status.dirty ? "Yes" : "No"}`,
|
|
130
|
+
`Commit: ${status.lastCommit || "unknown"}`
|
|
131
|
+
];
|
|
132
|
+
if (status.commitsBehind) {
|
|
133
|
+
lines.push(`Behind: ${status.commitsBehind} commits`);
|
|
134
|
+
}
|
|
135
|
+
if (status.commitsAhead) {
|
|
136
|
+
lines.push(`Ahead: ${status.commitsAhead} commits`);
|
|
137
|
+
}
|
|
138
|
+
return lines.join(`
|
|
139
|
+
`);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
throw new Error(`Failed to get git status for '${repoName}': ${error}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
var server = new Server({
|
|
145
|
+
name: "@ebowwa/tooling-mcp",
|
|
146
|
+
version: "0.1.0"
|
|
147
|
+
}, {
|
|
148
|
+
capabilities: {
|
|
149
|
+
tools: {}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
153
|
+
return {
|
|
154
|
+
tools: [
|
|
155
|
+
{
|
|
156
|
+
name: "status",
|
|
157
|
+
description: "Get status of all repos in the monorepo",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "list_repos",
|
|
165
|
+
description: "List all discovered repos in the monorepo",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "get_repo_path",
|
|
173
|
+
description: "Get the filesystem path to a specific repo",
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: "object",
|
|
176
|
+
properties: {
|
|
177
|
+
repo: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "Repository name (e.g., 'packages', 'com.hetzner.codespaces')"
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
required: ["repo"]
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "sync",
|
|
187
|
+
description: "Sync all repos or a specific repo",
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: {
|
|
191
|
+
repo: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "Optional repository name to sync (if omitted, syncs all repos)"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "git_status",
|
|
200
|
+
description: "Get detailed git status for a repo",
|
|
201
|
+
inputSchema: {
|
|
202
|
+
type: "object",
|
|
203
|
+
properties: {
|
|
204
|
+
repo: {
|
|
205
|
+
type: "string",
|
|
206
|
+
description: "Repository name"
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
required: ["repo"]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
216
|
+
const { name, arguments: args } = request.params;
|
|
217
|
+
try {
|
|
218
|
+
const { StateManager, SyncManager, getGitStatus } = await loadTooling();
|
|
219
|
+
switch (name) {
|
|
220
|
+
case "status": {
|
|
221
|
+
const result = await statusTool(StateManager, SyncManager);
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: "text", text: result }]
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
case "list_repos": {
|
|
227
|
+
const result = await listReposTool(StateManager);
|
|
228
|
+
return {
|
|
229
|
+
content: [{ type: "text", text: result }]
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
case "get_repo_path": {
|
|
233
|
+
const repo = args.repo;
|
|
234
|
+
const result = await getRepoPathTool(StateManager, repo);
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: "text", text: result }]
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
case "sync": {
|
|
240
|
+
const repo = args.repo;
|
|
241
|
+
const result = await syncTool(StateManager, SyncManager, repo);
|
|
242
|
+
return {
|
|
243
|
+
content: [{ type: "text", text: result }]
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
case "git_status": {
|
|
247
|
+
const repo = args.repo;
|
|
248
|
+
const result = await gitStatusTool(StateManager, getGitStatus, repo);
|
|
249
|
+
return {
|
|
250
|
+
content: [{ type: "text", text: result }]
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
default:
|
|
254
|
+
return {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: `Unknown tool: ${name}`
|
|
259
|
+
}
|
|
260
|
+
],
|
|
261
|
+
isError: true
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
return {
|
|
266
|
+
content: [
|
|
267
|
+
{
|
|
268
|
+
type: "text",
|
|
269
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
isError: true
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
async function main() {
|
|
277
|
+
const transport = new StdioServerTransport;
|
|
278
|
+
await server.connect(transport);
|
|
279
|
+
console.error("@ebowwa/tooling-mcp server running on stdio");
|
|
280
|
+
}
|
|
281
|
+
main().catch((error) => {
|
|
282
|
+
console.error("Fatal error in tooling MCP server:", error);
|
|
283
|
+
process.exit(1);
|
|
284
|
+
});
|