@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
package/stdio.ts
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Codespaces Tooling MCP Server (stdio protocol)
|
|
4
|
+
*
|
|
5
|
+
* Model Context Protocol server for monorepo tooling operations
|
|
6
|
+
* Uses stdio transport for Claude Code integration
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import {
|
|
12
|
+
CallToolRequestSchema,
|
|
13
|
+
ListToolsRequestSchema,
|
|
14
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
|
|
16
|
+
// The tooling functions are imported from @ebowwa/tooling package
|
|
17
|
+
// These will be lazy-loaded to avoid import errors
|
|
18
|
+
let toolingModule: {
|
|
19
|
+
StateManager: any;
|
|
20
|
+
SyncManager: any;
|
|
21
|
+
getGitStatus: any;
|
|
22
|
+
} | null = null;
|
|
23
|
+
|
|
24
|
+
async function loadTooling() {
|
|
25
|
+
if (toolingModule === null) {
|
|
26
|
+
try {
|
|
27
|
+
// Import from installed @ebowwa/tooling package
|
|
28
|
+
const tooling = await import("@ebowwa/tooling");
|
|
29
|
+
|
|
30
|
+
toolingModule = {
|
|
31
|
+
StateManager: tooling.StateManager,
|
|
32
|
+
SyncManager: tooling.SyncManager,
|
|
33
|
+
getGitStatus: tooling.getGitStatus,
|
|
34
|
+
};
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new Error(`Failed to load @ebowwa/tooling: ${error}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return toolingModule;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ==============
|
|
43
|
+
// MCP Tools
|
|
44
|
+
//=============
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get status of all repos in the monorepo
|
|
48
|
+
*/
|
|
49
|
+
async function statusTool(StateManager: any, SyncManager: any): Promise<string> {
|
|
50
|
+
const state = new StateManager();
|
|
51
|
+
const sync = new SyncManager(state);
|
|
52
|
+
|
|
53
|
+
const statuses = await sync.getStatus();
|
|
54
|
+
const lines = [
|
|
55
|
+
"📊 Codespaces Monorepo Status",
|
|
56
|
+
"=".repeat(40),
|
|
57
|
+
"",
|
|
58
|
+
`Phase: ${state.phase}`,
|
|
59
|
+
`Current repo: ${state.currentRepo || "none"}`,
|
|
60
|
+
"",
|
|
61
|
+
"Repos:",
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
for (const [repo, status] of Object.entries(statuses)) {
|
|
65
|
+
const dirty = status.dirty ? " ⚠️ dirty" : "";
|
|
66
|
+
const behind = status.commitsBehind ? ` ↓${status.commitsBehind}` : "";
|
|
67
|
+
const ahead = status.commitsAhead ? ` ↑${status.commitsAhead}` : "";
|
|
68
|
+
|
|
69
|
+
lines.push(` ${repo}:`);
|
|
70
|
+
lines.push(` branch: ${status.branch}${dirty}${behind}${ahead}`);
|
|
71
|
+
lines.push(` commit: ${status.lastCommit?.slice(0, 8) || "unknown"}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return lines.join("\n");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* List all discovered repos
|
|
79
|
+
*/
|
|
80
|
+
async function listReposTool(StateManager: any): Promise<string> {
|
|
81
|
+
const state = new StateManager();
|
|
82
|
+
|
|
83
|
+
const lines = [
|
|
84
|
+
"📁 Discovered Repositories",
|
|
85
|
+
"=".repeat(40),
|
|
86
|
+
"",
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
for (const [name, remote] of Object.entries(state.remotes)) {
|
|
90
|
+
lines.push(` ${name}:`);
|
|
91
|
+
lines.push(` path: ${remote.path}`);
|
|
92
|
+
lines.push(` url: ${remote.url}`);
|
|
93
|
+
lines.push(` main branch: ${remote.mainBranch}`);
|
|
94
|
+
lines.push("");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return lines.join("\n");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get the filesystem path to a repo
|
|
102
|
+
*/
|
|
103
|
+
async function getRepoPathTool(StateManager: any, repoName: string): Promise<string> {
|
|
104
|
+
const state = new StateManager();
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const path = state.getRepoPath(repoName);
|
|
108
|
+
return `Path to '${repoName}': ${path}`;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new Error(`Unknown repo: ${repoName}. Available repos: ${Object.keys(state.remotes).join(", ")}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Sync all repos or a specific repo
|
|
116
|
+
*/
|
|
117
|
+
async function syncTool(StateManager: any, SyncManager: any, repo?: string): Promise<string> {
|
|
118
|
+
const state = new StateManager();
|
|
119
|
+
const sync = new SyncManager(state);
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
let result;
|
|
123
|
+
|
|
124
|
+
if (repo) {
|
|
125
|
+
result = await sync.syncRepo(repo);
|
|
126
|
+
return `Synced ${repo}: ${result.synced ? "✓ success" : "✗ failed"}${result.error ? ` - ${result.error}` : ""}`;
|
|
127
|
+
} else {
|
|
128
|
+
result = await sync.syncAll();
|
|
129
|
+
const lines = ["🔄 Sync Results", ""];
|
|
130
|
+
|
|
131
|
+
for (const [name, r] of Object.entries(result.repos)) {
|
|
132
|
+
lines.push(` ${r.synced ? "✓" : "✗"} ${name}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (result.errors.length > 0) {
|
|
136
|
+
lines.push("", "Errors:");
|
|
137
|
+
result.errors.forEach((e: string) => lines.push(` - ${e}`));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return lines.join("\n");
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new Error(`Sync failed: ${error}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get detailed git status for a repo
|
|
149
|
+
*/
|
|
150
|
+
async function gitStatusTool(StateManager: any, getGitStatus: any, repoName: string): Promise<string> {
|
|
151
|
+
const state = new StateManager();
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const path = state.getRepoPath(repoName);
|
|
155
|
+
const status = await getGitStatus(path);
|
|
156
|
+
|
|
157
|
+
const lines = [
|
|
158
|
+
`📋 Git Status: ${repoName}`,
|
|
159
|
+
"=".repeat(40),
|
|
160
|
+
"",
|
|
161
|
+
`Branch: ${status.branch}`,
|
|
162
|
+
`Dirty: ${status.dirty ? "Yes" : "No"}`,
|
|
163
|
+
`Commit: ${status.lastCommit || "unknown"}`,
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
if (status.commitsBehind) {
|
|
167
|
+
lines.push(`Behind: ${status.commitsBehind} commits`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (status.commitsAhead) {
|
|
171
|
+
lines.push(`Ahead: ${status.commitsAhead} commits`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return lines.join("\n");
|
|
175
|
+
} catch (error) {
|
|
176
|
+
throw new Error(`Failed to get git status for '${repoName}': ${error}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ==============
|
|
181
|
+
// MCP Server
|
|
182
|
+
//=============
|
|
183
|
+
|
|
184
|
+
// Create MCP server
|
|
185
|
+
const server = new Server(
|
|
186
|
+
{
|
|
187
|
+
name: "@ebowwa/tooling-mcp",
|
|
188
|
+
version: "0.1.0",
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
capabilities: {
|
|
192
|
+
tools: {},
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// List available tools
|
|
198
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
199
|
+
return {
|
|
200
|
+
tools: [
|
|
201
|
+
{
|
|
202
|
+
name: "status",
|
|
203
|
+
description: "Get status of all repos in the monorepo",
|
|
204
|
+
inputSchema: {
|
|
205
|
+
type: "object",
|
|
206
|
+
properties: {},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "list_repos",
|
|
211
|
+
description: "List all discovered repos in the monorepo",
|
|
212
|
+
inputSchema: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "get_repo_path",
|
|
219
|
+
description: "Get the filesystem path to a specific repo",
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {
|
|
223
|
+
repo: {
|
|
224
|
+
type: "string",
|
|
225
|
+
description: "Repository name (e.g., 'packages', 'com.hetzner.codespaces')",
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
required: ["repo"],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "sync",
|
|
233
|
+
description: "Sync all repos or a specific repo",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: {
|
|
237
|
+
repo: {
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "Optional repository name to sync (if omitted, syncs all repos)",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "git_status",
|
|
246
|
+
description: "Get detailed git status for a repo",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
repo: {
|
|
251
|
+
type: "string",
|
|
252
|
+
description: "Repository name",
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
required: ["repo"],
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Handle tool calls
|
|
263
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
264
|
+
const { name, arguments: args } = request.params;
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
// Load tooling module lazily
|
|
268
|
+
const { StateManager, SyncManager, getGitStatus } = await loadTooling();
|
|
269
|
+
|
|
270
|
+
switch (name) {
|
|
271
|
+
case "status": {
|
|
272
|
+
const result = await statusTool(StateManager, SyncManager);
|
|
273
|
+
return {
|
|
274
|
+
content: [{ type: "text", text: result }],
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case "list_repos": {
|
|
279
|
+
const result = await listReposTool(StateManager);
|
|
280
|
+
return {
|
|
281
|
+
content: [{ type: "text", text: result }],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
case "get_repo_path": {
|
|
286
|
+
const repo = (args as { repo: string }).repo;
|
|
287
|
+
const result = await getRepoPathTool(StateManager, repo);
|
|
288
|
+
return {
|
|
289
|
+
content: [{ type: "text", text: result }],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case "sync": {
|
|
294
|
+
const repo = (args as { repo?: string }).repo;
|
|
295
|
+
const result = await syncTool(StateManager, SyncManager, repo);
|
|
296
|
+
return {
|
|
297
|
+
content: [{ type: "text", text: result }],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
case "git_status": {
|
|
302
|
+
const repo = (args as { repo: string }).repo;
|
|
303
|
+
const result = await gitStatusTool(StateManager, getGitStatus, repo);
|
|
304
|
+
return {
|
|
305
|
+
content: [{ type: "text", text: result }],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
default:
|
|
310
|
+
return {
|
|
311
|
+
content: [
|
|
312
|
+
{
|
|
313
|
+
type: "text",
|
|
314
|
+
text: `Unknown tool: ${name}`,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
isError: true,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
} catch (error) {
|
|
321
|
+
return {
|
|
322
|
+
content: [
|
|
323
|
+
{
|
|
324
|
+
type: "text",
|
|
325
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
isError: true,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Start server
|
|
334
|
+
async function main() {
|
|
335
|
+
const transport = new StdioServerTransport();
|
|
336
|
+
await server.connect(transport);
|
|
337
|
+
console.error("@ebowwa/tooling-mcp server running on stdio");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
main().catch((error) => {
|
|
341
|
+
console.error("Fatal error in tooling MCP server:", error);
|
|
342
|
+
process.exit(1);
|
|
343
|
+
});
|