mcp-copilot-worker 1.0.1

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.
Files changed (87) hide show
  1. package/README.md +209 -0
  2. package/bin/mcp-copilot-worker.mjs +3 -0
  3. package/dist/src/app.d.ts +71 -0
  4. package/dist/src/app.js +306 -0
  5. package/dist/src/app.js.map +1 -0
  6. package/dist/src/cli/doctor.d.ts +2 -0
  7. package/dist/src/cli/doctor.js +99 -0
  8. package/dist/src/cli/doctor.js.map +1 -0
  9. package/dist/src/config/defaults.d.ts +3 -0
  10. package/dist/src/config/defaults.js +4 -0
  11. package/dist/src/config/defaults.js.map +1 -0
  12. package/dist/src/index.d.ts +2 -0
  13. package/dist/src/index.js +98 -0
  14. package/dist/src/index.js.map +1 -0
  15. package/dist/src/mcp/system-status.d.ts +19 -0
  16. package/dist/src/mcp/system-status.js +20 -0
  17. package/dist/src/mcp/system-status.js.map +1 -0
  18. package/dist/src/mcp/task-markdown.d.ts +3 -0
  19. package/dist/src/mcp/task-markdown.js +70 -0
  20. package/dist/src/mcp/task-markdown.js.map +1 -0
  21. package/dist/src/mcp/tool-banners.d.ts +4 -0
  22. package/dist/src/mcp/tool-banners.js +26 -0
  23. package/dist/src/mcp/tool-banners.js.map +1 -0
  24. package/dist/src/mcp/tool-definitions.d.ts +111 -0
  25. package/dist/src/mcp/tool-definitions.js +134 -0
  26. package/dist/src/mcp/tool-definitions.js.map +1 -0
  27. package/dist/src/services/copilot-runtime.d.ts +35 -0
  28. package/dist/src/services/copilot-runtime.js +237 -0
  29. package/dist/src/services/copilot-runtime.js.map +1 -0
  30. package/dist/src/services/fleet-mode.d.ts +15 -0
  31. package/dist/src/services/fleet-mode.js +26 -0
  32. package/dist/src/services/fleet-mode.js.map +1 -0
  33. package/dist/src/services/model-catalog.d.ts +16 -0
  34. package/dist/src/services/model-catalog.js +95 -0
  35. package/dist/src/services/model-catalog.js.map +1 -0
  36. package/dist/src/services/output-log.d.ts +7 -0
  37. package/dist/src/services/output-log.js +59 -0
  38. package/dist/src/services/output-log.js.map +1 -0
  39. package/dist/src/services/profile-manager.d.ts +34 -0
  40. package/dist/src/services/profile-manager.js +113 -0
  41. package/dist/src/services/profile-manager.js.map +1 -0
  42. package/dist/src/services/question-registry.d.ts +29 -0
  43. package/dist/src/services/question-registry.js +115 -0
  44. package/dist/src/services/question-registry.js.map +1 -0
  45. package/dist/src/services/spawn-validation.d.ts +9 -0
  46. package/dist/src/services/spawn-validation.js +53 -0
  47. package/dist/src/services/spawn-validation.js.map +1 -0
  48. package/dist/src/services/task-manager.d.ts +34 -0
  49. package/dist/src/services/task-manager.js +107 -0
  50. package/dist/src/services/task-manager.js.map +1 -0
  51. package/dist/src/services/task-persistence.d.ts +20 -0
  52. package/dist/src/services/task-persistence.js +67 -0
  53. package/dist/src/services/task-persistence.js.map +1 -0
  54. package/dist/src/services/task-store.d.ts +12 -0
  55. package/dist/src/services/task-store.js +167 -0
  56. package/dist/src/services/task-store.js.map +1 -0
  57. package/dist/src/services/workspace-isolation.d.ts +13 -0
  58. package/dist/src/services/workspace-isolation.js +74 -0
  59. package/dist/src/services/workspace-isolation.js.map +1 -0
  60. package/dist/src/templates/agent-prompt.d.ts +6 -0
  61. package/dist/src/templates/agent-prompt.js +58 -0
  62. package/dist/src/templates/agent-prompt.js.map +1 -0
  63. package/dist/src/types/task.d.ts +79 -0
  64. package/dist/src/types/task.js +22 -0
  65. package/dist/src/types/task.js.map +1 -0
  66. package/package.json +63 -0
  67. package/src/app.ts +344 -0
  68. package/src/cli/doctor.ts +112 -0
  69. package/src/config/defaults.ts +3 -0
  70. package/src/index.ts +128 -0
  71. package/src/mcp/system-status.ts +41 -0
  72. package/src/mcp/task-markdown.ts +81 -0
  73. package/src/mcp/tool-banners.ts +32 -0
  74. package/src/mcp/tool-definitions.ts +163 -0
  75. package/src/services/copilot-runtime.ts +305 -0
  76. package/src/services/fleet-mode.ts +39 -0
  77. package/src/services/model-catalog.ts +136 -0
  78. package/src/services/output-log.ts +68 -0
  79. package/src/services/profile-manager.ts +165 -0
  80. package/src/services/question-registry.ts +169 -0
  81. package/src/services/spawn-validation.ts +75 -0
  82. package/src/services/task-manager.ts +144 -0
  83. package/src/services/task-persistence.ts +100 -0
  84. package/src/services/task-store.ts +207 -0
  85. package/src/services/workspace-isolation.ts +100 -0
  86. package/src/templates/agent-prompt.ts +71 -0
  87. package/src/types/task.ts +95 -0
