@rivus/agent 0.8.6 → 0.9.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/mcp.d.ts +2 -1
- package/dist/mcp.js +43 -33
- package/package.json +1 -1
package/dist/mcp.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ declare function toControlContext(input: {
|
|
|
40
40
|
declare const BACKGROUND_SESSION_MCP_SERVER_NAME = "rivus-background-sessions";
|
|
41
41
|
declare const BACKGROUND_SESSION_MCP_SERVER_VERSION = "1.0.0";
|
|
42
42
|
interface BackgroundSessionMcpServerOptions {
|
|
43
|
-
readonly
|
|
43
|
+
readonly capability: string;
|
|
44
44
|
readonly controlUrl: string;
|
|
45
45
|
readonly controlToken: string;
|
|
46
46
|
readonly enabledTools?: ReadonlyArray<BackgroundSessionControlCommand>;
|
|
@@ -74,6 +74,7 @@ interface BackgroundSessionControlHttpServerOptions {
|
|
|
74
74
|
}
|
|
75
75
|
interface BackgroundSessionControlHttpServer {
|
|
76
76
|
port(): number;
|
|
77
|
+
issueCapability(context: BackgroundSessionControlContext): string;
|
|
77
78
|
close(): Promise<void>;
|
|
78
79
|
start(): Promise<void>;
|
|
79
80
|
}
|
package/dist/mcp.js
CHANGED
|
@@ -24,15 +24,14 @@ function createBackgroundSessionMcpServer(options) {
|
|
|
24
24
|
const tools = createBackgroundSessionToolContracts().filter(({ id }) => enabledTools.has(id.slice(11))).map((contract) => ({
|
|
25
25
|
description: contract.description,
|
|
26
26
|
inputSchema: contract.inputSchema,
|
|
27
|
-
name: contract.id
|
|
28
|
-
outputSchema: { type: "object" }
|
|
27
|
+
name: contract.id
|
|
29
28
|
}));
|
|
30
29
|
const serverInfo = {
|
|
31
30
|
name: options.serverName ?? "rivus-background-sessions",
|
|
32
31
|
version: options.serverVersion ?? "1.0.0"
|
|
33
32
|
};
|
|
34
33
|
return { run: () => runMcpServer({
|
|
35
|
-
|
|
34
|
+
capability: options.capability,
|
|
36
35
|
controlToken: options.controlToken,
|
|
37
36
|
controlUrl: options.controlUrl,
|
|
38
37
|
fetchImplementation,
|
|
@@ -45,10 +44,10 @@ function createBackgroundSessionMcpServer(options) {
|
|
|
45
44
|
async function runBackgroundSessionMcpServer(options = {}) {
|
|
46
45
|
const controlUrl = requireEnv("RIVUS_MCP_CONTROL_URL");
|
|
47
46
|
const controlToken = requireEnv("RIVUS_MCP_CONTROL_TOKEN");
|
|
48
|
-
const
|
|
47
|
+
const capability = requireEnv("RIVUS_MCP_CAPABILITY");
|
|
49
48
|
const enabledTools = optionalEnv("RIVUS_MCP_TOOLS")?.split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
50
49
|
await createBackgroundSessionMcpServer({
|
|
51
|
-
|
|
50
|
+
capability,
|
|
52
51
|
controlToken,
|
|
53
52
|
controlUrl,
|
|
54
53
|
...enabledTools ? { enabledTools } : {},
|
|
@@ -60,16 +59,17 @@ async function runBackgroundSessionMcpServer(options = {}) {
|
|
|
60
59
|
async function runMcpServer(options) {
|
|
61
60
|
const send = (message) => {
|
|
62
61
|
const body = JSON.stringify(message);
|
|
63
|
-
options.output.write(
|
|
62
|
+
options.output.write(`${body}\n`);
|
|
64
63
|
};
|
|
65
64
|
for await (const message of readMcpMessages(options.input)) {
|
|
66
|
-
if (!isRecord(message) ||
|
|
65
|
+
if (!isRecord(message) || !isJsonRpcRequestId(message.id)) continue;
|
|
67
66
|
const method = typeof message.method === "string" ? message.method : void 0;
|
|
68
67
|
if (!method) continue;
|
|
69
68
|
try {
|
|
70
69
|
switch (method) {
|
|
71
70
|
case "initialize":
|
|
72
71
|
send({
|
|
72
|
+
jsonrpc: "2.0",
|
|
73
73
|
id: message.id,
|
|
74
74
|
result: {
|
|
75
75
|
capabilities: { tools: { listChanged: false } },
|
|
@@ -80,12 +80,14 @@ async function runMcpServer(options) {
|
|
|
80
80
|
break;
|
|
81
81
|
case "ping":
|
|
82
82
|
send({
|
|
83
|
+
jsonrpc: "2.0",
|
|
83
84
|
id: message.id,
|
|
84
85
|
result: {}
|
|
85
86
|
});
|
|
86
87
|
break;
|
|
87
88
|
case "tools/list":
|
|
88
89
|
send({
|
|
90
|
+
jsonrpc: "2.0",
|
|
89
91
|
id: message.id,
|
|
90
92
|
result: { tools: options.tools }
|
|
91
93
|
});
|
|
@@ -96,6 +98,7 @@ async function runMcpServer(options) {
|
|
|
96
98
|
const command = name.startsWith("background.") ? name.slice(11) : void 0;
|
|
97
99
|
if (!command || !options.tools.some((tool) => tool.name === name)) {
|
|
98
100
|
send({
|
|
101
|
+
jsonrpc: "2.0",
|
|
99
102
|
id: message.id,
|
|
100
103
|
error: {
|
|
101
104
|
code: -32602,
|
|
@@ -106,8 +109,8 @@ async function runMcpServer(options) {
|
|
|
106
109
|
}
|
|
107
110
|
const response = await options.fetchImplementation(options.controlUrl, {
|
|
108
111
|
body: JSON.stringify({
|
|
112
|
+
capability: options.capability,
|
|
109
113
|
command,
|
|
110
|
-
context: options.controlContext,
|
|
111
114
|
input: params.arguments ?? {}
|
|
112
115
|
}),
|
|
113
116
|
headers: {
|
|
@@ -120,6 +123,7 @@ async function runMcpServer(options) {
|
|
|
120
123
|
if (!response.ok || envelope.error) {
|
|
121
124
|
const messageText = envelope.error?.message ?? `background session control failed with ${response.status}`;
|
|
122
125
|
send({
|
|
126
|
+
jsonrpc: "2.0",
|
|
123
127
|
id: message.id,
|
|
124
128
|
error: {
|
|
125
129
|
code: -32e3,
|
|
@@ -129,6 +133,7 @@ async function runMcpServer(options) {
|
|
|
129
133
|
break;
|
|
130
134
|
}
|
|
131
135
|
send({
|
|
136
|
+
jsonrpc: "2.0",
|
|
132
137
|
id: message.id,
|
|
133
138
|
result: {
|
|
134
139
|
content: [{
|
|
@@ -141,6 +146,7 @@ async function runMcpServer(options) {
|
|
|
141
146
|
break;
|
|
142
147
|
}
|
|
143
148
|
default: send({
|
|
149
|
+
jsonrpc: "2.0",
|
|
144
150
|
id: message.id,
|
|
145
151
|
error: {
|
|
146
152
|
code: -32601,
|
|
@@ -150,6 +156,7 @@ async function runMcpServer(options) {
|
|
|
150
156
|
}
|
|
151
157
|
} catch (error) {
|
|
152
158
|
send({
|
|
159
|
+
jsonrpc: "2.0",
|
|
153
160
|
id: message.id,
|
|
154
161
|
error: {
|
|
155
162
|
code: -32e3,
|
|
@@ -163,36 +170,25 @@ async function* readMcpMessages(input) {
|
|
|
163
170
|
let buffer = "";
|
|
164
171
|
for await (const chunk of input) {
|
|
165
172
|
buffer += chunk.toString("utf8");
|
|
166
|
-
let
|
|
167
|
-
while (
|
|
168
|
-
|
|
169
|
-
buffer =
|
|
170
|
-
|
|
173
|
+
let newlineIndex = buffer.indexOf("\n");
|
|
174
|
+
while (newlineIndex >= 0) {
|
|
175
|
+
const line = buffer.slice(0, newlineIndex).replace(/\r$/, "");
|
|
176
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
177
|
+
if (line.trim() !== "") yield parseMcpMessage(line);
|
|
178
|
+
newlineIndex = buffer.indexOf("\n");
|
|
171
179
|
}
|
|
172
180
|
}
|
|
181
|
+
if (buffer.trim() !== "") yield parseMcpMessage(buffer.trim());
|
|
173
182
|
}
|
|
174
|
-
function
|
|
175
|
-
const headerEnd = buffer.indexOf("\r\n\r\n");
|
|
176
|
-
if (headerEnd < 0) return void 0;
|
|
177
|
-
const contentLength = buffer.slice(0, headerEnd).split("\r\n").find((header) => /^Content-Length:\s*\d+$/i.test(header));
|
|
178
|
-
if (!contentLength) throw new BackgroundSessionMcpError("MCP message is missing a Content-Length header");
|
|
179
|
-
const byteLength = Number(contentLength.slice(15).trim());
|
|
180
|
-
const headerBytes = Buffer.byteLength(buffer.slice(0, headerEnd + 4));
|
|
181
|
-
if (Buffer.byteLength(buffer) < headerBytes + byteLength) return void 0;
|
|
182
|
-
const payload = Buffer.from(buffer).subarray(headerBytes, headerBytes + byteLength).toString("utf8");
|
|
183
|
+
function parseMcpMessage(payload) {
|
|
183
184
|
try {
|
|
184
|
-
return
|
|
185
|
-
byteLength: headerBytes + byteLength,
|
|
186
|
-
message: JSON.parse(payload)
|
|
187
|
-
};
|
|
185
|
+
return JSON.parse(payload);
|
|
188
186
|
} catch {
|
|
189
187
|
throw new BackgroundSessionMcpError("invalid MCP JSON payload");
|
|
190
188
|
}
|
|
191
189
|
}
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
if (!isRecord(parsed)) throw new BackgroundSessionMcpError("RIVUS_MCP_CONTEXT must be a JSON object");
|
|
195
|
-
return parsed;
|
|
190
|
+
function isJsonRpcRequestId(value) {
|
|
191
|
+
return value === null || typeof value === "string" || typeof value === "number";
|
|
196
192
|
}
|
|
197
193
|
function requireEnv(name) {
|
|
198
194
|
const value = process.env[name]?.trim();
|
|
@@ -335,6 +331,8 @@ const COMMANDS = [
|
|
|
335
331
|
function createBackgroundSessionControlHttpServer(options) {
|
|
336
332
|
let server;
|
|
337
333
|
let boundPort = options.port;
|
|
334
|
+
const capabilities = /* @__PURE__ */ new Map();
|
|
335
|
+
const capabilitiesBySessionKey = /* @__PURE__ */ new Map();
|
|
338
336
|
const serverHandle = createServer((request, response) => {
|
|
339
337
|
(async () => {
|
|
340
338
|
try {
|
|
@@ -349,11 +347,13 @@ function createBackgroundSessionControlHttpServer(options) {
|
|
|
349
347
|
if (request.method !== "POST" || request.url !== "/background-sessions") throw new BackgroundSessionControlHttpError(404, "not found");
|
|
350
348
|
if (request.headers.authorization !== `Bearer ${options.token}`) throw new BackgroundSessionControlHttpError(401, "unauthorized");
|
|
351
349
|
const envelope = parseEnvelope(await readBody(request, 64 * 1024));
|
|
350
|
+
const context = capabilities.get(envelope.capability);
|
|
351
|
+
if (!context) throw new BackgroundSessionControlHttpError(401, "unknown or expired capability");
|
|
352
352
|
const command = envelope.command;
|
|
353
353
|
if (!COMMANDS.includes(command)) throw new BackgroundSessionControlHttpError(400, `unsupported background session command: ${String(command)}`);
|
|
354
354
|
respond(response, 200, {
|
|
355
355
|
ok: true,
|
|
356
|
-
result: await options.control.handle(command,
|
|
356
|
+
result: await options.control.handle(command, context, envelope.input)
|
|
357
357
|
});
|
|
358
358
|
} catch (error) {
|
|
359
359
|
respond(response, error instanceof BackgroundSessionControlHttpError ? error.statusCode : 500, {
|
|
@@ -372,8 +372,18 @@ function createBackgroundSessionControlHttpServer(options) {
|
|
|
372
372
|
close: async () => {
|
|
373
373
|
const active = server;
|
|
374
374
|
server = void 0;
|
|
375
|
+
capabilities.clear();
|
|
376
|
+
capabilitiesBySessionKey.clear();
|
|
375
377
|
if (active) await new Promise((resolve) => active.close(() => resolve()));
|
|
376
378
|
},
|
|
379
|
+
issueCapability: (context) => {
|
|
380
|
+
const existing = capabilitiesBySessionKey.get(context.sessionKey);
|
|
381
|
+
if (existing) return existing;
|
|
382
|
+
const capability = randomUUID();
|
|
383
|
+
capabilities.set(capability, context);
|
|
384
|
+
capabilitiesBySessionKey.set(context.sessionKey, capability);
|
|
385
|
+
return capability;
|
|
386
|
+
},
|
|
377
387
|
start: async () => {
|
|
378
388
|
if (server) return;
|
|
379
389
|
await new Promise((resolve, reject) => {
|
|
@@ -399,10 +409,10 @@ function parseEnvelope(body) {
|
|
|
399
409
|
const parsed = JSON.parse(body);
|
|
400
410
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new BackgroundSessionControlHttpError(400, "invalid background session control envelope");
|
|
401
411
|
const record = parsed;
|
|
402
|
-
if (typeof record.command !== "string" || record.
|
|
412
|
+
if (typeof record.command !== "string" || typeof record.capability !== "string") throw new BackgroundSessionControlHttpError(400, "background session control envelope requires command and capability");
|
|
403
413
|
return {
|
|
414
|
+
capability: record.capability,
|
|
404
415
|
command: record.command,
|
|
405
|
-
context: record.context,
|
|
406
416
|
input: record.input
|
|
407
417
|
};
|
|
408
418
|
}
|