kojee-mcp 0.7.5-staging.16 → 0.7.5-staging.18
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/{chunk-LY4DBSBS.js → chunk-Q2UVK7VA.js} +96 -2
- package/dist/{chunk-XXHYGH7G.js → chunk-UGEBGCYU.js} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{event-stream-K6G7B2KY.js → event-stream-5ZEL7FXN.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/lib.d.ts +17 -0
- package/dist/lib.js +1 -1
- package/dist/{registry-BP5V3W5Y.js → registry-MCJAVXJW.js} +3 -3
- package/dist/{tail-stream-MODWTGVA.js → tail-stream-PQXEY3TI.js} +1 -1
- package/package.json +1 -1
|
@@ -22,6 +22,10 @@ import os from "os";
|
|
|
22
22
|
import path from "path";
|
|
23
23
|
|
|
24
24
|
// src/tool-registry.ts
|
|
25
|
+
import { readFileSync, statSync } from "fs";
|
|
26
|
+
import { basename } from "path";
|
|
27
|
+
var UPLOAD_TOOL = "tandem_upload_attachment";
|
|
28
|
+
var MAX_UPLOAD_FILE_BYTES = 25 * 1024 * 1024;
|
|
25
29
|
var ToolRegistry = class {
|
|
26
30
|
constructor(gateway) {
|
|
27
31
|
this.gateway = gateway;
|
|
@@ -68,8 +72,87 @@ var ToolRegistry = class {
|
|
|
68
72
|
}
|
|
69
73
|
this.tools.set(tool.name, tool);
|
|
70
74
|
}
|
|
75
|
+
this.augmentUploadTool();
|
|
71
76
|
console.error(`[tools] Registered ${this.tools.size} tools from gateway`);
|
|
72
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Teach the gateway's `tandem_upload_attachment` tool about a `file_path` arg
|
|
80
|
+
* that THIS proxy resolves in-process (see {@link resolveUploadFilePath}): the
|
|
81
|
+
* model emits a local path, the proxy reads the bytes and forwards them as
|
|
82
|
+
* `data_base64`, so a multi-MB image never has to be generated into the model's
|
|
83
|
+
* tool call. Purely additive — the original `data_base64` shape still works. A
|
|
84
|
+
* no-op if the gateway doesn't expose the tool (older backend).
|
|
85
|
+
*/
|
|
86
|
+
augmentUploadTool() {
|
|
87
|
+
const tool = this.tools.get(UPLOAD_TOOL);
|
|
88
|
+
if (!tool) return;
|
|
89
|
+
const schema = { ...tool.inputSchema ?? {} };
|
|
90
|
+
const properties = { ...schema.properties ?? {} };
|
|
91
|
+
if ("file_path" in properties) return;
|
|
92
|
+
properties.file_path = {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Absolute path to a local image file. Your local kojee-mcp-server reads the bytes and uploads them for you (downscaled server-side), so the image is NEVER placed in your prompt/tool-call. Prefer this over data_base64 for photos. Provide file_path OR data_base64, not both; filename is derived from the path when omitted."
|
|
95
|
+
};
|
|
96
|
+
schema.properties = properties;
|
|
97
|
+
if (Array.isArray(schema.required)) {
|
|
98
|
+
schema.required = schema.required.filter(
|
|
99
|
+
(k) => k !== "data_base64" && k !== "filename"
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const dependent = {
|
|
103
|
+
...schema.dependentRequired ?? {},
|
|
104
|
+
data_base64: ["filename"]
|
|
105
|
+
};
|
|
106
|
+
schema.dependentRequired = dependent;
|
|
107
|
+
this.tools.set(UPLOAD_TOOL, { ...tool, inputSchema: schema });
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolve a `tandem_upload_attachment` call that used `file_path`: read the file
|
|
111
|
+
* locally, base64-encode the RAW bytes (Buffer→base64 is byte-exact — no text
|
|
112
|
+
* round-trip, so no U+FFFD corruption), and rewrite the args to the `data_base64`
|
|
113
|
+
* shape the gateway expects. Returns the args to forward, or throws with a clear
|
|
114
|
+
* message the caller surfaces as an isError result.
|
|
115
|
+
*/
|
|
116
|
+
resolveUploadFilePath(args) {
|
|
117
|
+
const filePath = args.file_path;
|
|
118
|
+
const hasFilePath = typeof filePath === "string" && filePath.length > 0;
|
|
119
|
+
const hasBase64 = typeof args.data_base64 === "string" && args.data_base64.length > 0;
|
|
120
|
+
const hasFilename = typeof args.filename === "string" && args.filename.trim().length > 0;
|
|
121
|
+
if (!hasFilePath) {
|
|
122
|
+
if (hasBase64 && !hasFilename) {
|
|
123
|
+
throw new Error("filename is required when uploading with data_base64");
|
|
124
|
+
}
|
|
125
|
+
return args;
|
|
126
|
+
}
|
|
127
|
+
if (hasBase64) {
|
|
128
|
+
throw new Error("provide file_path OR data_base64, not both");
|
|
129
|
+
}
|
|
130
|
+
let size;
|
|
131
|
+
try {
|
|
132
|
+
const st = statSync(filePath);
|
|
133
|
+
if (!st.isFile()) throw new Error("not a regular file");
|
|
134
|
+
size = st.size;
|
|
135
|
+
} catch (err) {
|
|
136
|
+
throw new Error(`cannot read file_path '${filePath}': ${err?.message ?? String(err)}`);
|
|
137
|
+
}
|
|
138
|
+
if (size > MAX_UPLOAD_FILE_BYTES) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`file_path is ${size} bytes; exceeds the ${MAX_UPLOAD_FILE_BYTES}-byte upload cap`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const buf = readFileSync(filePath);
|
|
144
|
+
if (buf.length > MAX_UPLOAD_FILE_BYTES) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`file_path is ${buf.length} bytes; exceeds the ${MAX_UPLOAD_FILE_BYTES}-byte upload cap`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const { file_path: _drop, ...rest } = args;
|
|
150
|
+
return {
|
|
151
|
+
...rest,
|
|
152
|
+
data_base64: buf.toString("base64"),
|
|
153
|
+
filename: hasFilename ? args.filename : basename(filePath)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
73
156
|
/**
|
|
74
157
|
* Return all registered tools for the MCP ListTools response — gateway tools
|
|
75
158
|
* first (minus any a local tool shadows), then local tools.
|
|
@@ -114,9 +197,20 @@ var ToolRegistry = class {
|
|
|
114
197
|
isError: true
|
|
115
198
|
};
|
|
116
199
|
}
|
|
200
|
+
let outgoing = args;
|
|
201
|
+
if (name === UPLOAD_TOOL) {
|
|
202
|
+
try {
|
|
203
|
+
outgoing = this.resolveUploadFilePath(args);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
return {
|
|
206
|
+
content: [{ type: "text", text: err?.message ?? String(err) }],
|
|
207
|
+
isError: true
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
117
211
|
return this.gateway.sendRpc("tools/call", {
|
|
118
212
|
name,
|
|
119
|
-
arguments:
|
|
213
|
+
arguments: outgoing
|
|
120
214
|
});
|
|
121
215
|
}
|
|
122
216
|
/** Total number of registered tools (gateway + local, shadowed names once). */
|
|
@@ -296,7 +390,7 @@ async function startProxy(config) {
|
|
|
296
390
|
console.error(`[kojee-mcp] Tandem memberships: ${tandemMembershipCount === -1 ? "unknown" : tandemMembershipCount}`);
|
|
297
391
|
let server;
|
|
298
392
|
const isHeadlessDaemon = !!process.env["KOJEE_HEADLESS"] || !!process.env["KOJEE_WEBHOOK_EXPECTED"];
|
|
299
|
-
const { selectDelivery } = await import("./registry-
|
|
393
|
+
const { selectDelivery } = await import("./registry-MCJAVXJW.js");
|
|
300
394
|
const delivery = selectDelivery(adapter.runtime, {
|
|
301
395
|
supportsChannels: adapter.supportsChannels,
|
|
302
396
|
headless: isHeadlessDaemon,
|
|
@@ -205,7 +205,7 @@ function buildStreamUrl(brokerUrl, sinceValue, seatName) {
|
|
|
205
205
|
if (sinceValue !== null) params.set("since", sinceValue);
|
|
206
206
|
if (seatName) params.set("seat_name", seatName);
|
|
207
207
|
const qs = params.toString();
|
|
208
|
-
return `${brokerUrl}/api/v2/tandems/stream
|
|
208
|
+
return `${brokerUrl}/api/v2/tandems/stream/${qs ? "?" + qs : ""}`;
|
|
209
209
|
}
|
|
210
210
|
async function openStream(opts, controller, sinceValue, dispatcher) {
|
|
211
211
|
const url = buildStreamUrl(opts.brokerUrl, sinceValue, opts.seatName);
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-EIAUW6KO.js";
|
|
5
5
|
import {
|
|
6
6
|
startProxy
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Q2UVK7VA.js";
|
|
8
8
|
import "./chunk-CCVRXJJ2.js";
|
|
9
9
|
import {
|
|
10
10
|
pairedConfigPath
|
|
@@ -134,7 +134,7 @@ program.command("tail <path>").description("Stream a file's appends and follow (
|
|
|
134
134
|
"--replay",
|
|
135
135
|
"Replay the whole file from the start before following (default: follow from EOF, no replay)"
|
|
136
136
|
).action(async (filePath, opts) => {
|
|
137
|
-
const { runTail } = await import("./tail-stream-
|
|
137
|
+
const { runTail } = await import("./tail-stream-PQXEY3TI.js");
|
|
138
138
|
try {
|
|
139
139
|
await runTail(filePath, { wakeOnly: opts.wakeOnly === true, replay: opts.replay === true });
|
|
140
140
|
} catch (err) {
|
package/dist/index.js
CHANGED
package/dist/lib.d.ts
CHANGED
|
@@ -521,6 +521,23 @@ declare class ToolRegistry {
|
|
|
521
521
|
* Fetch all tools with full schemas from the gateway in a single RPC call.
|
|
522
522
|
*/
|
|
523
523
|
discoverTools(): Promise<void>;
|
|
524
|
+
/**
|
|
525
|
+
* Teach the gateway's `tandem_upload_attachment` tool about a `file_path` arg
|
|
526
|
+
* that THIS proxy resolves in-process (see {@link resolveUploadFilePath}): the
|
|
527
|
+
* model emits a local path, the proxy reads the bytes and forwards them as
|
|
528
|
+
* `data_base64`, so a multi-MB image never has to be generated into the model's
|
|
529
|
+
* tool call. Purely additive — the original `data_base64` shape still works. A
|
|
530
|
+
* no-op if the gateway doesn't expose the tool (older backend).
|
|
531
|
+
*/
|
|
532
|
+
private augmentUploadTool;
|
|
533
|
+
/**
|
|
534
|
+
* Resolve a `tandem_upload_attachment` call that used `file_path`: read the file
|
|
535
|
+
* locally, base64-encode the RAW bytes (Buffer→base64 is byte-exact — no text
|
|
536
|
+
* round-trip, so no U+FFFD corruption), and rewrite the args to the `data_base64`
|
|
537
|
+
* shape the gateway expects. Returns the args to forward, or throws with a clear
|
|
538
|
+
* message the caller surfaces as an isError result.
|
|
539
|
+
*/
|
|
540
|
+
private resolveUploadFilePath;
|
|
524
541
|
/**
|
|
525
542
|
* Return all registered tools for the MCP ListTools response — gateway tools
|
|
526
543
|
* first (minus any a local tool shadows), then local tools.
|
package/dist/lib.js
CHANGED
|
@@ -125,7 +125,7 @@ function createClaudeCodeDelivery() {
|
|
|
125
125
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
126
126
|
const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
|
|
127
127
|
const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
|
|
128
|
-
const { startEventStream } = await import("./event-stream-
|
|
128
|
+
const { startEventStream } = await import("./event-stream-5ZEL7FXN.js");
|
|
129
129
|
const { createMcpServer } = await import("./server-VM3ZIGSZ.js");
|
|
130
130
|
const { deriveDiscoveryKey } = await import("./ancestry-A2F5KQ6A.js");
|
|
131
131
|
const { resolveSharedSessionId } = await import("./cc-session-id-5HHQJ7VC.js");
|
|
@@ -325,7 +325,7 @@ function createWebhookDelivery(name, pendingLedger) {
|
|
|
325
325
|
const { resolveWebhookConfig } = await import("./webhook-config-O4WMQ532.js");
|
|
326
326
|
const { createWebhookSink } = await import("./webhook-sink-N6AUTFL3.js");
|
|
327
327
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
328
|
-
const { startEventStream } = await import("./event-stream-
|
|
328
|
+
const { startEventStream } = await import("./event-stream-5ZEL7FXN.js");
|
|
329
329
|
const { createMcpServer } = await import("./server-VM3ZIGSZ.js");
|
|
330
330
|
sweepStaleEventLogs();
|
|
331
331
|
eventLog = startEventLog({
|
|
@@ -521,7 +521,7 @@ function createOpenclawDelivery(deps = {}) {
|
|
|
521
521
|
async start(ctx) {
|
|
522
522
|
const { startEventLog, sweepStaleEventLogs } = await import("./event-log-M22X3FAI.js");
|
|
523
523
|
const { resubscribeMemberships } = await import("./resubscribe-G5OGDZJD.js");
|
|
524
|
-
const { startEventStream } = await import("./event-stream-
|
|
524
|
+
const { startEventStream } = await import("./event-stream-5ZEL7FXN.js");
|
|
525
525
|
const { createMcpServer } = await import("./server-VM3ZIGSZ.js");
|
|
526
526
|
sweepStaleEventLogs();
|
|
527
527
|
eventLog = startEventLog({
|