code-engine-mcp-server 1.0.4 → 1.0.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 +441 -81
- package/build/index.js +1268 -81
- package/build/index.js.map +1 -1
- package/docs/CONTRIBUTING.md +92 -26
- package/docs/MCP_INSPECTOR_TROUBLESHOOTING.md +301 -0
- package/docs/SETUP_INSTRUCTIONS.md +272 -177
- package/docs/images/inspector-1-setup.png +0 -0
- package/docs/images/inspector-2-connected.png +0 -0
- package/docs/images/inspector-3-tools-list.png +0 -0
- package/docs/images/inspector-4-tool-selected.png +0 -0
- package/docs/images/inspector-5-tool-result.png +0 -0
- package/docs/images/inspector-localhost-1-setup.png +0 -0
- package/docs/images/inspector-localhost-2-connected.png +0 -0
- package/docs/images/inspector-localhost-3-result.png +0 -0
- package/docs/images/inspector-localhost-3-tool-selected.png +0 -0
- package/docs/images/inspector-localhost-4-result-detail.png +0 -0
- package/docs/images/inspector-localhost-4-result.png +0 -0
- package/package.json +7 -2
- package/docs/API_CALL_SCENARIOS.md +0 -594
- package/docs/CODE_ENGINE_API_REFERENCE.md +0 -764
package/build/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
11
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
12
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
-
import {
|
|
13
|
+
import { execFile, spawn } from 'child_process';
|
|
14
14
|
import { promisify } from 'util';
|
|
15
15
|
import axios from 'axios';
|
|
16
16
|
import { config as loadDotenv } from 'dotenv';
|
|
@@ -20,7 +20,70 @@ import { fileURLToPath } from 'url';
|
|
|
20
20
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
21
|
loadDotenv({ path: resolve(__dirname, '../../.env') });
|
|
22
22
|
loadDotenv({ path: resolve(__dirname, '../.env') });
|
|
23
|
-
const
|
|
23
|
+
const execFileAsync = promisify(execFile);
|
|
24
|
+
// ── Input validation helpers ───────────────────────────────────────────────
|
|
25
|
+
// All container-facing user inputs are validated before they reach any shell
|
|
26
|
+
// or execFile call, preventing command injection.
|
|
27
|
+
function validateRuntime(r) {
|
|
28
|
+
const s = String(r || 'docker');
|
|
29
|
+
if (s !== 'docker' && s !== 'podman')
|
|
30
|
+
throw new Error(`Invalid container runtime "${s}" — must be "docker" or "podman"`);
|
|
31
|
+
return s;
|
|
32
|
+
}
|
|
33
|
+
// Image names: registry/namespace/name:tag or name@sha256:digest
|
|
34
|
+
function validateImageName(v) {
|
|
35
|
+
const s = String(v || '');
|
|
36
|
+
if (!s || !/^[a-zA-Z0-9._\-/:@]+$/.test(s))
|
|
37
|
+
throw new Error(`Invalid image name "${s}"`);
|
|
38
|
+
return s;
|
|
39
|
+
}
|
|
40
|
+
// Container IDs: short hex, full hex, or alphanumeric container name
|
|
41
|
+
function validateContainerId(v) {
|
|
42
|
+
const s = String(v || '');
|
|
43
|
+
if (!s || !/^[a-zA-Z0-9_.\-]+$/.test(s))
|
|
44
|
+
throw new Error(`Invalid container ID/name "${s}"`);
|
|
45
|
+
return s;
|
|
46
|
+
}
|
|
47
|
+
// Port mappings: hostPort:containerPort, e.g. 8080:8080
|
|
48
|
+
function validatePortMapping(v) {
|
|
49
|
+
const s = String(v || '');
|
|
50
|
+
if (!s || !/^\d{1,5}:\d{1,5}$/.test(s))
|
|
51
|
+
throw new Error(`Invalid port mapping "${s}" — expected "hostPort:containerPort"`);
|
|
52
|
+
return s;
|
|
53
|
+
}
|
|
54
|
+
// Environment variable names: POSIX identifier rules
|
|
55
|
+
function validateEnvKey(v) {
|
|
56
|
+
const s = String(v || '');
|
|
57
|
+
if (!s || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(s))
|
|
58
|
+
throw new Error(`Invalid environment variable name "${s}"`);
|
|
59
|
+
return s;
|
|
60
|
+
}
|
|
61
|
+
// Registry hostnames: hostname[:port]
|
|
62
|
+
function validateRegistryHost(v) {
|
|
63
|
+
const s = String(v || '');
|
|
64
|
+
if (!s || !/^[a-zA-Z0-9._\-]+(:\d+)?$/.test(s))
|
|
65
|
+
throw new Error(`Invalid registry hostname "${s}"`);
|
|
66
|
+
return s;
|
|
67
|
+
}
|
|
68
|
+
// Run registry login by piping the password via stdin — never via echo|pipe shell interpolation
|
|
69
|
+
function spawnWithStdin(cmd, args, stdinData) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const proc = spawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
72
|
+
let stdout = '';
|
|
73
|
+
let stderr = '';
|
|
74
|
+
proc.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
75
|
+
proc.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
76
|
+
proc.on('close', (code) => {
|
|
77
|
+
if (code === 0)
|
|
78
|
+
resolve({ stdout, stderr });
|
|
79
|
+
else
|
|
80
|
+
reject(new Error(`Command "${cmd} ${args.join(' ')}" exited with code ${code}: ${stderr || stdout}`));
|
|
81
|
+
});
|
|
82
|
+
proc.on('error', reject);
|
|
83
|
+
proc.stdin.write(stdinData);
|
|
84
|
+
proc.stdin.end();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
24
87
|
const CE_REGIONS = ['us-south', 'us-east', 'eu-de', 'eu-gb', 'jp-tok', 'jp-osa', 'au-syd', 'ca-tor', 'br-sao'];
|
|
25
88
|
// Helper function to get IAM token
|
|
26
89
|
async function getIAMToken(apiKey) {
|
|
@@ -86,7 +149,7 @@ async function resolveProjectId(nameOrId, token) {
|
|
|
86
149
|
// Create MCP server
|
|
87
150
|
const server = new Server({
|
|
88
151
|
name: 'code-engine-mcp-server',
|
|
89
|
-
version: '1.0.
|
|
152
|
+
version: '1.0.7',
|
|
90
153
|
}, {
|
|
91
154
|
capabilities: {
|
|
92
155
|
tools: {},
|
|
@@ -233,6 +296,69 @@ const containerTools = [
|
|
|
233
296
|
required: ['image'],
|
|
234
297
|
},
|
|
235
298
|
},
|
|
299
|
+
{
|
|
300
|
+
name: 'tag_container_image',
|
|
301
|
+
description: 'Tag a local container image with a new name/tag — useful to retag before pushing to ICR',
|
|
302
|
+
inputSchema: {
|
|
303
|
+
type: 'object',
|
|
304
|
+
properties: {
|
|
305
|
+
source_image: { type: 'string', description: 'Existing image name/tag (e.g. myapp:latest)' },
|
|
306
|
+
target_image: { type: 'string', description: 'New image name/tag (e.g. us.icr.io/mynamespace/myapp:v1.0.0)' },
|
|
307
|
+
runtime: { type: 'string', enum: ['docker', 'podman'], description: 'Container runtime (default: auto-detected)' },
|
|
308
|
+
},
|
|
309
|
+
required: ['source_image', 'target_image'],
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: 'remove_local_image',
|
|
314
|
+
description: 'Remove a local container image to free disk space',
|
|
315
|
+
inputSchema: {
|
|
316
|
+
type: 'object',
|
|
317
|
+
properties: {
|
|
318
|
+
image_name: { type: 'string', description: 'Image name/tag to remove (e.g. us.icr.io/mynamespace/myapp:v1.0.0)' },
|
|
319
|
+
force: { type: 'boolean', description: 'Force removal even if the image is used by a stopped container (default: false)' },
|
|
320
|
+
runtime: { type: 'string', enum: ['docker', 'podman'] },
|
|
321
|
+
},
|
|
322
|
+
required: ['image_name'],
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: 'login_to_registry',
|
|
327
|
+
description: 'Log in to a container registry (e.g. IBM Container Registry) so images can be pushed/pulled. Uses IBM Cloud IAM token for ICR or username/password for other registries.',
|
|
328
|
+
inputSchema: {
|
|
329
|
+
type: 'object',
|
|
330
|
+
properties: {
|
|
331
|
+
registry: { type: 'string', description: 'Registry hostname (default: us.icr.io for ICR)' },
|
|
332
|
+
username: { type: 'string', description: 'Username — use "iamapikey" for ICR with an IBM Cloud API key' },
|
|
333
|
+
password: { type: 'string', description: 'Password or API key. For ICR leave blank to use IBMCLOUD_API_KEY env var.' },
|
|
334
|
+
runtime: { type: 'string', enum: ['docker', 'podman'] },
|
|
335
|
+
},
|
|
336
|
+
required: ['registry'],
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: 'inspect_container_image',
|
|
341
|
+
description: 'Inspect a local container image — shows architecture, labels, environment variables, entrypoint, exposed ports, and layer count',
|
|
342
|
+
inputSchema: {
|
|
343
|
+
type: 'object',
|
|
344
|
+
properties: {
|
|
345
|
+
image_name: { type: 'string', description: 'Image name/tag to inspect' },
|
|
346
|
+
runtime: { type: 'string', enum: ['docker', 'podman'] },
|
|
347
|
+
},
|
|
348
|
+
required: ['image_name'],
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: 'prune_images',
|
|
353
|
+
description: 'Remove unused/dangling container images to reclaim disk space. By default removes only dangling images; use all=true to remove all unused images.',
|
|
354
|
+
inputSchema: {
|
|
355
|
+
type: 'object',
|
|
356
|
+
properties: {
|
|
357
|
+
all: { type: 'boolean', description: 'Remove all unused images, not just dangling ones (default: false)' },
|
|
358
|
+
runtime: { type: 'string', enum: ['docker', 'podman'] },
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
236
362
|
];
|
|
237
363
|
// Code Engine Tools
|
|
238
364
|
const codeEngineTools = [
|
|
@@ -322,6 +448,8 @@ const codeEngineTools = [
|
|
|
322
448
|
scale_cpu_limit: { type: 'string', description: 'CPU limit (e.g. 1, 0.5)' },
|
|
323
449
|
scale_memory_limit: { type: 'string', description: 'Memory limit (e.g. 4G, 2G)' },
|
|
324
450
|
env_vars: { type: 'object', description: 'Key/value environment variables' },
|
|
451
|
+
run_args: { type: 'array', items: { type: 'string' }, description: 'Arguments passed to the container entrypoint (run_arguments). Required for supergateway: ["--stdio", "npx -y <mcp-server>", "--outputTransport", "sse"]' },
|
|
452
|
+
run_commands: { type: 'array', items: { type: 'string' }, description: 'Override the container entrypoint (run_commands). Rarely needed — use run_args for passing flags.' },
|
|
325
453
|
},
|
|
326
454
|
required: ['project_id', 'name', 'image'],
|
|
327
455
|
},
|
|
@@ -340,6 +468,8 @@ const codeEngineTools = [
|
|
|
340
468
|
scale_max_instances: { type: 'number' },
|
|
341
469
|
scale_cpu_limit: { type: 'string' },
|
|
342
470
|
scale_memory_limit: { type: 'string' },
|
|
471
|
+
run_args: { type: 'array', items: { type: 'string' }, description: 'Arguments passed to the container entrypoint (run_arguments)' },
|
|
472
|
+
run_commands: { type: 'array', items: { type: 'string' }, description: 'Override the container entrypoint (run_commands)' },
|
|
343
473
|
},
|
|
344
474
|
required: ['project_id', 'app_name'],
|
|
345
475
|
},
|
|
@@ -383,15 +513,16 @@ const codeEngineTools = [
|
|
|
383
513
|
},
|
|
384
514
|
{
|
|
385
515
|
name: 'ce_get_app_logs',
|
|
386
|
-
description: 'Get logs for a specific Code Engine
|
|
516
|
+
description: 'Get logs for a Code Engine application. Retrieves logs from all running pods (or a specific instance) via the Code Engine Kubernetes API proxy.',
|
|
387
517
|
inputSchema: {
|
|
388
518
|
type: 'object',
|
|
389
519
|
properties: {
|
|
390
|
-
project_id: { type: 'string' },
|
|
391
|
-
app_name: { type: 'string' },
|
|
392
|
-
instance_name: { type: 'string', description: '
|
|
520
|
+
project_id: { type: 'string', description: 'Project ID or name' },
|
|
521
|
+
app_name: { type: 'string', description: 'Application name' },
|
|
522
|
+
instance_name: { type: 'string', description: 'Optional: specific pod/instance name to filter to (e.g. my-app-00001-deployment-abcde). If omitted, logs from all pods are returned.' },
|
|
523
|
+
tail_lines: { type: 'number', description: 'Number of log lines to return per pod (default: 100)' },
|
|
393
524
|
},
|
|
394
|
-
required: ['project_id', 'app_name'
|
|
525
|
+
required: ['project_id', 'app_name'],
|
|
395
526
|
},
|
|
396
527
|
},
|
|
397
528
|
// --- Builds ---
|
|
@@ -777,6 +908,19 @@ const codeEngineTools = [
|
|
|
777
908
|
required: ['project_id', 'secret_name', 'data'],
|
|
778
909
|
},
|
|
779
910
|
},
|
|
911
|
+
{
|
|
912
|
+
name: 'ce_refresh_icr_pull_secret',
|
|
913
|
+
description: 'Refresh a Code Engine registry pull secret for IBM Container Registry (ICR) using the server\'s own IBM Cloud API key. Automatically deletes the existing secret and recreates it with fresh credentials. Use this when deployments fail with "no_revision_ready" or "unknown" status — a common cause is a stale or expired ICR pull secret. No API key input required — the server uses its own IBMCLOUD_API_KEY.',
|
|
914
|
+
inputSchema: {
|
|
915
|
+
type: 'object',
|
|
916
|
+
properties: {
|
|
917
|
+
project_id: { type: 'string' },
|
|
918
|
+
secret_name: { type: 'string', description: 'Name of the registry secret to refresh (default: icr-pull-secret)' },
|
|
919
|
+
icr_host: { type: 'string', description: 'ICR registry hostname (default: us.icr.io)' },
|
|
920
|
+
},
|
|
921
|
+
required: ['project_id'],
|
|
922
|
+
},
|
|
923
|
+
},
|
|
780
924
|
{
|
|
781
925
|
name: 'ce_renew_tls_secret_from_pem',
|
|
782
926
|
description: 'Renew an existing TLS secret in Code Engine by reading updated PEM files from disk. Use this when a Let\'s Encrypt cert has been renewed (every 90 days). Updates the secret in-place — no need to delete and recreate or update domain mappings.',
|
|
@@ -867,31 +1011,574 @@ const codeEngineTools = [
|
|
|
867
1011
|
inputSchema: {
|
|
868
1012
|
type: 'object',
|
|
869
1013
|
properties: {
|
|
870
|
-
project_id_or_name: { type: 'string', description: 'Code Engine project name or ID' },
|
|
871
|
-
app_name: { type: 'string', description: 'The Code Engine app to map the custom domain to' },
|
|
872
|
-
domain_name: { type: 'string', description: 'Your custom domain (e.g. myapp.example.com)' },
|
|
873
|
-
tls_secret_name: { type: 'string', description: 'Name to give the TLS secret in Code Engine (e.g. myapp-tls). Must be unique in the project.' },
|
|
874
|
-
cert_pem_path: { type: 'string', description: 'Path to the certificate chain PEM file — typically ~/certbot/config/live/<domain>/fullchain.pem' },
|
|
875
|
-
key_pem_path: { type: 'string', description: 'Path to the private key PEM file — typically ~/certbot/config/live/<domain>/privkey.pem' },
|
|
1014
|
+
project_id_or_name: { type: 'string', description: 'Code Engine project name or ID' },
|
|
1015
|
+
app_name: { type: 'string', description: 'The Code Engine app to map the custom domain to' },
|
|
1016
|
+
domain_name: { type: 'string', description: 'Your custom domain (e.g. myapp.example.com)' },
|
|
1017
|
+
tls_secret_name: { type: 'string', description: 'Name to give the TLS secret in Code Engine (e.g. myapp-tls). Must be unique in the project.' },
|
|
1018
|
+
cert_pem_path: { type: 'string', description: 'Path to the certificate chain PEM file — typically ~/certbot/config/live/<domain>/fullchain.pem' },
|
|
1019
|
+
key_pem_path: { type: 'string', description: 'Path to the private key PEM file — typically ~/certbot/config/live/<domain>/privkey.pem' },
|
|
1020
|
+
},
|
|
1021
|
+
required: ['project_id_or_name', 'app_name', 'domain_name', 'tls_secret_name', 'cert_pem_path', 'key_pem_path'],
|
|
1022
|
+
},
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
name: 'proc_build_run_and_deploy',
|
|
1026
|
+
description: 'PROCEDURE: Code Engine source-to-image build + deploy in one step — starts a build run from an existing build configuration, polls until it succeeds, then creates or updates the application with the new image, and waits for it to become ready. Returns the public URL. The build configuration must already exist (use ce_create_build to set one up).',
|
|
1027
|
+
inputSchema: {
|
|
1028
|
+
type: 'object',
|
|
1029
|
+
properties: {
|
|
1030
|
+
project_id_or_name: { type: 'string', description: 'Code Engine project name or ID' },
|
|
1031
|
+
build_name: { type: 'string', description: 'Name of the existing Code Engine build configuration to run (use ce_list_builds to find it)' },
|
|
1032
|
+
app_name: { type: 'string', description: 'Application to create or update after the build succeeds' },
|
|
1033
|
+
image_secret: { type: 'string', description: 'Registry pull secret name in Code Engine (e.g. icr-pull-secret)' },
|
|
1034
|
+
port: { type: 'number', description: 'Container port the app listens on (default 8080)' },
|
|
1035
|
+
build_timeout_seconds: { type: 'number', description: 'Max seconds to wait for the build to finish (default 600)' },
|
|
1036
|
+
deploy_timeout_seconds: { type: 'number', description: 'Max seconds to wait for the app to become ready (default 180)' },
|
|
1037
|
+
},
|
|
1038
|
+
required: ['project_id_or_name', 'build_name', 'app_name', 'image_secret'],
|
|
1039
|
+
},
|
|
1040
|
+
},
|
|
1041
|
+
// ─── App Revisions ────────────────────────────────────────────────────────────
|
|
1042
|
+
{
|
|
1043
|
+
name: 'ce_list_app_revisions',
|
|
1044
|
+
description: 'List all revisions (deployed versions) of a Code Engine application',
|
|
1045
|
+
inputSchema: {
|
|
1046
|
+
type: 'object',
|
|
1047
|
+
properties: {
|
|
1048
|
+
project_id: { type: 'string' },
|
|
1049
|
+
app_name: { type: 'string' },
|
|
1050
|
+
},
|
|
1051
|
+
required: ['project_id', 'app_name'],
|
|
1052
|
+
},
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
name: 'ce_get_app_revision',
|
|
1056
|
+
description: 'Get details of a specific revision of a Code Engine application',
|
|
1057
|
+
inputSchema: {
|
|
1058
|
+
type: 'object',
|
|
1059
|
+
properties: {
|
|
1060
|
+
project_id: { type: 'string' },
|
|
1061
|
+
app_name: { type: 'string' },
|
|
1062
|
+
revision_name: { type: 'string' },
|
|
1063
|
+
},
|
|
1064
|
+
required: ['project_id', 'app_name', 'revision_name'],
|
|
1065
|
+
},
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
name: 'ce_delete_app_revision',
|
|
1069
|
+
description: 'Delete a specific revision of a Code Engine application',
|
|
1070
|
+
inputSchema: {
|
|
1071
|
+
type: 'object',
|
|
1072
|
+
properties: {
|
|
1073
|
+
project_id: { type: 'string' },
|
|
1074
|
+
app_name: { type: 'string' },
|
|
1075
|
+
revision_name: { type: 'string' },
|
|
1076
|
+
},
|
|
1077
|
+
required: ['project_id', 'app_name', 'revision_name'],
|
|
1078
|
+
},
|
|
1079
|
+
},
|
|
1080
|
+
// ─── Update operations ────────────────────────────────────────────────────────
|
|
1081
|
+
{
|
|
1082
|
+
name: 'ce_update_job',
|
|
1083
|
+
description: 'Update an existing Code Engine job definition (PATCH)',
|
|
1084
|
+
inputSchema: {
|
|
1085
|
+
type: 'object',
|
|
1086
|
+
properties: {
|
|
1087
|
+
project_id: { type: 'string' },
|
|
1088
|
+
job_name: { type: 'string' },
|
|
1089
|
+
image: { type: 'string', description: 'New container image reference' },
|
|
1090
|
+
image_secret: { type: 'string' },
|
|
1091
|
+
scale_array_spec: { type: 'string', description: 'Array indices to run (e.g. "0-9")' },
|
|
1092
|
+
scale_cpu_limit: { type: 'string' },
|
|
1093
|
+
scale_memory_limit: { type: 'string' },
|
|
1094
|
+
env_vars: { type: 'object', description: 'Key/value environment variables' },
|
|
1095
|
+
},
|
|
1096
|
+
required: ['project_id', 'job_name'],
|
|
1097
|
+
},
|
|
1098
|
+
},
|
|
1099
|
+
{
|
|
1100
|
+
name: 'ce_update_build',
|
|
1101
|
+
description: 'Update an existing Code Engine build configuration (PATCH)',
|
|
1102
|
+
inputSchema: {
|
|
1103
|
+
type: 'object',
|
|
1104
|
+
properties: {
|
|
1105
|
+
project_id: { type: 'string' },
|
|
1106
|
+
build_name: { type: 'string' },
|
|
1107
|
+
output_image: { type: 'string' },
|
|
1108
|
+
output_secret: { type: 'string' },
|
|
1109
|
+
source_url: { type: 'string' },
|
|
1110
|
+
source_revision: { type: 'string' },
|
|
1111
|
+
strategy_size: { type: 'string', enum: ['small', 'medium', 'large', 'xlarge', 'xxlarge'] },
|
|
1112
|
+
strategy_spec_file: { type: 'string', description: 'Path to Dockerfile or buildpacks config (default: Dockerfile)' },
|
|
1113
|
+
},
|
|
1114
|
+
required: ['project_id', 'build_name'],
|
|
1115
|
+
},
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
name: 'ce_update_config_map',
|
|
1119
|
+
description: 'Update an existing Code Engine configmap (PATCH)',
|
|
1120
|
+
inputSchema: {
|
|
1121
|
+
type: 'object',
|
|
1122
|
+
properties: {
|
|
1123
|
+
project_id: { type: 'string' },
|
|
1124
|
+
config_map_name: { type: 'string' },
|
|
1125
|
+
data: { type: 'object', description: 'New key/value data to replace configmap contents' },
|
|
1126
|
+
},
|
|
1127
|
+
required: ['project_id', 'config_map_name', 'data'],
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
name: 'ce_update_domain_mapping',
|
|
1132
|
+
description: 'Update an existing Code Engine custom domain mapping (PATCH) — e.g. change the target app',
|
|
1133
|
+
inputSchema: {
|
|
1134
|
+
type: 'object',
|
|
1135
|
+
properties: {
|
|
1136
|
+
project_id: { type: 'string' },
|
|
1137
|
+
domain_name: { type: 'string' },
|
|
1138
|
+
app_name: { type: 'string', description: 'New application to route traffic to' },
|
|
1139
|
+
tls_secret: { type: 'string', description: 'New TLS secret name (optional)' },
|
|
1140
|
+
},
|
|
1141
|
+
required: ['project_id', 'domain_name'],
|
|
1142
|
+
},
|
|
1143
|
+
},
|
|
1144
|
+
// ─── Functions ────────────────────────────────────────────────────────────────
|
|
1145
|
+
{
|
|
1146
|
+
name: 'ce_list_function_runtimes',
|
|
1147
|
+
description: 'List all available function runtimes supported by IBM Code Engine (no project required)',
|
|
1148
|
+
inputSchema: {
|
|
1149
|
+
type: 'object',
|
|
1150
|
+
properties: {
|
|
1151
|
+
region: { type: 'string', description: 'CE region (e.g. us-south). Defaults to us-south.' },
|
|
1152
|
+
},
|
|
1153
|
+
required: [],
|
|
1154
|
+
},
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
name: 'ce_list_functions',
|
|
1158
|
+
description: 'List all serverless functions in a Code Engine project',
|
|
1159
|
+
inputSchema: {
|
|
1160
|
+
type: 'object',
|
|
1161
|
+
properties: {
|
|
1162
|
+
project_id: { type: 'string' },
|
|
1163
|
+
},
|
|
1164
|
+
required: ['project_id'],
|
|
1165
|
+
},
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
name: 'ce_get_function',
|
|
1169
|
+
description: 'Get details of a specific serverless function in a Code Engine project',
|
|
1170
|
+
inputSchema: {
|
|
1171
|
+
type: 'object',
|
|
1172
|
+
properties: {
|
|
1173
|
+
project_id: { type: 'string' },
|
|
1174
|
+
function_name: { type: 'string' },
|
|
1175
|
+
},
|
|
1176
|
+
required: ['project_id', 'function_name'],
|
|
1177
|
+
},
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
name: 'ce_create_function',
|
|
1181
|
+
description: 'Create a serverless function in a Code Engine project',
|
|
1182
|
+
inputSchema: {
|
|
1183
|
+
type: 'object',
|
|
1184
|
+
properties: {
|
|
1185
|
+
project_id: { type: 'string' },
|
|
1186
|
+
name: { type: 'string' },
|
|
1187
|
+
runtime: { type: 'string', description: 'Runtime identifier (e.g. nodejs-20, python-3.11). Use ce_list_function_runtimes to see all options.' },
|
|
1188
|
+
code_reference: { type: 'string', description: 'Inline code as a data URL or reference to a code bundle image' },
|
|
1189
|
+
code_main: { type: 'string', description: 'Entry point function name (default: main)' },
|
|
1190
|
+
scale_concurrency: { type: 'number', description: 'Max requests per instance (default: 1)' },
|
|
1191
|
+
scale_cpu_limit: { type: 'string', description: 'CPU limit (default: 1)' },
|
|
1192
|
+
scale_memory_limit: { type: 'string', description: 'Memory limit (default: 4G)' },
|
|
1193
|
+
env_vars: { type: 'object', description: 'Key/value environment variables' },
|
|
1194
|
+
},
|
|
1195
|
+
required: ['project_id', 'name', 'runtime', 'code_reference'],
|
|
1196
|
+
},
|
|
1197
|
+
},
|
|
1198
|
+
{
|
|
1199
|
+
name: 'ce_update_function',
|
|
1200
|
+
description: 'Update an existing Code Engine serverless function (PATCH)',
|
|
1201
|
+
inputSchema: {
|
|
1202
|
+
type: 'object',
|
|
1203
|
+
properties: {
|
|
1204
|
+
project_id: { type: 'string' },
|
|
1205
|
+
function_name: { type: 'string' },
|
|
1206
|
+
runtime: { type: 'string' },
|
|
1207
|
+
code_reference: { type: 'string' },
|
|
1208
|
+
code_main: { type: 'string' },
|
|
1209
|
+
scale_concurrency: { type: 'number' },
|
|
1210
|
+
scale_cpu_limit: { type: 'string' },
|
|
1211
|
+
scale_memory_limit: { type: 'string' },
|
|
1212
|
+
env_vars: { type: 'object' },
|
|
1213
|
+
},
|
|
1214
|
+
required: ['project_id', 'function_name'],
|
|
1215
|
+
},
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
name: 'ce_delete_function',
|
|
1219
|
+
description: 'Delete a serverless function from a Code Engine project',
|
|
1220
|
+
inputSchema: {
|
|
1221
|
+
type: 'object',
|
|
1222
|
+
properties: {
|
|
1223
|
+
project_id: { type: 'string' },
|
|
1224
|
+
function_name: { type: 'string' },
|
|
1225
|
+
},
|
|
1226
|
+
required: ['project_id', 'function_name'],
|
|
1227
|
+
},
|
|
1228
|
+
},
|
|
1229
|
+
// ─── Service Bindings ─────────────────────────────────────────────────────────
|
|
1230
|
+
{
|
|
1231
|
+
name: 'ce_list_bindings',
|
|
1232
|
+
description: 'List all service bindings in a Code Engine project (bindings connect IBM Cloud services to apps/jobs)',
|
|
1233
|
+
inputSchema: {
|
|
1234
|
+
type: 'object',
|
|
1235
|
+
properties: {
|
|
1236
|
+
project_id: { type: 'string' },
|
|
1237
|
+
},
|
|
1238
|
+
required: ['project_id'],
|
|
1239
|
+
},
|
|
1240
|
+
},
|
|
1241
|
+
{
|
|
1242
|
+
name: 'ce_create_binding',
|
|
1243
|
+
description: 'Create a service binding to connect an IBM Cloud service instance to a Code Engine component',
|
|
1244
|
+
inputSchema: {
|
|
1245
|
+
type: 'object',
|
|
1246
|
+
properties: {
|
|
1247
|
+
project_id: { type: 'string' },
|
|
1248
|
+
component_name: { type: 'string', description: 'Name of the CE app or job to bind the service to' },
|
|
1249
|
+
component_resource_type: { type: 'string', enum: ['app_v2', 'job_v2', 'function_v2'], description: 'Resource type of the component' },
|
|
1250
|
+
secret_name: { type: 'string', description: 'Name of the operator secret referencing the IBM Cloud service instance' },
|
|
1251
|
+
prefix: { type: 'string', description: 'Prefix for environment variable names injected into the component (optional)' },
|
|
1252
|
+
},
|
|
1253
|
+
required: ['project_id', 'component_name', 'component_resource_type', 'secret_name'],
|
|
1254
|
+
},
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
name: 'ce_get_binding',
|
|
1258
|
+
description: 'Get details of a specific service binding in a Code Engine project',
|
|
1259
|
+
inputSchema: {
|
|
1260
|
+
type: 'object',
|
|
1261
|
+
properties: {
|
|
1262
|
+
project_id: { type: 'string' },
|
|
1263
|
+
binding_id: { type: 'string', description: 'The binding ID (use ce_list_bindings to find it)' },
|
|
1264
|
+
},
|
|
1265
|
+
required: ['project_id', 'binding_id'],
|
|
1266
|
+
},
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
name: 'ce_delete_binding',
|
|
1270
|
+
description: 'Delete a service binding from a Code Engine project',
|
|
1271
|
+
inputSchema: {
|
|
1272
|
+
type: 'object',
|
|
1273
|
+
properties: {
|
|
1274
|
+
project_id: { type: 'string' },
|
|
1275
|
+
binding_id: { type: 'string' },
|
|
1276
|
+
},
|
|
1277
|
+
required: ['project_id', 'binding_id'],
|
|
1278
|
+
},
|
|
1279
|
+
},
|
|
1280
|
+
// ─── Project extras ────────────────────────────────────────────────────────────
|
|
1281
|
+
{
|
|
1282
|
+
name: 'ce_get_project_status',
|
|
1283
|
+
description: 'Get the status details of a Code Engine project (readiness, enabled components, etc.)',
|
|
1284
|
+
inputSchema: {
|
|
1285
|
+
type: 'object',
|
|
1286
|
+
properties: {
|
|
1287
|
+
project_id: { type: 'string' },
|
|
1288
|
+
},
|
|
1289
|
+
required: ['project_id'],
|
|
1290
|
+
},
|
|
1291
|
+
},
|
|
1292
|
+
{
|
|
1293
|
+
name: 'ce_list_egress_ips',
|
|
1294
|
+
description: 'List the public egress IP addresses used by a Code Engine project (useful for allowlisting in firewalls)',
|
|
1295
|
+
inputSchema: {
|
|
1296
|
+
type: 'object',
|
|
1297
|
+
properties: {
|
|
1298
|
+
project_id: { type: 'string' },
|
|
1299
|
+
},
|
|
1300
|
+
required: ['project_id'],
|
|
1301
|
+
},
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
name: 'ce_list_allowed_outbound_destinations',
|
|
1305
|
+
description: 'List allowed outbound destinations configured for a Code Engine project',
|
|
1306
|
+
inputSchema: {
|
|
1307
|
+
type: 'object',
|
|
1308
|
+
properties: {
|
|
1309
|
+
project_id: { type: 'string' },
|
|
1310
|
+
},
|
|
1311
|
+
required: ['project_id'],
|
|
1312
|
+
},
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
name: 'ce_create_allowed_outbound_destination',
|
|
1316
|
+
description: 'Create an allowed outbound destination (CIDR block or domain) for a Code Engine project',
|
|
1317
|
+
inputSchema: {
|
|
1318
|
+
type: 'object',
|
|
1319
|
+
properties: {
|
|
1320
|
+
project_id: { type: 'string' },
|
|
1321
|
+
name: { type: 'string', description: 'Name for this allowed outbound destination rule' },
|
|
1322
|
+
type: { type: 'string', enum: ['cidr_block', 'fqdn'], description: 'Type of destination: CIDR block or fully-qualified domain name' },
|
|
1323
|
+
cidr_block: { type: 'string', description: 'CIDR block (required when type=cidr_block, e.g. 1.2.3.4/24)' },
|
|
1324
|
+
fqdn: { type: 'string', description: 'Fully-qualified domain name (required when type=fqdn, e.g. example.com)' },
|
|
1325
|
+
},
|
|
1326
|
+
required: ['project_id', 'name', 'type'],
|
|
1327
|
+
},
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
name: 'ce_get_allowed_outbound_destination',
|
|
1331
|
+
description: 'Get details of a specific allowed outbound destination in a Code Engine project',
|
|
1332
|
+
inputSchema: {
|
|
1333
|
+
type: 'object',
|
|
1334
|
+
properties: {
|
|
1335
|
+
project_id: { type: 'string' },
|
|
1336
|
+
destination_name: { type: 'string' },
|
|
1337
|
+
},
|
|
1338
|
+
required: ['project_id', 'destination_name'],
|
|
1339
|
+
},
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
name: 'ce_update_allowed_outbound_destination',
|
|
1343
|
+
description: 'Update an existing allowed outbound destination in a Code Engine project (PATCH)',
|
|
1344
|
+
inputSchema: {
|
|
1345
|
+
type: 'object',
|
|
1346
|
+
properties: {
|
|
1347
|
+
project_id: { type: 'string' },
|
|
1348
|
+
destination_name: { type: 'string' },
|
|
1349
|
+
cidr_block: { type: 'string', description: 'New CIDR block value' },
|
|
1350
|
+
fqdn: { type: 'string', description: 'New FQDN value' },
|
|
1351
|
+
},
|
|
1352
|
+
required: ['project_id', 'destination_name'],
|
|
1353
|
+
},
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
name: 'ce_delete_allowed_outbound_destination',
|
|
1357
|
+
description: 'Delete an allowed outbound destination from a Code Engine project',
|
|
1358
|
+
inputSchema: {
|
|
1359
|
+
type: 'object',
|
|
1360
|
+
properties: {
|
|
1361
|
+
project_id: { type: 'string' },
|
|
1362
|
+
destination_name: { type: 'string' },
|
|
1363
|
+
},
|
|
1364
|
+
required: ['project_id', 'destination_name'],
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
// ─── Persistent Data Stores ───────────────────────────────────────────────────
|
|
1368
|
+
{
|
|
1369
|
+
name: 'ce_list_persistent_data_stores',
|
|
1370
|
+
description: 'List all persistent data stores (cloud object storage bindings) in a Code Engine project',
|
|
1371
|
+
inputSchema: {
|
|
1372
|
+
type: 'object',
|
|
1373
|
+
properties: {
|
|
1374
|
+
project_id: { type: 'string' },
|
|
1375
|
+
},
|
|
1376
|
+
required: ['project_id'],
|
|
1377
|
+
},
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
name: 'ce_create_persistent_data_store',
|
|
1381
|
+
description: 'Create a persistent data store binding (COS bucket) in a Code Engine project',
|
|
1382
|
+
inputSchema: {
|
|
1383
|
+
type: 'object',
|
|
1384
|
+
properties: {
|
|
1385
|
+
project_id: { type: 'string' },
|
|
1386
|
+
name: { type: 'string', description: 'Name for this persistent data store' },
|
|
1387
|
+
secret_name: { type: 'string', description: 'Name of the secret containing COS credentials' },
|
|
1388
|
+
bucket_name: { type: 'string', description: 'COS bucket name to bind' },
|
|
1389
|
+
endpoint: { type: 'string', description: 'COS endpoint URL (e.g. https://s3.us-south.cloud-object-storage.appdomain.cloud)' },
|
|
1390
|
+
},
|
|
1391
|
+
required: ['project_id', 'name', 'secret_name', 'bucket_name'],
|
|
1392
|
+
},
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
name: 'ce_get_persistent_data_store',
|
|
1396
|
+
description: 'Get details of a specific persistent data store in a Code Engine project',
|
|
1397
|
+
inputSchema: {
|
|
1398
|
+
type: 'object',
|
|
1399
|
+
properties: {
|
|
1400
|
+
project_id: { type: 'string' },
|
|
1401
|
+
data_store_name: { type: 'string' },
|
|
1402
|
+
},
|
|
1403
|
+
required: ['project_id', 'data_store_name'],
|
|
1404
|
+
},
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
name: 'ce_delete_persistent_data_store',
|
|
1408
|
+
description: 'Delete a persistent data store from a Code Engine project',
|
|
1409
|
+
inputSchema: {
|
|
1410
|
+
type: 'object',
|
|
1411
|
+
properties: {
|
|
1412
|
+
project_id: { type: 'string' },
|
|
1413
|
+
data_store_name: { type: 'string' },
|
|
1414
|
+
},
|
|
1415
|
+
required: ['project_id', 'data_store_name'],
|
|
1416
|
+
},
|
|
1417
|
+
},
|
|
1418
|
+
// ─── Fleets ────────────────────────────────────────────────────────────────────
|
|
1419
|
+
{
|
|
1420
|
+
name: 'ce_list_fleets',
|
|
1421
|
+
description: 'List all fleets in a Code Engine project',
|
|
1422
|
+
inputSchema: {
|
|
1423
|
+
type: 'object',
|
|
1424
|
+
properties: {
|
|
1425
|
+
project_id: { type: 'string' },
|
|
1426
|
+
},
|
|
1427
|
+
required: ['project_id'],
|
|
1428
|
+
},
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
name: 'ce_create_fleet',
|
|
1432
|
+
description: 'Create a fleet in a Code Engine project',
|
|
1433
|
+
inputSchema: {
|
|
1434
|
+
type: 'object',
|
|
1435
|
+
properties: {
|
|
1436
|
+
project_id: { type: 'string' },
|
|
1437
|
+
name: { type: 'string' },
|
|
1438
|
+
image: { type: 'string', description: 'Container image reference' },
|
|
1439
|
+
image_secret: { type: 'string' },
|
|
1440
|
+
scale_cpu_limit: { type: 'string' },
|
|
1441
|
+
scale_memory_limit: { type: 'string' },
|
|
1442
|
+
env_vars: { type: 'object' },
|
|
1443
|
+
},
|
|
1444
|
+
required: ['project_id', 'name', 'image'],
|
|
1445
|
+
},
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
name: 'ce_get_fleet',
|
|
1449
|
+
description: 'Get details of a specific fleet in a Code Engine project',
|
|
1450
|
+
inputSchema: {
|
|
1451
|
+
type: 'object',
|
|
1452
|
+
properties: {
|
|
1453
|
+
project_id: { type: 'string' },
|
|
1454
|
+
fleet_id: { type: 'string' },
|
|
1455
|
+
},
|
|
1456
|
+
required: ['project_id', 'fleet_id'],
|
|
1457
|
+
},
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
name: 'ce_delete_fleet',
|
|
1461
|
+
description: 'Delete a fleet from a Code Engine project',
|
|
1462
|
+
inputSchema: {
|
|
1463
|
+
type: 'object',
|
|
1464
|
+
properties: {
|
|
1465
|
+
project_id: { type: 'string' },
|
|
1466
|
+
fleet_id: { type: 'string' },
|
|
1467
|
+
},
|
|
1468
|
+
required: ['project_id', 'fleet_id'],
|
|
1469
|
+
},
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
name: 'ce_cancel_fleet',
|
|
1473
|
+
description: 'Cancel a running fleet in a Code Engine project',
|
|
1474
|
+
inputSchema: {
|
|
1475
|
+
type: 'object',
|
|
1476
|
+
properties: {
|
|
1477
|
+
project_id: { type: 'string' },
|
|
1478
|
+
fleet_id: { type: 'string' },
|
|
1479
|
+
},
|
|
1480
|
+
required: ['project_id', 'fleet_id'],
|
|
1481
|
+
},
|
|
1482
|
+
},
|
|
1483
|
+
// ─── Fleet Tasks ──────────────────────────────────────────────────────────────
|
|
1484
|
+
{
|
|
1485
|
+
name: 'ce_list_fleet_tasks',
|
|
1486
|
+
description: 'List all tasks for a fleet in a Code Engine project',
|
|
1487
|
+
inputSchema: {
|
|
1488
|
+
type: 'object',
|
|
1489
|
+
properties: {
|
|
1490
|
+
project_id: { type: 'string' },
|
|
1491
|
+
fleet_id: { type: 'string' },
|
|
1492
|
+
},
|
|
1493
|
+
required: ['project_id', 'fleet_id'],
|
|
1494
|
+
},
|
|
1495
|
+
},
|
|
1496
|
+
{
|
|
1497
|
+
name: 'ce_get_fleet_task',
|
|
1498
|
+
description: 'Get details of a specific task within a fleet',
|
|
1499
|
+
inputSchema: {
|
|
1500
|
+
type: 'object',
|
|
1501
|
+
properties: {
|
|
1502
|
+
project_id: { type: 'string' },
|
|
1503
|
+
fleet_id: { type: 'string' },
|
|
1504
|
+
task_id: { type: 'string' },
|
|
1505
|
+
},
|
|
1506
|
+
required: ['project_id', 'fleet_id', 'task_id'],
|
|
1507
|
+
},
|
|
1508
|
+
},
|
|
1509
|
+
// ─── Fleet Workers ────────────────────────────────────────────────────────────
|
|
1510
|
+
{
|
|
1511
|
+
name: 'ce_list_fleet_workers',
|
|
1512
|
+
description: 'List all workers for a fleet in a Code Engine project',
|
|
1513
|
+
inputSchema: {
|
|
1514
|
+
type: 'object',
|
|
1515
|
+
properties: {
|
|
1516
|
+
project_id: { type: 'string' },
|
|
1517
|
+
fleet_id: { type: 'string' },
|
|
1518
|
+
},
|
|
1519
|
+
required: ['project_id', 'fleet_id'],
|
|
1520
|
+
},
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
name: 'ce_get_fleet_worker',
|
|
1524
|
+
description: 'Get details of a specific worker within a fleet',
|
|
1525
|
+
inputSchema: {
|
|
1526
|
+
type: 'object',
|
|
1527
|
+
properties: {
|
|
1528
|
+
project_id: { type: 'string' },
|
|
1529
|
+
fleet_id: { type: 'string' },
|
|
1530
|
+
worker_id: { type: 'string' },
|
|
1531
|
+
},
|
|
1532
|
+
required: ['project_id', 'fleet_id', 'worker_id'],
|
|
1533
|
+
},
|
|
1534
|
+
},
|
|
1535
|
+
// ─── Subnet Pools ─────────────────────────────────────────────────────────────
|
|
1536
|
+
{
|
|
1537
|
+
name: 'ce_list_subnet_pools',
|
|
1538
|
+
description: 'List all subnet pools in a Code Engine project',
|
|
1539
|
+
inputSchema: {
|
|
1540
|
+
type: 'object',
|
|
1541
|
+
properties: {
|
|
1542
|
+
project_id: { type: 'string' },
|
|
1543
|
+
},
|
|
1544
|
+
required: ['project_id'],
|
|
1545
|
+
},
|
|
1546
|
+
},
|
|
1547
|
+
{
|
|
1548
|
+
name: 'ce_create_subnet_pool',
|
|
1549
|
+
description: 'Create a subnet pool in a Code Engine project',
|
|
1550
|
+
inputSchema: {
|
|
1551
|
+
type: 'object',
|
|
1552
|
+
properties: {
|
|
1553
|
+
project_id: { type: 'string' },
|
|
1554
|
+
name: { type: 'string' },
|
|
1555
|
+
cidr: { type: 'string', description: 'CIDR block for the subnet pool (e.g. 10.0.0.0/24)' },
|
|
1556
|
+
},
|
|
1557
|
+
required: ['project_id', 'name', 'cidr'],
|
|
1558
|
+
},
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
name: 'ce_get_subnet_pool',
|
|
1562
|
+
description: 'Get details of a specific subnet pool in a Code Engine project',
|
|
1563
|
+
inputSchema: {
|
|
1564
|
+
type: 'object',
|
|
1565
|
+
properties: {
|
|
1566
|
+
project_id: { type: 'string' },
|
|
1567
|
+
subnet_pool_id: { type: 'string' },
|
|
876
1568
|
},
|
|
877
|
-
required: ['
|
|
1569
|
+
required: ['project_id', 'subnet_pool_id'],
|
|
878
1570
|
},
|
|
879
1571
|
},
|
|
880
1572
|
{
|
|
881
|
-
name: '
|
|
882
|
-
description: '
|
|
1573
|
+
name: 'ce_delete_subnet_pool',
|
|
1574
|
+
description: 'Delete a subnet pool from a Code Engine project',
|
|
883
1575
|
inputSchema: {
|
|
884
1576
|
type: 'object',
|
|
885
1577
|
properties: {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
app_name: { type: 'string', description: 'Application to create or update after the build succeeds' },
|
|
889
|
-
image_secret: { type: 'string', description: 'Registry pull secret name in Code Engine (e.g. icr-pull-secret)' },
|
|
890
|
-
port: { type: 'number', description: 'Container port the app listens on (default 8080)' },
|
|
891
|
-
build_timeout_seconds: { type: 'number', description: 'Max seconds to wait for the build to finish (default 600)' },
|
|
892
|
-
deploy_timeout_seconds: { type: 'number', description: 'Max seconds to wait for the app to become ready (default 180)' },
|
|
1578
|
+
project_id: { type: 'string' },
|
|
1579
|
+
subnet_pool_id: { type: 'string' },
|
|
893
1580
|
},
|
|
894
|
-
required: ['
|
|
1581
|
+
required: ['project_id', 'subnet_pool_id'],
|
|
895
1582
|
},
|
|
896
1583
|
},
|
|
897
1584
|
];
|
|
@@ -1013,8 +1700,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1013
1700
|
return { content: [{ type: 'text', text: JSON.stringify({ valid, summary, errors, warnings, info, dockerfile: dfPath }, null, 2) }], ...(valid ? {} : { isError: true }) };
|
|
1014
1701
|
}
|
|
1015
1702
|
case 'detect_container_runtime': {
|
|
1016
|
-
const { stdout: dockerVersion } = await
|
|
1017
|
-
const { stdout: podmanVersion } = await
|
|
1703
|
+
const { stdout: dockerVersion } = await execFileAsync('docker', ['--version']).catch(() => ({ stdout: '' }));
|
|
1704
|
+
const { stdout: podmanVersion } = await execFileAsync('podman', ['--version']).catch(() => ({ stdout: '' }));
|
|
1018
1705
|
return {
|
|
1019
1706
|
content: [
|
|
1020
1707
|
{
|
|
@@ -1029,9 +1716,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1029
1716
|
};
|
|
1030
1717
|
}
|
|
1031
1718
|
case 'build_container_image': {
|
|
1032
|
-
const runtime = args.runtime || 'docker';
|
|
1033
|
-
const
|
|
1034
|
-
const
|
|
1719
|
+
const runtime = validateRuntime(args.runtime || 'docker');
|
|
1720
|
+
const imageName = validateImageName(args.image_name);
|
|
1721
|
+
const buildArgs = ['build', '-t', imageName];
|
|
1722
|
+
if (args.dockerfile_path)
|
|
1723
|
+
buildArgs.push('-f', args.dockerfile_path);
|
|
1724
|
+
buildArgs.push(args.context_path || '.');
|
|
1725
|
+
const { stdout, stderr } = await execFileAsync(runtime, buildArgs);
|
|
1035
1726
|
// Container runtimes write build progress to stderr — label it clearly
|
|
1036
1727
|
const build_output = [stdout, stderr].filter(Boolean).join('\n').trim();
|
|
1037
1728
|
return {
|
|
@@ -1040,7 +1731,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1040
1731
|
type: 'text',
|
|
1041
1732
|
text: JSON.stringify({
|
|
1042
1733
|
success: true,
|
|
1043
|
-
command:
|
|
1734
|
+
command: `${runtime} ${buildArgs.join(' ')}`,
|
|
1044
1735
|
build_output,
|
|
1045
1736
|
}, null, 2),
|
|
1046
1737
|
},
|
|
@@ -1048,16 +1739,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1048
1739
|
};
|
|
1049
1740
|
}
|
|
1050
1741
|
case 'push_container_image': {
|
|
1051
|
-
const runtime = args.runtime || 'docker';
|
|
1052
|
-
const
|
|
1053
|
-
const { stdout, stderr } = await
|
|
1742
|
+
const runtime = validateRuntime(args.runtime || 'docker');
|
|
1743
|
+
const imageName = validateImageName(args.image_name);
|
|
1744
|
+
const { stdout, stderr } = await execFileAsync(runtime, ['push', imageName]);
|
|
1054
1745
|
return {
|
|
1055
1746
|
content: [
|
|
1056
1747
|
{
|
|
1057
1748
|
type: 'text',
|
|
1058
1749
|
text: JSON.stringify({
|
|
1059
1750
|
success: true,
|
|
1060
|
-
command:
|
|
1751
|
+
command: `${runtime} push ${imageName}`,
|
|
1061
1752
|
output: stdout,
|
|
1062
1753
|
error: stderr
|
|
1063
1754
|
}, null, 2),
|
|
@@ -1066,9 +1757,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1066
1757
|
};
|
|
1067
1758
|
}
|
|
1068
1759
|
case 'list_local_images': {
|
|
1069
|
-
const runtime = args.runtime || 'docker';
|
|
1070
|
-
const
|
|
1071
|
-
const { stdout } = await execAsync(cmd);
|
|
1760
|
+
const runtime = validateRuntime(args.runtime || 'docker');
|
|
1761
|
+
const { stdout } = await execFileAsync(runtime, ['images', '--format', '{{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.Size}}']);
|
|
1072
1762
|
return {
|
|
1073
1763
|
content: [
|
|
1074
1764
|
{
|
|
@@ -1079,25 +1769,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1079
1769
|
};
|
|
1080
1770
|
}
|
|
1081
1771
|
case 'test_container_locally': {
|
|
1082
|
-
const runtime = args.runtime || 'docker';
|
|
1083
|
-
|
|
1772
|
+
const runtime = validateRuntime(args.runtime || 'docker');
|
|
1773
|
+
const runArgs = ['run', '-d'];
|
|
1084
1774
|
if (args.port_mapping) {
|
|
1085
|
-
|
|
1775
|
+
runArgs.push('-p', validatePortMapping(args.port_mapping));
|
|
1086
1776
|
}
|
|
1087
1777
|
if (args.env_vars) {
|
|
1088
|
-
Object.entries(args.env_vars)
|
|
1089
|
-
|
|
1090
|
-
|
|
1778
|
+
for (const [key, value] of Object.entries(args.env_vars)) {
|
|
1779
|
+
// Pass as a single arg: execFile does not invoke a shell so KEY=VALUE is safe
|
|
1780
|
+
runArgs.push('-e', `${validateEnvKey(key)}=${value}`);
|
|
1781
|
+
}
|
|
1091
1782
|
}
|
|
1092
|
-
|
|
1093
|
-
const { stdout } = await
|
|
1783
|
+
runArgs.push(validateImageName(args.image_name));
|
|
1784
|
+
const { stdout } = await execFileAsync(runtime, runArgs);
|
|
1094
1785
|
return {
|
|
1095
1786
|
content: [
|
|
1096
1787
|
{
|
|
1097
1788
|
type: 'text',
|
|
1098
1789
|
text: JSON.stringify({
|
|
1099
1790
|
container_id: stdout.trim(),
|
|
1100
|
-
command:
|
|
1791
|
+
command: `${runtime} ${runArgs.join(' ')}`,
|
|
1101
1792
|
message: 'Container started successfully'
|
|
1102
1793
|
}, null, 2),
|
|
1103
1794
|
},
|
|
@@ -1105,9 +1796,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1105
1796
|
};
|
|
1106
1797
|
}
|
|
1107
1798
|
case 'get_container_logs': {
|
|
1108
|
-
const runtime = args.runtime || 'docker';
|
|
1109
|
-
const
|
|
1110
|
-
const { stdout } = await execAsync(cmd);
|
|
1799
|
+
const runtime = validateRuntime(args.runtime || 'docker');
|
|
1800
|
+
const { stdout } = await execFileAsync(runtime, ['logs', validateContainerId(args.container_id)]);
|
|
1111
1801
|
return {
|
|
1112
1802
|
content: [
|
|
1113
1803
|
{
|
|
@@ -1118,11 +1808,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1118
1808
|
};
|
|
1119
1809
|
}
|
|
1120
1810
|
case 'stop_local_container': {
|
|
1121
|
-
const runtime = args.runtime || 'docker';
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
await
|
|
1125
|
-
await execAsync(rmCmd);
|
|
1811
|
+
const runtime = validateRuntime(args.runtime || 'docker');
|
|
1812
|
+
const containerId = validateContainerId(args.container_id);
|
|
1813
|
+
await execFileAsync(runtime, ['stop', containerId]);
|
|
1814
|
+
await execFileAsync(runtime, ['rm', containerId]);
|
|
1126
1815
|
return {
|
|
1127
1816
|
content: [
|
|
1128
1817
|
{
|
|
@@ -1136,10 +1825,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1136
1825
|
};
|
|
1137
1826
|
}
|
|
1138
1827
|
case 'list_local_containers': {
|
|
1139
|
-
const runtime = args.runtime || 'docker';
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
|
|
1828
|
+
const runtime = validateRuntime(args.runtime || 'docker');
|
|
1829
|
+
const psArgs = ['ps', '--format', '{{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'];
|
|
1830
|
+
if (args.all)
|
|
1831
|
+
psArgs.push('-a');
|
|
1832
|
+
const { stdout } = await execFileAsync(runtime, psArgs);
|
|
1143
1833
|
return {
|
|
1144
1834
|
content: [
|
|
1145
1835
|
{
|
|
@@ -1149,6 +1839,60 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1149
1839
|
],
|
|
1150
1840
|
};
|
|
1151
1841
|
}
|
|
1842
|
+
case 'tag_container_image': {
|
|
1843
|
+
const runtime = validateRuntime(args.runtime || (await execFileAsync('podman', ['--version']).then(() => 'podman').catch(() => 'docker')));
|
|
1844
|
+
const src = validateImageName(args.source_image);
|
|
1845
|
+
const tgt = validateImageName(args.target_image);
|
|
1846
|
+
await execFileAsync(runtime, ['tag', src, tgt]);
|
|
1847
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, command: `${runtime} tag ${src} ${tgt}`, source: src, target: tgt }, null, 2) }] };
|
|
1848
|
+
}
|
|
1849
|
+
case 'remove_local_image': {
|
|
1850
|
+
const runtime = validateRuntime(args.runtime || (await execFileAsync('podman', ['--version']).then(() => 'podman').catch(() => 'docker')));
|
|
1851
|
+
const rmiArgs = ['rmi'];
|
|
1852
|
+
if (args.force)
|
|
1853
|
+
rmiArgs.push('-f');
|
|
1854
|
+
rmiArgs.push(validateImageName(args.image_name));
|
|
1855
|
+
const { stdout, stderr } = await execFileAsync(runtime, rmiArgs);
|
|
1856
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, command: `${runtime} ${rmiArgs.join(' ')}`, output: [stdout, stderr].filter(Boolean).join('\n').trim() }, null, 2) }] };
|
|
1857
|
+
}
|
|
1858
|
+
case 'login_to_registry': {
|
|
1859
|
+
const runtime = validateRuntime(args.runtime || (await execFileAsync('podman', ['--version']).then(() => 'podman').catch(() => 'docker')));
|
|
1860
|
+
const registry = validateRegistryHost(args.registry || 'us.icr.io');
|
|
1861
|
+
const username = args.username || 'iamapikey';
|
|
1862
|
+
// Use supplied password, or fall back to IBMCLOUD_API_KEY for ICR
|
|
1863
|
+
const password = args.password || getApiKey();
|
|
1864
|
+
// Pipe password via stdin — never via echo|shell to avoid command injection
|
|
1865
|
+
const { stdout, stderr } = await spawnWithStdin(runtime, ['login', registry, '-u', username, '--password-stdin'], password);
|
|
1866
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, registry, username, output: [stdout, stderr].filter(Boolean).join('\n').trim() }, null, 2) }] };
|
|
1867
|
+
}
|
|
1868
|
+
case 'inspect_container_image': {
|
|
1869
|
+
const runtime = validateRuntime(args.runtime || (await execFileAsync('podman', ['--version']).then(() => 'podman').catch(() => 'docker')));
|
|
1870
|
+
const { stdout } = await execFileAsync(runtime, ['inspect', validateImageName(args.image_name)]);
|
|
1871
|
+
const raw = JSON.parse(stdout);
|
|
1872
|
+
const img = Array.isArray(raw) ? raw[0] : raw;
|
|
1873
|
+
const summary = {
|
|
1874
|
+
id: img.Id?.substring(0, 12),
|
|
1875
|
+
created: img.Created,
|
|
1876
|
+
architecture: img.Architecture,
|
|
1877
|
+
os: img.Os,
|
|
1878
|
+
size_mb: img.Size ? (img.Size / 1024 / 1024).toFixed(1) : undefined,
|
|
1879
|
+
labels: img.Config?.Labels || img.Labels,
|
|
1880
|
+
env: img.Config?.Env,
|
|
1881
|
+
entrypoint: img.Config?.Entrypoint,
|
|
1882
|
+
cmd: img.Config?.Cmd,
|
|
1883
|
+
exposed_ports: img.Config?.ExposedPorts ? Object.keys(img.Config.ExposedPorts) : [],
|
|
1884
|
+
layers: img.RootFS?.Layers?.length ?? img.GraphDriver?.Data?.LowerDir?.split(':').length,
|
|
1885
|
+
};
|
|
1886
|
+
return { content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] };
|
|
1887
|
+
}
|
|
1888
|
+
case 'prune_images': {
|
|
1889
|
+
const runtime = validateRuntime(args.runtime || (await execFileAsync('podman', ['--version']).then(() => 'podman').catch(() => 'docker')));
|
|
1890
|
+
const pruneArgs = ['image', 'prune', '-f'];
|
|
1891
|
+
if (args.all)
|
|
1892
|
+
pruneArgs.push('-a');
|
|
1893
|
+
const { stdout, stderr } = await execFileAsync(runtime, pruneArgs);
|
|
1894
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, command: `${runtime} ${pruneArgs.join(' ')}`, output: [stdout, stderr].filter(Boolean).join('\n').trim() }, null, 2) }] };
|
|
1895
|
+
}
|
|
1152
1896
|
case 'icr_list_namespaces': {
|
|
1153
1897
|
const apiKey = getApiKey();
|
|
1154
1898
|
const token = await getIAMToken(apiKey);
|
|
@@ -1259,6 +2003,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1259
2003
|
type: 'literal', name, value,
|
|
1260
2004
|
}));
|
|
1261
2005
|
}
|
|
2006
|
+
if (args.run_args)
|
|
2007
|
+
body.run_arguments = args.run_args;
|
|
2008
|
+
if (args.run_commands)
|
|
2009
|
+
body.run_commands = args.run_commands;
|
|
1262
2010
|
const response = await axios.post(`${base}/apps`, body, { headers });
|
|
1263
2011
|
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
1264
2012
|
}
|
|
@@ -1283,6 +2031,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1283
2031
|
patch.scale_cpu_limit = args.scale_cpu_limit;
|
|
1284
2032
|
if (args.scale_memory_limit)
|
|
1285
2033
|
patch.scale_memory_limit = args.scale_memory_limit;
|
|
2034
|
+
if (args.run_args)
|
|
2035
|
+
patch.run_arguments = args.run_args;
|
|
2036
|
+
if (args.run_commands)
|
|
2037
|
+
patch.run_commands = args.run_commands;
|
|
1286
2038
|
const response = await axios.patch(`${base}/apps/${args.app_name}`, patch, {
|
|
1287
2039
|
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/merge-patch+json', 'If-Match': entityTag },
|
|
1288
2040
|
});
|
|
@@ -1313,22 +2065,46 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1313
2065
|
}
|
|
1314
2066
|
case 'ce_get_app_logs': {
|
|
1315
2067
|
const token = await getIAMToken(getApiKey());
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
2068
|
+
const projectId = await resolveProjectId(args.project_id, token);
|
|
2069
|
+
const region = await getProjectRegion(projectId, token);
|
|
2070
|
+
const ceHeaders = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
|
|
2071
|
+
// Get app details to extract namespace/subdomain from the endpoint URL
|
|
2072
|
+
const appRes = await axios.get(`https://api.${region}.codeengine.cloud.ibm.com/v2/projects/${projectId}/apps/${args.app_name}`, { headers: ceHeaders });
|
|
2073
|
+
// Endpoint pattern: https://{app}.{subdomain}.{region}.codeengine.appdomain.cloud
|
|
2074
|
+
const endpoint = appRes.data.endpoint || '';
|
|
2075
|
+
const subdomainMatch = endpoint.match(/https?:\/\/[^.]+\.([^.]+)\.[^.]+\.codeengine/);
|
|
2076
|
+
if (!subdomainMatch) {
|
|
2077
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: 'Could not determine project namespace from app endpoint', endpoint }, null, 2) }] };
|
|
1320
2078
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
2079
|
+
const namespace = subdomainMatch[1];
|
|
2080
|
+
const proxyBase = `https://proxy.${region}.codeengine.cloud.ibm.com`;
|
|
2081
|
+
const tailLines = args.tail_lines ?? 100;
|
|
2082
|
+
const kubeHeaders = { Authorization: `Bearer ${token}` };
|
|
2083
|
+
// List pods for the app via Kubernetes API proxy
|
|
2084
|
+
const labelSelector = encodeURIComponent(`serving.knative.dev/service=${args.app_name}`);
|
|
2085
|
+
const podsRes = await axios.get(`${proxyBase}/api/v1/namespaces/${namespace}/pods?labelSelector=${labelSelector}`, { headers: kubeHeaders });
|
|
2086
|
+
const pods = podsRes.data.items || [];
|
|
2087
|
+
if (pods.length === 0) {
|
|
2088
|
+
return { content: [{ type: 'text', text: JSON.stringify({ message: `No pods found for app '${args.app_name}'`, namespace, app: args.app_name }, null, 2) }] };
|
|
2089
|
+
}
|
|
2090
|
+
// Filter to a specific instance if requested
|
|
2091
|
+
const targetPods = args.instance_name
|
|
2092
|
+
? pods.filter((p) => p.metadata.name === args.instance_name || p.metadata.name.startsWith(String(args.instance_name)))
|
|
2093
|
+
: pods;
|
|
2094
|
+
// Fetch logs for each pod
|
|
2095
|
+
const results = [];
|
|
2096
|
+
for (const pod of targetPods) {
|
|
2097
|
+
const podName = pod.metadata.name;
|
|
2098
|
+
const podPhase = pod.status?.phase ?? 'Unknown';
|
|
2099
|
+
try {
|
|
2100
|
+
const logRes = await axios.get(`${proxyBase}/api/v1/namespaces/${namespace}/pods/${podName}/log?container=user-container&tailLines=${tailLines}`, { headers: kubeHeaders });
|
|
2101
|
+
results.push({ pod: podName, status: podPhase, logs: logRes.data });
|
|
2102
|
+
}
|
|
2103
|
+
catch (logErr) {
|
|
2104
|
+
results.push({ pod: podName, status: podPhase, error: logErr.response?.data?.message ?? logErr.message });
|
|
1329
2105
|
}
|
|
1330
|
-
throw err;
|
|
1331
2106
|
}
|
|
2107
|
+
return { content: [{ type: 'text', text: JSON.stringify({ app: args.app_name, namespace, region, pods_found: pods.length, results }, null, 2) }] };
|
|
1332
2108
|
}
|
|
1333
2109
|
case 'ce_list_builds': {
|
|
1334
2110
|
const token = await getIAMToken(getApiKey());
|
|
@@ -1570,6 +2346,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1570
2346
|
const response = await axios.patch(`${base}/secrets/${args.secret_name}`, body, { headers: patchHeaders });
|
|
1571
2347
|
return { content: [{ type: 'text', text: JSON.stringify({ name: response.data.name, format: response.data.format, updated_at: response.data.updated_at, message: 'Secret updated successfully' }, null, 2) }] };
|
|
1572
2348
|
}
|
|
2349
|
+
case 'ce_refresh_icr_pull_secret': {
|
|
2350
|
+
const apiKey = getApiKey();
|
|
2351
|
+
const token = await getIAMToken(apiKey);
|
|
2352
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2353
|
+
const secretName = args.secret_name || 'icr-pull-secret';
|
|
2354
|
+
const icrHost = args.icr_host || 'us.icr.io';
|
|
2355
|
+
// Delete existing secret if present (ignore 404)
|
|
2356
|
+
try {
|
|
2357
|
+
await axios.delete(`${base}/secrets/${secretName}`, { headers });
|
|
2358
|
+
}
|
|
2359
|
+
catch (e) {
|
|
2360
|
+
if (e.response?.status !== 404)
|
|
2361
|
+
throw e;
|
|
2362
|
+
}
|
|
2363
|
+
// Recreate with current API key credentials
|
|
2364
|
+
const body = { name: secretName, format: 'registry', data: { username: 'iamapikey', password: apiKey, server: icrHost } };
|
|
2365
|
+
const response = await axios.post(`${base}/secrets`, body, { headers });
|
|
2366
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
2367
|
+
name: response.data.name,
|
|
2368
|
+
format: response.data.format,
|
|
2369
|
+
server: icrHost,
|
|
2370
|
+
created_at: response.data.created_at,
|
|
2371
|
+
message: `Registry pull secret "${secretName}" refreshed with current API key credentials. You can now redeploy your application.`,
|
|
2372
|
+
}, null, 2) }] };
|
|
2373
|
+
}
|
|
1573
2374
|
case 'ce_renew_tls_secret_from_pem': {
|
|
1574
2375
|
const fs = await import('fs');
|
|
1575
2376
|
const os = await import('os');
|
|
@@ -1734,10 +2535,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1734
2535
|
}
|
|
1735
2536
|
if (exposedPorts.includes(80))
|
|
1736
2537
|
valErrors.push('Port 80 is not allowed in Code Engine. Use port 8080.');
|
|
1737
|
-
// nginx sed pattern check
|
|
2538
|
+
// nginx sed pattern check — catch both exact-space and \s* patterns that fail in Alpine BusyBox sed
|
|
1738
2539
|
dfLines.filter(l => /sed\s+-i/.test(l) && /listen/.test(l)).forEach(sl => {
|
|
1739
|
-
if (/listen\s{2,}80;/.test(sl)) {
|
|
1740
|
-
valWarnings.push(`Fragile nginx sed pattern: "${sl.trim()}" —
|
|
2540
|
+
if (/listen\s{2,}80;/.test(sl) || /listen\\s[*+?]/.test(sl)) {
|
|
2541
|
+
valWarnings.push(`Fragile nginx sed pattern: "${sl.trim()}" — Alpine BusyBox sed does not support \\s*, \\s+. Use 's/listen[[:space:]]*80;/listen 8080;/g' (POSIX character class) instead`);
|
|
1741
2542
|
}
|
|
1742
2543
|
});
|
|
1743
2544
|
if (valErrors.length > 0) {
|
|
@@ -1758,7 +2559,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1758
2559
|
// 1) detect runtime
|
|
1759
2560
|
let runtime = 'podman';
|
|
1760
2561
|
try {
|
|
1761
|
-
await
|
|
2562
|
+
await execFileAsync('podman', ['--version']);
|
|
1762
2563
|
}
|
|
1763
2564
|
catch {
|
|
1764
2565
|
runtime = 'docker';
|
|
@@ -1773,20 +2574,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1773
2574
|
const imageTag = args.image_tag || 'latest';
|
|
1774
2575
|
const imageName = `${icrHost}/${args.icr_namespace}/${args.app_name}:${imageTag}`;
|
|
1775
2576
|
const contextPath = contextPathRaw;
|
|
1776
|
-
const
|
|
1777
|
-
const { stdout: buildStdout, stderr: buildStderr } = await
|
|
2577
|
+
const buildCmdArgs = ['build', '--platform', 'linux/amd64', '-t', validateImageName(imageName), contextPath];
|
|
2578
|
+
const { stdout: buildStdout, stderr: buildStderr } = await execFileAsync(runtime, buildCmdArgs);
|
|
1778
2579
|
const buildOutput = [buildStdout, buildStderr].filter(Boolean).join('\n').trim();
|
|
1779
2580
|
// show last 20 lines of build output so it's not overwhelming
|
|
1780
2581
|
const buildLines = buildOutput.split('\n');
|
|
1781
2582
|
const buildSummary = buildLines.length > 20 ? `...${buildLines.slice(-20).join('\n')}` : buildOutput;
|
|
1782
2583
|
steps.push(`[3/5] Built ${imageName} for linux/amd64:\n${buildSummary}`);
|
|
1783
2584
|
// 4) push
|
|
1784
|
-
const
|
|
1785
|
-
const { stdout: pushStdout, stderr: pushStderr } = await execAsync(pushCmd);
|
|
2585
|
+
const { stdout: pushStdout, stderr: pushStderr } = await execFileAsync(runtime, ['push', imageName]);
|
|
1786
2586
|
const pushOutput = [pushStdout, pushStderr].filter(Boolean).join('\n').trim();
|
|
1787
2587
|
steps.push(`[4/5] Pushed to ${icrHost}:\n${pushOutput}`);
|
|
1788
|
-
// 5)
|
|
2588
|
+
// 4.5) auto-refresh the ICR pull secret so Code Engine can always pull the freshly-pushed image
|
|
2589
|
+
// A stale secret (409 already-exists but wrong password) causes "no_revision_ready" / "reason: unknown".
|
|
1789
2590
|
const { base: base1, headers: headers1 } = await ceApi(projectId1, token1);
|
|
2591
|
+
const pullSecretName = args.image_secret || 'icr-pull-secret';
|
|
2592
|
+
try {
|
|
2593
|
+
try {
|
|
2594
|
+
await axios.delete(`${base1}/secrets/${pullSecretName}`, { headers: headers1 });
|
|
2595
|
+
}
|
|
2596
|
+
catch (e) {
|
|
2597
|
+
if (e.response?.status !== 404)
|
|
2598
|
+
throw e;
|
|
2599
|
+
}
|
|
2600
|
+
await axios.post(`${base1}/secrets`, { name: pullSecretName, format: 'registry', data: { username: 'iamapikey', password: getApiKey(), server: icrHost } }, { headers: headers1 });
|
|
2601
|
+
steps.push(`[4.5/5] Refreshed pull secret "${pullSecretName}" with current credentials`);
|
|
2602
|
+
}
|
|
2603
|
+
catch (e) {
|
|
2604
|
+
steps.push(`[4.5/5] Warning: Could not refresh pull secret "${pullSecretName}": ${e.message}`);
|
|
2605
|
+
}
|
|
2606
|
+
// 5) create or update CE app
|
|
1790
2607
|
const appPayload1 = {
|
|
1791
2608
|
image_reference: imageName,
|
|
1792
2609
|
image_secret: args.image_secret,
|
|
@@ -1941,6 +2758,376 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1941
2758
|
app_poll_history: appPollHistory,
|
|
1942
2759
|
}, null, 2) }] };
|
|
1943
2760
|
}
|
|
2761
|
+
// ─── App Revisions ─────────────────────────────────────────────────────────
|
|
2762
|
+
case 'ce_list_app_revisions': {
|
|
2763
|
+
const token = await getIAMToken(getApiKey());
|
|
2764
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2765
|
+
const response = await axios.get(`${base}/apps/${args.app_name}/revisions`, { headers });
|
|
2766
|
+
return { content: [{ type: 'text', text: JSON.stringify({ revisions: response.data.revisions || [], total: response.data.revisions?.length || 0 }, null, 2) }] };
|
|
2767
|
+
}
|
|
2768
|
+
case 'ce_get_app_revision': {
|
|
2769
|
+
const token = await getIAMToken(getApiKey());
|
|
2770
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2771
|
+
const response = await axios.get(`${base}/apps/${args.app_name}/revisions/${args.revision_name}`, { headers });
|
|
2772
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2773
|
+
}
|
|
2774
|
+
case 'ce_delete_app_revision': {
|
|
2775
|
+
const token = await getIAMToken(getApiKey());
|
|
2776
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2777
|
+
await axios.delete(`${base}/apps/${args.app_name}/revisions/${args.revision_name}`, { headers });
|
|
2778
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Revision ${args.revision_name} deleted` }, null, 2) }] };
|
|
2779
|
+
}
|
|
2780
|
+
// ─── Update operations ──────────────────────────────────────────────────────
|
|
2781
|
+
case 'ce_update_job': {
|
|
2782
|
+
const token = await getIAMToken(getApiKey());
|
|
2783
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2784
|
+
const current = await axios.get(`${base}/jobs/${args.job_name}`, { headers });
|
|
2785
|
+
const etag = current.data.entity_tag;
|
|
2786
|
+
const patch = {};
|
|
2787
|
+
if (args.image)
|
|
2788
|
+
patch.image_reference = args.image;
|
|
2789
|
+
if (args.image_secret)
|
|
2790
|
+
patch.image_secret = args.image_secret;
|
|
2791
|
+
if (args.scale_array_spec)
|
|
2792
|
+
patch.scale_array_spec = args.scale_array_spec;
|
|
2793
|
+
if (args.scale_cpu_limit)
|
|
2794
|
+
patch.scale_cpu_limit = args.scale_cpu_limit;
|
|
2795
|
+
if (args.scale_memory_limit)
|
|
2796
|
+
patch.scale_memory_limit = args.scale_memory_limit;
|
|
2797
|
+
if (args.env_vars) {
|
|
2798
|
+
patch.run_env_variables = Object.entries(args.env_vars).map(([name, value]) => ({ type: 'literal', name, value }));
|
|
2799
|
+
}
|
|
2800
|
+
const response = await axios.patch(`${base}/jobs/${args.job_name}`, patch, {
|
|
2801
|
+
headers: { ...headers, 'Content-Type': 'application/merge-patch+json', 'If-Match': etag },
|
|
2802
|
+
});
|
|
2803
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2804
|
+
}
|
|
2805
|
+
case 'ce_update_build': {
|
|
2806
|
+
const token = await getIAMToken(getApiKey());
|
|
2807
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2808
|
+
const current = await axios.get(`${base}/builds/${args.build_name}`, { headers });
|
|
2809
|
+
const etag = current.data.entity_tag;
|
|
2810
|
+
const patch = {};
|
|
2811
|
+
if (args.output_image)
|
|
2812
|
+
patch.output_image = args.output_image;
|
|
2813
|
+
if (args.output_secret)
|
|
2814
|
+
patch.output_secret = args.output_secret;
|
|
2815
|
+
if (args.source_url)
|
|
2816
|
+
patch.source_url = args.source_url;
|
|
2817
|
+
if (args.source_revision)
|
|
2818
|
+
patch.source_revision = args.source_revision;
|
|
2819
|
+
if (args.strategy_size)
|
|
2820
|
+
patch.strategy_size = args.strategy_size;
|
|
2821
|
+
if (args.strategy_spec_file)
|
|
2822
|
+
patch.strategy_spec_file = args.strategy_spec_file;
|
|
2823
|
+
const response = await axios.patch(`${base}/builds/${args.build_name}`, patch, {
|
|
2824
|
+
headers: { ...headers, 'Content-Type': 'application/merge-patch+json', 'If-Match': etag },
|
|
2825
|
+
});
|
|
2826
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2827
|
+
}
|
|
2828
|
+
case 'ce_update_config_map': {
|
|
2829
|
+
const token = await getIAMToken(getApiKey());
|
|
2830
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2831
|
+
const current = await axios.get(`${base}/config_maps/${args.config_map_name}`, { headers });
|
|
2832
|
+
const etag = current.data.entity_tag;
|
|
2833
|
+
const response = await axios.patch(`${base}/config_maps/${args.config_map_name}`, { data: args.data }, {
|
|
2834
|
+
headers: { ...headers, 'Content-Type': 'application/merge-patch+json', 'If-Match': etag },
|
|
2835
|
+
});
|
|
2836
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2837
|
+
}
|
|
2838
|
+
case 'ce_update_domain_mapping': {
|
|
2839
|
+
const token = await getIAMToken(getApiKey());
|
|
2840
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2841
|
+
const current = await axios.get(`${base}/domain_mappings/${encodeURIComponent(args.domain_name)}`, { headers });
|
|
2842
|
+
const etag = current.data.entity_tag;
|
|
2843
|
+
const patch = {};
|
|
2844
|
+
if (args.app_name)
|
|
2845
|
+
patch.component = { resource_type: 'app_v2', name: args.app_name };
|
|
2846
|
+
if (args.tls_secret)
|
|
2847
|
+
patch.tls_secret = args.tls_secret;
|
|
2848
|
+
const response = await axios.patch(`${base}/domain_mappings/${encodeURIComponent(args.domain_name)}`, patch, {
|
|
2849
|
+
headers: { ...headers, 'Content-Type': 'application/merge-patch+json', 'If-Match': etag },
|
|
2850
|
+
});
|
|
2851
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2852
|
+
}
|
|
2853
|
+
// ─── Functions ──────────────────────────────────────────────────────────────
|
|
2854
|
+
case 'ce_list_function_runtimes': {
|
|
2855
|
+
const token = await getIAMToken(getApiKey());
|
|
2856
|
+
const region = args.region || 'us-south';
|
|
2857
|
+
const response = await axios.get(`https://api.${region}.codeengine.cloud.ibm.com/v2/function_runtimes`, { headers: { Authorization: `Bearer ${token}` } });
|
|
2858
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2859
|
+
}
|
|
2860
|
+
case 'ce_list_functions': {
|
|
2861
|
+
const token = await getIAMToken(getApiKey());
|
|
2862
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2863
|
+
const response = await axios.get(`${base}/functions`, { headers });
|
|
2864
|
+
return { content: [{ type: 'text', text: JSON.stringify({ functions: response.data.functions || [], total: response.data.functions?.length || 0 }, null, 2) }] };
|
|
2865
|
+
}
|
|
2866
|
+
case 'ce_get_function': {
|
|
2867
|
+
const token = await getIAMToken(getApiKey());
|
|
2868
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2869
|
+
const response = await axios.get(`${base}/functions/${args.function_name}`, { headers });
|
|
2870
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2871
|
+
}
|
|
2872
|
+
case 'ce_create_function': {
|
|
2873
|
+
const token = await getIAMToken(getApiKey());
|
|
2874
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2875
|
+
const body = {
|
|
2876
|
+
name: args.name,
|
|
2877
|
+
runtime: args.runtime,
|
|
2878
|
+
code_reference: args.code_reference,
|
|
2879
|
+
code_main: args.code_main ?? 'main',
|
|
2880
|
+
scale_concurrency: args.scale_concurrency ?? 1,
|
|
2881
|
+
scale_cpu_limit: args.scale_cpu_limit ?? '1',
|
|
2882
|
+
scale_memory_limit: args.scale_memory_limit ?? '4G',
|
|
2883
|
+
};
|
|
2884
|
+
if (args.env_vars) {
|
|
2885
|
+
body.run_env_variables = Object.entries(args.env_vars).map(([name, value]) => ({ type: 'literal', name, value }));
|
|
2886
|
+
}
|
|
2887
|
+
const response = await axios.post(`${base}/functions`, body, { headers });
|
|
2888
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2889
|
+
}
|
|
2890
|
+
case 'ce_update_function': {
|
|
2891
|
+
const token = await getIAMToken(getApiKey());
|
|
2892
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2893
|
+
const current = await axios.get(`${base}/functions/${args.function_name}`, { headers });
|
|
2894
|
+
const etag = current.data.entity_tag;
|
|
2895
|
+
const patch = {};
|
|
2896
|
+
if (args.runtime)
|
|
2897
|
+
patch.runtime = args.runtime;
|
|
2898
|
+
if (args.code_reference)
|
|
2899
|
+
patch.code_reference = args.code_reference;
|
|
2900
|
+
if (args.code_main)
|
|
2901
|
+
patch.code_main = args.code_main;
|
|
2902
|
+
if (args.scale_concurrency !== undefined)
|
|
2903
|
+
patch.scale_concurrency = args.scale_concurrency;
|
|
2904
|
+
if (args.scale_cpu_limit)
|
|
2905
|
+
patch.scale_cpu_limit = args.scale_cpu_limit;
|
|
2906
|
+
if (args.scale_memory_limit)
|
|
2907
|
+
patch.scale_memory_limit = args.scale_memory_limit;
|
|
2908
|
+
if (args.env_vars) {
|
|
2909
|
+
patch.run_env_variables = Object.entries(args.env_vars).map(([name, value]) => ({ type: 'literal', name, value }));
|
|
2910
|
+
}
|
|
2911
|
+
const response = await axios.patch(`${base}/functions/${args.function_name}`, patch, {
|
|
2912
|
+
headers: { ...headers, 'Content-Type': 'application/merge-patch+json', 'If-Match': etag },
|
|
2913
|
+
});
|
|
2914
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2915
|
+
}
|
|
2916
|
+
case 'ce_delete_function': {
|
|
2917
|
+
const token = await getIAMToken(getApiKey());
|
|
2918
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2919
|
+
await axios.delete(`${base}/functions/${args.function_name}`, { headers });
|
|
2920
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Function ${args.function_name} deleted` }, null, 2) }] };
|
|
2921
|
+
}
|
|
2922
|
+
// ─── Service Bindings ────────────────────────────────────────────────────────
|
|
2923
|
+
case 'ce_list_bindings': {
|
|
2924
|
+
const token = await getIAMToken(getApiKey());
|
|
2925
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2926
|
+
const response = await axios.get(`${base}/bindings`, { headers });
|
|
2927
|
+
return { content: [{ type: 'text', text: JSON.stringify({ bindings: response.data.bindings || [], total: response.data.bindings?.length || 0 }, null, 2) }] };
|
|
2928
|
+
}
|
|
2929
|
+
case 'ce_create_binding': {
|
|
2930
|
+
const token = await getIAMToken(getApiKey());
|
|
2931
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2932
|
+
const body = {
|
|
2933
|
+
component: { resource_type: args.component_resource_type, name: args.component_name },
|
|
2934
|
+
secret_name: args.secret_name,
|
|
2935
|
+
};
|
|
2936
|
+
if (args.prefix)
|
|
2937
|
+
body.prefix = args.prefix;
|
|
2938
|
+
const response = await axios.post(`${base}/bindings`, body, { headers });
|
|
2939
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2940
|
+
}
|
|
2941
|
+
case 'ce_get_binding': {
|
|
2942
|
+
const token = await getIAMToken(getApiKey());
|
|
2943
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2944
|
+
const response = await axios.get(`${base}/bindings/${args.binding_id}`, { headers });
|
|
2945
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2946
|
+
}
|
|
2947
|
+
case 'ce_delete_binding': {
|
|
2948
|
+
const token = await getIAMToken(getApiKey());
|
|
2949
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2950
|
+
await axios.delete(`${base}/bindings/${args.binding_id}`, { headers });
|
|
2951
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Binding ${args.binding_id} deleted` }, null, 2) }] };
|
|
2952
|
+
}
|
|
2953
|
+
// ─── Project extras ─────────────────────────────────────────────────────────
|
|
2954
|
+
case 'ce_get_project_status': {
|
|
2955
|
+
const token = await getIAMToken(getApiKey());
|
|
2956
|
+
const region = await getProjectRegion(args.project_id, token);
|
|
2957
|
+
const response = await axios.get(`https://api.${region}.codeengine.cloud.ibm.com/v2/projects/${args.project_id}/status_details`, { headers: { Authorization: `Bearer ${token}` } });
|
|
2958
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2959
|
+
}
|
|
2960
|
+
case 'ce_list_egress_ips': {
|
|
2961
|
+
const token = await getIAMToken(getApiKey());
|
|
2962
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2963
|
+
const response = await axios.get(`${base}/egress_ips`, { headers });
|
|
2964
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2965
|
+
}
|
|
2966
|
+
case 'ce_list_allowed_outbound_destinations': {
|
|
2967
|
+
const token = await getIAMToken(getApiKey());
|
|
2968
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2969
|
+
const response = await axios.get(`${base}/allowed_outbound_destinations`, { headers });
|
|
2970
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2971
|
+
}
|
|
2972
|
+
case 'ce_create_allowed_outbound_destination': {
|
|
2973
|
+
const token = await getIAMToken(getApiKey());
|
|
2974
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2975
|
+
const body = { name: args.name, type: args.type };
|
|
2976
|
+
if (args.cidr_block)
|
|
2977
|
+
body.cidr_block = args.cidr_block;
|
|
2978
|
+
if (args.fqdn)
|
|
2979
|
+
body.fqdn = args.fqdn;
|
|
2980
|
+
const response = await axios.post(`${base}/allowed_outbound_destinations`, body, { headers });
|
|
2981
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2982
|
+
}
|
|
2983
|
+
case 'ce_get_allowed_outbound_destination': {
|
|
2984
|
+
const token = await getIAMToken(getApiKey());
|
|
2985
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2986
|
+
const response = await axios.get(`${base}/allowed_outbound_destinations/${args.destination_name}`, { headers });
|
|
2987
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
2988
|
+
}
|
|
2989
|
+
case 'ce_update_allowed_outbound_destination': {
|
|
2990
|
+
const token = await getIAMToken(getApiKey());
|
|
2991
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
2992
|
+
const current = await axios.get(`${base}/allowed_outbound_destinations/${args.destination_name}`, { headers });
|
|
2993
|
+
const etag = current.data.entity_tag;
|
|
2994
|
+
const patch = {};
|
|
2995
|
+
if (args.cidr_block)
|
|
2996
|
+
patch.cidr_block = args.cidr_block;
|
|
2997
|
+
if (args.fqdn)
|
|
2998
|
+
patch.fqdn = args.fqdn;
|
|
2999
|
+
const response = await axios.patch(`${base}/allowed_outbound_destinations/${args.destination_name}`, patch, {
|
|
3000
|
+
headers: { ...headers, 'Content-Type': 'application/merge-patch+json', 'If-Match': etag },
|
|
3001
|
+
});
|
|
3002
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3003
|
+
}
|
|
3004
|
+
case 'ce_delete_allowed_outbound_destination': {
|
|
3005
|
+
const token = await getIAMToken(getApiKey());
|
|
3006
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3007
|
+
await axios.delete(`${base}/allowed_outbound_destinations/${args.destination_name}`, { headers });
|
|
3008
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Allowed outbound destination ${args.destination_name} deleted` }, null, 2) }] };
|
|
3009
|
+
}
|
|
3010
|
+
// ─── Persistent Data Stores ─────────────────────────────────────────────────
|
|
3011
|
+
case 'ce_list_persistent_data_stores': {
|
|
3012
|
+
const token = await getIAMToken(getApiKey());
|
|
3013
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3014
|
+
const response = await axios.get(`${base}/persistent_data_stores`, { headers });
|
|
3015
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3016
|
+
}
|
|
3017
|
+
case 'ce_create_persistent_data_store': {
|
|
3018
|
+
const token = await getIAMToken(getApiKey());
|
|
3019
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3020
|
+
const body = { name: args.name, secret_name: args.secret_name, bucket_name: args.bucket_name };
|
|
3021
|
+
if (args.endpoint)
|
|
3022
|
+
body.endpoint = args.endpoint;
|
|
3023
|
+
const response = await axios.post(`${base}/persistent_data_stores`, body, { headers });
|
|
3024
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3025
|
+
}
|
|
3026
|
+
case 'ce_get_persistent_data_store': {
|
|
3027
|
+
const token = await getIAMToken(getApiKey());
|
|
3028
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3029
|
+
const response = await axios.get(`${base}/persistent_data_stores/${args.data_store_name}`, { headers });
|
|
3030
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3031
|
+
}
|
|
3032
|
+
case 'ce_delete_persistent_data_store': {
|
|
3033
|
+
const token = await getIAMToken(getApiKey());
|
|
3034
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3035
|
+
await axios.delete(`${base}/persistent_data_stores/${args.data_store_name}`, { headers });
|
|
3036
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Persistent data store ${args.data_store_name} deleted` }, null, 2) }] };
|
|
3037
|
+
}
|
|
3038
|
+
// ─── Fleets ─────────────────────────────────────────────────────────────────
|
|
3039
|
+
case 'ce_list_fleets': {
|
|
3040
|
+
const token = await getIAMToken(getApiKey());
|
|
3041
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3042
|
+
const response = await axios.get(`${base}/fleets`, { headers });
|
|
3043
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3044
|
+
}
|
|
3045
|
+
case 'ce_create_fleet': {
|
|
3046
|
+
const token = await getIAMToken(getApiKey());
|
|
3047
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3048
|
+
const body = { name: args.name, image_reference: args.image };
|
|
3049
|
+
if (args.image_secret)
|
|
3050
|
+
body.image_secret = args.image_secret;
|
|
3051
|
+
if (args.scale_cpu_limit)
|
|
3052
|
+
body.scale_cpu_limit = args.scale_cpu_limit;
|
|
3053
|
+
if (args.scale_memory_limit)
|
|
3054
|
+
body.scale_memory_limit = args.scale_memory_limit;
|
|
3055
|
+
if (args.env_vars) {
|
|
3056
|
+
body.run_env_variables = Object.entries(args.env_vars).map(([name, value]) => ({ type: 'literal', name, value }));
|
|
3057
|
+
}
|
|
3058
|
+
const response = await axios.post(`${base}/fleets`, body, { headers });
|
|
3059
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3060
|
+
}
|
|
3061
|
+
case 'ce_get_fleet': {
|
|
3062
|
+
const token = await getIAMToken(getApiKey());
|
|
3063
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3064
|
+
const response = await axios.get(`${base}/fleets/${args.fleet_id}`, { headers });
|
|
3065
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3066
|
+
}
|
|
3067
|
+
case 'ce_delete_fleet': {
|
|
3068
|
+
const token = await getIAMToken(getApiKey());
|
|
3069
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3070
|
+
await axios.delete(`${base}/fleets/${args.fleet_id}`, { headers });
|
|
3071
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Fleet ${args.fleet_id} deleted` }, null, 2) }] };
|
|
3072
|
+
}
|
|
3073
|
+
case 'ce_cancel_fleet': {
|
|
3074
|
+
const token = await getIAMToken(getApiKey());
|
|
3075
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3076
|
+
const response = await axios.post(`${base}/fleets/${args.fleet_id}/cancel`, {}, { headers });
|
|
3077
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3078
|
+
}
|
|
3079
|
+
// ─── Fleet Tasks ────────────────────────────────────────────────────────────
|
|
3080
|
+
case 'ce_list_fleet_tasks': {
|
|
3081
|
+
const token = await getIAMToken(getApiKey());
|
|
3082
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3083
|
+
const response = await axios.get(`${base}/fleets/${args.fleet_id}/tasks`, { headers });
|
|
3084
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3085
|
+
}
|
|
3086
|
+
case 'ce_get_fleet_task': {
|
|
3087
|
+
const token = await getIAMToken(getApiKey());
|
|
3088
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3089
|
+
const response = await axios.get(`${base}/fleets/${args.fleet_id}/tasks/${args.task_id}`, { headers });
|
|
3090
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3091
|
+
}
|
|
3092
|
+
// ─── Fleet Workers ──────────────────────────────────────────────────────────
|
|
3093
|
+
case 'ce_list_fleet_workers': {
|
|
3094
|
+
const token = await getIAMToken(getApiKey());
|
|
3095
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3096
|
+
const response = await axios.get(`${base}/fleets/${args.fleet_id}/workers`, { headers });
|
|
3097
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3098
|
+
}
|
|
3099
|
+
case 'ce_get_fleet_worker': {
|
|
3100
|
+
const token = await getIAMToken(getApiKey());
|
|
3101
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3102
|
+
const response = await axios.get(`${base}/fleets/${args.fleet_id}/workers/${args.worker_id}`, { headers });
|
|
3103
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3104
|
+
}
|
|
3105
|
+
// ─── Subnet Pools ────────────────────────────────────────────────────────────
|
|
3106
|
+
case 'ce_list_subnet_pools': {
|
|
3107
|
+
const token = await getIAMToken(getApiKey());
|
|
3108
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3109
|
+
const response = await axios.get(`${base}/subnet_pools`, { headers });
|
|
3110
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3111
|
+
}
|
|
3112
|
+
case 'ce_create_subnet_pool': {
|
|
3113
|
+
const token = await getIAMToken(getApiKey());
|
|
3114
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3115
|
+
const body = { name: args.name, cidr: args.cidr };
|
|
3116
|
+
const response = await axios.post(`${base}/subnet_pools`, body, { headers });
|
|
3117
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3118
|
+
}
|
|
3119
|
+
case 'ce_get_subnet_pool': {
|
|
3120
|
+
const token = await getIAMToken(getApiKey());
|
|
3121
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3122
|
+
const response = await axios.get(`${base}/subnet_pools/${args.subnet_pool_id}`, { headers });
|
|
3123
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
3124
|
+
}
|
|
3125
|
+
case 'ce_delete_subnet_pool': {
|
|
3126
|
+
const token = await getIAMToken(getApiKey());
|
|
3127
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
3128
|
+
await axios.delete(`${base}/subnet_pools/${args.subnet_pool_id}`, { headers });
|
|
3129
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Subnet pool ${args.subnet_pool_id} deleted` }, null, 2) }] };
|
|
3130
|
+
}
|
|
1944
3131
|
default:
|
|
1945
3132
|
throw new Error(`Unknown tool: ${name}`);
|
|
1946
3133
|
}
|