@noimosai/platform-mcp 0.1.4 → 0.1.5
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/cli/cli.js +6 -8
- package/dist/cli/download.js +17 -10
- package/dist/cli/project-sync.js +13 -7
- package/dist/client/browser.js +11 -5
- package/dist/mcp/context.js +2 -2
- package/dist/mcp/operations.js +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/cli/cli.js
CHANGED
|
@@ -8,15 +8,11 @@ import { CliInputError, cliInputReason, isCliInputFailure, jsonOption } from "./
|
|
|
8
8
|
import { uploadLocalInput } from "../client/local-input.js";
|
|
9
9
|
import { localProfilePath, runtimeConfig } from "../client/local-profile.js";
|
|
10
10
|
import { localProjectConfig } from "../client/local-projects.js";
|
|
11
|
-
import { runLocalCommand } from "./setup.js";
|
|
12
11
|
import { isTeamResource, teamResourceCommand } from "./team-resources.js";
|
|
13
|
-
import { runWalletCommand } from "./wallet.js";
|
|
14
12
|
import { PLATFORM_CLI_VERSION } from "./version.js";
|
|
15
13
|
import { AgentRun, agentRunSchema, agentParallelSchema, agentResultSchema, agentResultsSchema, agentResumeSchema, resultFilesFor as resultFiles } from "../client/agent-run.js";
|
|
16
14
|
import { platformTeamQuerySchema } from "../client/index.js";
|
|
17
15
|
import { platformProjectStatusQuerySchema } from "../client/index.js";
|
|
18
|
-
import { deactivateProjectFiles } from "./project-files.js";
|
|
19
|
-
import { projectSyncFactory } from "./project-sync.js";
|
|
20
16
|
import { PLATFORM_META_LIMITS, PLATFORM_TOOL_REQUEST_MAX_BYTES, platformMetaSearchSchema, platformMetaSchemasSchema, platformMetaConnectionsSchema, platformMetaExecuteSchema, platformMetaWorkbenchSchema, platformMetaBashSchema, platformIdempotencyKeySchema, platformJsonObjectSchema, withEvidentValues } from "../client/index.js";
|
|
21
17
|
function metaInput(schema, input) {
|
|
22
18
|
const json = platformJsonObjectSchema.safeParse(input);
|
|
@@ -427,7 +423,7 @@ export async function runCli(args, env, output, error, transport = fetch, stdin
|
|
|
427
423
|
else if (args.length === 5 && args[0] === "download" && args[3] === "--out" && args[4].trim()) {
|
|
428
424
|
const id = platformExecutionIdSchema.parse(args[1]);
|
|
429
425
|
const artifactId = platformArtifactIdSchema.parse(args[2]);
|
|
430
|
-
command = (client) => downloadArtifact(client, id, artifactId, args[4], transport);
|
|
426
|
+
command = (client) => downloadArtifact(client, id, artifactId, args[4], transport, cliCommand);
|
|
431
427
|
}
|
|
432
428
|
else if (args.length === 3 && args[0] === "artifact-url") {
|
|
433
429
|
const id = platformExecutionIdSchema.parse(args[1]);
|
|
@@ -526,7 +522,9 @@ export async function runRelvoCli(args, env, output, error, transport = fetch, s
|
|
|
526
522
|
const { values } = parseArgs({ args: args.slice(1), options: { profile: { type: "string" }, mode: { type: "string" }, project: { type: "string" } } });
|
|
527
523
|
if (!values.profile)
|
|
528
524
|
throw new Error("relvo mcp requires the managed --profile path.");
|
|
529
|
-
const { startPlatformMcp } = await
|
|
525
|
+
const [{ startPlatformMcp }, { deactivateProjectFiles }, { projectSyncFactory }] = await Promise.all([
|
|
526
|
+
import("../mcp/runtime.js"), import("./project-files.js"), import("./project-sync.js")
|
|
527
|
+
]);
|
|
530
528
|
const profileHome = dirname(dirname(dirname(values.profile)));
|
|
531
529
|
await startPlatformMcp(options.version, values.profile, values.mode, values.project, id => deactivateProjectFiles(id, env, profileHome), projectSyncFactory(env, profileHome));
|
|
532
530
|
return process.exitCode ? 1 : 0;
|
|
@@ -546,7 +544,7 @@ export async function runRelvoCli(args, env, output, error, transport = fetch, s
|
|
|
546
544
|
};
|
|
547
545
|
if (args[0] === "wallet" && !args.some((arg) => ["--help", "-h"].includes(arg))) {
|
|
548
546
|
try {
|
|
549
|
-
await runWalletCommand(args, env, output, transport, options);
|
|
547
|
+
await (await import("./wallet.js")).runWalletCommand(args, env, output, transport, options);
|
|
550
548
|
return 0;
|
|
551
549
|
}
|
|
552
550
|
catch (failure) {
|
|
@@ -556,7 +554,7 @@ export async function runRelvoCli(args, env, output, error, transport = fetch, s
|
|
|
556
554
|
const local = ["setup", "login", "doctor", "logout", "skills"].includes(args[0]) || args[0] === "team-skills" && ["sync", "push", "check", "apply", "dismiss", "auto-sync"].includes(args[1]) || args[0] === "project" && ["list", "add", "use"].includes(args[1]);
|
|
557
555
|
if (local && !args.some((arg) => ["--help", "-h"].includes(arg))) {
|
|
558
556
|
try {
|
|
559
|
-
await runLocalCommand(args, env, output, transport, options);
|
|
557
|
+
await (await import("./setup.js")).runLocalCommand(args, env, output, transport, options);
|
|
560
558
|
return 0;
|
|
561
559
|
}
|
|
562
560
|
catch (failure) {
|
package/dist/cli/download.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { link, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { link, lstat, mkdtemp, rm, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { outputArtifacts } from "../client/agent-run.js";
|
|
5
5
|
const STALL_MS = 60_000;
|
|
6
|
-
|
|
6
|
+
const errorCode = (error) => error instanceof Error && "code" in error ? error.code : undefined;
|
|
7
|
+
const existingOutput = (path) => new Error(`The --out file already exists: ${path}. Choose a new file name; nothing was overwritten.`);
|
|
8
|
+
export async function downloadArtifact(client, id, artifactId, filename, transport, cliCommand) {
|
|
7
9
|
const execution = (await client.execution(id)).data;
|
|
8
10
|
if (["queued", "running", "waiting"].includes(execution.status))
|
|
9
|
-
throw new Error(`This execution is still ${execution.status}. Wait for it with
|
|
11
|
+
throw new Error(`This execution is still ${execution.status}. Wait for it with ${cliCommand} wait ${id}, then download.`);
|
|
10
12
|
const artifact = outputArtifacts(execution.output).find((entry) => entry.id === artifactId);
|
|
11
13
|
if (!artifact)
|
|
12
14
|
throw new Error("Artifact is not present in this execution. Inspect execution output for available artifact IDs.");
|
|
15
|
+
const path = resolve(filename);
|
|
16
|
+
if (!await stat(dirname(path)).then(entry => entry.isDirectory(), () => false))
|
|
17
|
+
throw new Error(`The folder for --out does not exist: ${dirname(path)}. No file was saved.`);
|
|
18
|
+
if (await lstat(path).then(() => true, (error) => { if (errorCode(error) === "ENOENT")
|
|
19
|
+
return false; throw error; }))
|
|
20
|
+
throw existingOutput(path);
|
|
13
21
|
const { data } = await client.artifactDownload(id, artifactId);
|
|
14
22
|
const url = new URL(data.url);
|
|
15
23
|
if (url.hostname !== "storage.googleapis.com" && !url.hostname.endsWith(".storage.googleapis.com"))
|
|
@@ -25,7 +33,7 @@ export async function downloadArtifact(client, id, artifactId, filename, transpo
|
|
|
25
33
|
clearTimeout(watchdog);
|
|
26
34
|
throw new Error("Artifact download failed. Retrieve a fresh URL and try again.");
|
|
27
35
|
}
|
|
28
|
-
const reader = response.body.getReader(), chunks = [];
|
|
36
|
+
const reader = response.body.getReader(), chunks = [], digest = createHash("sha256");
|
|
29
37
|
let bytes = 0, oversized = false;
|
|
30
38
|
try {
|
|
31
39
|
for (;;) {
|
|
@@ -40,6 +48,7 @@ export async function downloadArtifact(client, id, artifactId, filename, transpo
|
|
|
40
48
|
break;
|
|
41
49
|
}
|
|
42
50
|
chunks.push(part.value);
|
|
51
|
+
digest.update(part.value);
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
catch {
|
|
@@ -51,19 +60,17 @@ export async function downloadArtifact(client, id, artifactId, filename, transpo
|
|
|
51
60
|
}
|
|
52
61
|
if (oversized)
|
|
53
62
|
throw new Error("Artifact exceeded its recorded size; no file was saved.");
|
|
54
|
-
|
|
55
|
-
if (bytes !== artifact.sizeBytes || createHash("sha256").update(buffer).digest("hex") !== artifact.sha256)
|
|
63
|
+
if (bytes !== artifact.sizeBytes || digest.digest("hex") !== artifact.sha256)
|
|
56
64
|
throw new Error("Artifact integrity check failed; no file was saved.");
|
|
57
|
-
const path = resolve(filename);
|
|
58
65
|
const temporary = await mkdtemp(join(dirname(path), ".noimos-platform-")).catch((error) => {
|
|
59
|
-
if (error
|
|
66
|
+
if (errorCode(error) === "ENOENT")
|
|
60
67
|
throw new Error(`The folder for --out does not exist: ${dirname(path)}. No file was saved.`);
|
|
61
68
|
throw error;
|
|
62
69
|
});
|
|
63
70
|
try {
|
|
64
71
|
const staged = join(temporary, "artifact");
|
|
65
|
-
await writeFile(staged,
|
|
66
|
-
await link(staged, path);
|
|
72
|
+
await writeFile(staged, Buffer.concat(chunks, bytes), { flag: "wx", mode: 0o600 });
|
|
73
|
+
await link(staged, path).catch((error) => { throw errorCode(error) === "EEXIST" ? existingOutput(path) : error; });
|
|
67
74
|
}
|
|
68
75
|
finally {
|
|
69
76
|
await rm(temporary, { recursive: true, force: true });
|
package/dist/cli/project-sync.js
CHANGED
|
@@ -140,10 +140,12 @@ async function context(path, env, options = {}) {
|
|
|
140
140
|
const roots = harnessRoots(home, env, await readCustomHarnesses(home));
|
|
141
141
|
return { profile, roots, clients: await targets(roots, profile, options.selected), state: await readState(path, profile) };
|
|
142
142
|
}
|
|
143
|
-
async function inspect(client, profile) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
async function inspect(client, profile, verifyProject = true) {
|
|
144
|
+
if (verifyProject) {
|
|
145
|
+
const { data } = await client.project();
|
|
146
|
+
if (data.developerProjectId !== profile.grant.developerProjectId || data.developerOrganizationId !== profile.grant.developerOrganizationId)
|
|
147
|
+
throw new Error("The saved login does not match this project. No project files were changed.");
|
|
148
|
+
}
|
|
147
149
|
const items = (await client.teamSkills("all")).data.skills.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
148
150
|
if (new Set(items.map(item => item.slug)).size !== items.length)
|
|
149
151
|
throw new Error("The project returned duplicate items. No project files were changed.");
|
|
@@ -199,8 +201,12 @@ export function localProjectSync(path, env, options = {}) {
|
|
|
199
201
|
const client = options.connected?.client ?? new PlatformClient({ apiUrl: ctx.profile.apiUrl, apiKey: ctx.profile.grant.apiKey, local: ctx.profile.local, source: "cli" }, options.transport);
|
|
200
202
|
return { ...ctx, client };
|
|
201
203
|
};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
let polledLogin;
|
|
205
|
+
const capture = async (ctx, poll = false) => {
|
|
206
|
+
const login = `${ctx.profile.grant.apiKey}\n${ctx.profile.grant.developerOrganizationId}\n${ctx.profile.grant.developerProjectId}`;
|
|
207
|
+
const items = (await inspect(ctx.client, ctx.profile, !poll || polledLogin !== login)).filter(item => ctx.clients.some(name => teamItemTargets(item.harnesses, harnessView(ctx.roots, name).like)));
|
|
208
|
+
if (poll)
|
|
209
|
+
polledLogin = login;
|
|
204
210
|
const scriptsPreviouslyAllowed = await teamSkillsUseScripts(ctx.roots, ctx.clients, ctx.profile.grant.developerProjectId)
|
|
205
211
|
|| await teamHarnessUsesScripts(ctx.roots, ctx.clients, ctx.profile.grant.developerProjectId);
|
|
206
212
|
const current = await readLocalProfile(path);
|
|
@@ -222,7 +228,7 @@ export function localProjectSync(path, env, options = {}) {
|
|
|
222
228
|
if (env.RELVO_PROJECT_AUTO_SYNC === "0")
|
|
223
229
|
return undefined;
|
|
224
230
|
const ctx = await prepare();
|
|
225
|
-
return !ctx.state.enabled ? undefined : (await capture(ctx)).review;
|
|
231
|
+
return !ctx.state.enabled ? undefined : (await capture(ctx, true)).review;
|
|
226
232
|
},
|
|
227
233
|
apply: (snapshot, confirmedByUser, allowScripts, removeExecutableSettings = false) => withProfileLock(path, async () => {
|
|
228
234
|
confirmed(confirmedByUser);
|
package/dist/client/browser.js
CHANGED
|
@@ -319,6 +319,10 @@ const publicErrors = {
|
|
|
319
319
|
platform_voice_provider_unavailable: "The voice provider for this model is unavailable. Choose another model.",
|
|
320
320
|
platform_voice_session_cancelled: "The voice session was cancelled or has ended.",
|
|
321
321
|
};
|
|
322
|
+
const noimosPublicErrors = {
|
|
323
|
+
platform_invalid_developer_api_key: "This API key is no longer valid: it expired, was revoked or rotated, or its member lost access. Create a new key in the console.",
|
|
324
|
+
platform_developer_api_key_required: "This is not a valid API key for this service. Check the key in the console.",
|
|
325
|
+
};
|
|
322
326
|
const PLATFORM_ERROR_CODE = /^platform_[a-z0-9_]{1,100}$/;
|
|
323
327
|
const REFUSALS_WITH_DETAILS = new Set(["platform_invalid_tool_arguments", "platform_custom_tool_invalid", "platform_custom_tool_schema_invalid", "platform_custom_tool_version_conflict",
|
|
324
328
|
"platform_team_skill_invalid", "platform_team_skill_version_conflict", "platform_workflow_invalid", "platform_workflow_version_conflict", "platform_workflow_inputs_invalid",
|
|
@@ -326,9 +330,9 @@ const REFUSALS_WITH_DETAILS = new Set(["platform_invalid_tool_arguments", "platf
|
|
|
326
330
|
const refusalDetailsSchema = z.object({ fields: platformInvalidArgumentFieldsSchema.optional(), reason: z.string().max(200).optional(), version: z.number().int().optional(),
|
|
327
331
|
chargeCeilingMicros: z.number().int().nonnegative().optional() });
|
|
328
332
|
const usd = (micros) => `$${(Number(micros) / 1_000_000).toFixed(micros.length > 4 ? 2 : 4)}`;
|
|
329
|
-
function shortfallText({ reason, requiredMicros, availableMicros }) {
|
|
333
|
+
function shortfallText({ reason, requiredMicros, availableMicros }, relvo) {
|
|
330
334
|
if (reason === "balance")
|
|
331
|
-
return ` It needs ${usd(requiredMicros)} and the balance has ${usd(availableMicros)}. Top up the balance (relvo wallet top-up, or the console), then retry with the same idempotency key.`;
|
|
335
|
+
return ` It needs ${usd(requiredMicros)} and the balance has ${usd(availableMicros)}. Top up the balance (${relvo ? "relvo wallet top-up, or the console" : "in the console"}), then retry with the same idempotency key.`;
|
|
332
336
|
const scope = reason === "project_monthly_limit" ? "project's monthly" : reason === "member_monthly_limit" ? "member's monthly" : reason === "api_key_daily_limit" ? "API key's daily" : reason === "organization_daily_limit" ? "organization's daily" : "organization's monthly";
|
|
333
337
|
const reset = reason.endsWith("_daily_limit") ? " The daily count starts again at 00:00 UTC." : "";
|
|
334
338
|
return ` It needs ${usd(requiredMicros)} and ${usd(availableMicros)} remains of the ${scope} spending limit.${reset} Topping up does not help; a person must raise the limit in the console.`;
|
|
@@ -349,9 +353,11 @@ export class PlatformApiError extends Error {
|
|
|
349
353
|
summary;
|
|
350
354
|
billing;
|
|
351
355
|
constructor(status, code, productName = PLATFORM_PRODUCT_NAMES.relvo, fields = [], shortfall, provider, scope, reconnectUrl, billingUrl, actionUrl, connectionProviders = [], noExecutionCharge = false, refusal = {}) {
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
356
|
+
const relvo = productName === PLATFORM_PRODUCT_NAMES.relvo;
|
|
357
|
+
const summary = code && !relvo && Object.hasOwn(noimosPublicErrors, code) ? noimosPublicErrors[code]
|
|
358
|
+
: code && Object.hasOwn(publicErrors, code) ? publicErrors[code] : code ? `The API refused this request: ${code}.`
|
|
359
|
+
: status >= 500 ? "The service failed this request. Retry shortly; the key and project do not need to change." : "Check the developer key, project access and limits in the console.";
|
|
360
|
+
super(`${productName} API request failed (${status}). ${summary}${refusal.retryAfterSeconds ? ` Retry after ${refusal.retryAfterSeconds} seconds; retrying sooner counts against this key.` : ""}${fields.length ? ` Invalid fields: ${fields.join(", ")}.` : ""}${refusal.reason ? ` Reason: ${refusal.reason}` : ""}${refusal.version === undefined ? "" : ` Current version: ${refusal.version}.`}${refusal.chargeCeilingMicros === undefined ? "" : ` chargeCeilingMicros: ${refusal.chargeCeilingMicros}.`}${shortfall ? shortfallText(shortfall, relvo) : ""}${provider ? ` Provider key needed: ${provider}.` : ""}${scope ? ` Missing scope: ${scope}; obtain the user's additional approval before retrying.` : ""}${reconnectUrl ? ` Show the user this page to reconnect it, then retry: ${reconnectUrl}` : ""}${billingUrl ? ` Billing: ${billingUrl}` : ""}${actionUrl ? ` Show the user this page to complete the required approval or configuration, then retry the original request: ${actionUrl}` : ""}`);
|
|
355
361
|
this.status = status;
|
|
356
362
|
this.code = code;
|
|
357
363
|
this.fields = fields;
|
package/dist/mcp/context.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PlatformApiError, wrapUntrusted, TOOL_RESULT_CONTEXT_MAX_BYTES, toolResultSelectionSchema } from "../client/index.js";
|
|
1
|
+
import { PlatformApiError, PLATFORM_DISTRIBUTION, wrapUntrusted, TOOL_RESULT_CONTEXT_MAX_BYTES, toolResultSelectionSchema } from "../client/index.js";
|
|
2
2
|
import { RELVO_TOOL_RESULT_RULE } from "./instructions.js";
|
|
3
3
|
export const MCP_RESULT_MAX_BYTES = TOOL_RESULT_CONTEXT_MAX_BYTES - 256;
|
|
4
4
|
const bytes = (value) => Buffer.byteLength(JSON.stringify(value));
|
|
@@ -20,7 +20,7 @@ export function fitRows(rows, reply, fits, oversized) {
|
|
|
20
20
|
kept.pop();
|
|
21
21
|
return oversized && kept.length && !fits(reply(kept)) ? reply(kept.map(oversized)) : reply(kept);
|
|
22
22
|
}
|
|
23
|
-
export function mcpRequestFailure(error, message = error instanceof Error ? error.message :
|
|
23
|
+
export function mcpRequestFailure(error, message = error instanceof Error ? error.message : `${PLATFORM_DISTRIBUTION.name} request failed.`) {
|
|
24
24
|
const reply = error instanceof PlatformApiError
|
|
25
25
|
? boundedMcpResult({ error: { code: error.code, message: error.guidance ? error.summary : message,
|
|
26
26
|
...(error.shortfall ? { shortfall: error.shortfall } : {}), ...(error.billingUrl ? { billingUrl: error.billingUrl } : {}),
|
package/dist/mcp/operations.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PLATFORM_DISTRIBUTION } from "../client/index.js";
|
|
1
2
|
import { withUpdateNotice } from "./update-notice.js";
|
|
2
3
|
import { mcpRequestFailure } from "./context.js";
|
|
3
4
|
export class PlatformOperations {
|
|
@@ -29,7 +30,7 @@ export class PlatformOperations {
|
|
|
29
30
|
return await tool.call(input, context);
|
|
30
31
|
}
|
|
31
32
|
catch (error) {
|
|
32
|
-
return mcpRequestFailure(error, error instanceof Error ? error.message :
|
|
33
|
+
return mcpRequestFailure(error, error instanceof Error ? error.message : `${PLATFORM_DISTRIBUTION.name} operation failed.`);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
attach(server, notice, only) {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noimosai/platform-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@noimosai/platform-mcp",
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.5",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/server": "^2.0.0",
|