@okostream/mcp 0.12.5 → 0.12.7
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/README.md +20 -6
- package/dist/server.js +160 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,8 +82,9 @@ published version. The server reports the latest version during the key
|
|
|
82
82
|
exchange: `oko_whoami` and `oko_connection_status` return `update`, and the
|
|
83
83
|
first results of a session carry an `UPDATE AVAILABLE` block while the client
|
|
84
84
|
is behind, with the exact update step. Global installs update with
|
|
85
|
-
`npm install -g @okostream/mcp@latest
|
|
86
|
-
|
|
85
|
+
`npm install -g @okostream/mcp@latest`. Claude Desktop is not offered for
|
|
86
|
+
now (the extension bundle is discontinued; the Desktop path returns when the
|
|
87
|
+
package ships through a directory).
|
|
87
88
|
|
|
88
89
|
## When a tool refuses
|
|
89
90
|
|
|
@@ -96,10 +97,23 @@ Refusals are explicit and never require guessing:
|
|
|
96
97
|
OKO team.
|
|
97
98
|
- `PLATFORM_REJECTED_READ` — no access to that resource, or it does not exist.
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
**One install path.** Every supported client — Claude Code, Codex, and any
|
|
101
|
+
MCP client that takes a command with arguments and env — runs the package
|
|
102
|
+
through `npx -y @okostream/mcp@latest` (Node.js required): the latest version
|
|
103
|
+
is installed on each restart, so the server never falls behind. The `.mcpb`
|
|
104
|
+
extension bundle for Claude Desktop is no longer distributed — it never
|
|
105
|
+
updated itself — and the Claude Desktop path is archived until the package
|
|
106
|
+
ships through a directory. The key exchange refuses versions behind the
|
|
107
|
+
latest published one and tells the assistant how to update.
|
|
108
|
+
|
|
109
|
+
Image URLs are fetched by the OKO server first, so a VPN, proxy or captive
|
|
110
|
+
portal on the organizer's machine is not in the way. If the server cannot
|
|
111
|
+
reach the URL (a private host, a login wall, a timeout), the MCP client
|
|
112
|
+
fetches it from the local machine; on a machine where a VPN or proxy
|
|
113
|
+
intercepts DNS, public hosts resolve to fake addresses (`198.18.x.x`), such
|
|
114
|
+
uploads still work, and the result says so. When both roads fail, the error
|
|
115
|
+
names both reasons and asks for a file from disk (`OKO_IMAGE_ROOTS`), which
|
|
116
|
+
always works. The result's `sourceVia` says who fetched the image.
|
|
103
117
|
|
|
104
118
|
## Capabilities
|
|
105
119
|
|
package/dist/server.js
CHANGED
|
@@ -70503,7 +70503,7 @@ var OKO_CONNECTION_STATUSES = [
|
|
|
70503
70503
|
"rate_limited",
|
|
70504
70504
|
"unavailable"
|
|
70505
70505
|
];
|
|
70506
|
-
function nextSteps(status, minimumVersion) {
|
|
70506
|
+
function nextSteps(status, minimumVersion, latestVersion) {
|
|
70507
70507
|
switch (status) {
|
|
70508
70508
|
case "connected":
|
|
70509
70509
|
return [{
|
|
@@ -70543,7 +70543,7 @@ function nextSteps(status, minimumVersion) {
|
|
|
70543
70543
|
return [
|
|
70544
70544
|
{
|
|
70545
70545
|
id: "update_package",
|
|
70546
|
-
label: `Update to @okostream/mcp@latest${
|
|
70546
|
+
label: `Update to @okostream/mcp@latest${latestVersion ? ` (${latestVersion} is published` : ""}${minimumVersion ? `${latestVersion ? "; " : " ("}minimum ${minimumVersion}` : ""}${latestVersion || minimumVersion ? ")" : ""}.`,
|
|
70547
70547
|
command: "npx -y @okostream/mcp@latest"
|
|
70548
70548
|
},
|
|
70549
70549
|
{
|
|
@@ -70566,7 +70566,7 @@ function nextSteps(status, minimumVersion) {
|
|
|
70566
70566
|
function buildConnectionStatus(status, options2 = {}) {
|
|
70567
70567
|
const connected = status === "connected";
|
|
70568
70568
|
const errorCode = connected ? null : status === "update_required" ? "CLIENT_UPGRADE_REQUIRED" : status === "rate_limited" ? "RATE_LIMITED" : status === "unavailable" ? "SERVICE_UNAVAILABLE" : "AUTH_REQUIRED";
|
|
70569
|
-
const message = connected ? "OKO MCP is connected to an active organizer account." : status === "not_configured" ? "OKO MCP is installed, but OKO_API_KEY is not configured." : status === "invalid_key" ? "OKO MCP is installed, but the configured API key is invalid or revoked." : status === "update_required" ? "This OKO MCP client version is no longer supported." : status === "rate_limited" ? "OKO temporarily rate-limited the connection attempt." : "OKO MCP could not verify the organizer connection.";
|
|
70569
|
+
const message = connected ? "OKO MCP is connected to an active organizer account." : status === "not_configured" ? "OKO MCP is installed, but OKO_API_KEY is not configured." : status === "invalid_key" ? "OKO MCP is installed, but the configured API key is invalid or revoked." : status === "update_required" ? options2.serverMessage ?? "This OKO MCP client version is no longer supported." : status === "rate_limited" ? "OKO temporarily rate-limited the connection attempt." : "OKO MCP could not verify the organizer connection.";
|
|
70570
70570
|
return {
|
|
70571
70571
|
connected,
|
|
70572
70572
|
status,
|
|
@@ -70576,8 +70576,9 @@ function buildConnectionStatus(status, options2 = {}) {
|
|
|
70576
70576
|
organizerAccountRequired: true,
|
|
70577
70577
|
credentialConfigured: status !== "not_configured",
|
|
70578
70578
|
links: OKO_ONBOARDING_URLS,
|
|
70579
|
-
nextSteps: nextSteps(status, options2.minimumVersion),
|
|
70579
|
+
nextSteps: nextSteps(status, options2.minimumVersion, options2.latestVersion),
|
|
70580
70580
|
...options2.minimumVersion ? { minimumVersion: options2.minimumVersion } : {},
|
|
70581
|
+
...options2.latestVersion ? { latestVersion: options2.latestVersion } : {},
|
|
70581
70582
|
...options2.uid ? { uid: options2.uid } : {},
|
|
70582
70583
|
...options2.scopes ? { scopes: options2.scopes } : {},
|
|
70583
70584
|
...options2.update ? { update: options2.update } : {}
|
|
@@ -116223,11 +116224,14 @@ function sanitizeScopes(scopes) {
|
|
|
116223
116224
|
}
|
|
116224
116225
|
|
|
116225
116226
|
// build/version.js
|
|
116226
|
-
var OKO_MCP_VERSION = "0.12.
|
|
116227
|
+
var OKO_MCP_VERSION = "0.12.7";
|
|
116227
116228
|
|
|
116228
116229
|
// build/auth.js
|
|
116229
116230
|
var state = null;
|
|
116230
116231
|
var latestPublishedVersion = null;
|
|
116232
|
+
function recordLatestPublishedVersion(version9) {
|
|
116233
|
+
latestPublishedVersion = version9 && parseVersion(version9) ? version9.trim() : null;
|
|
116234
|
+
}
|
|
116231
116235
|
var OkoScopeError = class extends Error {
|
|
116232
116236
|
scope;
|
|
116233
116237
|
constructor(scope) {
|
|
@@ -116258,7 +116262,7 @@ function isNewerVersion(candidate, current) {
|
|
|
116258
116262
|
var MCP_UPDATE_HOW_TO = [
|
|
116259
116263
|
"Restart the MCP client. With the default `npx -y @okostream/mcp@latest` command the restart installs the newest version automatically.",
|
|
116260
116264
|
"Global install: run `npm install -g @okostream/mcp@latest`, then restart the client.",
|
|
116261
|
-
|
|
116265
|
+
'Claude Desktop with the extension bundle: the bundle is discontinued and never updated itself; switch to Claude Code, Codex, or the "Other MCP" configuration from https://okostream.app/app/mcp.'
|
|
116262
116266
|
];
|
|
116263
116267
|
function updateStatus() {
|
|
116264
116268
|
const latest = latestPublishedVersion;
|
|
@@ -116279,13 +116283,28 @@ function currentUid() {
|
|
|
116279
116283
|
var OkoAuthError = class extends Error {
|
|
116280
116284
|
status;
|
|
116281
116285
|
minimumVersion;
|
|
116282
|
-
|
|
116286
|
+
latestVersion;
|
|
116287
|
+
serverMessage;
|
|
116288
|
+
constructor(status, message, details = {}) {
|
|
116283
116289
|
super(message);
|
|
116284
116290
|
this.status = status;
|
|
116285
|
-
this.minimumVersion = minimumVersion;
|
|
116286
116291
|
this.name = "OkoAuthError";
|
|
116292
|
+
this.minimumVersion = details.minimumVersion;
|
|
116293
|
+
this.latestVersion = details.latestVersion;
|
|
116294
|
+
this.serverMessage = details.serverMessage;
|
|
116287
116295
|
}
|
|
116288
116296
|
};
|
|
116297
|
+
function versionField(value) {
|
|
116298
|
+
return typeof value === "string" && parseVersion(value) ? value.trim() : void 0;
|
|
116299
|
+
}
|
|
116300
|
+
function upgradeRequiredError(body) {
|
|
116301
|
+
const parsed = body && typeof body === "object" ? body : {};
|
|
116302
|
+
const minimumVersion = versionField(parsed.minimumVersion);
|
|
116303
|
+
const latestVersion = versionField(parsed.latestVersion);
|
|
116304
|
+
const serverMessage = typeof parsed.message === "string" && parsed.message.trim() ? parsed.message.trim() : void 0;
|
|
116305
|
+
const fallback = `OKO MCP ${OKO_MCP_VERSION} is no longer supported. Update to @okostream/mcp@latest (minimum ${minimumVersion ?? "the current release"}${latestVersion ? `, latest ${latestVersion}` : ""}) and restart the MCP client.`;
|
|
116306
|
+
return new OkoAuthError("update_required", serverMessage ?? fallback, { minimumVersion, latestVersion, serverMessage });
|
|
116307
|
+
}
|
|
116289
116308
|
async function exchangeApiKey() {
|
|
116290
116309
|
if (!env.apiKey) {
|
|
116291
116310
|
throw new OkoAuthError("not_configured", "OKO MCP is installed but not connected. Configure OKO_API_KEY, then restart or reconnect the MCP server.");
|
|
@@ -116304,8 +116323,10 @@ async function exchangeApiKey() {
|
|
|
116304
116323
|
const status = response.status;
|
|
116305
116324
|
const errorBody = await response.json().catch(() => ({}));
|
|
116306
116325
|
if (status === 426) {
|
|
116307
|
-
const
|
|
116308
|
-
|
|
116326
|
+
const error3 = upgradeRequiredError(errorBody);
|
|
116327
|
+
if (error3.latestVersion)
|
|
116328
|
+
recordLatestPublishedVersion(error3.latestVersion);
|
|
116329
|
+
throw error3;
|
|
116309
116330
|
}
|
|
116310
116331
|
if (status === 429) {
|
|
116311
116332
|
throw new OkoAuthError("rate_limited", "OKO API key exchange is temporarily rate-limited. Wait one minute, then restart the MCP client.");
|
|
@@ -116342,7 +116363,7 @@ async function ensureAuth(requiredScopes = []) {
|
|
|
116342
116363
|
expiresAt: now + exchanged.expiresIn * 1e3
|
|
116343
116364
|
};
|
|
116344
116365
|
if (exchanged.latestVersion)
|
|
116345
|
-
|
|
116366
|
+
recordLatestPublishedVersion(exchanged.latestVersion);
|
|
116346
116367
|
}
|
|
116347
116368
|
for (const scope of requiredScopes) {
|
|
116348
116369
|
if (!state.scopes.includes(scope)) {
|
|
@@ -116366,16 +116387,21 @@ async function getConnectionStatus() {
|
|
|
116366
116387
|
});
|
|
116367
116388
|
} catch (error3) {
|
|
116368
116389
|
if (error3 instanceof OkoAuthError) {
|
|
116369
|
-
return buildConnectionStatus(error3.status,
|
|
116390
|
+
return buildConnectionStatus(error3.status, authErrorOptions(error3));
|
|
116370
116391
|
}
|
|
116371
116392
|
return buildConnectionStatus("unavailable");
|
|
116372
116393
|
}
|
|
116373
116394
|
}
|
|
116395
|
+
function authErrorOptions(error3) {
|
|
116396
|
+
return {
|
|
116397
|
+
minimumVersion: error3.minimumVersion,
|
|
116398
|
+
latestVersion: error3.latestVersion,
|
|
116399
|
+
serverMessage: error3.serverMessage
|
|
116400
|
+
};
|
|
116401
|
+
}
|
|
116374
116402
|
function authErrorResult(error3) {
|
|
116375
116403
|
return {
|
|
116376
|
-
...textResult(buildConnectionStatus(error3.status,
|
|
116377
|
-
minimumVersion: error3.minimumVersion
|
|
116378
|
-
})),
|
|
116404
|
+
...textResult(buildConnectionStatus(error3.status, authErrorOptions(error3))),
|
|
116379
116405
|
isError: true
|
|
116380
116406
|
};
|
|
116381
116407
|
}
|
|
@@ -116465,6 +116491,15 @@ function isPublicUnicastAddress(address) {
|
|
|
116465
116491
|
return false;
|
|
116466
116492
|
return import_ipaddr.default.parse(normalized).range() === "unicast";
|
|
116467
116493
|
}
|
|
116494
|
+
function pinnedLookup(address, family) {
|
|
116495
|
+
return (_hostname, options2, callback) => {
|
|
116496
|
+
if (options2 && typeof options2 === "object" && options2.all) {
|
|
116497
|
+
callback(null, [{ address, family }]);
|
|
116498
|
+
return;
|
|
116499
|
+
}
|
|
116500
|
+
callback(null, address, family);
|
|
116501
|
+
};
|
|
116502
|
+
}
|
|
116468
116503
|
|
|
116469
116504
|
// build/tools/eventAccess.js
|
|
116470
116505
|
var EVENT_ACCESS_FIELD_BY_MODE = {
|
|
@@ -116640,9 +116675,7 @@ async function readRemoteImage(rawUrl, redirects = 0) {
|
|
|
116640
116675
|
remoteFetchNotes.push(`Image fetched through a VPN/proxy with DNS interception: ${url.hostname} resolved to ${pinned.address}, so OKO could not verify the destination address itself; the connection went through the proxy. This is expected on such machines and fine for public image links.`);
|
|
116641
116676
|
}
|
|
116642
116677
|
const dispatcher = new import_undici.Agent({
|
|
116643
|
-
connect: {
|
|
116644
|
-
lookup: (_hostname, _options, callback) => callback(null, pinned.address, pinned.family)
|
|
116645
|
-
}
|
|
116678
|
+
connect: { lookup: pinnedLookup(pinned.address, pinned.family) }
|
|
116646
116679
|
});
|
|
116647
116680
|
const controller = new AbortController();
|
|
116648
116681
|
const timeout = setTimeout(() => controller.abort(), REMOTE_IMAGE_TIMEOUT_MS);
|
|
@@ -116673,7 +116706,7 @@ async function readRemoteImage(rawUrl, redirects = 0) {
|
|
|
116673
116706
|
if (Number.isFinite(contentLength) && contentLength > MAX_SOURCE_IMAGE_BYTES) {
|
|
116674
116707
|
throw new Error("Image URL is larger than 15MB.");
|
|
116675
116708
|
}
|
|
116676
|
-
return responseBufferWithLimit(response, MAX_SOURCE_IMAGE_BYTES);
|
|
116709
|
+
return await responseBufferWithLimit(response, MAX_SOURCE_IMAGE_BYTES);
|
|
116677
116710
|
} catch (err) {
|
|
116678
116711
|
if (err instanceof Error && err.name === "AbortError") {
|
|
116679
116712
|
throw new Error("Image URL fetch timed out.");
|
|
@@ -116710,12 +116743,65 @@ async function readLocalImage(inputPath) {
|
|
|
116710
116743
|
throw new Error("Image file is larger than 15MB.");
|
|
116711
116744
|
return readFile(filePath);
|
|
116712
116745
|
}
|
|
116713
|
-
|
|
116714
|
-
if (
|
|
116715
|
-
return
|
|
116716
|
-
|
|
116717
|
-
|
|
116718
|
-
|
|
116746
|
+
function serverFetchRefusal(error3) {
|
|
116747
|
+
if (!error3 || typeof error3 !== "object")
|
|
116748
|
+
return null;
|
|
116749
|
+
const details = error3.details;
|
|
116750
|
+
if (details && typeof details === "object" && details.code === "REMOTE_FETCH_FAILED") {
|
|
116751
|
+
const record2 = details;
|
|
116752
|
+
const fallbackMessage = String(error3.message ?? "the server could not fetch the URL");
|
|
116753
|
+
return {
|
|
116754
|
+
reason: typeof record2.reason === "string" ? record2.reason : "unknown",
|
|
116755
|
+
message: typeof record2.message === "string" ? record2.message : fallbackMessage
|
|
116756
|
+
};
|
|
116757
|
+
}
|
|
116758
|
+
const code = error3.code;
|
|
116759
|
+
const message = String(error3.message ?? "");
|
|
116760
|
+
if (code === "functions/invalid-argument" && message.includes("Image payload is empty or too large")) {
|
|
116761
|
+
return { reason: "server_without_url_support", message: "The OKO server release in use does not fetch URLs yet." };
|
|
116762
|
+
}
|
|
116763
|
+
return null;
|
|
116764
|
+
}
|
|
116765
|
+
var SERVER_FETCH_NOTE = "Image fetched by the OKO server directly from the URL; this machine's network was not involved.";
|
|
116766
|
+
function localFallbackNote(serverFailure) {
|
|
116767
|
+
return `The OKO server could not fetch the URL (${serverFailure.reason}: ${serverFailure.message}), so the image was fetched from this machine instead.`;
|
|
116768
|
+
}
|
|
116769
|
+
function remoteImageUnavailableMessage(url, serverFailure, localError) {
|
|
116770
|
+
const localMessage = localError instanceof Error ? localError.message : String(localError);
|
|
116771
|
+
return `Image URL could not be fetched by the OKO server or from this machine. Server (${serverFailure.reason}): ${serverFailure.message} This machine: ${localMessage} Ask the organizer to download the image and pass its path via source.path (allowed directories come from OKO_IMAGE_ROOTS), or to share a public HTTPS link that returns the image file directly. URL: ${url}`;
|
|
116772
|
+
}
|
|
116773
|
+
async function uploadImageFromSource(eventId, kind, cardId, source) {
|
|
116774
|
+
if (source.path) {
|
|
116775
|
+
const buffer2 = await readLocalImage(source.path);
|
|
116776
|
+
const uploaded2 = await uploadMasterImage(eventId, kind, cardId, { imageBase64: buffer2.toString("base64") });
|
|
116777
|
+
return { uploaded: uploaded2, sourceBytes: buffer2.byteLength, sourceVia: "client", notes: [] };
|
|
116778
|
+
}
|
|
116779
|
+
if (!source.url)
|
|
116780
|
+
throw new Error("Provide image source path or url.");
|
|
116781
|
+
let serverFailure;
|
|
116782
|
+
try {
|
|
116783
|
+
const uploaded2 = await uploadMasterImage(eventId, kind, cardId, { sourceUrl: source.url });
|
|
116784
|
+
return { uploaded: uploaded2, sourceBytes: uploaded2.sourceBytes ?? 0, sourceVia: "server", notes: [SERVER_FETCH_NOTE] };
|
|
116785
|
+
} catch (error3) {
|
|
116786
|
+
const refusal = serverFetchRefusal(error3);
|
|
116787
|
+
if (!refusal)
|
|
116788
|
+
throw error3;
|
|
116789
|
+
serverFailure = refusal;
|
|
116790
|
+
}
|
|
116791
|
+
let buffer;
|
|
116792
|
+
try {
|
|
116793
|
+
buffer = await readRemoteImage(source.url);
|
|
116794
|
+
} catch (localError) {
|
|
116795
|
+
takeRemoteFetchNotes();
|
|
116796
|
+
throw new Error(remoteImageUnavailableMessage(source.url, serverFailure, localError));
|
|
116797
|
+
}
|
|
116798
|
+
const uploaded = await uploadMasterImage(eventId, kind, cardId, { imageBase64: buffer.toString("base64") });
|
|
116799
|
+
return {
|
|
116800
|
+
uploaded,
|
|
116801
|
+
sourceBytes: buffer.byteLength,
|
|
116802
|
+
sourceVia: "client",
|
|
116803
|
+
notes: [localFallbackNote(serverFailure), ...takeRemoteFetchNotes()]
|
|
116804
|
+
};
|
|
116719
116805
|
}
|
|
116720
116806
|
function readImageRef(eventData, kind, cardId) {
|
|
116721
116807
|
const blocks = eventData.blocks && typeof eventData.blocks === "object" ? eventData.blocks : {};
|
|
@@ -116853,8 +116939,8 @@ var cardItemInputSchema = external_exports.object({
|
|
|
116853
116939
|
formId: cardIdSchema.nullable().optional()
|
|
116854
116940
|
}).strict();
|
|
116855
116941
|
var imageSourceSchema = external_exports.object({
|
|
116856
|
-
path: external_exports.string().min(1).optional().describe("Local image path on the MCP client machine."),
|
|
116857
|
-
url: external_exports.string().url().optional().describe("HTTPS image URL
|
|
116942
|
+
path: external_exports.string().min(1).optional().describe("Local image path on the MCP client machine (inside OKO_IMAGE_ROOTS)."),
|
|
116943
|
+
url: external_exports.string().url().optional().describe("Public HTTPS image URL. The OKO server fetches it first (works behind VPNs and proxies); if the server cannot reach it, the MCP client fetches it from this machine; if both fail, ask the organizer for a local file via path.")
|
|
116858
116944
|
}).strict().refine((source) => Boolean(source.path) !== Boolean(source.url), {
|
|
116859
116945
|
message: "Provide exactly one image source: path or url."
|
|
116860
116946
|
});
|
|
@@ -116920,13 +117006,13 @@ async function deleteStorageUrls(eventId, masterUrl, fallbackUrl) {
|
|
|
116920
117006
|
const warnings = await deleteStoragePaths(eventId, paths);
|
|
116921
117007
|
return { attempted: [...new Set(paths)].length, warnings };
|
|
116922
117008
|
}
|
|
116923
|
-
async function uploadMasterImage(eventId, kind, cardId,
|
|
117009
|
+
async function uploadMasterImage(eventId, kind, cardId, source) {
|
|
116924
117010
|
const uploadImage = httpsCallable(functions, "uploadMcpImage");
|
|
116925
117011
|
const result = await uploadImage({
|
|
116926
117012
|
eventId,
|
|
116927
117013
|
kind,
|
|
116928
117014
|
...cardId ? { cardId } : {},
|
|
116929
|
-
imageBase64:
|
|
117015
|
+
...source.sourceUrl ? { sourceUrl: source.sourceUrl } : { imageBase64: source.imageBase64 }
|
|
116930
117016
|
});
|
|
116931
117017
|
return result.data;
|
|
116932
117018
|
}
|
|
@@ -117007,8 +117093,6 @@ async function uploadEventImageForUser(uid, eventId, kind, cardId, source) {
|
|
|
117007
117093
|
throw new Error("cardId is only valid when kind is card.");
|
|
117008
117094
|
const event = await readEvent(eventId, uid, "edit");
|
|
117009
117095
|
const previous = readImageRef(event, kind, cardId);
|
|
117010
|
-
const sourceBuffer = await readImageSource(source);
|
|
117011
|
-
const remoteNotes = takeRemoteFetchNotes();
|
|
117012
117096
|
const eventBlocks = event.blocks && typeof event.blocks === "object" ? event.blocks : {};
|
|
117013
117097
|
const placeholderCfg = eventBlocks.video?.placeholder && typeof eventBlocks.video.placeholder === "object" ? eventBlocks.video.placeholder : {};
|
|
117014
117098
|
const blockEnabled = kind === "promoBanner" ? eventBlocks.promoBanner?.enabled === true : kind === "videoPlaceholder" ? placeholderCfg.enabled === true : readCardsConfig(event).enabled;
|
|
@@ -117022,7 +117106,7 @@ async function uploadEventImageForUser(uid, eventId, kind, cardId, source) {
|
|
|
117022
117106
|
const previousMasterPath = storagePathFromDownloadUrl(previous.imageMasterUrl);
|
|
117023
117107
|
const deleteWarnings = [];
|
|
117024
117108
|
let deletesAttempted = 0;
|
|
117025
|
-
const uploaded = await
|
|
117109
|
+
const { uploaded, sourceBytes, sourceVia, notes: sourceNotes } = await uploadImageFromSource(eventId, kind, cardId, source);
|
|
117026
117110
|
if (!uploaded)
|
|
117027
117111
|
throw new Error("Image upload did not return a result.");
|
|
117028
117112
|
if (kind !== "card" || previousMasterPath && previousMasterPath !== uploaded.storagePath || !previous.imageMasterUrl && previous.imageUrl) {
|
|
@@ -117037,14 +117121,15 @@ async function uploadEventImageForUser(uid, eventId, kind, cardId, source) {
|
|
|
117037
117121
|
storagePath: uploaded.storagePath,
|
|
117038
117122
|
masterUrl: uploaded.masterUrl,
|
|
117039
117123
|
variantsPending: true,
|
|
117040
|
-
sourceBytes
|
|
117124
|
+
sourceBytes,
|
|
117125
|
+
sourceVia,
|
|
117041
117126
|
masterBytes: uploaded.masterBytes,
|
|
117042
117127
|
imageWidth: uploaded.imageWidth,
|
|
117043
117128
|
imageHeight: uploaded.imageHeight,
|
|
117044
117129
|
note: "Image master uploaded. CF generateImageVariants should publish thumb/card/hero variants and update imageUrl/imageVariants in about 10-30 seconds.",
|
|
117045
117130
|
storageDeletesAttempted: deletesAttempted,
|
|
117046
117131
|
storageDeleteWarnings: deleteWarnings,
|
|
117047
|
-
hints: [...
|
|
117132
|
+
hints: [...sourceNotes, ...uploadHints]
|
|
117048
117133
|
};
|
|
117049
117134
|
}
|
|
117050
117135
|
|
|
@@ -117186,6 +117271,7 @@ var imageOutputSchema = external_exports.object({
|
|
|
117186
117271
|
masterUrl: external_exports.string().url(),
|
|
117187
117272
|
variantsPending: external_exports.boolean(),
|
|
117188
117273
|
sourceBytes: external_exports.number().int().nonnegative(),
|
|
117274
|
+
sourceVia: external_exports.enum(["server", "client"]).optional(),
|
|
117189
117275
|
masterBytes: external_exports.number().int().nonnegative(),
|
|
117190
117276
|
imageWidth: external_exports.number().int().positive(),
|
|
117191
117277
|
imageHeight: external_exports.number().int().positive(),
|
|
@@ -118033,6 +118119,25 @@ async function assertCanCreateInWorkspace(uid, workspaceId) {
|
|
|
118033
118119
|
}
|
|
118034
118120
|
}
|
|
118035
118121
|
}
|
|
118122
|
+
function buildCreateWebinarDocument(input) {
|
|
118123
|
+
return {
|
|
118124
|
+
ownerId: input.uid,
|
|
118125
|
+
workspaceId: input.workspaceId,
|
|
118126
|
+
createdAt: input.createdAt,
|
|
118127
|
+
title: input.title.trim(),
|
|
118128
|
+
pinned: false,
|
|
118129
|
+
video: input.video,
|
|
118130
|
+
meta: {
|
|
118131
|
+
eventDate: input.eventDate,
|
|
118132
|
+
speakerName: input.speakerName,
|
|
118133
|
+
description: input.description
|
|
118134
|
+
},
|
|
118135
|
+
blocks: {
|
|
118136
|
+
chat: { enabled: input.chatEnabled, visibility: "always" },
|
|
118137
|
+
promoBanner: { enabled: false, text: "", link: "", visibility: "always" }
|
|
118138
|
+
}
|
|
118139
|
+
};
|
|
118140
|
+
}
|
|
118036
118141
|
async function addEventWithRtdbMirror(eventData) {
|
|
118037
118142
|
const eventRef = await addDoc(collection(firestore, "events"), eventData);
|
|
118038
118143
|
try {
|
|
@@ -118317,23 +118422,17 @@ function registerEventTools(server2) {
|
|
|
118317
118422
|
const workspaceId = args.workspaceId ?? uid;
|
|
118318
118423
|
await assertCanCreateInWorkspace(uid, workspaceId);
|
|
118319
118424
|
const video = normalizeVideo(args.videoUrl);
|
|
118320
|
-
const docRef = await addEventWithRtdbMirror({
|
|
118321
|
-
|
|
118425
|
+
const docRef = await addEventWithRtdbMirror(buildCreateWebinarDocument({
|
|
118426
|
+
uid,
|
|
118322
118427
|
workspaceId,
|
|
118323
|
-
|
|
118324
|
-
title: args.title.trim(),
|
|
118325
|
-
pinned: false,
|
|
118428
|
+
title: args.title,
|
|
118326
118429
|
video,
|
|
118327
|
-
|
|
118328
|
-
|
|
118329
|
-
|
|
118330
|
-
|
|
118331
|
-
|
|
118332
|
-
|
|
118333
|
-
chat: { enabled: args.chatEnabled, visibility: "always" },
|
|
118334
|
-
promoBanner: { enabled: false, text: "", link: "", visibility: "always" }
|
|
118335
|
-
}
|
|
118336
|
-
});
|
|
118430
|
+
eventDate: args.eventDate ? Timestamp.fromDate(new Date(args.eventDate)) : null,
|
|
118431
|
+
speakerName: args.speakerName?.trim() || null,
|
|
118432
|
+
description: args.description?.trim() || null,
|
|
118433
|
+
chatEnabled: args.chatEnabled,
|
|
118434
|
+
createdAt: serverTimestamp2()
|
|
118435
|
+
}));
|
|
118337
118436
|
return textResult({
|
|
118338
118437
|
id: docRef.id,
|
|
118339
118438
|
...eventLinks(docRef.id),
|
|
@@ -118643,10 +118742,10 @@ function registerImageTools(server2) {
|
|
|
118643
118742
|
"eventId: event to edit.",
|
|
118644
118743
|
"kind: promoBanner, videoPlaceholder, or card.",
|
|
118645
118744
|
"cardId: required when kind=card; must match an existing blocks.cards.items id.",
|
|
118646
|
-
"source: exactly one of local path or HTTPS image URL.",
|
|
118745
|
+
"source: exactly one of local path or public HTTPS image URL.",
|
|
118647
118746
|
"confirm: must be true because the public event image may be replaced."
|
|
118648
118747
|
],
|
|
118649
|
-
returns: "eventId, kind, imageId, Storage path, masterUrl, byte counts, and a note that CF-generated variants are pending.",
|
|
118748
|
+
returns: "eventId, kind, imageId, Storage path, masterUrl, byte counts, sourceVia (server or client fetched the URL), hints, and a note that CF-generated variants are pending.",
|
|
118650
118749
|
examples: [
|
|
118651
118750
|
'Use kind="promoBanner" with source.url to set a webinar banner from an image link.',
|
|
118652
118751
|
'Use kind="card" with cardId after the card item already exists.'
|
|
@@ -118654,7 +118753,7 @@ function registerImageTools(server2) {
|
|
|
118654
118753
|
notes: [
|
|
118655
118754
|
"Uploading an image does not enable its block: toggle the block independently via oko_configure_blocks, in either order.",
|
|
118656
118755
|
"The MCP server reads local paths on the organizer machine.",
|
|
118657
|
-
"
|
|
118756
|
+
"A URL is fetched by the OKO server first (no VPN or proxy in the way), then from this machine if the server could not reach it; if both fail, the error names both reasons \u2014 ask the organizer to download the image and pass source.path. Public HTTPS only, max 15MB; private/local hosts are blocked.",
|
|
118658
118757
|
"For card images, imageId is the cardId so OKO CF4 can update the correct item.",
|
|
118659
118758
|
IMAGE_ADVICE.promoBanner,
|
|
118660
118759
|
IMAGE_ADVICE.videoPlaceholder,
|
|
@@ -118979,26 +119078,28 @@ function platformRejectionResult(toolName, args, readOnly) {
|
|
|
118979
119078
|
isError: true
|
|
118980
119079
|
};
|
|
118981
119080
|
}
|
|
118982
|
-
var
|
|
119081
|
+
var UPDATE_NOTICE_FULL_LIMIT = 3;
|
|
118983
119082
|
var updateNoticesShown = 0;
|
|
119083
|
+
function updateNoticeText(status, shownSoFar) {
|
|
119084
|
+
if (shownSoFar < UPDATE_NOTICE_FULL_LIMIT) {
|
|
119085
|
+
return `UPDATE AVAILABLE: ${status.message} How to update: ${(status.howTo ?? []).join(" ")}`;
|
|
119086
|
+
}
|
|
119087
|
+
return `UPDATE AVAILABLE: OKO MCP ${status.current} \u2192 ${status.latest}. Restart the MCP client to install it (npx does it automatically); see oko_connection_status for the steps.`;
|
|
119088
|
+
}
|
|
118984
119089
|
function withUpdateNotice(result) {
|
|
118985
|
-
if (updateNoticesShown >= UPDATE_NOTICE_LIMIT)
|
|
118986
|
-
return result;
|
|
118987
119090
|
const status = updateStatus();
|
|
118988
119091
|
if (!status.updateAvailable || !result || typeof result !== "object")
|
|
118989
119092
|
return result;
|
|
118990
119093
|
const typed = result;
|
|
118991
119094
|
if (!Array.isArray(typed.content))
|
|
118992
119095
|
return result;
|
|
119096
|
+
const text = updateNoticeText(status, updateNoticesShown);
|
|
118993
119097
|
updateNoticesShown += 1;
|
|
118994
119098
|
return {
|
|
118995
119099
|
...typed,
|
|
118996
119100
|
content: [
|
|
118997
119101
|
...typed.content,
|
|
118998
|
-
{
|
|
118999
|
-
type: "text",
|
|
119000
|
-
text: `UPDATE AVAILABLE: ${status.message} How to update: ${(status.howTo ?? []).join(" ")}`
|
|
119001
|
-
}
|
|
119102
|
+
{ type: "text", text }
|
|
119002
119103
|
]
|
|
119003
119104
|
};
|
|
119004
119105
|
}
|