package/src/app.ts ADDED
@@ -0,0 +1,344 @@
1
+ import { OutputLogService } from './services/output-log.js';
2
+ import { ProfileManager } from './services/profile-manager.js';
3
+ import { QuestionRegistry } from './services/question-registry.js';
4
+ import { TaskManager } from './services/task-manager.js';
5
+ import { CopilotRuntime } from './services/copilot-runtime.js';
6
+ import { prepareTaskWorkspace } from './services/workspace-isolation.js';
7
+ import { DEFAULT_ISOLATION_MODE, DEFAULT_TIMEOUT_MS } from './config/defaults.js';
8
+ import { buildAnswerBanner, buildMessageBanner } from './mcp/tool-banners.js';
9
+ import { createToolDefinitions, type AnswerAgentInput, type CancelAgentInput, type MessageAgentInput, type SpawnAgentInput } from './mcp/tool-definitions.js';
10
+ import { renderTaskListMarkdown, renderTaskMarkdown } from './mcp/task-markdown.js';
11
+ import { buildSystemStatus } from './mcp/system-status.js';
12
+ import type { ContextFile, TaskRecord } from './types/task.js';
13
+ import { TaskStatus } from './types/task.js';
14
+ import { validateSpawnAgentInput } from './services/spawn-validation.js';
15
+
16
+ export class CopilotMcpApp {
17
+ readonly outputLog = new OutputLogService();
18
+ readonly profileManager = ProfileManager.fromEnvironment();
19
+ readonly taskManager = new TaskManager({
20
+ outputLog: this.outputLog,
21
+ getProfiles: () => this.profileManager.toPersistedState(),
22
+ });
23
+ readonly questionRegistry = new QuestionRegistry(this.taskManager);
24
+ readonly runtime = new CopilotRuntime({
25
+ taskManager: this.taskManager,
26
+ profileManager: this.profileManager,
27
+ questionRegistry: this.questionRegistry,
28
+ });
29
+
30
+ async initialize(cwd: string): Promise<void> {
31
+ const restored = await this.taskManager.load(cwd);
32
+ this.profileManager.hydrate(restored.profiles);
33
+ }
34
+
35
+ async listTools() {
36
+ const modelField = await this.runtime.describeCurrentProfileModels(process.cwd());
37
+ return createToolDefinitions({
38
+ messageBanner: buildMessageBanner(this.taskManager),
39
+ answerBanner: buildAnswerBanner(this.questionRegistry),
40
+ modelSuggestions: modelField.latestModelIds,
41
+ modelDescription: modelField.description,
42
+ });
43
+ }
44
+
45
+ async spawnAgent(input: SpawnAgentInput): Promise<string> {
46
+ const baseCwd = input.cwd ?? process.cwd();
47
+ const validated = await validateSpawnAgentInput({ input, baseCwd });
48
+ const isolationMode = input.isolation_mode ?? DEFAULT_ISOLATION_MODE;
49
+ const workspace = await prepareTaskWorkspace({
50
+ baseCwd,
51
+ taskId: `${Date.now().toString(36)}-workspace`,
52
+ isolationMode,
53
+ });
54
+
55
+ const task = await this.taskManager.createTask({
56
+ prompt: input.prompt,
57
+ cwd: workspace.cwd,
58
+ baseCwd,
59
+ model: await this.runtime.resolveRequestedModel(baseCwd, input.model),
60
+ agentType: input.agent_type,
61
+ labels: input.labels,
62
+ contextFiles: validated.contextFiles,
63
+ dependsOn: input.depends_on,
64
+ timeoutMs: input.timeout ?? DEFAULT_TIMEOUT_MS,
65
+ isolationMode,
66
+ });
67
+
68
+ if (task.status === TaskStatus.WAITING) {
69
+ return [
70
+ '✅ **Task queued (waiting for dependencies)**',
71
+ `task_id: \`${task.id}\``,
72
+ task.outputFilePath ? `output_file: \`${task.outputFilePath}\`` : null,
73
+ '',
74
+ '**Monitor:** Read `task:///all` — shows all tasks with status and pending questions in one table.',
75
+ ].filter(Boolean).join('\n');
76
+ }
77
+
78
+ void this.startTask(task.id).catch(() => {});
79
+
80
+ return [
81
+ '✅ **Task launched**',
82
+ `task_id: \`${task.id}\``,
83
+ task.outputFilePath ? `read logs: \`cat -n ${task.outputFilePath}\`` : null,
84
+ task.outputFilePath ? 'Use `cat -n` to read with line numbers, then on subsequent reads use `tail -n +<N>` to skip already-read lines.' : null,
85
+ '',
86
+ '**What to do next:**',
87
+ '- If you still have more agents to launch, launch them now — all agents run in parallel.',
88
+ '- Once all agents are launched, run `sleep 30` and then check status.',
89
+ `- To check status, read the MCP resource \`task:///${task.id}\` — it will show current progress, output, and whether the agent needs input.`,
90
+ task.outputFilePath ? `- For a quick progress check without reading the full resource, run \`wc -l ${task.outputFilePath}\` — a growing line count means the agent is still working.` : null,
91
+ ].filter(Boolean).join('\n');
92
+ }
93
+
94
+ async messageAgent(input: MessageAgentInput): Promise<string> {
95
+ const original = this.taskManager.getTask(input.task_id);
96
+ if (!original) {
97
+ throw new Error(`Task not found: ${input.task_id}`);
98
+ }
99
+ if (!original.sessionId) {
100
+ throw new Error(`Task ${input.task_id} has no resumable session`);
101
+ }
102
+
103
+ const task = await this.taskManager.createTask({
104
+ prompt: input.message ?? 'continue',
105
+ cwd: input.cwd ?? original.cwd,
106
+ baseCwd: input.cwd ?? original.baseCwd ?? original.cwd,
107
+ model: original.model,
108
+ agentType: original.agentType,
109
+ labels: [...original.labels, `continued-from:${original.id}`],
110
+ contextFiles: original.contextFiles,
111
+ timeoutMs: input.timeout ?? original.timeoutMs ?? DEFAULT_TIMEOUT_MS,
112
+ sessionId: original.sessionId,
113
+ parentTaskId: original.id,
114
+ profileId: original.profileId,
115
+ profileConfigDir: original.profileConfigDir,
116
+ isolationMode: original.isolationMode,
117
+ });
118
+
119
+ void this.startTask(task.id).catch(() => {});
120
+
121
+ return [
122
+ '✅ **Message sent**',
123
+ `task_id: \`${task.id}\``,
124
+ '',
125
+ task.outputFilePath ? `read logs: \`cat -n ${task.outputFilePath}\`` : null,
126
+ task.outputFilePath ? 'Use `cat -n` to read with line numbers, then on subsequent reads use `tail -n +<N>` to skip already-read lines.' : null,
127
+ ].filter(Boolean).join('\n');
128
+ }
129
+
130
+ async cancelAgent(input: CancelAgentInput): Promise<string> {
131
+ if (input.task_id === 'all') {
132
+ if (!input.clear || !input.confirm) {
133
+ throw new Error('Use clear=true and confirm=true with task_id="all"');
134
+ }
135
+
136
+ const count = await this.taskManager.clearAllTasks();
137
+ return `✅ Cleared **${count}** tasks from workspace.`;
138
+ }
139
+
140
+ const taskIds = Array.isArray(input.task_id) ? input.task_id : [input.task_id];
141
+ const results = await Promise.all(taskIds.map((taskId) => this.taskManager.cancelTask(taskId)));
142
+ return `✅ Cancelled ${results.filter(Boolean).length}/${taskIds.length} task(s).`;
143
+ }
144
+
145
+ async answerAgent(input: AnswerAgentInput): Promise<string> {
146
+ const rawAnswer = input.answer ?? Object.values(input.answers ?? {})[0];
147
+ if (!rawAnswer) {
148
+ throw new Error('Provide answer or answers');
149
+ }
150
+
151
+ const result = this.questionRegistry.submitAnswer(input.task_id, rawAnswer);
152
+ if (!result.success) {
153
+ throw new Error(result.error);
154
+ }
155
+
156
+ return [
157
+ '✅ **Answer submitted**',
158
+ `task_id: \`${input.task_id}\``,
159
+ `**Answer:** ${result.resolvedAnswer}`,
160
+ ].join('\n');
161
+ }
162
+
163
+ listResources() {
164
+ return [
165
+ {
166
+ uri: 'system:///status',
167
+ name: 'System Status',
168
+ description: 'Current task and profile counts',
169
+ mimeType: 'application/json',
170
+ },
171
+ {
172
+ uri: 'task:///all',
173
+ name: 'All Tasks',
174
+ description: `${this.taskManager.getAllTasks().length} tracked tasks`,
175
+ mimeType: 'text/markdown',
176
+ },
177
+ ...this.taskManager.getAllTasks().flatMap((task) => ([
178
+ {
179
+ uri: `task:///${task.id}`,
180
+ name: task.id,
181
+ description: task.status,
182
+ mimeType: 'text/markdown',
183
+ },
184
+ {
185
+ uri: `task:///${task.id}/session`,
186
+ name: `${task.id} session`,
187
+ description: `Execution log for ${task.id}`,
188
+ mimeType: 'application/json',
189
+ },
190
+ ])),
191
+ ];
192
+ }
193
+
194
+ async readResource(uri: string): Promise<{ mimeType: string; text: string }> {
195
+ if (uri === 'system:///status') {
196
+ return {
197
+ mimeType: 'application/json',
198
+ text: JSON.stringify(
199
+ buildSystemStatus({
200
+ tasks: this.taskManager.getAllTasks(),
201
+ profiles: this.profileManager.getProfiles(),
202
+ }),
203
+ null,
204
+ 2,
205
+ ),
206
+ };
207
+ }
208
+
209
+ if (uri === 'task:///all') {
210
+ return {
211
+ mimeType: 'text/markdown',
212
+ text: renderTaskListMarkdown(this.taskManager.getAllTasks()),
213
+ };
214
+ }
215
+
216
+ if (uri.startsWith('task:///') && uri.endsWith('/session')) {
217
+ const taskId = uri.slice('task:///'.length, -'/session'.length);
218
+ const task = this.getTaskOrThrow(taskId);
219
+ return {
220
+ mimeType: 'application/json',
221
+ text: JSON.stringify({
222
+ taskId: task.id,
223
+ status: task.status,
224
+ output: task.output,
225
+ }),
226
+ };
227
+ }
228
+
229
+ if (uri.startsWith('task:///')) {
230
+ const taskId = uri.slice('task:///'.length);
231
+ const task = this.getTaskOrThrow(taskId);
232
+ return {
233
+ mimeType: 'text/markdown',
234
+ text: renderTaskMarkdown(task),
235
+ };
236
+ }
237
+
238
+ throw new Error(`Unknown resource URI: ${uri}`);
239
+ }
240
+
241
+ async shutdown(): Promise<void> {
242
+ await this.runtime.shutdown();
243
+ }
244
+
245
+ listTaskSummaries() {
246
+ return this.taskManager.getAllTasks().map((task) => this.toMcpTask(task));
247
+ }
248
+
249
+ getTaskSummary(taskId: string) {
250
+ return this.toMcpTask(this.getTaskOrThrow(taskId));
251
+ }
252
+
253
+ getTaskPayload(taskId: string) {
254
+ const task = this.getTaskOrThrow(taskId);
255
+ return {
256
+ taskId: task.id,
257
+ status: task.status,
258
+ output: task.output,
259
+ error: task.error,
260
+ outputFilePath: task.outputFilePath,
261
+ pendingQuestion: task.pendingQuestion,
262
+ };
263
+ }
264
+
265
+ async cancelTaskById(taskId: string) {
266
+ const cancelled = await this.taskManager.cancelTask(taskId);
267
+ if (!cancelled && !this.taskManager.getTask(taskId)) {
268
+ throw new Error(`Task not found: ${taskId}`);
269
+ }
270
+ return this.getTaskSummary(taskId);
271
+ }
272
+
273
+ private async startTask(taskId: string): Promise<void> {
274
+ const task = this.taskManager.getTask(taskId);
275
+ if (!task) {
276
+ return;
277
+ }
278
+
279
+ try {
280
+ await this.runtime.runTask(task);
281
+ const ready = await this.taskManager.releaseReadyTasks();
282
+ await Promise.all(ready.map((released) => this.startTask(released.id)));
283
+ } catch (error) {
284
+ await this.taskManager.updateTask(taskId, {
285
+ status: TaskStatus.FAILED,
286
+ error: error instanceof Error ? error.message : String(error),
287
+ endTime: new Date().toISOString(),
288
+ }).catch(() => {});
289
+ }
290
+ }
291
+
292
+ private getTaskOrThrow(taskId: string) {
293
+ const task = this.taskManager.getTask(taskId);
294
+ if (!task) {
295
+ throw new Error(`Task not found: ${taskId}`);
296
+ }
297
+ return task;
298
+ }
299
+
300
+ private toMcpTask(task: TaskRecord) {
301
+ return {
302
+ taskId: task.id,
303
+ status: this.toMcpTaskStatus(task.status),
304
+ statusMessage: this.buildTaskStatusMessage(task),
305
+ createdAt: task.createdAt,
306
+ lastUpdatedAt: task.updatedAt,
307
+ ttl: task.timeoutMs ?? null,
308
+ pollInterval: 2_000,
309
+ };
310
+ }
311
+
312
+ private toMcpTaskStatus(status: TaskStatus): 'working' | 'input_required' | 'completed' | 'failed' | 'cancelled' {
313
+ switch (status) {
314
+ case TaskStatus.WAITING_ANSWER:
315
+ return 'input_required';
316
+ case TaskStatus.COMPLETED:
317
+ return 'completed';
318
+ case TaskStatus.CANCELLED:
319
+ return 'cancelled';
320
+ case TaskStatus.FAILED:
321
+ case TaskStatus.TIMED_OUT:
322
+ case TaskStatus.RATE_LIMITED:
323
+ return 'failed';
324
+ default:
325
+ return 'working';
326
+ }
327
+ }
328
+
329
+ private buildTaskStatusMessage(task: TaskRecord): string | undefined {
330
+ if (task.pendingQuestion?.question) {
331
+ return task.pendingQuestion.question;
332
+ }
333
+ if (task.status === TaskStatus.WAITING && (task.dependsOn?.length ?? 0) > 0) {
334
+ return `Waiting for dependencies: ${(task.dependsOn ?? []).join(', ')}`;
335
+ }
336
+ if (task.error) {
337
+ return task.error;
338
+ }
339
+ if (task.status === TaskStatus.COMPLETED) {
340
+ return 'Task completed';
341
+ }
342
+ return undefined;
343
+ }
344
+ }
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFile } from 'node:child_process';
4
+ import process from 'node:process';
5
+ import { promisify } from 'node:util';
6
+
7
+ import { CopilotClient } from '@github/copilot-sdk';
8
+
9
+ import { buildCopilotClientOptions } from '../services/copilot-runtime.js';
10
+ import { ProfileManager } from '../services/profile-manager.js';
11
+
12
+ const execFileAsync = promisify(execFile);
13
+
14
+ async function commandVersion(command: string, args: string[] = ['--version']): Promise<string | null> {
15
+ try {
16
+ const { stdout, stderr } = await execFileAsync(command, args, { encoding: 'utf8' });
17
+ return (stdout || stderr).trim() || null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ async function inspectProfiles() {
24
+ const profileManager = ProfileManager.fromEnvironment();
25
+ const profiles = profileManager.getProfiles();
26
+
27
+ return Promise.all(
28
+ profiles.map(async (profile) => {
29
+ const client = new CopilotClient(
30
+ buildCopilotClientOptions({ cwd: process.cwd(), profile }),
31
+ );
32
+
33
+ try {
34
+ await client.start();
35
+ const auth = await client.getAuthStatus();
36
+ const models = auth.isAuthenticated
37
+ ? await client.listModels().catch(() => [])
38
+ : [];
39
+
40
+ return {
41
+ id: profile.id,
42
+ configDir: profile.configDir,
43
+ authenticated: auth.isAuthenticated,
44
+ login: auth.login,
45
+ models: models.slice(0, 10).map((model) => model.id),
46
+ };
47
+ } catch (error) {
48
+ return {
49
+ id: profile.id,
50
+ configDir: profile.configDir,
51
+ authenticated: false,
52
+ error: error instanceof Error ? error.message : String(error),
53
+ models: [],
54
+ };
55
+ } finally {
56
+ await client.stop().catch(async () => {
57
+ await client.forceStop().catch(() => {});
58
+ });
59
+ }
60
+ }),
61
+ );
62
+ }
63
+
64
+ async function main(): Promise<void> {
65
+ const json = process.argv.includes('--json');
66
+ const inspectedProfiles = await inspectProfiles();
67
+ const report = {
68
+ node: process.version,
69
+ copilot: await commandVersion('copilot'),
70
+ mcpc: await commandVersion('mcpc'),
71
+ cwd: process.cwd(),
72
+ copilotCliPath: buildCopilotClientOptions({
73
+ cwd: process.cwd(),
74
+ profile: {
75
+ id: 'doctor',
76
+ configDir: process.env.COPILOT_CONFIG_DIRS?.split(',')[0]?.trim() || `${process.env.HOME}/.copilot`,
77
+ failureCount: 0,
78
+ },
79
+ }).cliPath,
80
+ profiles: inspectedProfiles,
81
+ };
82
+
83
+ if (json) {
84
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
85
+ return;
86
+ }
87
+
88
+ process.stdout.write(`Node: ${report.node}\n`);
89
+ process.stdout.write(`Copilot CLI: ${report.copilot ?? 'not found'}\n`);
90
+ process.stdout.write(`Copilot CLI Path: ${report.copilotCliPath}\n`);
91
+ process.stdout.write(`mcpc: ${report.mcpc ?? 'not found'}\n`);
92
+ process.stdout.write(`cwd: ${report.cwd}\n`);
93
+ process.stdout.write('\nProfiles:\n');
94
+ for (const profile of report.profiles) {
95
+ process.stdout.write(`- ${profile.id} (${profile.configDir}) authenticated=${String(profile.authenticated)}`);
96
+ if ('login' in profile && profile.login) {
97
+ process.stdout.write(` login=${profile.login}`);
98
+ }
99
+ if ('error' in profile && profile.error) {
100
+ process.stdout.write(` error=${profile.error}`);
101
+ }
102
+ process.stdout.write('\n');
103
+ if (profile.models.length > 0) {
104
+ process.stdout.write(` models: ${profile.models.join(', ')}\n`);
105
+ }
106
+ }
107
+ }
108
+
109
+ main().catch((error) => {
110
+ console.error(error);
111
+ process.exit(1);
112
+ });
@@ -0,0 +1,3 @@
1
+ export const DEFAULT_MODEL = 'gpt-5.4';
2
+ export const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
3
+ export const DEFAULT_ISOLATION_MODE = 'isolated';
package/src/index.ts ADDED
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createRequire } from 'node:module';
4
+
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import {
8
+ CancelTaskRequestSchema,
9
+ CallToolRequestSchema,
10
+ GetTaskPayloadRequestSchema,
11
+ GetTaskRequestSchema,
12
+ ListTasksRequestSchema,
13
+ ListResourcesRequestSchema,
14
+ ListToolsRequestSchema,
15
+ ReadResourceRequestSchema,
16
+ } from '@modelcontextprotocol/sdk/types.js';
17
+
18
+ import { CopilotMcpApp } from './app.js';
19
+
20
+ const require = createRequire(import.meta.url);
21
+ const pkg = require('../package.json') as { version: string };
22
+
23
+ const app = new CopilotMcpApp();
24
+ const server = new Server(
25
+ { name: 'mcp-copilot-worker', version: pkg.version },
26
+ {
27
+ capabilities: {
28
+ tools: {},
29
+ resources: {},
30
+ tasks: {
31
+ list: {},
32
+ cancel: {},
33
+ },
34
+ },
35
+ },
36
+ );
37
+
38
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
39
+ tools: (await app.listTools()).map((tool) => ({
40
+ name: tool.name,
41
+ description: tool.description,
42
+ inputSchema: tool.inputSchema,
43
+ })),
44
+ }));
45
+
46
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
47
+ const tools = await app.listTools();
48
+ const tool = tools.find((candidate) => candidate.name === request.params.name);
49
+ if (!tool) {
50
+ return {
51
+ content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
52
+ isError: true as const,
53
+ };
54
+ }
55
+
56
+ try {
57
+ const args = tool.validate(request.params.arguments ?? {});
58
+
59
+ switch (tool.name) {
60
+ case 'spawn-agent':
61
+ return { content: [{ type: 'text', text: await app.spawnAgent(args as never) }] };
62
+ case 'message-agent':
63
+ return { content: [{ type: 'text', text: await app.messageAgent(args as never) }] };
64
+ case 'cancel-agent':
65
+ return { content: [{ type: 'text', text: await app.cancelAgent(args as never) }] };
66
+ case 'answer-agent':
67
+ return { content: [{ type: 'text', text: await app.answerAgent(args as never) }] };
68
+ default:
69
+ return {
70
+ content: [{ type: 'text', text: `Unhandled tool: ${tool.name}` }],
71
+ isError: true as const,
72
+ };
73
+ }
74
+ } catch (error) {
75
+ return {
76
+ content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
77
+ isError: true as const,
78
+ };
79
+ }
80
+ });
81
+
82
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
83
+ resources: app.listResources(),
84
+ }));
85
+
86
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
87
+ const resource = await app.readResource(request.params.uri);
88
+ return {
89
+ contents: [
90
+ {
91
+ uri: request.params.uri,
92
+ mimeType: resource.mimeType,
93
+ text: resource.text,
94
+ },
95
+ ],
96
+ };
97
+ });
98
+
99
+ server.setRequestHandler(ListTasksRequestSchema, async () => ({
100
+ tasks: app.listTaskSummaries(),
101
+ }));
102
+
103
+ server.setRequestHandler(GetTaskRequestSchema, async (request) => app.getTaskSummary(request.params.taskId));
104
+
105
+ server.setRequestHandler(GetTaskPayloadRequestSchema, async (request) => app.getTaskPayload(request.params.taskId));
106
+
107
+ server.setRequestHandler(CancelTaskRequestSchema, async (request) => app.cancelTaskById(request.params.taskId));
108
+
109
+ async function main(): Promise<void> {
110
+ await app.initialize(process.cwd());
111
+
112
+ const transport = new StdioServerTransport();
113
+ await server.connect(transport);
114
+ process.stdin.resume();
115
+
116
+ const shutdown = async () => {
117
+ await app.shutdown().catch(() => {});
118
+ process.exit(0);
119
+ };
120
+
121
+ process.on('SIGINT', () => void shutdown());
122
+ process.on('SIGTERM', () => void shutdown());
123
+ }
124
+
125
+ main().catch((error) => {
126
+ console.error(error);
127
+ process.exit(1);
128
+ });
@@ -0,0 +1,41 @@
1
+ import type { CopilotProfile } from '../services/profile-manager.js';
2
+ import type { TaskRecord } from '../types/task.js';
3
+ import { TaskStatus } from '../types/task.js';
4
+
5
+ export interface SystemStatus {
6
+ tasks: {
7
+ total: number;
8
+ running: number;
9
+ waiting: number;
10
+ waitingAnswer: number;
11
+ terminal: number;
12
+ };
13
+ profiles: {
14
+ total: number;
15
+ coolingDown: number;
16
+ };
17
+ }
18
+
19
+ export function buildSystemStatus(input: {
20
+ tasks: TaskRecord[];
21
+ profiles: CopilotProfile[];
22
+ }): SystemStatus {
23
+ return {
24
+ tasks: {
25
+ total: input.tasks.length,
26
+ running: input.tasks.filter((task) => task.status === TaskStatus.RUNNING).length,
27
+ waiting: input.tasks.filter((task) => task.status === TaskStatus.WAITING).length,
28
+ waitingAnswer: input.tasks.filter((task) => task.status === TaskStatus.WAITING_ANSWER).length,
29
+ terminal: input.tasks.filter((task) =>
30
+ task.status === TaskStatus.COMPLETED ||
31
+ task.status === TaskStatus.FAILED ||
32
+ task.status === TaskStatus.CANCELLED ||
33
+ task.status === TaskStatus.TIMED_OUT,
34
+ ).length,
35
+ },
36
+ profiles: {
37
+ total: input.profiles.length,
38
+ coolingDown: input.profiles.filter((profile) => profile.cooldownUntil !== undefined).length,
39
+ },
40
+ };
41
+ }