@biznetgio/neo-app 0.4.33 → 0.4.35
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/package.json +1 -1
- package/src/mcp/tools.js +50 -14
package/package.json
CHANGED
package/src/mcp/tools.js
CHANGED
|
@@ -40,6 +40,26 @@ function requireEnv(env) {
|
|
|
40
40
|
return e;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// deleteAndConverge deletes a resource, then polls its endpoint until the inventory
|
|
44
|
+
// actually converges (GET → 404), so a delete isn't reported done while the record
|
|
45
|
+
// still exists. Returns a durable operation reference (`del-<id>`) + convergence
|
|
46
|
+
// state (handoff 3.6; 0.4.34 retest §9 "every delete returns a durable operation ID").
|
|
47
|
+
async function deleteAndConverge(api, path, resourceId, kind) {
|
|
48
|
+
const { status, data } = await api.del(path);
|
|
49
|
+
let converged = false, checks = 0;
|
|
50
|
+
for (let i = 0; i < 12; i++) {
|
|
51
|
+
checks++;
|
|
52
|
+
try {
|
|
53
|
+
await api.get(path);
|
|
54
|
+
await new Promise((r) => setTimeout(r, 500)); // still present — keep polling
|
|
55
|
+
} catch (e) {
|
|
56
|
+
if (e && e.status === 404) { converged = true; break; }
|
|
57
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { operationId: 'del-' + resourceId, kind, status: (data && data.status) || 'deleted', converged, convergenceChecks: checks };
|
|
61
|
+
}
|
|
62
|
+
|
|
43
63
|
// gbFromSize parses "10Gi"/"10G"/"10" → 10 (integer GB), else undefined.
|
|
44
64
|
function gbFromSize(v) {
|
|
45
65
|
if (v == null) return undefined;
|
|
@@ -820,8 +840,16 @@ export const TOOLS = [
|
|
|
820
840
|
],
|
|
821
841
|
branches: {
|
|
822
842
|
app_command: 'Provide serviceId (an existing service) OR image, plus command (argv array).',
|
|
823
|
-
http_endpoint: 'Provide url and method (GET/POST/…).',
|
|
843
|
+
http_endpoint: 'Provide url and method (GET/POST/…). Optional: headers (object), body + contentType, expectedStatus (exact code required for success), timeoutSeconds, retryCount, concurrencyPolicy (Allow|Forbid|Replace).',
|
|
824
844
|
},
|
|
845
|
+
httpOptions: [
|
|
846
|
+
{ field: 'headers', ask: 'Any request headers? (e.g. Authorization) — object', optional: true },
|
|
847
|
+
{ field: 'body', ask: 'A request body? (+ contentType, e.g. application/json)', optional: true },
|
|
848
|
+
{ field: 'expectedStatus', ask: 'A specific expected HTTP status for success? (default: any 2xx)', optional: true },
|
|
849
|
+
{ field: 'timeoutSeconds', ask: 'A per-run timeout (seconds)?', optional: true },
|
|
850
|
+
{ field: 'retryCount', ask: 'How many retries on failure?', optional: true },
|
|
851
|
+
{ field: 'concurrencyPolicy', ask: 'If a run overruns the next schedule: Allow | Forbid | Replace?', optional: true },
|
|
852
|
+
],
|
|
825
853
|
examples: [
|
|
826
854
|
{ intent: 'Run `node cleanup.js` in my web service every night at 2am Jakarta', args: { name: 'nightly-cleanup', mode: 'app_command', scheduleType: 'cron', schedule: '0 2 * * *', timezone: 'Asia/Jakarta', serviceId: '<web service id>', command: ['node', 'cleanup.js'], environment: 'staging' } },
|
|
827
855
|
{ intent: 'Ping a health URL every 15 minutes', args: { name: 'healthcheck', mode: 'http_endpoint', scheduleType: 'interval', intervalMinutes: 15, url: 'https://example.app/health', method: 'GET', environment: 'production' } },
|
|
@@ -849,6 +877,13 @@ export const TOOLS = [
|
|
|
849
877
|
command: { type: 'array', items: { type: 'string' }, description: 'Command argv (mode=app_command)' },
|
|
850
878
|
url: str('Target URL (mode=http_endpoint)'),
|
|
851
879
|
method: str('HTTP method (mode=http_endpoint), e.g. GET/POST'),
|
|
880
|
+
headers: { type: 'object', description: 'HTTP request headers (mode=http_endpoint), e.g. {"Authorization":"Bearer …"}. Secret header values are redacted when read back.' },
|
|
881
|
+
body: str('HTTP request body (mode=http_endpoint)'),
|
|
882
|
+
contentType: str('Content-Type for the body (mode=http_endpoint), e.g. "application/json" — merged into headers'),
|
|
883
|
+
expectedStatus: int('Exact HTTP status a run must receive to count as success (mode=http_endpoint; default: any 2xx)'),
|
|
884
|
+
timeoutSeconds: int('Per-run timeout in seconds (optional) → activeDeadlineSeconds'),
|
|
885
|
+
retryCount: int('Retries on failure (optional) → backoffLimit'),
|
|
886
|
+
concurrencyPolicy: { type: 'string', enum: ['Allow', 'Forbid', 'Replace'], description: 'Overlap policy if a run is still going when the next is due (optional, default Allow)' },
|
|
852
887
|
environment: { type: 'string', enum: ['staging', 'production'], description: 'REQUIRED — staging or production (never defaulted, handoff §2)' },
|
|
853
888
|
confirm: { type: 'boolean', description: 'Approve & Create: without confirm:true the tool returns the cost (crons are billed per run — variable) and creates nothing. Re-call with confirm:true to create.' },
|
|
854
889
|
},
|
|
@@ -858,7 +893,11 @@ export const TOOLS = [
|
|
|
858
893
|
requireEnv(args.environment);
|
|
859
894
|
const gate = await confirmCost(api, args, {}); // cron cost is variable (per-run) — gate surfaces the rate
|
|
860
895
|
if (gate) return gate;
|
|
861
|
-
|
|
896
|
+
// Merge contentType into headers for convenience (unless already set).
|
|
897
|
+
if (args.contentType) {
|
|
898
|
+
args = { ...args, headers: { 'Content-Type': args.contentType, ...(args.headers || {}) } };
|
|
899
|
+
}
|
|
900
|
+
const { projectId, confirm, orgId, contentType, ...body } = args;
|
|
862
901
|
const { status, data } = await api.post(`/api/v1/projects/${projectId}/crons`, body);
|
|
863
902
|
return redactSecrets(result(status, data));
|
|
864
903
|
},
|
|
@@ -1046,7 +1085,7 @@ export const TOOLS = [
|
|
|
1046
1085
|
{
|
|
1047
1086
|
name: 'neo_plan_stack',
|
|
1048
1087
|
description:
|
|
1049
|
-
'Create an immutable, COSTED plan for a whole stack (services + databases/queues/buckets + scheduled jobs + bindings) WITHOUT creating anything — zero-write (handoff 3.3). Supports kind: app|database|queue|bucket|cron and bindings to neo_db|neo_queue|neo_object_storage. Returns planId, planHash, itemized monthly/hourly cost, and ordered phases. Show the cost to the user for approval, then call neo_apply_stack with planId+planHash. The plan expires in 15 minutes.',
|
|
1088
|
+
'Create an immutable, COSTED plan for a whole stack (services + databases/queues/buckets + scheduled jobs + bindings) WITHOUT creating anything — zero-write (handoff 3.3). Supports kind: app|database|queue|bucket|cron and bindings to neo_db|neo_queue|neo_object_storage. An http_endpoint cron can target a same-stack web app by name (targetService+targetPath) — its URL is resolved at apply. Returns planId, planHash, itemized monthly/hourly cost, and ordered phases. Show the cost to the user for approval, then call neo_apply_stack with planId+planHash. The plan expires in 15 minutes.',
|
|
1050
1089
|
mutating: false,
|
|
1051
1090
|
schema: {
|
|
1052
1091
|
type: 'object',
|
|
@@ -1081,7 +1120,9 @@ export const TOOLS = [
|
|
|
1081
1120
|
timezone: str('cron: IANA timezone (optional)'),
|
|
1082
1121
|
serviceId: str('cron app_command: target service — another resource NAME in this stack, or a literal service id'),
|
|
1083
1122
|
command: { type: 'array', items: { type: 'string' }, description: 'cron app_command: command argv' },
|
|
1084
|
-
url: str('cron http_endpoint: target URL'),
|
|
1123
|
+
url: str('cron http_endpoint: literal target URL'),
|
|
1124
|
+
targetService: str('cron http_endpoint: name of a web app resource IN THIS STACK to hit — its deployed URL is resolved at apply (use instead of a literal url for a same-stack service)'),
|
|
1125
|
+
targetPath: str('cron http_endpoint: path appended to the targetService URL (e.g. "/cron")'),
|
|
1085
1126
|
method: str('cron http_endpoint: HTTP method'),
|
|
1086
1127
|
},
|
|
1087
1128
|
required: ['kind', 'name'],
|
|
@@ -1226,8 +1267,7 @@ export const TOOLS = [
|
|
|
1226
1267
|
destructive: true,
|
|
1227
1268
|
schema: { type: 'object', properties: { appId: str('Service/app id (from neo_list_services)') }, required: ['appId'] },
|
|
1228
1269
|
async call(api, args) {
|
|
1229
|
-
|
|
1230
|
-
return result(status, data);
|
|
1270
|
+
return deleteAndConverge(api, `/api/v1/apps/${args.appId}`, args.appId, 'service');
|
|
1231
1271
|
},
|
|
1232
1272
|
},
|
|
1233
1273
|
{
|
|
@@ -1237,8 +1277,7 @@ export const TOOLS = [
|
|
|
1237
1277
|
destructive: true,
|
|
1238
1278
|
schema: { type: 'object', properties: { databaseId: str('Database id') }, required: ['databaseId'] },
|
|
1239
1279
|
async call(api, args) {
|
|
1240
|
-
|
|
1241
|
-
return result(status, data);
|
|
1280
|
+
return deleteAndConverge(api, `/api/v1/databases/${args.databaseId}`, args.databaseId, 'database');
|
|
1242
1281
|
},
|
|
1243
1282
|
},
|
|
1244
1283
|
{
|
|
@@ -1248,8 +1287,7 @@ export const TOOLS = [
|
|
|
1248
1287
|
destructive: true,
|
|
1249
1288
|
schema: { type: 'object', properties: { queueId: str('Queue id') }, required: ['queueId'] },
|
|
1250
1289
|
async call(api, args) {
|
|
1251
|
-
|
|
1252
|
-
return result(status, data);
|
|
1290
|
+
return deleteAndConverge(api, `/api/v1/queues/${args.queueId}`, args.queueId, 'queue');
|
|
1253
1291
|
},
|
|
1254
1292
|
},
|
|
1255
1293
|
{
|
|
@@ -1259,8 +1297,7 @@ export const TOOLS = [
|
|
|
1259
1297
|
destructive: true,
|
|
1260
1298
|
schema: { type: 'object', properties: { cronId: str('Cron/scheduled-job id') }, required: ['cronId'] },
|
|
1261
1299
|
async call(api, args) {
|
|
1262
|
-
|
|
1263
|
-
return result(status, data);
|
|
1300
|
+
return deleteAndConverge(api, `/api/v1/crons/${args.cronId}`, args.cronId, 'cron');
|
|
1264
1301
|
},
|
|
1265
1302
|
},
|
|
1266
1303
|
{
|
|
@@ -1270,8 +1307,7 @@ export const TOOLS = [
|
|
|
1270
1307
|
destructive: true,
|
|
1271
1308
|
schema: { type: 'object', properties: { bucketId: str('Bucket id') }, required: ['bucketId'] },
|
|
1272
1309
|
async call(api, args) {
|
|
1273
|
-
|
|
1274
|
-
return result(status, data);
|
|
1310
|
+
return deleteAndConverge(api, `/api/v1/buckets/${args.bucketId}`, args.bucketId, 'bucket');
|
|
1275
1311
|
},
|
|
1276
1312
|
},
|
|
1277
1313
|
{
|