@biznetgio/neo-app 0.4.22 → 0.4.24
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 +62 -8
package/package.json
CHANGED
package/src/mcp/tools.js
CHANGED
|
@@ -105,7 +105,7 @@ export const TOOLS = [
|
|
|
105
105
|
controlPlane,
|
|
106
106
|
resourceTypes,
|
|
107
107
|
// Verbs this MCP build exposes today (no create-DB/queue/cron/bucket, no costed plan/apply yet).
|
|
108
|
-
lifecycleVerbs: ['plan', 'apply', 'get_operation', 'create', 'list', 'get', 'deploy', 'scale', 'logs', 'get_variables', 'set_variable', 'estimate', 'bind', 'unbind', 'run_cron', 'delete', 'list_approvals', 'get_approval', 'get_credit', 'list_templates'],
|
|
108
|
+
lifecycleVerbs: ['plan', 'apply', 'get_operation', 'wait_operation', 'create', 'list', 'get', 'update', 'deploy', 'scale', 'logs', 'get_variables', 'set_variable', 'estimate', 'scan_repo', 'bind', 'unbind', 'run_cron', 'presign_object', 'delete', 'list_approvals', 'get_approval', 'get_credit', 'list_templates'],
|
|
109
109
|
bindings: { supported: true, targets: ['neo_db', 'neo_queue'], writeOnly: true, note: 'neo_bind_resource injects DATABASE_URL/NATS_URL server-side at deploy; the secret is never returned (handoff 5)' },
|
|
110
110
|
sourceTypes: ['git', 'image', 'dockerfile', 'local', 'template'],
|
|
111
111
|
environments: ['staging', 'production'],
|
|
@@ -232,6 +232,26 @@ export const TOOLS = [
|
|
|
232
232
|
return out;
|
|
233
233
|
},
|
|
234
234
|
},
|
|
235
|
+
{
|
|
236
|
+
name: 'neo_set_service_variable',
|
|
237
|
+
description:
|
|
238
|
+
'Set ONE PER-SERVICE environment variable on a specific service (merged server-side — other vars are preserved, unlike project-wide neo_set_variable). Pass secret:true to store it as a cluster Secret. Redeploy the service (neo_deploy) for it to take effect. The value is never echoed back.',
|
|
239
|
+
mutating: true,
|
|
240
|
+
schema: {
|
|
241
|
+
type: 'object',
|
|
242
|
+
properties: {
|
|
243
|
+
appId: str('Service/app id (from neo_list_services)'),
|
|
244
|
+
key: str('Variable name'),
|
|
245
|
+
value: str('Variable value'),
|
|
246
|
+
secret: { type: 'boolean', description: 'Store as a secret (default false)' },
|
|
247
|
+
},
|
|
248
|
+
required: ['appId', 'key', 'value'],
|
|
249
|
+
},
|
|
250
|
+
async call(api, args) {
|
|
251
|
+
const { status, data } = await api.post(`/api/v1/apps/${args.appId}/variables`, { key: args.key, value: args.value, secret: !!args.secret });
|
|
252
|
+
return result(status, data); // {status:"set", key, redeployRequired} — no value returned
|
|
253
|
+
},
|
|
254
|
+
},
|
|
235
255
|
{
|
|
236
256
|
name: 'neo_scale_service',
|
|
237
257
|
description: 'Scale a service to N replicas. May return pending_approval under org policy.',
|
|
@@ -328,17 +348,20 @@ export const TOOLS = [
|
|
|
328
348
|
suggestedServiceType: s.type === 'static' ? 'static_site' : 'web',
|
|
329
349
|
suggestedBuildType: s.type === 'docker' ? 'dockerfile' : 'railpack',
|
|
330
350
|
contextDir: s.path || undefined,
|
|
351
|
+
detectedDependencies: s.deps || [], // database | queue | object_storage, inferred from the manifest
|
|
331
352
|
}));
|
|
353
|
+
const anyDeps = components.some((c) => c.detectedDependencies.length);
|
|
332
354
|
return {
|
|
333
355
|
repoUrl: args.repoUrl,
|
|
334
356
|
count: components.length,
|
|
335
357
|
components,
|
|
336
358
|
note:
|
|
337
|
-
components.length > 1
|
|
359
|
+
(components.length > 1
|
|
338
360
|
? 'Monorepo: multiple deployable components. Ask the user which to deploy and create each selected one with its `contextDir` — do NOT collapse them into one service (handoff 6). Worker vs web cannot be inferred from the tree alone; confirm the type with the user.'
|
|
339
361
|
: components.length === 1
|
|
340
362
|
? 'One deployable component found.'
|
|
341
|
-
: 'No deployable component found (no Dockerfile/package.json/go.mod/requirements.txt/index.html/… at any root).'
|
|
363
|
+
: 'No deployable component found (no Dockerfile/package.json/go.mod/requirements.txt/index.html/… at any root).')
|
|
364
|
+
+ (anyDeps ? ' A component lists detectedDependencies (database/queue/object_storage) from its manifest — consider planning those resources and binding them (neo_bind_resource); confirm with the user first.' : ''),
|
|
342
365
|
};
|
|
343
366
|
},
|
|
344
367
|
},
|
|
@@ -661,21 +684,30 @@ export const TOOLS = [
|
|
|
661
684
|
return result(status, data);
|
|
662
685
|
},
|
|
663
686
|
},
|
|
687
|
+
{
|
|
688
|
+
name: 'neo_presign_bucket_object',
|
|
689
|
+
description: 'Get a time-limited presigned URL to download an object from a bucket (handoff 4.7 — object get). The URL is a short-lived signed link, not long-term credentials.',
|
|
690
|
+
mutating: false,
|
|
691
|
+
schema: { type: 'object', properties: { bucketId: str('Bucket id'), object: str('Object key/path') }, required: ['bucketId', 'object'] },
|
|
692
|
+
async call(api, args) {
|
|
693
|
+
return (await api.get(`/api/v1/buckets/${args.bucketId}/objects/presign`, { query: { object: args.object } })).data;
|
|
694
|
+
},
|
|
695
|
+
},
|
|
664
696
|
// --- Typed bindings (handoff 5): attach a DB/queue to a consumer service. The
|
|
665
697
|
// connection string is injected as an env var SERVER-SIDE at the consumer's next
|
|
666
698
|
// deploy (marked a secret) — it is never returned to the agent (write-only). ---
|
|
667
699
|
{
|
|
668
700
|
name: 'neo_bind_resource',
|
|
669
701
|
description:
|
|
670
|
-
'Bind a managed database or
|
|
702
|
+
'Bind a managed database, queue, or object-storage bucket to a consumer service so its connection is injected as environment variable(s) at the consumer\'s next deploy — WITHOUT ever exposing the secret to you (handoff 5, write-only). envKey defaults: DATABASE_URL (neo_db), NATS_URL (neo_queue); for neo_object_storage it is a PREFIX (default S3) injecting <P>_ENDPOINT/_BUCKET/_REGION + secret <P>_ACCESS_KEY/_SECRET_KEY. After binding, redeploy the consumer. Target and consumer must be in the same project.',
|
|
671
703
|
mutating: true,
|
|
672
704
|
schema: {
|
|
673
705
|
type: 'object',
|
|
674
706
|
properties: {
|
|
675
707
|
appId: str('Consumer service/app id that will receive the env var (from neo_list_services)'),
|
|
676
|
-
targetType: { type: 'string', enum: ['neo_db', 'neo_queue'], description: 'neo_db | neo_queue — the resource to attach' },
|
|
677
|
-
targetId: str('The database or
|
|
678
|
-
envKey: str('Env var name
|
|
708
|
+
targetType: { type: 'string', enum: ['neo_db', 'neo_queue', 'neo_object_storage'], description: 'neo_db | neo_queue | neo_object_storage — the resource to attach' },
|
|
709
|
+
targetId: str('The database, queue, or bucket id to bind'),
|
|
710
|
+
envKey: str('Env var name / prefix (optional; default DATABASE_URL, NATS_URL, or S3 prefix)'),
|
|
679
711
|
},
|
|
680
712
|
required: ['appId', 'targetType', 'targetId'],
|
|
681
713
|
},
|
|
@@ -785,13 +817,35 @@ export const TOOLS = [
|
|
|
785
817
|
},
|
|
786
818
|
{
|
|
787
819
|
name: 'neo_get_operation',
|
|
788
|
-
description: 'Get the status of a stack apply (the operationId/planId from neo_apply_stack)
|
|
820
|
+
description: 'Get the status of a stack apply (the operationId/planId from neo_apply_stack). Overall status: applying | applied | partial | failed. Per-resource status: creating | deploying | deployed | ready | blocked | failed, plus a `healthy` flag + `url` for services (apply probes the public URL — "ready" means it actually responded) and `blockedBy` when a consumer\'s foundation failed. Poll until terminal.',
|
|
789
821
|
mutating: false,
|
|
790
822
|
schema: { type: 'object', properties: { operationId: str('Operation id (= plan id from neo_apply_stack)') }, required: ['operationId'] },
|
|
791
823
|
async call(api, args) {
|
|
792
824
|
return redactSecrets((await api.get(`/api/v1/plans/${args.operationId}`)).data);
|
|
793
825
|
},
|
|
794
826
|
},
|
|
827
|
+
{
|
|
828
|
+
name: 'neo_wait_operation',
|
|
829
|
+
description: 'Wait for a stack apply (operationId from neo_apply_stack) to finish — polls until the status is terminal (applied | partial | failed) or the timeout elapses, then returns the final operation with per-resource status (secrets redacted). Convenience over polling neo_get_operation yourself.',
|
|
830
|
+
mutating: false,
|
|
831
|
+
schema: {
|
|
832
|
+
type: 'object',
|
|
833
|
+
properties: { operationId: str('Operation id (= plan id)'), timeoutMs: int('Max wait in milliseconds (default 180000)') },
|
|
834
|
+
required: ['operationId'],
|
|
835
|
+
},
|
|
836
|
+
async call(api, args) {
|
|
837
|
+
const deadline = Date.now() + (args.timeoutMs && args.timeoutMs > 0 ? args.timeoutMs : 180000);
|
|
838
|
+
const terminal = new Set(['applied', 'partial', 'failed']);
|
|
839
|
+
let op;
|
|
840
|
+
for (;;) {
|
|
841
|
+
op = redactSecrets((await api.get(`/api/v1/plans/${args.operationId}`)).data);
|
|
842
|
+
if (terminal.has(op.status) || Date.now() >= deadline) break;
|
|
843
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
844
|
+
}
|
|
845
|
+
if (op && !terminal.has(op.status)) return { ...op, timedOut: true, note: 'still in progress at timeout — call neo_get_operation again' };
|
|
846
|
+
return op;
|
|
847
|
+
},
|
|
848
|
+
},
|
|
795
849
|
// --- Cleanup (handoff 3.6): MCP can delete every resource type it can reach, so
|
|
796
850
|
// an agent never leaks a resource it created. These are DESTRUCTIVE and permanent;
|
|
797
851
|
// the server hard-deletes the record and tears down the running workload. ---
|