@biznetgio/neo-app 0.4.23 → 0.4.25
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/api.js +12 -0
- package/src/mcp/tools.js +119 -11
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -58,6 +58,18 @@ export class NeoApi {
|
|
|
58
58
|
return { status: res.status, data: payload };
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// postForm sends a multipart/form-data body (FormData); fetch sets the boundary.
|
|
62
|
+
async postForm(path, form) {
|
|
63
|
+
const headers = {};
|
|
64
|
+
if (this.token) headers.Authorization = `Bearer ${this.token}`;
|
|
65
|
+
const res = await fetch(this.baseUrl + path, { method: 'POST', headers, body: form });
|
|
66
|
+
const payload = await res.json().catch(() => null);
|
|
67
|
+
if (res.status >= 400) {
|
|
68
|
+
throw new ApiError(res.status, (payload && payload.error) || res.statusText, payload);
|
|
69
|
+
}
|
|
70
|
+
return { status: res.status, data: payload };
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
get(path, opts) {
|
|
62
74
|
return this.request('GET', path, opts);
|
|
63
75
|
}
|
package/src/mcp/tools.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// map). Read-only vs mutating is annotated; mutating tools surface the server's
|
|
3
3
|
// 202 pending_approval result verbatim instead of treating it as success/failure.
|
|
4
4
|
|
|
5
|
-
import { resolve } from 'node:path';
|
|
6
|
-
import { statSync } from 'node:fs';
|
|
5
|
+
import { resolve, basename } from 'node:path';
|
|
6
|
+
import { statSync, readFileSync } from 'node:fs';
|
|
7
7
|
import { deploySource } from '../deploySource.js';
|
|
8
8
|
import { pkgVersion } from '../util.js';
|
|
9
9
|
import { estimateCost } from '../pricing.js';
|
|
@@ -94,10 +94,10 @@ export const TOOLS = [
|
|
|
94
94
|
{ type: 'template', create: true, deploy: true, scale: false, logs: true, variables: true, delete: true },
|
|
95
95
|
{ type: 'static_site', create: 'via_folder', deploy: true, scale: false, logs: true, variables: true, delete: true, note: 'neo_deploy_folder serviceType=static_site (auto-detected for static-only folders); auto-detection from a repo/monorepo is still TODO (handoff 6)' },
|
|
96
96
|
{ type: 'worker', create: 'via_folder', deploy: true, scale: true, logs: true, variables: true, delete: true, note: 'neo_deploy_folder serviceType=worker — no public ingress; repo auto-detection still TODO (handoff 6)' },
|
|
97
|
-
{ type: 'database', create: true, get: true, list: true, update: true, delete: true, note: 'neo_create/list/get/update(resize)/delete_database (
|
|
98
|
-
{ type: 'queue', create: true, get: true, list: true, delete: true, note: 'neo_create/list/get/delete_queue (0.4.15)
|
|
97
|
+
{ type: 'database', create: true, get: true, list: true, update: true, backup: true, delete: true, note: 'neo_create/list/get/update(resize)/delete_database + backup (list/create/run); restore still TODO (handoff 4.4)' },
|
|
98
|
+
{ type: 'queue', create: true, get: true, list: true, update: true, delete: true, note: 'neo_create/list/get/update/delete_queue (0.4.15-0.4.25) (handoff 4.5)' },
|
|
99
99
|
{ type: 'cron', create: true, get: true, list: true, run: true, runs: true, delete: true, note: 'neo_create/list/get/run/list_runs/delete_cron (0.4.15-0.4.21) (handoff 4.6)' },
|
|
100
|
-
{ type: 'object_storage', create: true, get: true, list: true, objects: true, delete: true, note: 'neo_create/list/get/delete_bucket + list/delete objects (0.4.15-0.4.
|
|
100
|
+
{ type: 'object_storage', create: true, get: true, list: true, objects: true, upload: true, delete: true, note: 'neo_create/list/get/delete_bucket + list/upload/presign(get)/delete objects (0.4.15-0.4.25) (handoff 4.7)' },
|
|
101
101
|
];
|
|
102
102
|
|
|
103
103
|
return {
|
|
@@ -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
|
},
|
|
@@ -494,6 +517,45 @@ export const TOOLS = [
|
|
|
494
517
|
return redactSecrets(result(status, data));
|
|
495
518
|
},
|
|
496
519
|
},
|
|
520
|
+
{
|
|
521
|
+
name: 'neo_list_backups',
|
|
522
|
+
description: 'List configured database backups in a project (handoff 4.4): schedule, destination, last run status.',
|
|
523
|
+
mutating: false,
|
|
524
|
+
schema: { type: 'object', properties: { projectId: str('Project id') }, required: ['projectId'] },
|
|
525
|
+
async call(api, args) {
|
|
526
|
+
return redactSecrets((await api.get(`/api/v1/projects/${args.projectId}/backups`)).data);
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: 'neo_create_backup',
|
|
531
|
+
description: 'Configure a backup for a managed database (handoff 4.4). `cron` sets a schedule (empty = manual only); `destination` is local (default) or s3. Trigger it with neo_run_backup.',
|
|
532
|
+
mutating: true,
|
|
533
|
+
schema: {
|
|
534
|
+
type: 'object',
|
|
535
|
+
properties: {
|
|
536
|
+
projectId: str('Project id'),
|
|
537
|
+
databaseId: str('Database id to back up'),
|
|
538
|
+
cron: str('Cron schedule (optional; empty = manual)'),
|
|
539
|
+
destination: { type: 'string', enum: ['local', 's3'], description: 'local (default) | s3' },
|
|
540
|
+
},
|
|
541
|
+
required: ['projectId', 'databaseId'],
|
|
542
|
+
},
|
|
543
|
+
async call(api, args) {
|
|
544
|
+
const { projectId, ...body } = args;
|
|
545
|
+
const { status, data } = await api.post(`/api/v1/projects/${projectId}/backups`, body);
|
|
546
|
+
return redactSecrets(result(status, data));
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: 'neo_run_backup',
|
|
551
|
+
description: 'Trigger a configured database backup to run now (handoff 4.4).',
|
|
552
|
+
mutating: true,
|
|
553
|
+
schema: { type: 'object', properties: { backupId: str('Backup id (from neo_list_backups)') }, required: ['backupId'] },
|
|
554
|
+
async call(api, args) {
|
|
555
|
+
const { status, data } = await api.post(`/api/v1/backups/${args.backupId}/run`);
|
|
556
|
+
return redactSecrets(result(status, data));
|
|
557
|
+
},
|
|
558
|
+
},
|
|
497
559
|
{
|
|
498
560
|
name: 'neo_create_queue',
|
|
499
561
|
description: 'Create a managed queue in a project (handoff 4.5). Credentials redacted. May return pending_approval.',
|
|
@@ -535,6 +597,27 @@ export const TOOLS = [
|
|
|
535
597
|
return redactSecrets((await api.get(`/api/v1/queues/${args.queueId}`)).data);
|
|
536
598
|
},
|
|
537
599
|
},
|
|
600
|
+
{
|
|
601
|
+
name: 'neo_update_queue',
|
|
602
|
+
description: 'Update a managed queue (handoff 4.5): plan class, DLQ, retention, and storage (grow-only — a smaller volumeSize is rejected). Redeploy the queue for it to take effect.',
|
|
603
|
+
mutating: true,
|
|
604
|
+
schema: {
|
|
605
|
+
type: 'object',
|
|
606
|
+
properties: {
|
|
607
|
+
queueId: str('Queue id'),
|
|
608
|
+
planClass: str('dev | standard'),
|
|
609
|
+
dlqEnabled: { type: 'boolean', description: 'Enable/disable the dead-letter queue' },
|
|
610
|
+
volumeSize: str('New storage size, e.g. "20Gi" — must be >= current (grow-only)'),
|
|
611
|
+
retention: { type: 'object', description: 'Retention policy {maxAgeSeconds, maxBytes, maxMsgs}' },
|
|
612
|
+
},
|
|
613
|
+
required: ['queueId'],
|
|
614
|
+
},
|
|
615
|
+
async call(api, args) {
|
|
616
|
+
const { queueId, ...body } = args;
|
|
617
|
+
const { status, data } = await api.put(`/api/v1/queues/${queueId}`, body);
|
|
618
|
+
return redactSecrets(result(status, data));
|
|
619
|
+
},
|
|
620
|
+
},
|
|
538
621
|
{
|
|
539
622
|
name: 'neo_create_cron',
|
|
540
623
|
description: 'Create a scheduled job (cron) in a project (handoff 4.6). Target is either an existing service command (mode=app_command, serviceId+command) or an HTTP endpoint (mode=http_endpoint, url+method). Schedule via scheduleType (cron|interval|once|seconds) + schedule/intervalMinutes. May return pending_approval.',
|
|
@@ -670,21 +753,46 @@ export const TOOLS = [
|
|
|
670
753
|
return (await api.get(`/api/v1/buckets/${args.bucketId}/objects/presign`, { query: { object: args.object } })).data;
|
|
671
754
|
},
|
|
672
755
|
},
|
|
756
|
+
{
|
|
757
|
+
name: 'neo_upload_bucket_object',
|
|
758
|
+
description: 'Upload a LOCAL file to an object-storage bucket (handoff 4.7 — object put). The path is on the machine running this MCP server. objectName defaults to the file\'s basename.',
|
|
759
|
+
mutating: true,
|
|
760
|
+
schema: {
|
|
761
|
+
type: 'object',
|
|
762
|
+
properties: {
|
|
763
|
+
bucketId: str('Bucket id'),
|
|
764
|
+
filePath: str('Local file path to upload'),
|
|
765
|
+
objectName: str('Object key/name in the bucket (optional; default the file basename)'),
|
|
766
|
+
},
|
|
767
|
+
required: ['bucketId', 'filePath'],
|
|
768
|
+
},
|
|
769
|
+
async call(api, args) {
|
|
770
|
+
const p = resolve(args.filePath);
|
|
771
|
+
const st = statSync(p);
|
|
772
|
+
if (!st.isFile()) throw new Error(`not a file: ${args.filePath}`);
|
|
773
|
+
const name = args.objectName || basename(p);
|
|
774
|
+
const form = new FormData();
|
|
775
|
+
form.append('object_name', name);
|
|
776
|
+
form.append('file', new Blob([readFileSync(p)]), name);
|
|
777
|
+
const { status, data } = await api.postForm(`/api/v1/buckets/${args.bucketId}/objects`, form);
|
|
778
|
+
return result(status, data);
|
|
779
|
+
},
|
|
780
|
+
},
|
|
673
781
|
// --- Typed bindings (handoff 5): attach a DB/queue to a consumer service. The
|
|
674
782
|
// connection string is injected as an env var SERVER-SIDE at the consumer's next
|
|
675
783
|
// deploy (marked a secret) — it is never returned to the agent (write-only). ---
|
|
676
784
|
{
|
|
677
785
|
name: 'neo_bind_resource',
|
|
678
786
|
description:
|
|
679
|
-
'Bind a managed database or
|
|
787
|
+
'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.',
|
|
680
788
|
mutating: true,
|
|
681
789
|
schema: {
|
|
682
790
|
type: 'object',
|
|
683
791
|
properties: {
|
|
684
792
|
appId: str('Consumer service/app id that will receive the env var (from neo_list_services)'),
|
|
685
|
-
targetType: { type: 'string', enum: ['neo_db', 'neo_queue'], description: 'neo_db | neo_queue — the resource to attach' },
|
|
686
|
-
targetId: str('The database or
|
|
687
|
-
envKey: str('Env var name
|
|
793
|
+
targetType: { type: 'string', enum: ['neo_db', 'neo_queue', 'neo_object_storage'], description: 'neo_db | neo_queue | neo_object_storage — the resource to attach' },
|
|
794
|
+
targetId: str('The database, queue, or bucket id to bind'),
|
|
795
|
+
envKey: str('Env var name / prefix (optional; default DATABASE_URL, NATS_URL, or S3 prefix)'),
|
|
688
796
|
},
|
|
689
797
|
required: ['appId', 'targetType', 'targetId'],
|
|
690
798
|
},
|