@modelprofile.com/mcp-crossharness 5.0.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/.smartconfig.json +34 -0
- package/cli.js +4 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.connection.codex.d.ts +66 -0
- package/dist_ts/classes.connection.codex.js +941 -0
- package/dist_ts/classes.connection.opencode.d.ts +34 -0
- package/dist_ts/classes.connection.opencode.js +365 -0
- package/dist_ts/classes.connectionregistry.d.ts +32 -0
- package/dist_ts/classes.connectionregistry.js +246 -0
- package/dist_ts/classes.dispatchregistry.d.ts +39 -0
- package/dist_ts/classes.dispatchregistry.js +177 -0
- package/dist_ts/classes.mcpserver.d.ts +21 -0
- package/dist_ts/classes.mcpserver.js +276 -0
- package/dist_ts/helpers.d.ts +4 -0
- package/dist_ts/helpers.js +17 -0
- package/dist_ts/index.d.ts +5 -0
- package/dist_ts/index.js +9 -0
- package/dist_ts/interfaces.d.ts +57 -0
- package/dist_ts/interfaces.js +2 -0
- package/dist_ts/plugins.d.ts +17 -0
- package/dist_ts/plugins.js +14 -0
- package/license.md +21 -0
- package/package.json +51 -0
- package/readme.hints.md +52 -0
- package/readme.md +204 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.connection.codex.ts +1120 -0
- package/ts/classes.connection.opencode.ts +443 -0
- package/ts/classes.connectionregistry.ts +309 -0
- package/ts/classes.dispatchregistry.ts +216 -0
- package/ts/classes.mcpserver.ts +363 -0
- package/ts/helpers.ts +20 -0
- package/ts/index.ts +10 -0
- package/ts/interfaces.ts +71 -0
- package/ts/plugins.ts +30 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { commitinfo } from './00_commitinfo_data.js';
|
|
3
|
+
import { ConnectionRegistry } from './classes.connectionregistry.js';
|
|
4
|
+
import { DispatchRegistry } from './classes.dispatchregistry.js';
|
|
5
|
+
const harnessNameSchema = plugins.z.enum(['claudecode', 'opencode', 'codex']);
|
|
6
|
+
export class CrossHarnessMcpServer {
|
|
7
|
+
connections;
|
|
8
|
+
dispatches;
|
|
9
|
+
activeServer;
|
|
10
|
+
stdinEndHandler;
|
|
11
|
+
stdinEndSource;
|
|
12
|
+
closePromise;
|
|
13
|
+
resourceShutdownPromise;
|
|
14
|
+
startCalled = false;
|
|
15
|
+
constructor(connectionsArg = new ConnectionRegistry(), dispatchesArg = new DispatchRegistry()) {
|
|
16
|
+
this.connections = connectionsArg;
|
|
17
|
+
this.dispatches = dispatchesArg;
|
|
18
|
+
}
|
|
19
|
+
async start(transportArg = new plugins.StdioServerTransport(), stdinArg = process.stdin) {
|
|
20
|
+
if (this.startCalled)
|
|
21
|
+
throw new Error('Crossharness MCP server instances are one-shot');
|
|
22
|
+
this.startCalled = true;
|
|
23
|
+
const server = new plugins.McpServer({
|
|
24
|
+
name: 'mcp-crossharness',
|
|
25
|
+
version: commitinfo.version,
|
|
26
|
+
});
|
|
27
|
+
server.registerTool('connect_harness', {
|
|
28
|
+
title: 'Connect to harness server',
|
|
29
|
+
description: 'Establish an explicit connection to an allowlisted harness web server. The exact origin must be listed in CROSSHARNESS_ALLOWED_SERVER_URLS. OpenCode uses http(s), Codex uses ws(s), and Claude Code is rejected because it exposes no URL-addressable session server. Returns a connectionId required by every connection-scoped operation; list_connections remains global.',
|
|
30
|
+
inputSchema: {
|
|
31
|
+
harness: harnessNameSchema,
|
|
32
|
+
serverUrl: plugins.z.string().max(2048).url().describe('Allowlisted harness server origin'),
|
|
33
|
+
directory: plugins.z
|
|
34
|
+
.string()
|
|
35
|
+
.max(4096)
|
|
36
|
+
.startsWith('/')
|
|
37
|
+
.describe('Absolute project directory on the harness server'),
|
|
38
|
+
},
|
|
39
|
+
}, async (args, extra) => this.wrapToolCall(async () => {
|
|
40
|
+
const info = await this.connections.connect(args.harness, args.serverUrl, args.directory, extra.signal);
|
|
41
|
+
return this.describeConnection(info);
|
|
42
|
+
}));
|
|
43
|
+
server.registerTool('disconnect_harness', {
|
|
44
|
+
title: 'Disconnect harness server',
|
|
45
|
+
description: 'Close and remove an established harness connection. In-flight requests fail; async dispatch records remain inspectable with check_reply.',
|
|
46
|
+
inputSchema: {
|
|
47
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
48
|
+
},
|
|
49
|
+
}, async (args, extra) => this.wrapToolCall(async () => this.describeConnection(await this.connections.disconnect(args.connectionId))));
|
|
50
|
+
server.registerTool('list_connections', {
|
|
51
|
+
title: 'List harness connections',
|
|
52
|
+
description: 'List explicit harness web-server connections established in this MCP process.',
|
|
53
|
+
inputSchema: {},
|
|
54
|
+
}, async () => this.wrapToolCall(async () => {
|
|
55
|
+
const connections = this.connections.list();
|
|
56
|
+
return connections.length > 0
|
|
57
|
+
? connections.map((connection) => this.describeConnection(connection)).join('\n\n')
|
|
58
|
+
: 'No harness server connections established.';
|
|
59
|
+
}));
|
|
60
|
+
server.registerTool('connection_status', {
|
|
61
|
+
title: 'Check harness connection',
|
|
62
|
+
description: 'Run the protocol health check for one explicit harness server connection.',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
65
|
+
},
|
|
66
|
+
}, async (args) => this.wrapToolCall(async () => {
|
|
67
|
+
const status = await this.connections.get(args.connectionId).getStatus();
|
|
68
|
+
return `${this.describeConnection(status)}\nhealthy: ${status.healthy ? 'yes' : 'no'}\ndetail: ${status.detail}`;
|
|
69
|
+
}));
|
|
70
|
+
server.registerTool('list_chats', {
|
|
71
|
+
title: 'List harness chats',
|
|
72
|
+
description: 'List chats from the server and directory bound to an explicit connection. No local session stores are read.',
|
|
73
|
+
inputSchema: {
|
|
74
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
75
|
+
limit: plugins.z.number().int().min(1).max(100).optional(),
|
|
76
|
+
},
|
|
77
|
+
}, async (args) => this.wrapToolCall(async () => {
|
|
78
|
+
const chats = await this.connections.get(args.connectionId).listChats(args.limit ?? 10);
|
|
79
|
+
if (chats.length === 0)
|
|
80
|
+
return 'No chats found for this connection.';
|
|
81
|
+
return [
|
|
82
|
+
'harness\tchatId\tupdatedAt\tactivity\ttitle',
|
|
83
|
+
...chats.map((chat) => `${chat.harness}\t${chat.chatId}\t${chat.updatedAt}\t${chat.isActive ? 'active' : 'idle'}\t${chat.title.replace(/\s+/g, ' ').slice(0, 120)}`),
|
|
84
|
+
].join('\n');
|
|
85
|
+
}));
|
|
86
|
+
server.registerTool('read_chat', {
|
|
87
|
+
title: 'Read harness chat',
|
|
88
|
+
description: 'Read a chat through the harness web server bound to an explicit connection.',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
91
|
+
chatId: plugins.z.string().min(1).max(1024),
|
|
92
|
+
limit: plugins.z.number().int().min(1).max(500).optional(),
|
|
93
|
+
maxChars: plugins.z.number().int().min(200).max(50000).optional(),
|
|
94
|
+
},
|
|
95
|
+
}, async (args) => this.wrapToolCall(async () => {
|
|
96
|
+
const transcript = await this.connections
|
|
97
|
+
.get(args.connectionId)
|
|
98
|
+
.readChat(args.chatId, args.limit ?? 20, args.maxChars ?? 2000);
|
|
99
|
+
const header = [
|
|
100
|
+
`harness: ${transcript.harness}`,
|
|
101
|
+
`chatId: ${transcript.chatId}`,
|
|
102
|
+
`title: ${transcript.title}`,
|
|
103
|
+
`directory: ${transcript.directory}`,
|
|
104
|
+
transcript.totalMessages === undefined
|
|
105
|
+
? `messages: showing ${transcript.messages.length}; server total unavailable`
|
|
106
|
+
: `messages: showing ${transcript.messages.length} of ${transcript.totalMessages}`,
|
|
107
|
+
].join('\n');
|
|
108
|
+
const body = transcript.messages
|
|
109
|
+
.map((message) => `-- [${message.role}]${message.timestamp ? ` ${message.timestamp}` : ''}\n${message.text}`)
|
|
110
|
+
.join('\n');
|
|
111
|
+
return `${header}\n\n${body}`;
|
|
112
|
+
}));
|
|
113
|
+
server.registerTool('send_message', {
|
|
114
|
+
title: 'Send message through harness server',
|
|
115
|
+
description: 'Send a message only through the harness web server bound to an explicit connection and wait for the reply. There is no CLI, process, local-store, discovery, or retry fallback.',
|
|
116
|
+
inputSchema: {
|
|
117
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
118
|
+
chatId: plugins.z.string().min(1).max(1024),
|
|
119
|
+
message: plugins.z.string().min(1).max(1024 * 1024),
|
|
120
|
+
timeoutSeconds: plugins.z.number().int().min(1).max(3600).optional(),
|
|
121
|
+
},
|
|
122
|
+
}, async (args, extra) => this.wrapToolCall(async () => {
|
|
123
|
+
const result = await this.connections
|
|
124
|
+
.get(args.connectionId)
|
|
125
|
+
.sendMessage(args.chatId, args.message, args.timeoutSeconds ?? 300, extra.signal);
|
|
126
|
+
return `${result.reply}\n\n[crossharness transport: ${result.via}]`;
|
|
127
|
+
}));
|
|
128
|
+
server.registerTool('send_message_async', {
|
|
129
|
+
title: 'Send message through harness server asynchronously',
|
|
130
|
+
description: 'Start a server-only send and return a dispatchId immediately. Both this connectionId and the dispatchId are required by check_reply.',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
133
|
+
chatId: plugins.z.string().min(1).max(1024),
|
|
134
|
+
message: plugins.z.string().min(1).max(1024 * 1024),
|
|
135
|
+
timeoutSeconds: plugins.z.number().int().min(1).max(3600).optional(),
|
|
136
|
+
},
|
|
137
|
+
}, async (args) => this.wrapToolCall(async () => {
|
|
138
|
+
const connection = this.connections.get(args.connectionId);
|
|
139
|
+
const record = this.dispatches.track(args.connectionId, connection.harness, args.chatId, () => connection.sendMessage(args.chatId, args.message, args.timeoutSeconds ?? 900));
|
|
140
|
+
return [
|
|
141
|
+
`connectionId: ${record.connectionId}`,
|
|
142
|
+
`dispatchId: ${record.dispatchId}`,
|
|
143
|
+
`harness: ${record.harness}`,
|
|
144
|
+
`chatId: ${record.chatId}`,
|
|
145
|
+
`startedAt: ${record.startedAt}`,
|
|
146
|
+
].join('\n');
|
|
147
|
+
}));
|
|
148
|
+
server.registerTool('check_reply', {
|
|
149
|
+
title: 'Check async server send',
|
|
150
|
+
description: 'Check an asynchronous send. The connectionId must own the dispatch. Records remain inspectable after disconnect.',
|
|
151
|
+
inputSchema: {
|
|
152
|
+
connectionId: plugins.z.string().max(128).describe('Connection id used for send_message_async'),
|
|
153
|
+
dispatchId: plugins.z.string().max(128).describe('Dispatch id returned by send_message_async'),
|
|
154
|
+
waitSeconds: plugins.z.number().int().min(0).max(60).optional(),
|
|
155
|
+
},
|
|
156
|
+
}, async (args, extra) => this.wrapToolCall(async () => {
|
|
157
|
+
const record = await this.dispatches.waitFor(args.connectionId, args.dispatchId, (args.waitSeconds ?? 0) * 1000, extra.signal);
|
|
158
|
+
if (!record)
|
|
159
|
+
throw new Error('unknown dispatchId for this connectionId');
|
|
160
|
+
const header = [
|
|
161
|
+
`connectionId: ${record.connectionId}`,
|
|
162
|
+
`dispatchId: ${record.dispatchId}`,
|
|
163
|
+
`status: ${record.status}`,
|
|
164
|
+
`harness: ${record.harness}`,
|
|
165
|
+
`chatId: ${record.chatId}`,
|
|
166
|
+
`startedAt: ${record.startedAt}`,
|
|
167
|
+
...(record.completedAt ? [`completedAt: ${record.completedAt}`] : []),
|
|
168
|
+
].join('\n');
|
|
169
|
+
if (record.status === 'working')
|
|
170
|
+
return `${header}\n\nStill working.`;
|
|
171
|
+
if (record.status === 'failed')
|
|
172
|
+
return `${header}\n\nError: ${record.error}`;
|
|
173
|
+
return `${header}\n\n${record.reply}\n\n[crossharness transport: ${record.via}]`;
|
|
174
|
+
}));
|
|
175
|
+
this.activeServer = server;
|
|
176
|
+
this.stdinEndHandler = () => this.triggerClose();
|
|
177
|
+
this.stdinEndSource = stdinArg;
|
|
178
|
+
this.stdinEndSource.once('end', this.stdinEndHandler);
|
|
179
|
+
this.stdinEndSource.once('close', this.stdinEndHandler);
|
|
180
|
+
this.stdinEndSource.once('error', this.stdinEndHandler);
|
|
181
|
+
server.server.onclose = () => {
|
|
182
|
+
this.clearTransportReferences();
|
|
183
|
+
void this.ensureResourceShutdown().catch(() => undefined);
|
|
184
|
+
};
|
|
185
|
+
try {
|
|
186
|
+
await server.connect(transportArg);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
this.clearTransportReferences();
|
|
190
|
+
await transportArg.close().catch(() => { });
|
|
191
|
+
await this.ensureResourceShutdown();
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async close() {
|
|
196
|
+
if (this.closePromise)
|
|
197
|
+
return this.closePromise;
|
|
198
|
+
this.closePromise = (async () => {
|
|
199
|
+
const server = this.activeServer;
|
|
200
|
+
let closeError;
|
|
201
|
+
try {
|
|
202
|
+
if (server)
|
|
203
|
+
await server.close();
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
closeError = error;
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
this.clearTransportReferences();
|
|
210
|
+
await this.ensureResourceShutdown();
|
|
211
|
+
}
|
|
212
|
+
if (closeError)
|
|
213
|
+
throw closeError;
|
|
214
|
+
})().finally(() => {
|
|
215
|
+
this.closePromise = undefined;
|
|
216
|
+
});
|
|
217
|
+
return this.closePromise;
|
|
218
|
+
}
|
|
219
|
+
triggerClose() {
|
|
220
|
+
void this.close().catch(() => { });
|
|
221
|
+
}
|
|
222
|
+
ensureResourceShutdown() {
|
|
223
|
+
if (this.resourceShutdownPromise)
|
|
224
|
+
return this.resourceShutdownPromise;
|
|
225
|
+
let shutdownPromise;
|
|
226
|
+
shutdownPromise = this.connections.closeAll().finally(() => {
|
|
227
|
+
this.dispatches.dispose();
|
|
228
|
+
}).catch((errorArg) => {
|
|
229
|
+
if (this.resourceShutdownPromise === shutdownPromise) {
|
|
230
|
+
this.resourceShutdownPromise = undefined;
|
|
231
|
+
}
|
|
232
|
+
throw errorArg;
|
|
233
|
+
});
|
|
234
|
+
this.resourceShutdownPromise = shutdownPromise;
|
|
235
|
+
return shutdownPromise;
|
|
236
|
+
}
|
|
237
|
+
clearTransportReferences() {
|
|
238
|
+
if (this.stdinEndHandler) {
|
|
239
|
+
this.stdinEndSource?.off('end', this.stdinEndHandler);
|
|
240
|
+
this.stdinEndSource?.off('close', this.stdinEndHandler);
|
|
241
|
+
this.stdinEndSource?.off('error', this.stdinEndHandler);
|
|
242
|
+
}
|
|
243
|
+
this.stdinEndHandler = undefined;
|
|
244
|
+
this.stdinEndSource = undefined;
|
|
245
|
+
this.activeServer = undefined;
|
|
246
|
+
}
|
|
247
|
+
describeConnection(infoArg) {
|
|
248
|
+
return [
|
|
249
|
+
`connectionId: ${infoArg.connectionId}`,
|
|
250
|
+
`harness: ${infoArg.harness}`,
|
|
251
|
+
`serverUrl: ${infoArg.serverUrl}`,
|
|
252
|
+
`directory: ${infoArg.directory}`,
|
|
253
|
+
`state: ${infoArg.state}`,
|
|
254
|
+
`connectedAt: ${infoArg.connectedAt}`,
|
|
255
|
+
...(infoArg.serverVersion ? [`serverVersion: ${infoArg.serverVersion}`] : []),
|
|
256
|
+
].join('\n');
|
|
257
|
+
}
|
|
258
|
+
async wrapToolCall(handlerArg) {
|
|
259
|
+
try {
|
|
260
|
+
const text = await handlerArg();
|
|
261
|
+
return { content: [{ type: 'text', text: text.slice(0, 100000) }] };
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
return {
|
|
265
|
+
content: [
|
|
266
|
+
{
|
|
267
|
+
type: 'text',
|
|
268
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`.slice(0, 100000),
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
isError: true,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tY3BzZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLm1jcHNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFFckQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUFDckUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFFakUsTUFBTSxpQkFBaUIsR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLFlBQVksRUFBRSxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQztBQUU5RSxNQUFNLE9BQU8scUJBQXFCO0lBQ2hCLFdBQVcsQ0FBcUI7SUFDaEMsVUFBVSxDQUFtQjtJQUNyQyxZQUFZLENBQXFEO0lBQ2pFLGVBQWUsQ0FBMkI7SUFDMUMsY0FBYyxDQUFvQztJQUNsRCxZQUFZLENBQTRCO0lBQ3hDLHVCQUF1QixDQUE0QjtJQUNuRCxXQUFXLEdBQUcsS0FBSyxDQUFDO0lBRTVCLFlBQVksY0FBYyxHQUFHLElBQUksa0JBQWtCLEVBQUUsRUFBRSxhQUFhLEdBQUcsSUFBSSxnQkFBZ0IsRUFBRTtRQUMzRixJQUFJLENBQUMsV0FBVyxHQUFHLGNBQWMsQ0FBQztRQUNsQyxJQUFJLENBQUMsVUFBVSxHQUFHLGFBQWEsQ0FBQztJQUNsQyxDQUFDO0lBRU0sS0FBSyxDQUFDLEtBQUssQ0FDaEIsZUFBa0MsSUFBSSxPQUFPLENBQUMsb0JBQW9CLEVBQUUsRUFDcEUsV0FBa0MsT0FBTyxDQUFDLEtBQUs7UUFFL0MsSUFBSSxJQUFJLENBQUMsV0FBVztZQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsZ0RBQWdELENBQUMsQ0FBQztRQUN4RixJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FB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
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const truncateText: (textArg: string, maxCharsArg: number) => string;
|
|
2
|
+
export declare const parseJson: (valueArg: string) => unknown;
|
|
3
|
+
export declare const isRecord: (valueArg: unknown) => valueArg is Record<string, unknown>;
|
|
4
|
+
export declare const errorMessage: (errorArg: unknown) => string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const truncateText = (textArg, maxCharsArg) => {
|
|
2
|
+
if (textArg.length <= maxCharsArg) {
|
|
3
|
+
return textArg;
|
|
4
|
+
}
|
|
5
|
+
return `${textArg.slice(0, maxCharsArg)}\n[truncated ${textArg.length - maxCharsArg} chars]`;
|
|
6
|
+
};
|
|
7
|
+
export const parseJson = (valueArg) => {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(valueArg);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export const isRecord = (valueArg) => Boolean(valueArg) && typeof valueArg === 'object' && !Array.isArray(valueArg);
|
|
16
|
+
export const errorMessage = (errorArg) => errorArg instanceof Error ? errorArg.message : String(errorArg);
|
|
17
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVscGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2hlbHBlcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLENBQUMsT0FBZSxFQUFFLFdBQW1CLEVBQVUsRUFBRTtJQUMzRSxJQUFJLE9BQU8sQ0FBQyxNQUFNLElBQUksV0FBVyxFQUFFLENBQUM7UUFDbEMsT0FBTyxPQUFPLENBQUM7SUFDakIsQ0FBQztJQUNELE9BQU8sR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxXQUFXLENBQUMsZ0JBQWdCLE9BQU8sQ0FBQyxNQUFNLEdBQUcsV0FBVyxTQUFTLENBQUM7QUFDL0YsQ0FBQyxDQUFDO0FBRUYsTUFBTSxDQUFDLE1BQU0sU0FBUyxHQUFHLENBQUMsUUFBZ0IsRUFBVyxFQUFFO0lBQ3JELElBQUksQ0FBQztRQUNILE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUM5QixDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztBQUNILENBQUMsQ0FBQztBQUVGLE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRyxDQUFDLFFBQWlCLEVBQXVDLEVBQUUsQ0FDakYsT0FBTyxDQUFDLFFBQVEsQ0FBQyxJQUFJLE9BQU8sUUFBUSxLQUFLLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7QUFFaEYsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLENBQUMsUUFBaUIsRUFBVSxFQUFFLENBQ3hELFFBQVEsWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQyJ9
|
package/dist_ts/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './interfaces.js';
|
|
2
|
+
export * from './classes.connectionregistry.js';
|
|
3
|
+
export * from './classes.dispatchregistry.js';
|
|
4
|
+
export * from './classes.mcpserver.js';
|
|
5
|
+
import { CrossHarnessMcpServer } from './classes.mcpserver.js';
|
|
6
|
+
export const runCli = async () => {
|
|
7
|
+
await new CrossHarnessMcpServer().start();
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsaUNBQWlDLENBQUM7QUFDaEQsY0FBYywrQkFBK0IsQ0FBQztBQUM5QyxjQUFjLHdCQUF3QixDQUFDO0FBRXZDLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRS9ELE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBRyxLQUFLLElBQW1CLEVBQUU7SUFDOUMsTUFBTSxJQUFJLHFCQUFxQixFQUFFLENBQUMsS0FBSyxFQUFFLENBQUM7QUFDNUMsQ0FBQyxDQUFDIn0=
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type THarnessName = 'claudecode' | 'opencode' | 'codex';
|
|
2
|
+
export type TConnectionState = 'connecting' | 'connected' | 'closing' | 'closed';
|
|
3
|
+
export interface IConnectionInfo {
|
|
4
|
+
connectionId: string;
|
|
5
|
+
harness: THarnessName;
|
|
6
|
+
serverUrl: string;
|
|
7
|
+
directory: string;
|
|
8
|
+
state: TConnectionState;
|
|
9
|
+
connectedAt: string;
|
|
10
|
+
serverVersion?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface IConnectionStatus extends IConnectionInfo {
|
|
13
|
+
healthy: boolean;
|
|
14
|
+
detail: string;
|
|
15
|
+
}
|
|
16
|
+
export interface IChatSummary {
|
|
17
|
+
harness: THarnessName;
|
|
18
|
+
chatId: string;
|
|
19
|
+
title: string;
|
|
20
|
+
directory: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
isActive?: boolean;
|
|
23
|
+
meta?: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
export interface IChatMessage {
|
|
26
|
+
role: string;
|
|
27
|
+
text: string;
|
|
28
|
+
timestamp?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface IChatTranscript {
|
|
31
|
+
harness: THarnessName;
|
|
32
|
+
chatId: string;
|
|
33
|
+
title: string;
|
|
34
|
+
directory: string;
|
|
35
|
+
totalMessages?: number;
|
|
36
|
+
messages: IChatMessage[];
|
|
37
|
+
}
|
|
38
|
+
export interface ISendResult {
|
|
39
|
+
harness: THarnessName;
|
|
40
|
+
chatId: string;
|
|
41
|
+
reply: string;
|
|
42
|
+
via: string;
|
|
43
|
+
}
|
|
44
|
+
export interface IHarnessConnection {
|
|
45
|
+
readonly connectionId: string;
|
|
46
|
+
readonly harness: THarnessName;
|
|
47
|
+
readonly serverUrl: string;
|
|
48
|
+
readonly directory: string;
|
|
49
|
+
readonly state: TConnectionState;
|
|
50
|
+
connect(): Promise<void>;
|
|
51
|
+
getInfo(): IConnectionInfo;
|
|
52
|
+
getStatus(): Promise<IConnectionStatus>;
|
|
53
|
+
listChats(limitArg: number): Promise<IChatSummary[]>;
|
|
54
|
+
readChat(chatIdArg: string, limitArg: number, maxCharsArg: number): Promise<IChatTranscript>;
|
|
55
|
+
sendMessage(chatIdArg: string, messageArg: string, timeoutSecondsArg: number, signalArg?: AbortSignal): Promise<ISendResult>;
|
|
56
|
+
close(reasonArg?: string): Promise<void>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as http from 'node:http';
|
|
3
|
+
import * as stream from 'node:stream';
|
|
4
|
+
import type { AddressInfo } from 'node:net';
|
|
5
|
+
export { crypto, http, stream };
|
|
6
|
+
export type { AddressInfo };
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
|
+
import { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js';
|
|
10
|
+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
11
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
12
|
+
import { WebSocket as WsWebSocket, WebSocketServer } from 'ws';
|
|
13
|
+
import type { RawData } from 'ws';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
export { InMemoryTransport, McpClient, McpServer, StdioServerTransport, WebSocketServer, WsWebSocket, z, };
|
|
16
|
+
export type { RawData };
|
|
17
|
+
export type { Transport };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Node native modules
|
|
2
|
+
import * as crypto from 'node:crypto';
|
|
3
|
+
import * as http from 'node:http';
|
|
4
|
+
import * as stream from 'node:stream';
|
|
5
|
+
export { crypto, http, stream };
|
|
6
|
+
// Third-party modules
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
|
+
import { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js';
|
|
10
|
+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
11
|
+
import { WebSocket as WsWebSocket, WebSocketServer } from 'ws';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
export { InMemoryTransport, McpClient, McpServer, StdioServerTransport, WebSocketServer, WsWebSocket, z, };
|
|
14
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsc0JBQXNCO0FBQ3RCLE9BQU8sS0FBSyxNQUFNLE1BQU0sYUFBYSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFDO0FBQ2xDLE9BQU8sS0FBSyxNQUFNLE1BQU0sYUFBYSxDQUFDO0FBR3RDLE9BQU8sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDO0FBR2hDLHNCQUFzQjtBQUN0QixPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDcEUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sMkNBQTJDLENBQUM7QUFDakYsT0FBTyxFQUFFLE1BQU0sSUFBSSxTQUFTLEVBQUUsTUFBTSwyQ0FBMkMsQ0FBQztBQUNoRixPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx1Q0FBdUMsQ0FBQztBQUUxRSxPQUFPLEVBQUUsU0FBUyxJQUFJLFdBQVcsRUFBRSxlQUFlLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFFL0QsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUV4QixPQUFPLEVBQ0wsaUJBQWlCLEVBQ2pCLFNBQVMsRUFDVCxTQUFTLEVBQ1Qsb0JBQW9CLEVBQ3BCLGVBQWUsRUFDZixXQUFXLEVBQ1gsQ0FBQyxHQUNGLENBQUMifQ==
|
package/license.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Task Venture Capital GmbH
|
|
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/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@modelprofile.com/mcp-crossharness",
|
|
3
|
+
"version": "5.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Connection-first MCP server for explicit OpenCode HTTP and Codex WebSocket session servers",
|
|
6
|
+
"main": "dist_ts/index.js",
|
|
7
|
+
"typings": "dist_ts/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"author": "Task Venture Capital GmbH",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://code.foss.global/modelprofile.com/mcp-crossharness.git"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://code.foss.global/modelprofile.com/mcp-crossharness",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://community.foss.global/"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=24"
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"mcp-crossharness": "./cli.js"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@git.zone/tsbuild": "^4.4.2",
|
|
27
|
+
"@git.zone/tsrun": "^2.0.6",
|
|
28
|
+
"@git.zone/tstest": "^4.0.0",
|
|
29
|
+
"@types/node": "^26.2.0",
|
|
30
|
+
"@types/ws": "^8.18.1"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
34
|
+
"ws": "8.21.2",
|
|
35
|
+
"zod": "^4.4.3"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"ts/**/*",
|
|
39
|
+
"dist_ts/**/*",
|
|
40
|
+
"cli.js",
|
|
41
|
+
".smartconfig.json",
|
|
42
|
+
"readme.md"
|
|
43
|
+
],
|
|
44
|
+
"browserslist": [
|
|
45
|
+
"last 1 chrome versions"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test": "(tstest test/ --verbose)",
|
|
49
|
+
"build": "(tsbuild)"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/readme.hints.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# readme.hints.md
|
|
2
|
+
|
|
3
|
+
Implementation findings for mcp-crossharness.
|
|
4
|
+
|
|
5
|
+
## Server-only architecture
|
|
6
|
+
|
|
7
|
+
- Every harness operation after connection setup is scoped to an opaque connection established from an explicit server origin and remote absolute directory; `list_connections` remains global.
|
|
8
|
+
- Duplicate harness/origin/directory bindings are rejected and each registry is capped at 32 connections.
|
|
9
|
+
- `CROSSHARNESS_ALLOWED_SERVER_URLS` is an exact-origin allowlist. Credentials are fixed environment variables, never tool inputs.
|
|
10
|
+
- URLs reject user info, query strings, fragments, and non-root paths. OpenCode fetch calls use `redirect: 'error'`.
|
|
11
|
+
- No production source reads harness stores, invokes harness CLIs, starts or stops processes, or discovers endpoints.
|
|
12
|
+
|
|
13
|
+
## OpenCode server API
|
|
14
|
+
|
|
15
|
+
Verified against OpenCode 1.18.13 docs, source, and generated SDK.
|
|
16
|
+
|
|
17
|
+
- Health: `GET /global/health`.
|
|
18
|
+
- Session list: `GET /session?directory=...&scope=project&roots=true&limit=...`.
|
|
19
|
+
- Session ownership: `GET /session/:id?directory=...`, then verify returned `directory` exactly.
|
|
20
|
+
- Transcript: `GET /session/:id/message?directory=...&limit=...`.
|
|
21
|
+
- Send: `POST /session/:id/message?directory=...` with a generated `messageID` and text part.
|
|
22
|
+
- OpenCode owns queuing. Crossharness intentionally does not serialize HTTP sends, subscribe to permission events, or issue session-wide aborts.
|
|
23
|
+
- Aborting a fetch does not prove that a queued/running server turn stopped, so timeout errors state the ambiguity and no retry occurs.
|
|
24
|
+
- Successful JSON response bodies are capped at 16 MiB; error bodies are capped at 64 KiB and only the first 2,000 characters are surfaced.
|
|
25
|
+
|
|
26
|
+
## Codex app-server API
|
|
27
|
+
|
|
28
|
+
Verified against Codex 0.146.0 using `codex app-server generate-ts` and official app-server documentation.
|
|
29
|
+
|
|
30
|
+
- Explicit transports are `ws://` and `wss://`; one initialized WebSocket is owned per Crossharness connection.
|
|
31
|
+
- Flow: `initialize` -> `initialized`; `thread/list`; `thread/read`; `thread/resume`; `turn/start`; correlated notifications; optional `turn/interrupt` on timeout.
|
|
32
|
+
- `thread/list` supports exact `cwd` filtering. `thread/read` results are still verified against the connection directory before reading or sending.
|
|
33
|
+
- Request responses are correlated by JSON-RPC id. Turn events are correlated by `threadId` and `turnId`; events can arrive before `turn/start` resolves, so one unbound turn per thread may claim the early id.
|
|
34
|
+
- Sends to one thread are sequenced; different threads remain concurrent.
|
|
35
|
+
- Codex send queues are bounded globally and per thread, outbound WebSocket buffering is capped at 4 MiB, and a send's timeout includes time spent waiting in the same-thread queue.
|
|
36
|
+
- Turn output is bounded by text size and delta count, including empty streaming deltas.
|
|
37
|
+
- Foreground MCP cancellation aborts the operation-scoped OpenCode request or safely interrupts the correlated Codex turn; asynchronous dispatches remain independent after acceptance.
|
|
38
|
+
- A timed-out send keeps its thread queue locked until `turn/interrupt` is acknowledged and `turn/completed` reports terminal state. Missing turn correlation or incomplete interruption closes the connection fail-closed.
|
|
39
|
+
- Socket close rejects all pending requests and turns. Disconnect removes the connection before closing so no new work can enter.
|
|
40
|
+
- Stdio EOF closes the MCP transport and every active or handshaking connection.
|
|
41
|
+
- Server requests are never ignored: command/file approvals decline, tool input returns empty answers, MCP elicitation declines, permission-profile requests grant no extra permissions for the turn, legacy approvals abort, and unknown requests receive `-32601`.
|
|
42
|
+
|
|
43
|
+
## Claude Code
|
|
44
|
+
|
|
45
|
+
Claude Code 2.1.219 and the current Agent SDK expose local CLI/library interfaces, not a URL-addressable session server. `connect_harness` rejects `claudecode` until such a server exists.
|
|
46
|
+
|
|
47
|
+
## Testing
|
|
48
|
+
|
|
49
|
+
- HTTP fixtures cover required Basic auth, allowlisting, redirect rejection, directory ownership, concurrent OpenCode sends, and disconnect state.
|
|
50
|
+
- WebSocket fixtures cover initialization, list/read/send, interleaved turns, bounded deltas and send buffering, safe server-request responses, timeout interruption, directory ownership, and disconnect failure propagation.
|
|
51
|
+
- Registry fixtures cover pending-connect shutdown and bounded dispatch admission before send invocation.
|
|
52
|
+
- Timeout fixtures cover OpenCode validation, Codex same-thread queueing, interruption completion, malformed terminal events, and stdio EOF cleanup.
|