@chatroomcp/chatroom 0.1.4 → 0.1.5
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/dist/cli/index.js +0 -0
- package/dist/mcp/server/plugin-mcp-registrar.d.ts +26 -0
- package/dist/mcp/server/plugin-mcp-registrar.js +63 -0
- package/dist/mcp/server/tool-support.d.ts +1 -1
- package/dist/operations/operation-log.d.ts +4 -0
- package/dist/operations/operation-log.js +10 -0
- package/dist/plugins/cloud/api-client.d.ts +5 -0
- package/dist/plugins/cloud/api-client.js +10 -0
- package/dist/plugins/cloud/controller.d.ts +1 -0
- package/dist/plugins/cloud/controller.js +5 -0
- package/dist/plugins/plugin-manager.js +6 -2
- package/dist/plugins/process/mcp.d.ts +2 -3
- package/dist/plugins/process/mcp.js +25 -36
- package/dist/plugins/process/plugin.js +3 -6
- package/dist/plugins/process/process-supervisor.d.ts +1 -0
- package/dist/plugins/process/process-supervisor.js +21 -16
- package/dist/plugins/types.d.ts +2 -2
- package/dist/plugins/web/http/cloud-api-router.js +1 -0
- package/dist/plugins/workspace/mcp.d.ts +2 -3
- package/dist/plugins/workspace/mcp.js +38 -67
- package/dist/plugins/workspace/plugin.js +3 -6
- package/dist/web/assets/{index-Cgwn3ot-.css → index-B4927Tpd.css} +1 -1
- package/dist/web/assets/{index-ClD90giy.js → index-KAI80R3I.js} +16 -16
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { McpServer, StandardSchemaWithJSON, ToolAnnotations } from "@modelcontextprotocol/server";
|
|
2
|
+
import type { OperationLog } from "../../operations/operation-log.js";
|
|
3
|
+
type PluginToolInput<Schema extends StandardSchemaWithJSON> = StandardSchemaWithJSON.InferOutput<Schema>;
|
|
4
|
+
type PluginToolAction<Input> = string | ((input: Input) => string);
|
|
5
|
+
export interface PluginToolExecution {
|
|
6
|
+
readonly operationId: string;
|
|
7
|
+
/** Keep the operation running after the MCP handler returns. The owner must finish it later. */
|
|
8
|
+
deferCompletion(): void;
|
|
9
|
+
}
|
|
10
|
+
export interface PluginToolConfig<InputSchema extends StandardSchemaWithJSON, OutputSchema extends StandardSchemaWithJSON> {
|
|
11
|
+
title: string;
|
|
12
|
+
description: string;
|
|
13
|
+
inputSchema: InputSchema;
|
|
14
|
+
outputSchema: OutputSchema;
|
|
15
|
+
annotations: ToolAnnotations;
|
|
16
|
+
action: PluginToolAction<PluginToolInput<InputSchema>>;
|
|
17
|
+
}
|
|
18
|
+
/** Framework-owned MCP registration boundary that guarantees every plugin tool is audited. */
|
|
19
|
+
export declare class PluginMcpRegistrar {
|
|
20
|
+
private readonly server;
|
|
21
|
+
private readonly operations;
|
|
22
|
+
private readonly pluginId;
|
|
23
|
+
constructor(server: McpServer, operations: OperationLog, pluginId: string);
|
|
24
|
+
registerTool<InputSchema extends StandardSchemaWithJSON, OutputSchema extends StandardSchemaWithJSON>(name: string, config: PluginToolConfig<InputSchema, OutputSchema>, handler: (input: PluginToolInput<InputSchema>, execution: PluginToolExecution) => Promise<unknown> | unknown): void;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { asChatRoomError } from "../../core/errors/chatroom-error.js";
|
|
2
|
+
import { mcpTool } from "./tool-support.js";
|
|
3
|
+
/** Framework-owned MCP registration boundary that guarantees every plugin tool is audited. */
|
|
4
|
+
export class PluginMcpRegistrar {
|
|
5
|
+
server;
|
|
6
|
+
operations;
|
|
7
|
+
pluginId;
|
|
8
|
+
constructor(server, operations, pluginId) {
|
|
9
|
+
this.server = server;
|
|
10
|
+
this.operations = operations;
|
|
11
|
+
this.pluginId = pluginId;
|
|
12
|
+
}
|
|
13
|
+
registerTool(name, config, handler) {
|
|
14
|
+
const { action, ...toolConfig } = config;
|
|
15
|
+
const callback = mcpTool(async (input) => {
|
|
16
|
+
const operation = this.operations.start({
|
|
17
|
+
pluginId: this.pluginId,
|
|
18
|
+
source: "mcp",
|
|
19
|
+
action: typeof action === "function" ? action(input) : action,
|
|
20
|
+
input,
|
|
21
|
+
...operationReferences(input),
|
|
22
|
+
});
|
|
23
|
+
let deferred = false;
|
|
24
|
+
const execution = {
|
|
25
|
+
operationId: operation.operationId,
|
|
26
|
+
deferCompletion() {
|
|
27
|
+
deferred = true;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
const result = await handler(input, execution);
|
|
32
|
+
if (!deferred)
|
|
33
|
+
this.operations.finish(operation.operationId, "success", result);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (this.operations.get(operation.operationId)?.status === "running") {
|
|
38
|
+
const normalized = asChatRoomError(error);
|
|
39
|
+
this.operations.finish(operation.operationId, "error", null, {
|
|
40
|
+
code: normalized.code,
|
|
41
|
+
message: normalized.message,
|
|
42
|
+
details: normalized.details,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
this.server.registerTool(name, toolConfig, callback);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function operationReferences(input) {
|
|
52
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
53
|
+
return {};
|
|
54
|
+
const record = input;
|
|
55
|
+
return {
|
|
56
|
+
...(typeof record.workspaceId === "string"
|
|
57
|
+
? { workspaceId: record.workspaceId }
|
|
58
|
+
: {}),
|
|
59
|
+
...(typeof record.processId === "string"
|
|
60
|
+
? { processId: record.processId }
|
|
61
|
+
: {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -35,8 +35,8 @@ export declare const workspaceOutputSchema: z.ZodObject<{
|
|
|
35
35
|
root: z.ZodString;
|
|
36
36
|
sourceRoot: z.ZodString;
|
|
37
37
|
mode: z.ZodEnum<{
|
|
38
|
-
worktree: "worktree";
|
|
39
38
|
checkout: "checkout";
|
|
39
|
+
worktree: "worktree";
|
|
40
40
|
}>;
|
|
41
41
|
createdAt: z.ZodString;
|
|
42
42
|
lastUsedAt: z.ZodString;
|
|
@@ -14,6 +14,10 @@ export declare class OperationLog {
|
|
|
14
14
|
run<T>(request: OperationStart, action: () => Promise<T>): Promise<T>;
|
|
15
15
|
list(query?: OperationQuery): Operation[];
|
|
16
16
|
get(operationId: string): Operation | null;
|
|
17
|
+
associate(operationId: string, references: {
|
|
18
|
+
workspaceId?: string | null;
|
|
19
|
+
processId?: string | null;
|
|
20
|
+
}): Operation;
|
|
17
21
|
clearHistory(): {
|
|
18
22
|
deleted: number;
|
|
19
23
|
preserved: number;
|
|
@@ -86,6 +86,16 @@ export class OperationLog {
|
|
|
86
86
|
get(operationId) {
|
|
87
87
|
return this.repository.get(operationId);
|
|
88
88
|
}
|
|
89
|
+
associate(operationId, references) {
|
|
90
|
+
const operation = this.require(operationId);
|
|
91
|
+
if (references.workspaceId !== undefined)
|
|
92
|
+
operation.workspaceId = references.workspaceId;
|
|
93
|
+
if (references.processId !== undefined)
|
|
94
|
+
operation.processId = references.processId;
|
|
95
|
+
this.repository.update(operation);
|
|
96
|
+
this.eventBus.emit({ type: "operation", operation });
|
|
97
|
+
return operation;
|
|
98
|
+
}
|
|
89
99
|
clearHistory() {
|
|
90
100
|
const result = this.repository.clearHistory();
|
|
91
101
|
this.eventBus.emit({ type: "operations-cleared", ...result });
|
|
@@ -3,6 +3,10 @@ import { type CloudEntitlement, type CloudLeaseState, type CloudServiceId } from
|
|
|
3
3
|
declare const recoveryCredentialSchema: z.ZodObject<{
|
|
4
4
|
recoveryKey: z.ZodString;
|
|
5
5
|
}, z.core.$strict>;
|
|
6
|
+
declare const registrationSchema: z.ZodObject<{
|
|
7
|
+
installationId: z.ZodString;
|
|
8
|
+
registered: z.ZodLiteral<true>;
|
|
9
|
+
}, z.core.$strict>;
|
|
6
10
|
declare const sessionSchema: z.ZodObject<{
|
|
7
11
|
purchaseToken: z.ZodString;
|
|
8
12
|
managementUrl: z.ZodString;
|
|
@@ -33,6 +37,7 @@ declare const prefixSchema: z.ZodObject<{
|
|
|
33
37
|
export declare class CloudApiClient {
|
|
34
38
|
private readonly baseUrl;
|
|
35
39
|
constructor(baseUrl: string);
|
|
40
|
+
registerDevice(identity: DeviceIdentity): Promise<z.infer<typeof registrationSchema>>;
|
|
36
41
|
createManagementSession(identity: DeviceIdentity): Promise<z.infer<typeof sessionSchema>>;
|
|
37
42
|
status(identity: DeviceIdentity, purchaseToken: string | null): Promise<{
|
|
38
43
|
managementSessionActive: boolean;
|
|
@@ -7,6 +7,12 @@ const recoveryCredentialSchema = z
|
|
|
7
7
|
recoveryKey: z.string().regex(/^crr\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/),
|
|
8
8
|
})
|
|
9
9
|
.strict();
|
|
10
|
+
const registrationSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
installationId: z.string().uuid(),
|
|
13
|
+
registered: z.literal(true),
|
|
14
|
+
})
|
|
15
|
+
.strict();
|
|
10
16
|
const sessionSchema = z
|
|
11
17
|
.object({
|
|
12
18
|
purchaseToken: z.string().min(24),
|
|
@@ -42,6 +48,10 @@ export class CloudApiClient {
|
|
|
42
48
|
constructor(baseUrl) {
|
|
43
49
|
this.baseUrl = baseUrl;
|
|
44
50
|
}
|
|
51
|
+
async registerDevice(identity) {
|
|
52
|
+
const payload = { devicePublicKey: identity.devicePublicKey };
|
|
53
|
+
return registrationSchema.parse(await this.devicePost("/v1/device/register", "register", identity, payload));
|
|
54
|
+
}
|
|
45
55
|
async createManagementSession(identity) {
|
|
46
56
|
const payload = { devicePublicKey: identity.devicePublicKey };
|
|
47
57
|
return sessionSchema.parse(await this.devicePost("/v1/device/session", "session", identity, payload));
|
|
@@ -65,6 +65,11 @@ export class CloudController {
|
|
|
65
65
|
if (this.state.lease)
|
|
66
66
|
this.connection = "disconnected";
|
|
67
67
|
}
|
|
68
|
+
async registerDevice() {
|
|
69
|
+
await this.api.registerDevice(this.identity());
|
|
70
|
+
this.lastError = null;
|
|
71
|
+
return this.status();
|
|
72
|
+
}
|
|
68
73
|
async managementUrl() {
|
|
69
74
|
const current = this.state.managementSession;
|
|
70
75
|
if (current && Date.parse(current.expiresAt) > Date.now()) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PluginMcpRegistrar } from "../mcp/server/plugin-mcp-registrar.js";
|
|
1
2
|
export class PluginManager {
|
|
2
3
|
context;
|
|
3
4
|
plugins;
|
|
@@ -13,8 +14,11 @@ export class PluginManager {
|
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
registerMcp(server) {
|
|
16
|
-
for (const plugin of this.active)
|
|
17
|
-
plugin.registerMcp
|
|
17
|
+
for (const plugin of this.active) {
|
|
18
|
+
if (!plugin.registerMcp)
|
|
19
|
+
continue;
|
|
20
|
+
plugin.registerMcp(new PluginMcpRegistrar(server, this.context.operations, plugin.id));
|
|
21
|
+
}
|
|
18
22
|
}
|
|
19
23
|
async stop() {
|
|
20
24
|
for (const plugin of [...this.active].reverse())
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { OperationLog } from "../../operations/operation-log.js";
|
|
1
|
+
import type { PluginMcpRegistrar } from "../../mcp/server/plugin-mcp-registrar.js";
|
|
3
2
|
import type { ProcessSupervisor } from "./process-supervisor.js";
|
|
4
|
-
export declare function registerProcessTools(
|
|
3
|
+
export declare function registerProcessTools(mcp: PluginMcpRegistrar, processes: ProcessSupervisor): void;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { closedRead, destructiveLocalMutation,
|
|
3
|
-
export function registerProcessTools(
|
|
4
|
-
|
|
2
|
+
import { closedRead, destructiveLocalMutation, openWorldMutation, processSnapshotSchema, } from "../../mcp/server/tool-support.js";
|
|
3
|
+
export function registerProcessTools(mcp, processes) {
|
|
4
|
+
mcp.registerTool("process_start", {
|
|
5
5
|
title: "Start process",
|
|
6
|
-
description: "Start a supervised
|
|
6
|
+
description: "Start a supervised executable directly and return a ProcessId immediately. Prefer invoking the target executable without a shell. For compound shell commands, use bash -c rather than bash -lc unless login-shell semantics are explicitly required, because a login shell may replace the inherited PATH (for example, removing NVM-managed Node.js commands).",
|
|
7
7
|
inputSchema: z.object({
|
|
8
8
|
command: z.string().min(1),
|
|
9
9
|
args: z.array(z.string()).default([]),
|
|
@@ -14,43 +14,37 @@ export function registerProcessTools(server, processes, operations) {
|
|
|
14
14
|
}),
|
|
15
15
|
outputSchema: processSnapshotSchema,
|
|
16
16
|
annotations: openWorldMutation,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
? {}
|
|
25
|
-
:
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
action: "start",
|
|
18
|
+
}, (input, execution) => {
|
|
19
|
+
execution.deferCompletion();
|
|
20
|
+
return processes.start({
|
|
21
|
+
cwd: input.cwd,
|
|
22
|
+
command: input.command,
|
|
23
|
+
args: input.args,
|
|
24
|
+
...(input.env ? { env: input.env } : {}),
|
|
25
|
+
pty: input.pty,
|
|
26
|
+
...(input.timeoutMs === undefined
|
|
27
|
+
? {}
|
|
28
|
+
: { timeoutMs: input.timeoutMs }),
|
|
29
|
+
}, { source: "mcp", operationId: execution.operationId });
|
|
30
|
+
});
|
|
31
|
+
mcp.registerTool("process_read", {
|
|
28
32
|
title: "Read process",
|
|
29
33
|
description: "Read bounded stdout/stderr and current process state.",
|
|
30
34
|
inputSchema: z.object({ processId: z.string() }),
|
|
31
35
|
outputSchema: processSnapshotSchema,
|
|
32
36
|
annotations: closedRead,
|
|
33
|
-
}, mcpTool((input) => operations.run({
|
|
34
|
-
pluginId: "process",
|
|
35
|
-
source: "mcp",
|
|
36
37
|
action: "read",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}, () => Promise.resolve(processes.read(input.processId)))));
|
|
40
|
-
server.registerTool("process_write", {
|
|
38
|
+
}, (input) => processes.read(input.processId));
|
|
39
|
+
mcp.registerTool("process_write", {
|
|
41
40
|
title: "Write process stdin",
|
|
42
41
|
description: "Write to stdin of a running process.",
|
|
43
42
|
inputSchema: z.object({ processId: z.string(), data: z.string() }),
|
|
44
43
|
outputSchema: processSnapshotSchema,
|
|
45
44
|
annotations: openWorldMutation,
|
|
46
|
-
}, mcpTool((input) => operations.run({
|
|
47
|
-
pluginId: "process",
|
|
48
|
-
source: "mcp",
|
|
49
45
|
action: "write",
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}, () => Promise.resolve(processes.write(input.processId, input.data)))));
|
|
53
|
-
server.registerTool("process_kill", {
|
|
46
|
+
}, (input) => processes.write(input.processId, input.data));
|
|
47
|
+
mcp.registerTool("process_kill", {
|
|
54
48
|
title: "Stop process",
|
|
55
49
|
description: "Terminate or force-kill a supervised process.",
|
|
56
50
|
inputSchema: z.object({
|
|
@@ -59,11 +53,6 @@ export function registerProcessTools(server, processes, operations) {
|
|
|
59
53
|
}),
|
|
60
54
|
outputSchema: processSnapshotSchema,
|
|
61
55
|
annotations: destructiveLocalMutation,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
source: "mcp",
|
|
65
|
-
action: input.force ? "kill" : "terminate",
|
|
66
|
-
processId: input.processId,
|
|
67
|
-
input,
|
|
68
|
-
}, () => Promise.resolve(processes.kill(input.processId, input.force)))));
|
|
56
|
+
action: (input) => (input.force ? "kill" : "terminate"),
|
|
57
|
+
}, (input) => processes.kill(input.processId, input.force));
|
|
69
58
|
}
|
|
@@ -6,23 +6,20 @@ import { ProcessSupervisor } from "./process-supervisor.js";
|
|
|
6
6
|
export const ProcessService = createServiceToken("process");
|
|
7
7
|
export function createProcessPlugin() {
|
|
8
8
|
let service = null;
|
|
9
|
-
let operations = null;
|
|
10
9
|
return {
|
|
11
10
|
id: "process",
|
|
12
11
|
activate(context) {
|
|
13
12
|
service = new ProcessSupervisor({ pipe: new PipeProcessBackend(), pty: new PtyProcessBackend() }, context.operations, context.events, context.config.process.maxOutputBytes, context.config.process.defaultTimeoutMs, context.config.process.maxCompletedProcesses);
|
|
14
|
-
operations = context.operations;
|
|
15
13
|
context.services.provide(ProcessService, service);
|
|
16
14
|
},
|
|
17
|
-
registerMcp(
|
|
18
|
-
if (!service
|
|
15
|
+
registerMcp(mcp) {
|
|
16
|
+
if (!service)
|
|
19
17
|
throw new Error("Process plugin is not active");
|
|
20
|
-
registerProcessTools(
|
|
18
|
+
registerProcessTools(mcp, service);
|
|
21
19
|
},
|
|
22
20
|
async deactivate() {
|
|
23
21
|
await service?.shutdown();
|
|
24
22
|
service = null;
|
|
25
|
-
operations = null;
|
|
26
23
|
},
|
|
27
24
|
};
|
|
28
25
|
}
|
|
@@ -28,20 +28,24 @@ export class ProcessSupervisor {
|
|
|
28
28
|
throw new ChatRoomError("CONFLICT", "Process supervisor is shutting down");
|
|
29
29
|
const processId = `proc_${randomUUID()}`;
|
|
30
30
|
const args = request.args ?? [];
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
const adoptedOperation = operationContext.operationId !== undefined;
|
|
32
|
+
const operationId = operationContext.operationId ??
|
|
33
|
+
this.operations.start({
|
|
34
|
+
pluginId: "process",
|
|
35
|
+
source: operationContext.source,
|
|
36
|
+
action: operationContext.action ?? "start",
|
|
37
|
+
processId,
|
|
38
|
+
input: {
|
|
39
|
+
command: request.command,
|
|
40
|
+
args,
|
|
41
|
+
cwd: request.cwd,
|
|
42
|
+
pty: request.pty ?? false,
|
|
43
|
+
timeoutMs: request.timeoutMs ?? this.defaultTimeoutMs,
|
|
44
|
+
env: request.env ?? {},
|
|
45
|
+
},
|
|
46
|
+
}).operationId;
|
|
47
|
+
if (adoptedOperation)
|
|
48
|
+
this.operations.associate(operationId, { processId });
|
|
45
49
|
let backend;
|
|
46
50
|
try {
|
|
47
51
|
// Child environments inherit only the runtime allowlist, then apply explicit request overrides.
|
|
@@ -51,7 +55,8 @@ export class ProcessSupervisor {
|
|
|
51
55
|
});
|
|
52
56
|
}
|
|
53
57
|
catch (error) {
|
|
54
|
-
|
|
58
|
+
if (!adoptedOperation)
|
|
59
|
+
this.operations.finish(operationId, "error", null, error);
|
|
55
60
|
throw new ChatRoomError("PROCESS_FAILED", `Failed to start process: ${request.command}`, undefined, { cause: error });
|
|
56
61
|
}
|
|
57
62
|
let resolveSettled;
|
|
@@ -71,7 +76,7 @@ export class ProcessSupervisor {
|
|
|
71
76
|
timeout: null,
|
|
72
77
|
forceTimeout: null,
|
|
73
78
|
timedOut: false,
|
|
74
|
-
operationId
|
|
79
|
+
operationId,
|
|
75
80
|
settled,
|
|
76
81
|
resolveSettled,
|
|
77
82
|
};
|
package/dist/plugins/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
1
|
import type { ChatRoomConfig } from "../config/types.js";
|
|
3
2
|
import type { AppDatabase } from "../infrastructure/database/app-database.js";
|
|
3
|
+
import type { PluginMcpRegistrar } from "../mcp/server/plugin-mcp-registrar.js";
|
|
4
4
|
import type { OperationLog } from "../operations/operation-log.js";
|
|
5
5
|
import type { RuntimeEventBus } from "../app/event-bus.js";
|
|
6
6
|
import type { ExternalAccessRegistry } from "../app/external-access-registry.js";
|
|
@@ -27,6 +27,6 @@ export interface PluginContext {
|
|
|
27
27
|
export interface InternalPlugin {
|
|
28
28
|
id: string;
|
|
29
29
|
activate(context: PluginContext): Promise<void> | void;
|
|
30
|
-
registerMcp?(
|
|
30
|
+
registerMcp?(mcp: PluginMcpRegistrar): void;
|
|
31
31
|
deactivate?(): Promise<void> | void;
|
|
32
32
|
}
|
|
@@ -5,6 +5,7 @@ import { asyncRoute } from "../../../presentation/http/http-utils.js";
|
|
|
5
5
|
export function createCloudApiRouter(controller, operations) {
|
|
6
6
|
const router = Router();
|
|
7
7
|
router.get("/cloud/status", (_req, res) => res.json(controller.status()));
|
|
8
|
+
router.post("/cloud/register", asyncRoute(async (_req, res) => res.json(await operations.run({ pluginId: "cloud", source: "gui", action: "register" }, () => controller.registerDevice()))));
|
|
8
9
|
router.post("/cloud/sync", asyncRoute(async (_req, res) => res.json(await operations.run({ pluginId: "cloud", source: "gui", action: "sync" }, () => controller.syncStatus()))));
|
|
9
10
|
router.post("/cloud/management", asyncRoute(async (_req, res) => res.json(await operations.run({ pluginId: "cloud", source: "gui", action: "management" }, () => controller.managementUrl()))));
|
|
10
11
|
router.post("/cloud/restore", asyncRoute(async (req, res) => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { OperationLog } from "../../operations/operation-log.js";
|
|
1
|
+
import type { PluginMcpRegistrar } from "../../mcp/server/plugin-mcp-registrar.js";
|
|
3
2
|
import type { WorkspaceService } from "./workspace-service.js";
|
|
4
|
-
export declare function registerWorkspaceTools(
|
|
3
|
+
export declare function registerWorkspaceTools(mcp: PluginMcpRegistrar, workspaces: WorkspaceService): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { changeSetSchema, closedRead, destructiveLocalMutation, fileInfoSchema, fileReadOutputSchema, fileWriteOutputSchema, localMutation, localWrite,
|
|
3
|
-
export function registerWorkspaceTools(
|
|
4
|
-
|
|
2
|
+
import { changeSetSchema, closedRead, destructiveLocalMutation, fileInfoSchema, fileReadOutputSchema, fileWriteOutputSchema, localMutation, localWrite, searchMatchSchema, workspaceOutputSchema, } from "../../mcp/server/tool-support.js";
|
|
3
|
+
export function registerWorkspaceTools(mcp, workspaces) {
|
|
4
|
+
mcp.registerTool("open_workspace", {
|
|
5
5
|
title: "Open workspace",
|
|
6
6
|
description: "Open or reuse an approved checkout, or create a new isolated managed Git worktree from the source checkout HEAD.",
|
|
7
7
|
inputSchema: z.object({
|
|
@@ -10,11 +10,12 @@ export function registerWorkspaceTools(server, workspaces, operations) {
|
|
|
10
10
|
}),
|
|
11
11
|
outputSchema: workspaceOutputSchema,
|
|
12
12
|
annotations: localMutation,
|
|
13
|
-
|
|
13
|
+
action: "open",
|
|
14
|
+
}, (input) => workspaces.open({
|
|
14
15
|
path: input.path,
|
|
15
16
|
...(input.mode ? { mode: input.mode } : {}),
|
|
16
|
-
}))
|
|
17
|
-
|
|
17
|
+
}));
|
|
18
|
+
mcp.registerTool("remove_workspace", {
|
|
18
19
|
title: "Remove workspace",
|
|
19
20
|
description: "Unregister a checkout workspace or remove a managed worktree. Dirty worktrees require force=true.",
|
|
20
21
|
inputSchema: z.object({
|
|
@@ -27,17 +28,12 @@ export function registerWorkspaceTools(server, workspaces, operations) {
|
|
|
27
28
|
mode: z.enum(["checkout", "worktree"]),
|
|
28
29
|
}),
|
|
29
30
|
annotations: destructiveLocalMutation,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
source: "mcp",
|
|
34
|
-
action: "remove",
|
|
35
|
-
workspaceId: input.workspaceId,
|
|
36
|
-
input,
|
|
37
|
-
}, () => workspaces.remove(input.workspaceId, input.force ?? false));
|
|
31
|
+
action: "remove",
|
|
32
|
+
}, async (input) => {
|
|
33
|
+
const removed = await workspaces.remove(input.workspaceId, input.force ?? false);
|
|
38
34
|
return { removed: true, workspaceId: removed.id, mode: removed.mode };
|
|
39
|
-
})
|
|
40
|
-
|
|
35
|
+
});
|
|
36
|
+
mcp.registerTool("fs_read", {
|
|
41
37
|
title: "Read file",
|
|
42
38
|
description: "Read a workspace-relative file through the ChatRoom filesystem boundary.",
|
|
43
39
|
inputSchema: z.object({
|
|
@@ -52,17 +48,12 @@ export function registerWorkspaceTools(server, workspaces, operations) {
|
|
|
52
48
|
}),
|
|
53
49
|
outputSchema: fileReadOutputSchema,
|
|
54
50
|
annotations: closedRead,
|
|
55
|
-
|
|
51
|
+
action: "fs.read",
|
|
52
|
+
}, async (input) => {
|
|
56
53
|
const fs = await workspaces.fs(input.workspaceId);
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
action: "fs.read",
|
|
61
|
-
workspaceId: input.workspaceId,
|
|
62
|
-
input,
|
|
63
|
-
}, () => fs.read(input.path, input.maxBytes === undefined ? {} : { maxBytes: input.maxBytes }));
|
|
64
|
-
}));
|
|
65
|
-
server.registerTool("fs_write", {
|
|
54
|
+
return fs.read(input.path, input.maxBytes === undefined ? {} : { maxBytes: input.maxBytes });
|
|
55
|
+
});
|
|
56
|
+
mcp.registerTool("fs_write", {
|
|
66
57
|
title: "Write file",
|
|
67
58
|
description: "Atomically write a workspace-relative file.",
|
|
68
59
|
inputSchema: z.object({
|
|
@@ -72,17 +63,12 @@ export function registerWorkspaceTools(server, workspaces, operations) {
|
|
|
72
63
|
}),
|
|
73
64
|
outputSchema: fileWriteOutputSchema,
|
|
74
65
|
annotations: localWrite,
|
|
75
|
-
|
|
66
|
+
action: "fs.write",
|
|
67
|
+
}, async (input) => {
|
|
76
68
|
const fs = await workspaces.fs(input.workspaceId);
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
action: "fs.write",
|
|
81
|
-
workspaceId: input.workspaceId,
|
|
82
|
-
input,
|
|
83
|
-
}, () => fs.write(input.path, input.content));
|
|
84
|
-
}));
|
|
85
|
-
server.registerTool("fs_list", {
|
|
69
|
+
return fs.write(input.path, input.content);
|
|
70
|
+
});
|
|
71
|
+
mcp.registerTool("fs_list", {
|
|
86
72
|
title: "List files",
|
|
87
73
|
description: "List files under a workspace-relative directory.",
|
|
88
74
|
inputSchema: z.object({
|
|
@@ -92,19 +78,14 @@ export function registerWorkspaceTools(server, workspaces, operations) {
|
|
|
92
78
|
}),
|
|
93
79
|
outputSchema: z.object({ files: z.array(fileInfoSchema) }),
|
|
94
80
|
annotations: closedRead,
|
|
95
|
-
|
|
81
|
+
action: "fs.list",
|
|
82
|
+
}, async (input) => {
|
|
96
83
|
const fs = await workspaces.fs(input.workspaceId);
|
|
97
84
|
return {
|
|
98
|
-
files: await
|
|
99
|
-
pluginId: "workspace",
|
|
100
|
-
source: "mcp",
|
|
101
|
-
action: "fs.list",
|
|
102
|
-
workspaceId: input.workspaceId,
|
|
103
|
-
input,
|
|
104
|
-
}, () => fs.list(input.path, { recursive: input.recursive })),
|
|
85
|
+
files: await fs.list(input.path, { recursive: input.recursive }),
|
|
105
86
|
};
|
|
106
|
-
})
|
|
107
|
-
|
|
87
|
+
});
|
|
88
|
+
mcp.registerTool("fs_search", {
|
|
108
89
|
title: "Search files",
|
|
109
90
|
description: "Search text under a workspace.",
|
|
110
91
|
inputSchema: z.object({
|
|
@@ -115,22 +96,17 @@ export function registerWorkspaceTools(server, workspaces, operations) {
|
|
|
115
96
|
}),
|
|
116
97
|
outputSchema: z.object({ matches: z.array(searchMatchSchema) }),
|
|
117
98
|
annotations: closedRead,
|
|
118
|
-
|
|
99
|
+
action: "fs.search",
|
|
100
|
+
}, async (input) => {
|
|
119
101
|
const fs = await workspaces.fs(input.workspaceId);
|
|
120
102
|
return {
|
|
121
|
-
matches: await
|
|
122
|
-
pluginId: "workspace",
|
|
123
|
-
source: "mcp",
|
|
124
|
-
action: "fs.search",
|
|
125
|
-
workspaceId: input.workspaceId,
|
|
126
|
-
input,
|
|
127
|
-
}, () => fs.search(input.query, {
|
|
103
|
+
matches: await fs.search(input.query, {
|
|
128
104
|
path: input.path,
|
|
129
105
|
maxResults: input.maxResults,
|
|
130
|
-
})
|
|
106
|
+
}),
|
|
131
107
|
};
|
|
132
|
-
})
|
|
133
|
-
|
|
108
|
+
});
|
|
109
|
+
mcp.registerTool("fs_patch", {
|
|
134
110
|
title: "Transactional patch",
|
|
135
111
|
description: "Validate all replacements before committing a multi-file text patch.",
|
|
136
112
|
inputSchema: z.object({
|
|
@@ -148,21 +124,16 @@ export function registerWorkspaceTools(server, workspaces, operations) {
|
|
|
148
124
|
}),
|
|
149
125
|
outputSchema: changeSetSchema,
|
|
150
126
|
annotations: destructiveLocalMutation,
|
|
151
|
-
|
|
127
|
+
action: "fs.patch",
|
|
128
|
+
}, async (input) => {
|
|
152
129
|
const fs = await workspaces.fs(input.workspaceId);
|
|
153
|
-
return
|
|
154
|
-
pluginId: "workspace",
|
|
155
|
-
source: "mcp",
|
|
156
|
-
action: "fs.patch",
|
|
157
|
-
workspaceId: input.workspaceId,
|
|
158
|
-
input,
|
|
159
|
-
}, () => fs.patch(input.replacements.map((item) => ({
|
|
130
|
+
return fs.patch(input.replacements.map((item) => ({
|
|
160
131
|
path: item.path,
|
|
161
132
|
oldText: item.oldText,
|
|
162
133
|
newText: item.newText,
|
|
163
134
|
...(item.occurrence === undefined
|
|
164
135
|
? {}
|
|
165
136
|
: { occurrence: item.occurrence }),
|
|
166
|
-
})))
|
|
167
|
-
})
|
|
137
|
+
})));
|
|
138
|
+
});
|
|
168
139
|
}
|