@modelprofile.com/mcp-crossharness 5.0.0 → 5.2.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.codexappserverclient.d.ts +71 -0
- package/dist_ts/classes.codexappserverclient.js +298 -0
- package/dist_ts/classes.connection.codex.d.ts +1 -9
- package/dist_ts/classes.connection.codex.js +22 -246
- package/dist_ts/classes.mcpserver.d.ts +4 -4
- package/dist_ts/classes.mcpserver.js +63 -215
- package/dist_ts/classes.mcptoolregistrar.d.ts +26 -0
- package/dist_ts/classes.mcptoolregistrar.js +257 -0
- package/dist_ts/index.d.ts +2 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/plugins.d.ts +2 -0
- package/dist_ts/plugins.js +1 -1
- package/package.json +2 -2
- package/readme.md +109 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.codexappserverclient.ts +331 -0
- package/ts/classes.connection.codex.ts +22 -256
- package/ts/classes.mcpserver.ts +67 -297
- package/ts/classes.mcptoolregistrar.ts +345 -0
- package/ts/index.ts +2 -0
- package/ts/plugins.ts +2 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { ConnectionRegistry } from './classes.connectionregistry.js';
|
|
3
|
+
import { DispatchRegistry } from './classes.dispatchregistry.js';
|
|
4
|
+
const harnessNameSchema = plugins.z.enum(['claudecode', 'opencode', 'codex']);
|
|
5
|
+
/**
|
|
6
|
+
* Registers the Crossharness tool surface on an externally owned MCP server and
|
|
7
|
+
* owns only the resources used by those tools.
|
|
8
|
+
*/
|
|
9
|
+
export class CrossHarnessMcpToolRegistrar {
|
|
10
|
+
connections;
|
|
11
|
+
dispatches;
|
|
12
|
+
registeredTools = [];
|
|
13
|
+
registeredServer;
|
|
14
|
+
registered = false;
|
|
15
|
+
closeStarted = false;
|
|
16
|
+
closePromise;
|
|
17
|
+
resourceShutdownPromise;
|
|
18
|
+
constructor(connectionsArg = new ConnectionRegistry(), dispatchesArg = new DispatchRegistry()) {
|
|
19
|
+
this.connections = connectionsArg;
|
|
20
|
+
this.dispatches = dispatchesArg;
|
|
21
|
+
}
|
|
22
|
+
/** Register the tool surface before the externally owned MCP server connects. */
|
|
23
|
+
register(serverArg) {
|
|
24
|
+
if (this.closeStarted) {
|
|
25
|
+
throw new Error('Crossharness MCP tool registrar is closed');
|
|
26
|
+
}
|
|
27
|
+
if (this.registered) {
|
|
28
|
+
throw new Error('Crossharness MCP tools are already registered');
|
|
29
|
+
}
|
|
30
|
+
if (serverArg.isConnected()) {
|
|
31
|
+
throw new Error('Crossharness MCP tools must be registered before the MCP server connects');
|
|
32
|
+
}
|
|
33
|
+
const registeredTools = [];
|
|
34
|
+
try {
|
|
35
|
+
registeredTools.push(serverArg.registerTool('connect_harness', {
|
|
36
|
+
title: 'Connect to harness server',
|
|
37
|
+
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.',
|
|
38
|
+
inputSchema: {
|
|
39
|
+
harness: harnessNameSchema,
|
|
40
|
+
serverUrl: plugins.z.string().max(2048).url().describe('Allowlisted harness server origin'),
|
|
41
|
+
directory: plugins.z
|
|
42
|
+
.string()
|
|
43
|
+
.max(4096)
|
|
44
|
+
.startsWith('/')
|
|
45
|
+
.describe('Absolute project directory on the harness server'),
|
|
46
|
+
},
|
|
47
|
+
}, async (args, extra) => this.wrapToolCall(async () => {
|
|
48
|
+
const info = await this.connections.connect(args.harness, args.serverUrl, args.directory, extra.signal);
|
|
49
|
+
return this.describeConnection(info);
|
|
50
|
+
})));
|
|
51
|
+
registeredTools.push(serverArg.registerTool('disconnect_harness', {
|
|
52
|
+
title: 'Disconnect harness server',
|
|
53
|
+
description: 'Close and remove an established harness connection. In-flight requests fail; async dispatch records remain inspectable with check_reply.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
56
|
+
},
|
|
57
|
+
}, async (args) => this.wrapToolCall(async () => this.describeConnection(await this.connections.disconnect(args.connectionId)))));
|
|
58
|
+
registeredTools.push(serverArg.registerTool('list_connections', {
|
|
59
|
+
title: 'List harness connections',
|
|
60
|
+
description: 'List explicit harness web-server connections established in this MCP process.',
|
|
61
|
+
inputSchema: {},
|
|
62
|
+
}, async () => this.wrapToolCall(async () => {
|
|
63
|
+
const connections = this.connections.list();
|
|
64
|
+
return connections.length > 0
|
|
65
|
+
? connections.map((connection) => this.describeConnection(connection)).join('\n\n')
|
|
66
|
+
: 'No harness server connections established.';
|
|
67
|
+
})));
|
|
68
|
+
registeredTools.push(serverArg.registerTool('connection_status', {
|
|
69
|
+
title: 'Check harness connection',
|
|
70
|
+
description: 'Run the protocol health check for one explicit harness server connection.',
|
|
71
|
+
inputSchema: {
|
|
72
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
73
|
+
},
|
|
74
|
+
}, async (args) => this.wrapToolCall(async () => {
|
|
75
|
+
const status = await this.connections.get(args.connectionId).getStatus();
|
|
76
|
+
return `${this.describeConnection(status)}\nhealthy: ${status.healthy ? 'yes' : 'no'}\ndetail: ${status.detail}`;
|
|
77
|
+
})));
|
|
78
|
+
registeredTools.push(serverArg.registerTool('list_chats', {
|
|
79
|
+
title: 'List harness chats',
|
|
80
|
+
description: 'List chats from the server and directory bound to an explicit connection. No local session stores are read.',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
83
|
+
limit: plugins.z.number().int().min(1).max(100).optional(),
|
|
84
|
+
},
|
|
85
|
+
}, async (args) => this.wrapToolCall(async () => {
|
|
86
|
+
const chats = await this.connections.get(args.connectionId).listChats(args.limit ?? 10);
|
|
87
|
+
if (chats.length === 0)
|
|
88
|
+
return 'No chats found for this connection.';
|
|
89
|
+
return [
|
|
90
|
+
'harness\tchatId\tupdatedAt\tactivity\ttitle',
|
|
91
|
+
...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)}`),
|
|
92
|
+
].join('\n');
|
|
93
|
+
})));
|
|
94
|
+
registeredTools.push(serverArg.registerTool('read_chat', {
|
|
95
|
+
title: 'Read harness chat',
|
|
96
|
+
description: 'Read a chat through the harness web server bound to an explicit connection.',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
99
|
+
chatId: plugins.z.string().min(1).max(1024),
|
|
100
|
+
limit: plugins.z.number().int().min(1).max(500).optional(),
|
|
101
|
+
maxChars: plugins.z.number().int().min(200).max(50000).optional(),
|
|
102
|
+
},
|
|
103
|
+
}, async (args) => this.wrapToolCall(async () => {
|
|
104
|
+
const transcript = await this.connections
|
|
105
|
+
.get(args.connectionId)
|
|
106
|
+
.readChat(args.chatId, args.limit ?? 20, args.maxChars ?? 2000);
|
|
107
|
+
const header = [
|
|
108
|
+
`harness: ${transcript.harness}`,
|
|
109
|
+
`chatId: ${transcript.chatId}`,
|
|
110
|
+
`title: ${transcript.title}`,
|
|
111
|
+
`directory: ${transcript.directory}`,
|
|
112
|
+
transcript.totalMessages === undefined
|
|
113
|
+
? `messages: showing ${transcript.messages.length}; server total unavailable`
|
|
114
|
+
: `messages: showing ${transcript.messages.length} of ${transcript.totalMessages}`,
|
|
115
|
+
].join('\n');
|
|
116
|
+
const body = transcript.messages
|
|
117
|
+
.map((message) => `-- [${message.role}]${message.timestamp ? ` ${message.timestamp}` : ''}\n${message.text}`)
|
|
118
|
+
.join('\n');
|
|
119
|
+
return `${header}\n\n${body}`;
|
|
120
|
+
})));
|
|
121
|
+
registeredTools.push(serverArg.registerTool('send_message', {
|
|
122
|
+
title: 'Send message through harness server',
|
|
123
|
+
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.',
|
|
124
|
+
inputSchema: {
|
|
125
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
126
|
+
chatId: plugins.z.string().min(1).max(1024),
|
|
127
|
+
message: plugins.z.string().min(1).max(1024 * 1024),
|
|
128
|
+
timeoutSeconds: plugins.z.number().int().min(1).max(3600).optional(),
|
|
129
|
+
},
|
|
130
|
+
}, async (args, extra) => this.wrapToolCall(async () => {
|
|
131
|
+
const result = await this.connections
|
|
132
|
+
.get(args.connectionId)
|
|
133
|
+
.sendMessage(args.chatId, args.message, args.timeoutSeconds ?? 300, extra.signal);
|
|
134
|
+
return `${result.reply}\n\n[crossharness transport: ${result.via}]`;
|
|
135
|
+
})));
|
|
136
|
+
registeredTools.push(serverArg.registerTool('send_message_async', {
|
|
137
|
+
title: 'Send message through harness server asynchronously',
|
|
138
|
+
description: 'Start a server-only send and return a dispatchId immediately. Both this connectionId and the dispatchId are required by check_reply.',
|
|
139
|
+
inputSchema: {
|
|
140
|
+
connectionId: plugins.z.string().max(128).describe('Connection id returned by connect_harness'),
|
|
141
|
+
chatId: plugins.z.string().min(1).max(1024),
|
|
142
|
+
message: plugins.z.string().min(1).max(1024 * 1024),
|
|
143
|
+
timeoutSeconds: plugins.z.number().int().min(1).max(3600).optional(),
|
|
144
|
+
},
|
|
145
|
+
}, async (args) => this.wrapToolCall(async () => {
|
|
146
|
+
const connection = this.connections.get(args.connectionId);
|
|
147
|
+
const record = this.dispatches.track(args.connectionId, connection.harness, args.chatId, () => connection.sendMessage(args.chatId, args.message, args.timeoutSeconds ?? 900));
|
|
148
|
+
return [
|
|
149
|
+
`connectionId: ${record.connectionId}`,
|
|
150
|
+
`dispatchId: ${record.dispatchId}`,
|
|
151
|
+
`harness: ${record.harness}`,
|
|
152
|
+
`chatId: ${record.chatId}`,
|
|
153
|
+
`startedAt: ${record.startedAt}`,
|
|
154
|
+
].join('\n');
|
|
155
|
+
})));
|
|
156
|
+
registeredTools.push(serverArg.registerTool('check_reply', {
|
|
157
|
+
title: 'Check async server send',
|
|
158
|
+
description: 'Check an asynchronous send. The connectionId must own the dispatch. Records remain inspectable after disconnect.',
|
|
159
|
+
inputSchema: {
|
|
160
|
+
connectionId: plugins.z.string().max(128).describe('Connection id used for send_message_async'),
|
|
161
|
+
dispatchId: plugins.z.string().max(128).describe('Dispatch id returned by send_message_async'),
|
|
162
|
+
waitSeconds: plugins.z.number().int().min(0).max(60).optional(),
|
|
163
|
+
},
|
|
164
|
+
}, async (args, extra) => this.wrapToolCall(async () => {
|
|
165
|
+
const record = await this.dispatches.waitFor(args.connectionId, args.dispatchId, (args.waitSeconds ?? 0) * 1000, extra.signal);
|
|
166
|
+
if (!record)
|
|
167
|
+
throw new Error('unknown dispatchId for this connectionId');
|
|
168
|
+
const header = [
|
|
169
|
+
`connectionId: ${record.connectionId}`,
|
|
170
|
+
`dispatchId: ${record.dispatchId}`,
|
|
171
|
+
`status: ${record.status}`,
|
|
172
|
+
`harness: ${record.harness}`,
|
|
173
|
+
`chatId: ${record.chatId}`,
|
|
174
|
+
`startedAt: ${record.startedAt}`,
|
|
175
|
+
...(record.completedAt ? [`completedAt: ${record.completedAt}`] : []),
|
|
176
|
+
].join('\n');
|
|
177
|
+
if (record.status === 'working')
|
|
178
|
+
return `${header}\n\nStill working.`;
|
|
179
|
+
if (record.status === 'failed')
|
|
180
|
+
return `${header}\n\nError: ${record.error}`;
|
|
181
|
+
return `${header}\n\n${record.reply}\n\n[crossharness transport: ${record.via}]`;
|
|
182
|
+
})));
|
|
183
|
+
}
|
|
184
|
+
catch (errorArg) {
|
|
185
|
+
for (const registeredTool of registeredTools.reverse())
|
|
186
|
+
registeredTool.remove();
|
|
187
|
+
throw errorArg;
|
|
188
|
+
}
|
|
189
|
+
this.registeredTools = registeredTools;
|
|
190
|
+
this.registeredServer = serverArg;
|
|
191
|
+
this.registered = true;
|
|
192
|
+
}
|
|
193
|
+
/** Close the externally owned MCP server before closing this registrar. */
|
|
194
|
+
async close() {
|
|
195
|
+
if (this.closePromise)
|
|
196
|
+
return this.closePromise;
|
|
197
|
+
if (this.registeredServer?.isConnected()) {
|
|
198
|
+
throw new Error('Crossharness MCP tool registrar cannot close while its MCP server is connected; close the shared MCP server first');
|
|
199
|
+
}
|
|
200
|
+
this.closeStarted = true;
|
|
201
|
+
this.removeRegisteredTools();
|
|
202
|
+
this.registeredServer = undefined;
|
|
203
|
+
this.closePromise = this.ensureResourceShutdown().finally(() => {
|
|
204
|
+
this.closePromise = undefined;
|
|
205
|
+
});
|
|
206
|
+
return this.closePromise;
|
|
207
|
+
}
|
|
208
|
+
removeRegisteredTools() {
|
|
209
|
+
for (const registeredTool of this.registeredTools.reverse())
|
|
210
|
+
registeredTool.remove();
|
|
211
|
+
this.registeredTools = [];
|
|
212
|
+
}
|
|
213
|
+
ensureResourceShutdown() {
|
|
214
|
+
if (this.resourceShutdownPromise)
|
|
215
|
+
return this.resourceShutdownPromise;
|
|
216
|
+
let shutdownPromise;
|
|
217
|
+
shutdownPromise = this.connections.closeAll().finally(() => {
|
|
218
|
+
this.dispatches.dispose();
|
|
219
|
+
}).catch((errorArg) => {
|
|
220
|
+
if (this.resourceShutdownPromise === shutdownPromise) {
|
|
221
|
+
this.resourceShutdownPromise = undefined;
|
|
222
|
+
}
|
|
223
|
+
throw errorArg;
|
|
224
|
+
});
|
|
225
|
+
this.resourceShutdownPromise = shutdownPromise;
|
|
226
|
+
return shutdownPromise;
|
|
227
|
+
}
|
|
228
|
+
describeConnection(infoArg) {
|
|
229
|
+
return [
|
|
230
|
+
`connectionId: ${infoArg.connectionId}`,
|
|
231
|
+
`harness: ${infoArg.harness}`,
|
|
232
|
+
`serverUrl: ${infoArg.serverUrl}`,
|
|
233
|
+
`directory: ${infoArg.directory}`,
|
|
234
|
+
`state: ${infoArg.state}`,
|
|
235
|
+
`connectedAt: ${infoArg.connectedAt}`,
|
|
236
|
+
...(infoArg.serverVersion ? [`serverVersion: ${infoArg.serverVersion}`] : []),
|
|
237
|
+
].join('\n');
|
|
238
|
+
}
|
|
239
|
+
async wrapToolCall(handlerArg) {
|
|
240
|
+
try {
|
|
241
|
+
const text = await handlerArg();
|
|
242
|
+
return { content: [{ type: 'text', text: text.slice(0, 100000) }] };
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
return {
|
|
246
|
+
content: [
|
|
247
|
+
{
|
|
248
|
+
type: 'text',
|
|
249
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`.slice(0, 100000),
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
isError: true,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5tY3B0b29scmVnaXN0cmFyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5tY3B0b29scmVnaXN0cmFyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3JFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRWpFLE1BQU0saUJBQWlCLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxZQUFZLEVBQUUsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUM7QUFFOUU7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLDRCQUE0QjtJQUN2QixXQUFXLENBQXFCO0lBQ2hDLFVBQVUsQ0FBbUI7SUFDckMsZUFBZSxHQUE2QixFQUFFLENBQUM7SUFDL0MsZ0JBQWdCLENBQXFEO0lBQ3JFLFVBQVUsR0FBRyxLQUFLLENBQUM7SUFDbkIsWUFBWSxHQUFHLEtBQUssQ0FBQztJQUNyQixZQUFZLENBQTRCO0lBQ3hDLHVCQUF1QixDQUE0QjtJQUUzRCxZQUFZLGNBQWMsR0FBRyxJQUFJLGtCQUFrQixFQUFFLEVBQUUsYUFBYSxHQUFHLElBQUksZ0JBQWdCLEVBQUU7UUFDM0YsSUFBSSxDQUFDLFdBQVcsR0FBRyxjQUFjLENBQUM7UUFDbEMsSUFBSSxDQUFDLFVBQVUsR0FBRyxhQUFhLENBQUM7SUFDbEMsQ0FBQztJQUVELGlGQUFpRjtJQUMxRSxRQUFRLENBQUMsU0FBaUQ7UUFDL0QsSUFBSSxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7WUFDdEIsTUFBTSxJQUFJLEtBQUssQ0FBQywyQ0FBMkMsQ0FBQyxDQUFDO1FBQy9ELENBQUM7UUFDRCxJQUFJLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztZQUNwQixNQUFNLElBQUksS0FBSyxDQUFD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
|
package/dist_ts/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export * from './interfaces.js';
|
|
2
|
+
export * from './classes.codexappserverclient.js';
|
|
2
3
|
export * from './classes.connectionregistry.js';
|
|
3
4
|
export * from './classes.dispatchregistry.js';
|
|
5
|
+
export * from './classes.mcptoolregistrar.js';
|
|
4
6
|
export * from './classes.mcpserver.js';
|
|
5
7
|
export declare const runCli: () => Promise<void>;
|
package/dist_ts/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export * from './interfaces.js';
|
|
2
|
+
export * from './classes.codexappserverclient.js';
|
|
2
3
|
export * from './classes.connectionregistry.js';
|
|
3
4
|
export * from './classes.dispatchregistry.js';
|
|
5
|
+
export * from './classes.mcptoolregistrar.js';
|
|
4
6
|
export * from './classes.mcpserver.js';
|
|
5
7
|
import { CrossHarnessMcpServer } from './classes.mcpserver.js';
|
|
6
8
|
export const runCli = async () => {
|
|
7
9
|
await new CrossHarnessMcpServer().start();
|
|
8
10
|
};
|
|
9
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMsbUNBQW1DLENBQUM7QUFDbEQsY0FBYyxpQ0FBaUMsQ0FBQztBQUNoRCxjQUFjLCtCQUErQixDQUFDO0FBQzlDLGNBQWMsK0JBQStCLENBQUM7QUFDOUMsY0FBYyx3QkFBd0IsQ0FBQztBQUV2QyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUUvRCxNQUFNLENBQUMsTUFBTSxNQUFNLEdBQUcsS0FBSyxJQUFtQixFQUFFO0lBQzlDLE1BQU0sSUFBSSxxQkFBcUIsRUFBRSxDQUFDLEtBQUssRUFBRSxDQUFDO0FBQzVDLENBQUMsQ0FBQyJ9
|
package/dist_ts/plugins.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { AddressInfo } from 'node:net';
|
|
|
5
5
|
export { crypto, http, stream };
|
|
6
6
|
export type { AddressInfo };
|
|
7
7
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import type { RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
9
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
10
|
import { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js';
|
|
10
11
|
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
@@ -14,4 +15,5 @@ import type { RawData } from 'ws';
|
|
|
14
15
|
import { z } from 'zod';
|
|
15
16
|
export { InMemoryTransport, McpClient, McpServer, StdioServerTransport, WebSocketServer, WsWebSocket, z, };
|
|
16
17
|
export type { RawData };
|
|
18
|
+
export type { RegisteredTool };
|
|
17
19
|
export type { Transport };
|
package/dist_ts/plugins.js
CHANGED
|
@@ -11,4 +11,4 @@ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
|
11
11
|
import { WebSocket as WsWebSocket, WebSocketServer } from 'ws';
|
|
12
12
|
import { z } from 'zod';
|
|
13
13
|
export { InMemoryTransport, McpClient, McpServer, StdioServerTransport, WebSocketServer, WsWebSocket, z, };
|
|
14
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
14
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsc0JBQXNCO0FBQ3RCLE9BQU8sS0FBSyxNQUFNLE1BQU0sYUFBYSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFDO0FBQ2xDLE9BQU8sS0FBSyxNQUFNLE1BQU0sYUFBYSxDQUFDO0FBR3RDLE9BQU8sRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDO0FBR2hDLHNCQUFzQjtBQUN0QixPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFFcEUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sMkNBQTJDLENBQUM7QUFDakYsT0FBTyxFQUFFLE1BQU0sSUFBSSxTQUFTLEVBQUUsTUFBTSwyQ0FBMkMsQ0FBQztBQUNoRixPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx1Q0FBdUMsQ0FBQztBQUUxRSxPQUFPLEVBQUUsU0FBUyxJQUFJLFdBQVcsRUFBRSxlQUFlLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFFL0QsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUV4QixPQUFPLEVBQ0wsaUJBQWlCLEVBQ2pCLFNBQVMsRUFDVCxTQUFTLEVBQ1Qsb0JBQW9CLEVBQ3BCLGVBQWUsRUFDZixXQUFXLEVBQ1gsQ0FBQyxHQUNGLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelprofile.com/mcp-crossharness",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Connection-first MCP server for explicit OpenCode HTTP and Codex WebSocket session servers",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@git.zone/tsbuild": "^4.4.2",
|
|
27
27
|
"@git.zone/tsrun": "^2.0.6",
|
|
28
28
|
"@git.zone/tstest": "^4.0.0",
|
|
29
|
-
"@types/node": "^26.
|
|
29
|
+
"@types/node": "^26.4.1",
|
|
30
30
|
"@types/ws": "^8.18.1"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
package/readme.md
CHANGED
|
@@ -8,7 +8,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
8
8
|
|
|
9
9
|
## Transport Contract
|
|
10
10
|
|
|
11
|
-
Crossharness only
|
|
11
|
+
Crossharness MCP tools only talk to explicitly connected harness web servers:
|
|
12
12
|
|
|
13
13
|
| Harness | Server transport | Support |
|
|
14
14
|
| --- | --- | --- |
|
|
@@ -140,13 +140,119 @@ Register the `mcp-crossharness` binary as a stdio MCP server in your client.
|
|
|
140
140
|
|
|
141
141
|
## Library API
|
|
142
142
|
|
|
143
|
+
### Native Codex app-server client
|
|
144
|
+
|
|
145
|
+
`CodexAppServerClient` exposes the underlying app-server protocol independently of the MCP
|
|
146
|
+
chat tools. It accepts an explicit WebSocket endpoint or caller-owned JSONL stdio streams;
|
|
147
|
+
it never discovers or spawns a server. The embedding application owns process lifecycle,
|
|
148
|
+
session authorization, history projection, and human approval policy.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { CodexAppServerClient } from '@modelprofile.com/mcp-crossharness';
|
|
152
|
+
|
|
153
|
+
const client = new CodexAppServerClient({
|
|
154
|
+
transport: { type: 'stdio', readable: child.stdout, writable: child.stdin },
|
|
155
|
+
clientInfo: { name: 'my-app', title: 'My App', version: '1.0.0' },
|
|
156
|
+
experimentalApi: true,
|
|
157
|
+
onNotification: ({ method, params }) => handleNotification(method, params),
|
|
158
|
+
onServerRequest: (request) => showApproval(request),
|
|
159
|
+
onClose: (error) => handleDisconnected(error),
|
|
160
|
+
});
|
|
161
|
+
await client.connect();
|
|
162
|
+
const models = await client.request('model/list', {});
|
|
163
|
+
// After a human decision, use the exact connection-scoped request ID:
|
|
164
|
+
client.respond(requestId, { decision: 'accept' });
|
|
165
|
+
client.close();
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
For WebSocket transport use `{ type: 'websocket', url: approvedServerUrl, token: bearerToken }`.
|
|
169
|
+
The caller must authorize the destination before supplying credentials. The client follows no
|
|
170
|
+
redirects and reads no credential environment variables. MCP tools retain their existing
|
|
171
|
+
allowlist and credential policy.
|
|
172
|
+
|
|
173
|
+
`request(method, params, timeoutMs?, signal?)` returns the protocol result as `unknown` for
|
|
174
|
+
validation by the caller. It performs no retries. A `CodexAppServerRequestError` exposes
|
|
175
|
+
`dispatched` and an optional server error `code`; a timeout or disconnect after dispatch
|
|
176
|
+
does not prove a mutation failed. Never replay an uncertain `thread/start` or `turn/start`.
|
|
177
|
+
The client handles initialize/initialized, response correlation, bounded framing, and close
|
|
178
|
+
cleanup. The default inbound frame limit is 4 MiB; `maxMessageBytes` accepts 1 KiB–64 MiB.
|
|
179
|
+
Outgoing buffers are capped at 4 MiB and pending requests at 256 in each direction.
|
|
180
|
+
|
|
181
|
+
Server request handlers return immediately so notifications continue during human input.
|
|
182
|
+
Each request carries an `AbortSignal`, aborted when it is answered, resolved by the server,
|
|
183
|
+
or disconnected. `respond(id, result)` and `rejectRequest(id, code, message)` return false
|
|
184
|
+
for an expired ID. Unhandled server requests receive method-not-supported; this low-level
|
|
185
|
+
client never grants or automatically declines a permission. `close()` ends the supplied
|
|
186
|
+
stdio streams but never kills a subprocess. The existing MCP Codex adapter delegates to
|
|
187
|
+
this client and retains its conservative automatic rejection policy.
|
|
188
|
+
|
|
189
|
+
### Composable tool registration
|
|
190
|
+
|
|
191
|
+
`CrossHarnessMcpToolRegistrar` registers the same nine Crossharness tools used by the standalone
|
|
192
|
+
server on an MCP SDK 1.30 `McpServer` owned by an embedding application:
|
|
193
|
+
|
|
143
194
|
```typescript
|
|
195
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
144
196
|
import {
|
|
145
197
|
ConnectionRegistry,
|
|
146
|
-
|
|
147
|
-
CrossHarnessMcpServer,
|
|
198
|
+
CrossHarnessMcpToolRegistrar,
|
|
148
199
|
} from '@modelprofile.com/mcp-crossharness';
|
|
149
200
|
|
|
201
|
+
const connections = new ConnectionRegistry({
|
|
202
|
+
allowedServerUrls: ['http://127.0.0.1:4096/'],
|
|
203
|
+
openCodeCredentials: {
|
|
204
|
+
serverUrl: 'http://127.0.0.1:4096/',
|
|
205
|
+
username: 'opencode',
|
|
206
|
+
password: controllerOwnedPassword,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
const crossHarnessTools = new CrossHarnessMcpToolRegistrar(connections);
|
|
210
|
+
const sharedServer = new McpServer({ name: 'controller', version: '1.0.0' });
|
|
211
|
+
|
|
212
|
+
crossHarnessTools.register(sharedServer);
|
|
213
|
+
await sharedServer.connect(hostOwnedTransport);
|
|
214
|
+
|
|
215
|
+
// During host shutdown, close the shared server before its Crossharness registrar.
|
|
216
|
+
await sharedServer.close();
|
|
217
|
+
await crossHarnessTools.close();
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The registrar takes lifecycle ownership of injected `ConnectionRegistry` and `DispatchRegistry`
|
|
221
|
+
instances. Call `register()` before connecting the shared server. A successful registrar can
|
|
222
|
+
register only once.
|
|
223
|
+
|
|
224
|
+
Composition supports an ordinary high-level `McpServer` tool surface, including host tools added
|
|
225
|
+
with `McpServer.registerTool()`. A duplicate high-level tool name throws and transactionally removes
|
|
226
|
+
the Crossharness tools added by that attempt without replacing the host's tool.
|
|
227
|
+
|
|
228
|
+
Do not preinstall custom low-level `tools/list` or `tools/call` handlers with
|
|
229
|
+
`sharedServer.server.setRequestHandler()`. SDK 1.30 adds the first high-level tool to its internal
|
|
230
|
+
map before discovering that low-level handler conflict and throwing, without returning a
|
|
231
|
+
`RegisteredTool` handle. The registrar cannot transactionally roll back that SDK mutation. Use the
|
|
232
|
+
ordinary high-level `McpServer.registerTool()` surface or a separate `McpServer` instead.
|
|
233
|
+
|
|
234
|
+
`close()` removes the registrar's tool registrations, closes every owned connection, and disposes
|
|
235
|
+
its dispatch registry. If the registered server is still connected, `close()` fails before
|
|
236
|
+
removing tools or mutating either registry. Close the shared server first, then call registrar
|
|
237
|
+
`close()`; failed connection cleanup remains retryable by calling it again. The registrar never
|
|
238
|
+
connects or closes the shared `McpServer`, never owns its transport, and installs no stdin,
|
|
239
|
+
process-signal, or shared-server close listeners.
|
|
240
|
+
|
|
241
|
+
`CrossHarnessMcpServer` is the standalone stdio lifecycle wrapper. It creates this exact registrar,
|
|
242
|
+
keeps the existing one-shot `start()` contract, and owns only its own MCP server, transport, and
|
|
243
|
+
stdin close handling in addition to delegating tool-resource cleanup to the registrar. It closes
|
|
244
|
+
the server before the registrar, retains the same server and transport when transport close fails,
|
|
245
|
+
and retries them on the next `close()`. Concurrent transport and registrar cleanup failures are
|
|
246
|
+
reported together as an `AggregateError`.
|
|
247
|
+
|
|
248
|
+
### Direct registry use
|
|
249
|
+
|
|
250
|
+
The registries remain public for applications that need the connection API without MCP tool
|
|
251
|
+
registration:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { ConnectionRegistry } from '@modelprofile.com/mcp-crossharness';
|
|
255
|
+
|
|
150
256
|
const connections = new ConnectionRegistry({
|
|
151
257
|
allowedServerUrls: ['http://127.0.0.1:4096/'],
|
|
152
258
|
openCodeCredentials: {
|
package/ts/00_commitinfo_data.ts
CHANGED