code-engine-mcp-server 1.0.0
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/LICENSE +21 -0
- package/README.md +387 -0
- package/build/client.d.ts +3 -0
- package/build/client.d.ts.map +1 -0
- package/build/client.js +194 -0
- package/build/client.js.map +1 -0
- package/build/context-discovery.d.ts +86 -0
- package/build/context-discovery.d.ts.map +1 -0
- package/build/context-discovery.js +291 -0
- package/build/context-discovery.js.map +1 -0
- package/build/deploy-tool-enhanced.d.ts +42 -0
- package/build/deploy-tool-enhanced.d.ts.map +1 -0
- package/build/deploy-tool-enhanced.js +323 -0
- package/build/deploy-tool-enhanced.js.map +1 -0
- package/build/deploy-tool.d.ts +27 -0
- package/build/deploy-tool.d.ts.map +1 -0
- package/build/deploy-tool.js +213 -0
- package/build/deploy-tool.js.map +1 -0
- package/build/index.d.ts +11 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +1117 -0
- package/build/index.js.map +1 -0
- package/build/simple-client.d.ts +3 -0
- package/build/simple-client.d.ts.map +1 -0
- package/build/simple-client.js +275 -0
- package/build/simple-client.js.map +1 -0
- package/build/test-all-tools.d.ts +3 -0
- package/build/test-all-tools.d.ts.map +1 -0
- package/build/test-all-tools.js +254 -0
- package/build/test-all-tools.js.map +1 -0
- package/build/test-integration.d.ts +3 -0
- package/build/test-integration.d.ts.map +1 -0
- package/build/test-integration.js +484 -0
- package/build/test-integration.js.map +1 -0
- package/docs/API_CALL_SCENARIOS.md +594 -0
- package/docs/CLIENT_README.md +310 -0
- package/docs/CLINE_CONFIG_EXAMPLE.json +14 -0
- package/docs/CODE_ENGINE_API_REFERENCE.md +764 -0
- package/docs/CODE_OF_CONDUCT.md +46 -0
- package/docs/CONTRIBUTING.md +38 -0
- package/docs/MAINTAINERS.md +15 -0
- package/docs/SETUP_INSTRUCTIONS.md +218 -0
- package/package.json +44 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Code Engine MCP Server
|
|
4
|
+
* Model Context Protocol server for IBM Code Engine and Docker/Podman integration
|
|
5
|
+
*
|
|
6
|
+
* Author: Markus van Kempen | markus.van.kempen@gmail.com
|
|
7
|
+
* Research | Floor 7½ 🏢🤏 | https://markusvankempen.github.io/
|
|
8
|
+
* No bug too small, no syntax too weird.
|
|
9
|
+
*/
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
import { exec } from 'child_process';
|
|
14
|
+
import { promisify } from 'util';
|
|
15
|
+
import axios from 'axios';
|
|
16
|
+
import { config as loadDotenv } from 'dotenv';
|
|
17
|
+
import { resolve, dirname } from 'path';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
// Load .env from the workspace root (parent of this package) and from the package dir
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
loadDotenv({ path: resolve(__dirname, '../../.env') });
|
|
22
|
+
loadDotenv({ path: resolve(__dirname, '../.env') });
|
|
23
|
+
const execAsync = promisify(exec);
|
|
24
|
+
const CE_REGIONS = ['us-south', 'us-east', 'eu-de', 'eu-gb', 'jp-tok', 'jp-osa', 'au-syd', 'ca-tor', 'br-sao'];
|
|
25
|
+
// Helper function to get IAM token
|
|
26
|
+
async function getIAMToken(apiKey) {
|
|
27
|
+
const response = await axios.post('https://iam.cloud.ibm.com/identity/token', new URLSearchParams({
|
|
28
|
+
grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
|
|
29
|
+
apikey: apiKey
|
|
30
|
+
}), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
|
|
31
|
+
return response.data.access_token;
|
|
32
|
+
}
|
|
33
|
+
// Helper: resolve which region a project lives in
|
|
34
|
+
async function getProjectRegion(projectId, token) {
|
|
35
|
+
for (const region of CE_REGIONS) {
|
|
36
|
+
try {
|
|
37
|
+
await axios.get(`https://api.${region}.codeengine.cloud.ibm.com/v2/projects/${projectId}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
38
|
+
return region;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// not in this region
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`Project ${projectId} not found in any region`);
|
|
45
|
+
}
|
|
46
|
+
// Helper: get authenticated CE API base URL for a project
|
|
47
|
+
async function ceApi(projectId, token) {
|
|
48
|
+
const region = await getProjectRegion(projectId, token);
|
|
49
|
+
return {
|
|
50
|
+
base: `https://api.${region}.codeengine.cloud.ibm.com/v2/projects/${projectId}`,
|
|
51
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Helper: get and validate API key
|
|
55
|
+
function getApiKey() {
|
|
56
|
+
const apiKey = process.env.IBM_CLOUD_API_KEY || process.env.IBMCLOUD_API_KEY;
|
|
57
|
+
if (!apiKey)
|
|
58
|
+
throw new Error('IBM_CLOUD_API_KEY or IBMCLOUD_API_KEY environment variable not set');
|
|
59
|
+
return apiKey;
|
|
60
|
+
}
|
|
61
|
+
// Create MCP server
|
|
62
|
+
const server = new Server({
|
|
63
|
+
name: 'code-engine-mcp-server',
|
|
64
|
+
version: '1.0.0',
|
|
65
|
+
}, {
|
|
66
|
+
capabilities: {
|
|
67
|
+
tools: {},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
// Docker/Podman Tools
|
|
71
|
+
const containerTools = [
|
|
72
|
+
{
|
|
73
|
+
name: 'detect_container_runtime',
|
|
74
|
+
description: 'Detect available container runtime (Docker or Podman)',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
properties: {},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'build_container_image',
|
|
82
|
+
description: 'Build a container image using Docker or Podman',
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
dockerfile_path: { type: 'string', description: 'Path to Dockerfile' },
|
|
87
|
+
image_name: { type: 'string', description: 'Image name with tag' },
|
|
88
|
+
context_path: { type: 'string', description: 'Build context path' },
|
|
89
|
+
runtime: { type: 'string', enum: ['docker', 'podman'], description: 'Container runtime' },
|
|
90
|
+
},
|
|
91
|
+
required: ['dockerfile_path', 'image_name', 'context_path'],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'push_container_image',
|
|
96
|
+
description: 'Push container image to registry',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
image_name: { type: 'string', description: 'Full image name with registry' },
|
|
101
|
+
runtime: { type: 'string', enum: ['docker', 'podman'] },
|
|
102
|
+
},
|
|
103
|
+
required: ['image_name'],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'list_local_images',
|
|
108
|
+
description: 'List all local container images',
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
runtime: { type: 'string', enum: ['docker', 'podman'] },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'test_container_locally',
|
|
118
|
+
description: 'Run container locally for testing',
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
image_name: { type: 'string' },
|
|
123
|
+
port_mapping: { type: 'string', description: 'Port mapping (e.g., 8080:8080)' },
|
|
124
|
+
env_vars: { type: 'object', description: 'Environment variables' },
|
|
125
|
+
runtime: { type: 'string', enum: ['docker', 'podman'] },
|
|
126
|
+
},
|
|
127
|
+
required: ['image_name'],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'get_container_logs',
|
|
132
|
+
description: 'Get logs from a running container',
|
|
133
|
+
inputSchema: {
|
|
134
|
+
type: 'object',
|
|
135
|
+
properties: {
|
|
136
|
+
container_id: { type: 'string' },
|
|
137
|
+
runtime: { type: 'string', enum: ['docker', 'podman'] },
|
|
138
|
+
},
|
|
139
|
+
required: ['container_id'],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'stop_local_container',
|
|
144
|
+
description: 'Stop and remove a local container',
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: 'object',
|
|
147
|
+
properties: {
|
|
148
|
+
container_id: { type: 'string' },
|
|
149
|
+
runtime: { type: 'string', enum: ['docker', 'podman'] },
|
|
150
|
+
},
|
|
151
|
+
required: ['container_id'],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'list_local_containers',
|
|
156
|
+
description: 'List all local containers (running and stopped)',
|
|
157
|
+
inputSchema: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
properties: {
|
|
160
|
+
runtime: { type: 'string', enum: ['docker', 'podman'] },
|
|
161
|
+
all: { type: 'boolean', description: 'Include stopped containers' },
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
// Code Engine Tools
|
|
167
|
+
const codeEngineTools = [
|
|
168
|
+
// --- Projects ---
|
|
169
|
+
{
|
|
170
|
+
name: 'ce_list_projects',
|
|
171
|
+
description: 'List all Code Engine projects across all or a specific region',
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: 'object',
|
|
174
|
+
properties: {
|
|
175
|
+
region: { type: 'string', description: 'IBM Cloud region (omit to search all regions)' },
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'ce_get_project',
|
|
181
|
+
description: 'Get details of a specific Code Engine project',
|
|
182
|
+
inputSchema: {
|
|
183
|
+
type: 'object',
|
|
184
|
+
properties: {
|
|
185
|
+
project_id: { type: 'string', description: 'Project ID (UUID)' },
|
|
186
|
+
},
|
|
187
|
+
required: ['project_id'],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'ce_create_project',
|
|
192
|
+
description: 'Create a new Code Engine project',
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
name: { type: 'string', description: 'Project name' },
|
|
197
|
+
region: { type: 'string', description: 'IBM Cloud region (e.g. us-south, ca-tor)' },
|
|
198
|
+
resource_group_id: { type: 'string', description: 'Resource group ID (optional)' },
|
|
199
|
+
},
|
|
200
|
+
required: ['name', 'region'],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: 'ce_delete_project',
|
|
205
|
+
description: 'Delete a Code Engine project',
|
|
206
|
+
inputSchema: {
|
|
207
|
+
type: 'object',
|
|
208
|
+
properties: {
|
|
209
|
+
project_id: { type: 'string' },
|
|
210
|
+
},
|
|
211
|
+
required: ['project_id'],
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
// --- Applications ---
|
|
215
|
+
{
|
|
216
|
+
name: 'ce_list_applications',
|
|
217
|
+
description: 'List applications in a Code Engine project',
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: {
|
|
221
|
+
project_id: { type: 'string' },
|
|
222
|
+
},
|
|
223
|
+
required: ['project_id'],
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'ce_get_application',
|
|
228
|
+
description: 'Get details of a Code Engine application',
|
|
229
|
+
inputSchema: {
|
|
230
|
+
type: 'object',
|
|
231
|
+
properties: {
|
|
232
|
+
project_id: { type: 'string' },
|
|
233
|
+
app_name: { type: 'string' },
|
|
234
|
+
},
|
|
235
|
+
required: ['project_id', 'app_name'],
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'ce_create_application',
|
|
240
|
+
description: 'Create a Code Engine application from a container image',
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: 'object',
|
|
243
|
+
properties: {
|
|
244
|
+
project_id: { type: 'string' },
|
|
245
|
+
name: { type: 'string' },
|
|
246
|
+
image: { type: 'string', description: 'Container image reference' },
|
|
247
|
+
port: { type: 'number', description: 'Container port (default 8080)' },
|
|
248
|
+
scale_min_instances: { type: 'number', description: 'Min instances (default 0)' },
|
|
249
|
+
scale_max_instances: { type: 'number', description: 'Max instances (default 10)' },
|
|
250
|
+
scale_cpu_limit: { type: 'string', description: 'CPU limit (e.g. 1, 0.5)' },
|
|
251
|
+
scale_memory_limit: { type: 'string', description: 'Memory limit (e.g. 4G, 2G)' },
|
|
252
|
+
env_vars: { type: 'object', description: 'Key/value environment variables' },
|
|
253
|
+
},
|
|
254
|
+
required: ['project_id', 'name', 'image'],
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: 'ce_update_application',
|
|
259
|
+
description: 'Update an existing Code Engine application (image, scaling, etc.)',
|
|
260
|
+
inputSchema: {
|
|
261
|
+
type: 'object',
|
|
262
|
+
properties: {
|
|
263
|
+
project_id: { type: 'string' },
|
|
264
|
+
app_name: { type: 'string' },
|
|
265
|
+
image: { type: 'string', description: 'New container image reference' },
|
|
266
|
+
scale_min_instances: { type: 'number' },
|
|
267
|
+
scale_max_instances: { type: 'number' },
|
|
268
|
+
scale_cpu_limit: { type: 'string' },
|
|
269
|
+
scale_memory_limit: { type: 'string' },
|
|
270
|
+
},
|
|
271
|
+
required: ['project_id', 'app_name'],
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: 'ce_delete_application',
|
|
276
|
+
description: 'Delete a Code Engine application',
|
|
277
|
+
inputSchema: {
|
|
278
|
+
type: 'object',
|
|
279
|
+
properties: {
|
|
280
|
+
project_id: { type: 'string' },
|
|
281
|
+
app_name: { type: 'string' },
|
|
282
|
+
},
|
|
283
|
+
required: ['project_id', 'app_name'],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'ce_list_app_instances',
|
|
288
|
+
description: 'List all running instances (pods) of a Code Engine application',
|
|
289
|
+
inputSchema: {
|
|
290
|
+
type: 'object',
|
|
291
|
+
properties: {
|
|
292
|
+
project_id: { type: 'string' },
|
|
293
|
+
app_name: { type: 'string' },
|
|
294
|
+
},
|
|
295
|
+
required: ['project_id', 'app_name'],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: 'ce_get_app_logs',
|
|
300
|
+
description: 'Get logs for a specific Code Engine application instance',
|
|
301
|
+
inputSchema: {
|
|
302
|
+
type: 'object',
|
|
303
|
+
properties: {
|
|
304
|
+
project_id: { type: 'string' },
|
|
305
|
+
app_name: { type: 'string' },
|
|
306
|
+
instance_name: { type: 'string', description: 'Instance name (e.g. my-app-00001-deployment-abcde)' },
|
|
307
|
+
},
|
|
308
|
+
required: ['project_id', 'app_name', 'instance_name'],
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
// --- Builds ---
|
|
312
|
+
{
|
|
313
|
+
name: 'ce_list_builds',
|
|
314
|
+
description: 'List build configurations in a Code Engine project',
|
|
315
|
+
inputSchema: {
|
|
316
|
+
type: 'object',
|
|
317
|
+
properties: {
|
|
318
|
+
project_id: { type: 'string' },
|
|
319
|
+
},
|
|
320
|
+
required: ['project_id'],
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: 'ce_create_build',
|
|
325
|
+
description: 'Create a build configuration for building container images from source',
|
|
326
|
+
inputSchema: {
|
|
327
|
+
type: 'object',
|
|
328
|
+
properties: {
|
|
329
|
+
project_id: { type: 'string' },
|
|
330
|
+
name: { type: 'string', description: 'Build configuration name' },
|
|
331
|
+
output_image: { type: 'string', description: 'Output image reference (e.g. icr.io/ns/app:v1)' },
|
|
332
|
+
output_secret: { type: 'string', description: 'Registry secret name for push access' },
|
|
333
|
+
source_type: { type: 'string', enum: ['local', 'git'], description: 'Source type (default: local)' },
|
|
334
|
+
strategy_type: { type: 'string', enum: ['dockerfile', 'buildpacks'], description: 'Build strategy (default: dockerfile)' },
|
|
335
|
+
strategy_spec_file: { type: 'string', description: 'Dockerfile path (default: Dockerfile)' },
|
|
336
|
+
strategy_size: { type: 'string', enum: ['small', 'medium', 'large', 'xlarge'], description: 'Build size (default: medium)' },
|
|
337
|
+
},
|
|
338
|
+
required: ['project_id', 'name', 'output_image', 'output_secret'],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'ce_get_build',
|
|
343
|
+
description: 'Get details of a specific Code Engine build configuration',
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: 'object',
|
|
346
|
+
properties: {
|
|
347
|
+
project_id: { type: 'string' },
|
|
348
|
+
build_name: { type: 'string' },
|
|
349
|
+
},
|
|
350
|
+
required: ['project_id', 'build_name'],
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: 'ce_delete_build',
|
|
355
|
+
description: 'Delete a Code Engine build configuration',
|
|
356
|
+
inputSchema: {
|
|
357
|
+
type: 'object',
|
|
358
|
+
properties: {
|
|
359
|
+
project_id: { type: 'string' },
|
|
360
|
+
build_name: { type: 'string' },
|
|
361
|
+
},
|
|
362
|
+
required: ['project_id', 'build_name'],
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
// --- Build Runs ---
|
|
366
|
+
{
|
|
367
|
+
name: 'ce_list_build_runs',
|
|
368
|
+
description: 'List build runs in a Code Engine project',
|
|
369
|
+
inputSchema: {
|
|
370
|
+
type: 'object',
|
|
371
|
+
properties: {
|
|
372
|
+
project_id: { type: 'string' },
|
|
373
|
+
},
|
|
374
|
+
required: ['project_id'],
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: 'ce_create_build_run',
|
|
379
|
+
description: 'Start a new build run from an existing build configuration',
|
|
380
|
+
inputSchema: {
|
|
381
|
+
type: 'object',
|
|
382
|
+
properties: {
|
|
383
|
+
project_id: { type: 'string' },
|
|
384
|
+
build_name: { type: 'string', description: 'Name of the build configuration to run' },
|
|
385
|
+
name: { type: 'string', description: 'Optional name for this build run' },
|
|
386
|
+
},
|
|
387
|
+
required: ['project_id', 'build_name'],
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: 'ce_get_build_run',
|
|
392
|
+
description: 'Get status and details of a build run',
|
|
393
|
+
inputSchema: {
|
|
394
|
+
type: 'object',
|
|
395
|
+
properties: {
|
|
396
|
+
project_id: { type: 'string' },
|
|
397
|
+
build_run_name: { type: 'string' },
|
|
398
|
+
},
|
|
399
|
+
required: ['project_id', 'build_run_name'],
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: 'ce_delete_build_run',
|
|
404
|
+
description: 'Delete a Code Engine build run',
|
|
405
|
+
inputSchema: {
|
|
406
|
+
type: 'object',
|
|
407
|
+
properties: {
|
|
408
|
+
project_id: { type: 'string' },
|
|
409
|
+
build_run_name: { type: 'string' },
|
|
410
|
+
},
|
|
411
|
+
required: ['project_id', 'build_run_name'],
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
// --- Jobs ---
|
|
415
|
+
{
|
|
416
|
+
name: 'ce_list_jobs',
|
|
417
|
+
description: 'List job definitions in a Code Engine project',
|
|
418
|
+
inputSchema: {
|
|
419
|
+
type: 'object',
|
|
420
|
+
properties: {
|
|
421
|
+
project_id: { type: 'string' },
|
|
422
|
+
},
|
|
423
|
+
required: ['project_id'],
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: 'ce_create_job',
|
|
428
|
+
description: 'Create a job definition in Code Engine',
|
|
429
|
+
inputSchema: {
|
|
430
|
+
type: 'object',
|
|
431
|
+
properties: {
|
|
432
|
+
project_id: { type: 'string' },
|
|
433
|
+
name: { type: 'string' },
|
|
434
|
+
image: { type: 'string', description: 'Container image reference' },
|
|
435
|
+
scale_array_spec: { type: 'string', description: 'Array indices (e.g. 0-9 for 10 instances)' },
|
|
436
|
+
scale_cpu_limit: { type: 'string' },
|
|
437
|
+
scale_memory_limit: { type: 'string' },
|
|
438
|
+
env_vars: { type: 'object' },
|
|
439
|
+
},
|
|
440
|
+
required: ['project_id', 'name', 'image'],
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: 'ce_create_job_run',
|
|
445
|
+
description: 'Submit a job run from an existing job definition',
|
|
446
|
+
inputSchema: {
|
|
447
|
+
type: 'object',
|
|
448
|
+
properties: {
|
|
449
|
+
project_id: { type: 'string' },
|
|
450
|
+
job_name: { type: 'string', description: 'Job definition name to run' },
|
|
451
|
+
name: { type: 'string', description: 'Optional name for this job run' },
|
|
452
|
+
},
|
|
453
|
+
required: ['project_id', 'job_name'],
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: 'ce_get_job',
|
|
458
|
+
description: 'Get details of a specific Code Engine job definition',
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: 'object',
|
|
461
|
+
properties: {
|
|
462
|
+
project_id: { type: 'string' },
|
|
463
|
+
job_name: { type: 'string' },
|
|
464
|
+
},
|
|
465
|
+
required: ['project_id', 'job_name'],
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: 'ce_delete_job',
|
|
470
|
+
description: 'Delete a Code Engine job definition',
|
|
471
|
+
inputSchema: {
|
|
472
|
+
type: 'object',
|
|
473
|
+
properties: {
|
|
474
|
+
project_id: { type: 'string' },
|
|
475
|
+
job_name: { type: 'string' },
|
|
476
|
+
},
|
|
477
|
+
required: ['project_id', 'job_name'],
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
name: 'ce_list_job_runs',
|
|
482
|
+
description: 'List job runs in a Code Engine project',
|
|
483
|
+
inputSchema: {
|
|
484
|
+
type: 'object',
|
|
485
|
+
properties: {
|
|
486
|
+
project_id: { type: 'string' },
|
|
487
|
+
job_name: { type: 'string', description: 'Filter by job definition name (optional)' },
|
|
488
|
+
limit: { type: 'number', description: 'Maximum results to return' },
|
|
489
|
+
start: { type: 'string', description: 'Pagination token' },
|
|
490
|
+
},
|
|
491
|
+
required: ['project_id'],
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: 'ce_get_job_run',
|
|
496
|
+
description: 'Get status and details of a job run',
|
|
497
|
+
inputSchema: {
|
|
498
|
+
type: 'object',
|
|
499
|
+
properties: {
|
|
500
|
+
project_id: { type: 'string' },
|
|
501
|
+
job_run_name: { type: 'string' },
|
|
502
|
+
},
|
|
503
|
+
required: ['project_id', 'job_run_name'],
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
name: 'ce_delete_job_run',
|
|
508
|
+
description: 'Delete a Code Engine job run',
|
|
509
|
+
inputSchema: {
|
|
510
|
+
type: 'object',
|
|
511
|
+
properties: {
|
|
512
|
+
project_id: { type: 'string' },
|
|
513
|
+
job_run_name: { type: 'string' },
|
|
514
|
+
},
|
|
515
|
+
required: ['project_id', 'job_run_name'],
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
// --- Secrets ---
|
|
519
|
+
{
|
|
520
|
+
name: 'ce_list_secrets',
|
|
521
|
+
description: 'List secrets in a Code Engine project',
|
|
522
|
+
inputSchema: {
|
|
523
|
+
type: 'object',
|
|
524
|
+
properties: {
|
|
525
|
+
project_id: { type: 'string' },
|
|
526
|
+
},
|
|
527
|
+
required: ['project_id'],
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: 'ce_create_secret',
|
|
532
|
+
description: 'Create a secret in a Code Engine project (generic, registry, ssh_auth, etc.)',
|
|
533
|
+
inputSchema: {
|
|
534
|
+
type: 'object',
|
|
535
|
+
properties: {
|
|
536
|
+
project_id: { type: 'string' },
|
|
537
|
+
name: { type: 'string' },
|
|
538
|
+
format: { type: 'string', enum: ['generic', 'ssh_auth', 'basic_auth', 'tls', 'registry'], description: 'Secret type' },
|
|
539
|
+
data: { type: 'object', description: 'Key/value pairs (values must be base64 encoded for binary formats)' },
|
|
540
|
+
},
|
|
541
|
+
required: ['project_id', 'name', 'format', 'data'],
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: 'ce_get_secret',
|
|
546
|
+
description: 'Get metadata for a specific secret (keys only, no values)',
|
|
547
|
+
inputSchema: {
|
|
548
|
+
type: 'object',
|
|
549
|
+
properties: {
|
|
550
|
+
project_id: { type: 'string' },
|
|
551
|
+
secret_name: { type: 'string' },
|
|
552
|
+
},
|
|
553
|
+
required: ['project_id', 'secret_name'],
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: 'ce_delete_secret',
|
|
558
|
+
description: 'Delete a secret from a Code Engine project',
|
|
559
|
+
inputSchema: {
|
|
560
|
+
type: 'object',
|
|
561
|
+
properties: {
|
|
562
|
+
project_id: { type: 'string' },
|
|
563
|
+
secret_name: { type: 'string' },
|
|
564
|
+
},
|
|
565
|
+
required: ['project_id', 'secret_name'],
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
// --- ConfigMaps ---
|
|
569
|
+
{
|
|
570
|
+
name: 'ce_list_config_maps',
|
|
571
|
+
description: 'List configmaps in a Code Engine project',
|
|
572
|
+
inputSchema: {
|
|
573
|
+
type: 'object',
|
|
574
|
+
properties: {
|
|
575
|
+
project_id: { type: 'string' },
|
|
576
|
+
},
|
|
577
|
+
required: ['project_id'],
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
name: 'ce_create_config_map',
|
|
582
|
+
description: 'Create a configmap in a Code Engine project',
|
|
583
|
+
inputSchema: {
|
|
584
|
+
type: 'object',
|
|
585
|
+
properties: {
|
|
586
|
+
project_id: { type: 'string' },
|
|
587
|
+
name: { type: 'string' },
|
|
588
|
+
data: { type: 'object', description: 'Key/value configuration data' },
|
|
589
|
+
},
|
|
590
|
+
required: ['project_id', 'name', 'data'],
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
name: 'ce_get_config_map',
|
|
595
|
+
description: 'Get details of a specific configmap in a Code Engine project',
|
|
596
|
+
inputSchema: {
|
|
597
|
+
type: 'object',
|
|
598
|
+
properties: {
|
|
599
|
+
project_id: { type: 'string' },
|
|
600
|
+
config_map_name: { type: 'string' },
|
|
601
|
+
},
|
|
602
|
+
required: ['project_id', 'config_map_name'],
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
name: 'ce_delete_config_map',
|
|
607
|
+
description: 'Delete a configmap from a Code Engine project',
|
|
608
|
+
inputSchema: {
|
|
609
|
+
type: 'object',
|
|
610
|
+
properties: {
|
|
611
|
+
project_id: { type: 'string' },
|
|
612
|
+
config_map_name: { type: 'string' },
|
|
613
|
+
},
|
|
614
|
+
required: ['project_id', 'config_map_name'],
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
];
|
|
618
|
+
// Register tools
|
|
619
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
620
|
+
tools: [...containerTools, ...codeEngineTools],
|
|
621
|
+
}));
|
|
622
|
+
// Handle tool calls
|
|
623
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
624
|
+
const { name, arguments: args } = request.params;
|
|
625
|
+
if (!args) {
|
|
626
|
+
return {
|
|
627
|
+
content: [
|
|
628
|
+
{
|
|
629
|
+
type: 'text',
|
|
630
|
+
text: JSON.stringify({ error: 'Missing arguments' }),
|
|
631
|
+
},
|
|
632
|
+
],
|
|
633
|
+
isError: true,
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
switch (name) {
|
|
638
|
+
case 'detect_container_runtime': {
|
|
639
|
+
const { stdout: dockerVersion } = await execAsync('docker --version').catch(() => ({ stdout: '' }));
|
|
640
|
+
const { stdout: podmanVersion } = await execAsync('podman --version').catch(() => ({ stdout: '' }));
|
|
641
|
+
return {
|
|
642
|
+
content: [
|
|
643
|
+
{
|
|
644
|
+
type: 'text',
|
|
645
|
+
text: JSON.stringify({
|
|
646
|
+
docker: dockerVersion ? dockerVersion.trim() : null,
|
|
647
|
+
podman: podmanVersion ? podmanVersion.trim() : null,
|
|
648
|
+
available: dockerVersion ? 'docker' : podmanVersion ? 'podman' : 'none',
|
|
649
|
+
}, null, 2),
|
|
650
|
+
},
|
|
651
|
+
],
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
case 'build_container_image': {
|
|
655
|
+
const runtime = args.runtime || 'docker';
|
|
656
|
+
const cmd = `${runtime} build -t ${args.image_name} -f ${args.dockerfile_path} ${args.context_path}`;
|
|
657
|
+
const { stdout, stderr } = await execAsync(cmd);
|
|
658
|
+
return {
|
|
659
|
+
content: [
|
|
660
|
+
{
|
|
661
|
+
type: 'text',
|
|
662
|
+
text: JSON.stringify({
|
|
663
|
+
success: true,
|
|
664
|
+
command: cmd,
|
|
665
|
+
output: stdout,
|
|
666
|
+
error: stderr
|
|
667
|
+
}, null, 2),
|
|
668
|
+
},
|
|
669
|
+
],
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
case 'push_container_image': {
|
|
673
|
+
const runtime = args.runtime || 'docker';
|
|
674
|
+
const cmd = `${runtime} push ${args.image_name}`;
|
|
675
|
+
const { stdout, stderr } = await execAsync(cmd);
|
|
676
|
+
return {
|
|
677
|
+
content: [
|
|
678
|
+
{
|
|
679
|
+
type: 'text',
|
|
680
|
+
text: JSON.stringify({
|
|
681
|
+
success: true,
|
|
682
|
+
command: cmd,
|
|
683
|
+
output: stdout,
|
|
684
|
+
error: stderr
|
|
685
|
+
}, null, 2),
|
|
686
|
+
},
|
|
687
|
+
],
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
case 'list_local_images': {
|
|
691
|
+
const runtime = args.runtime || 'docker';
|
|
692
|
+
const cmd = `${runtime} images --format "{{.Repository}}:{{.Tag}}\t{{.ID}}\t{{.Size}}"`;
|
|
693
|
+
const { stdout } = await execAsync(cmd);
|
|
694
|
+
return {
|
|
695
|
+
content: [
|
|
696
|
+
{
|
|
697
|
+
type: 'text',
|
|
698
|
+
text: stdout,
|
|
699
|
+
},
|
|
700
|
+
],
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
case 'test_container_locally': {
|
|
704
|
+
const runtime = args.runtime || 'docker';
|
|
705
|
+
let cmd = `${runtime} run -d`;
|
|
706
|
+
if (args.port_mapping) {
|
|
707
|
+
cmd += ` -p ${args.port_mapping}`;
|
|
708
|
+
}
|
|
709
|
+
if (args.env_vars) {
|
|
710
|
+
Object.entries(args.env_vars).forEach(([key, value]) => {
|
|
711
|
+
cmd += ` -e ${key}="${value}"`;
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
cmd += ` ${args.image_name}`;
|
|
715
|
+
const { stdout } = await execAsync(cmd);
|
|
716
|
+
return {
|
|
717
|
+
content: [
|
|
718
|
+
{
|
|
719
|
+
type: 'text',
|
|
720
|
+
text: JSON.stringify({
|
|
721
|
+
container_id: stdout.trim(),
|
|
722
|
+
command: cmd,
|
|
723
|
+
message: 'Container started successfully'
|
|
724
|
+
}, null, 2),
|
|
725
|
+
},
|
|
726
|
+
],
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
case 'get_container_logs': {
|
|
730
|
+
const runtime = args.runtime || 'docker';
|
|
731
|
+
const cmd = `${runtime} logs ${args.container_id}`;
|
|
732
|
+
const { stdout } = await execAsync(cmd);
|
|
733
|
+
return {
|
|
734
|
+
content: [
|
|
735
|
+
{
|
|
736
|
+
type: 'text',
|
|
737
|
+
text: stdout,
|
|
738
|
+
},
|
|
739
|
+
],
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
case 'stop_local_container': {
|
|
743
|
+
const runtime = args.runtime || 'docker';
|
|
744
|
+
const stopCmd = `${runtime} stop ${args.container_id}`;
|
|
745
|
+
const rmCmd = `${runtime} rm ${args.container_id}`;
|
|
746
|
+
await execAsync(stopCmd);
|
|
747
|
+
await execAsync(rmCmd);
|
|
748
|
+
return {
|
|
749
|
+
content: [
|
|
750
|
+
{
|
|
751
|
+
type: 'text',
|
|
752
|
+
text: JSON.stringify({
|
|
753
|
+
success: true,
|
|
754
|
+
message: `Container ${args.container_id} stopped and removed`
|
|
755
|
+
}, null, 2),
|
|
756
|
+
},
|
|
757
|
+
],
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
case 'list_local_containers': {
|
|
761
|
+
const runtime = args.runtime || 'docker';
|
|
762
|
+
const allFlag = args.all ? '-a' : '';
|
|
763
|
+
const cmd = `${runtime} ps ${allFlag} --format "{{.ID}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"`;
|
|
764
|
+
const { stdout } = await execAsync(cmd);
|
|
765
|
+
return {
|
|
766
|
+
content: [
|
|
767
|
+
{
|
|
768
|
+
type: 'text',
|
|
769
|
+
text: stdout,
|
|
770
|
+
},
|
|
771
|
+
],
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
case 'ce_list_projects': {
|
|
775
|
+
const apiKey = getApiKey();
|
|
776
|
+
const token = await getIAMToken(apiKey);
|
|
777
|
+
const regionsToCheck = args.region ? [args.region] : CE_REGIONS;
|
|
778
|
+
const uniqueProjects = new Map();
|
|
779
|
+
await Promise.allSettled(regionsToCheck.map(async (reg) => {
|
|
780
|
+
const response = await axios.get(`https://api.${reg}.codeengine.cloud.ibm.com/v2/projects`, { headers: { Authorization: `Bearer ${token}` } });
|
|
781
|
+
if (response.data.projects) {
|
|
782
|
+
response.data.projects.forEach((p) => {
|
|
783
|
+
if (!uniqueProjects.has(p.id))
|
|
784
|
+
uniqueProjects.set(p.id, { ...p, region: reg });
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
}));
|
|
788
|
+
const projects = Array.from(uniqueProjects.values());
|
|
789
|
+
return { content: [{ type: 'text', text: JSON.stringify({ projects, total: projects.length }, null, 2) }] };
|
|
790
|
+
}
|
|
791
|
+
case 'ce_get_project': {
|
|
792
|
+
const token = await getIAMToken(getApiKey());
|
|
793
|
+
const region = await getProjectRegion(args.project_id, token);
|
|
794
|
+
const response = await axios.get(`https://api.${region}.codeengine.cloud.ibm.com/v2/projects/${args.project_id}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
795
|
+
return { content: [{ type: 'text', text: JSON.stringify({ ...response.data, region }, null, 2) }] };
|
|
796
|
+
}
|
|
797
|
+
case 'ce_create_project': {
|
|
798
|
+
const token = await getIAMToken(getApiKey());
|
|
799
|
+
const region = args.region;
|
|
800
|
+
const body = { name: args.name };
|
|
801
|
+
if (args.resource_group_id)
|
|
802
|
+
body.resource_group_id = args.resource_group_id;
|
|
803
|
+
const response = await axios.post(`https://api.${region}.codeengine.cloud.ibm.com/v2/projects`, body, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } });
|
|
804
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
805
|
+
}
|
|
806
|
+
case 'ce_delete_project': {
|
|
807
|
+
const token = await getIAMToken(getApiKey());
|
|
808
|
+
const region = await getProjectRegion(args.project_id, token);
|
|
809
|
+
await axios.delete(`https://api.${region}.codeengine.cloud.ibm.com/v2/projects/${args.project_id}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
810
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Project ${args.project_id} deleted` }, null, 2) }] };
|
|
811
|
+
}
|
|
812
|
+
case 'ce_list_applications': {
|
|
813
|
+
const token = await getIAMToken(getApiKey());
|
|
814
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
815
|
+
const response = await axios.get(`${base}/apps`, { headers });
|
|
816
|
+
return { content: [{ type: 'text', text: JSON.stringify({ applications: response.data.apps || [], total: response.data.apps?.length || 0 }, null, 2) }] };
|
|
817
|
+
}
|
|
818
|
+
case 'ce_get_application': {
|
|
819
|
+
const token = await getIAMToken(getApiKey());
|
|
820
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
821
|
+
const response = await axios.get(`${base}/apps/${args.app_name}`, { headers });
|
|
822
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
823
|
+
}
|
|
824
|
+
case 'ce_create_application': {
|
|
825
|
+
const token = await getIAMToken(getApiKey());
|
|
826
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
827
|
+
const body = {
|
|
828
|
+
name: args.name,
|
|
829
|
+
image_reference: args.image,
|
|
830
|
+
image_port: args.port ?? 8080,
|
|
831
|
+
scale_min_instances: args.scale_min_instances ?? 0,
|
|
832
|
+
scale_max_instances: args.scale_max_instances ?? 10,
|
|
833
|
+
scale_cpu_limit: args.scale_cpu_limit ?? '1',
|
|
834
|
+
scale_memory_limit: args.scale_memory_limit ?? '4G',
|
|
835
|
+
};
|
|
836
|
+
if (args.env_vars) {
|
|
837
|
+
body.run_env_variables = Object.entries(args.env_vars).map(([name, value]) => ({
|
|
838
|
+
type: 'literal', name, value,
|
|
839
|
+
}));
|
|
840
|
+
}
|
|
841
|
+
const response = await axios.post(`${base}/apps`, body, { headers });
|
|
842
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
843
|
+
}
|
|
844
|
+
case 'ce_update_application': {
|
|
845
|
+
const token = await getIAMToken(getApiKey());
|
|
846
|
+
const { base } = await ceApi(args.project_id, token);
|
|
847
|
+
// CE PATCH requires If-Match with the current entity_tag
|
|
848
|
+
const current = await axios.get(`${base}/apps/${args.app_name}`, {
|
|
849
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
850
|
+
});
|
|
851
|
+
const entityTag = current.data.entity_tag ?? current.data.resource_version ?? '*';
|
|
852
|
+
const patch = {};
|
|
853
|
+
if (args.image)
|
|
854
|
+
patch.image_reference = args.image;
|
|
855
|
+
if (args.scale_min_instances !== undefined)
|
|
856
|
+
patch.scale_min_instances = args.scale_min_instances;
|
|
857
|
+
if (args.scale_max_instances !== undefined)
|
|
858
|
+
patch.scale_max_instances = args.scale_max_instances;
|
|
859
|
+
if (args.scale_cpu_limit)
|
|
860
|
+
patch.scale_cpu_limit = args.scale_cpu_limit;
|
|
861
|
+
if (args.scale_memory_limit)
|
|
862
|
+
patch.scale_memory_limit = args.scale_memory_limit;
|
|
863
|
+
const response = await axios.patch(`${base}/apps/${args.app_name}`, patch, {
|
|
864
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/merge-patch+json', 'If-Match': entityTag },
|
|
865
|
+
});
|
|
866
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
867
|
+
}
|
|
868
|
+
case 'ce_delete_application': {
|
|
869
|
+
const token = await getIAMToken(getApiKey());
|
|
870
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
871
|
+
await axios.delete(`${base}/apps/${args.app_name}`, { headers });
|
|
872
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Application ${args.app_name} deleted` }, null, 2) }] };
|
|
873
|
+
}
|
|
874
|
+
case 'ce_list_app_instances': {
|
|
875
|
+
const token = await getIAMToken(getApiKey());
|
|
876
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
877
|
+
const response = await axios.get(`${base}/apps/${args.app_name}/instances`, { headers });
|
|
878
|
+
return { content: [{ type: 'text', text: JSON.stringify({ instances: response.data.instances || [], total: response.data.instances?.length || 0 }, null, 2) }] };
|
|
879
|
+
}
|
|
880
|
+
case 'ce_get_app_logs': {
|
|
881
|
+
const token = await getIAMToken(getApiKey());
|
|
882
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
883
|
+
try {
|
|
884
|
+
const response = await axios.get(`${base}/apps/${args.app_name}/instances/${args.instance_name}/logs`, { headers });
|
|
885
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
886
|
+
}
|
|
887
|
+
catch (err) {
|
|
888
|
+
if (err.response?.status === 403) {
|
|
889
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
890
|
+
note: 'App instance logs are not accessible via the Code Engine REST API v2. ' +
|
|
891
|
+
'Configure IBM Log Analysis (IBM Cloud Logging) for your project to retrieve logs via the IBM Cloud Logs API.',
|
|
892
|
+
docs: 'https://cloud.ibm.com/docs/codeengine?topic=codeengine-view-logs',
|
|
893
|
+
status: 403,
|
|
894
|
+
}, null, 2) }] };
|
|
895
|
+
}
|
|
896
|
+
throw err;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
case 'ce_list_builds': {
|
|
900
|
+
const token = await getIAMToken(getApiKey());
|
|
901
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
902
|
+
const response = await axios.get(`${base}/builds`, { headers });
|
|
903
|
+
return { content: [{ type: 'text', text: JSON.stringify({ builds: response.data.builds || [], total: response.data.builds?.length || 0 }, null, 2) }] };
|
|
904
|
+
}
|
|
905
|
+
case 'ce_get_build': {
|
|
906
|
+
const token = await getIAMToken(getApiKey());
|
|
907
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
908
|
+
const response = await axios.get(`${base}/builds/${args.build_name}`, { headers });
|
|
909
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
910
|
+
}
|
|
911
|
+
case 'ce_delete_build': {
|
|
912
|
+
const token = await getIAMToken(getApiKey());
|
|
913
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
914
|
+
await axios.delete(`${base}/builds/${args.build_name}`, { headers });
|
|
915
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Build ${args.build_name} deleted` }, null, 2) }] };
|
|
916
|
+
}
|
|
917
|
+
case 'ce_create_build': {
|
|
918
|
+
const token = await getIAMToken(getApiKey());
|
|
919
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
920
|
+
const body = {
|
|
921
|
+
name: args.name,
|
|
922
|
+
output_image: args.output_image,
|
|
923
|
+
output_secret: args.output_secret,
|
|
924
|
+
source_type: args.source_type ?? 'local',
|
|
925
|
+
strategy_type: args.strategy_type ?? 'dockerfile',
|
|
926
|
+
strategy_spec_file: args.strategy_spec_file ?? 'Dockerfile',
|
|
927
|
+
strategy_size: args.strategy_size ?? 'medium',
|
|
928
|
+
};
|
|
929
|
+
const response = await axios.post(`${base}/builds`, body, { headers });
|
|
930
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
931
|
+
}
|
|
932
|
+
case 'ce_list_build_runs': {
|
|
933
|
+
const token = await getIAMToken(getApiKey());
|
|
934
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
935
|
+
const response = await axios.get(`${base}/build_runs`, { headers });
|
|
936
|
+
return { content: [{ type: 'text', text: JSON.stringify({ build_runs: response.data.build_runs || [], total: response.data.build_runs?.length || 0 }, null, 2) }] };
|
|
937
|
+
}
|
|
938
|
+
case 'ce_create_build_run': {
|
|
939
|
+
const token = await getIAMToken(getApiKey());
|
|
940
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
941
|
+
const body = { build_name: args.build_name };
|
|
942
|
+
if (args.name)
|
|
943
|
+
body.name = args.name;
|
|
944
|
+
const response = await axios.post(`${base}/build_runs`, body, { headers });
|
|
945
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
946
|
+
}
|
|
947
|
+
case 'ce_get_build_run': {
|
|
948
|
+
const token = await getIAMToken(getApiKey());
|
|
949
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
950
|
+
const response = await axios.get(`${base}/build_runs/${args.build_run_name}`, { headers });
|
|
951
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
952
|
+
}
|
|
953
|
+
case 'ce_delete_build_run': {
|
|
954
|
+
const token = await getIAMToken(getApiKey());
|
|
955
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
956
|
+
await axios.delete(`${base}/build_runs/${args.build_run_name}`, { headers });
|
|
957
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Build run ${args.build_run_name} deleted` }, null, 2) }] };
|
|
958
|
+
}
|
|
959
|
+
case 'ce_list_jobs': {
|
|
960
|
+
const token = await getIAMToken(getApiKey());
|
|
961
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
962
|
+
const response = await axios.get(`${base}/jobs`, { headers });
|
|
963
|
+
return { content: [{ type: 'text', text: JSON.stringify({ jobs: response.data.jobs || [], total: response.data.jobs?.length || 0 }, null, 2) }] };
|
|
964
|
+
}
|
|
965
|
+
case 'ce_create_job': {
|
|
966
|
+
const token = await getIAMToken(getApiKey());
|
|
967
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
968
|
+
const body = { name: args.name, image_reference: args.image, run_mode: 'task' };
|
|
969
|
+
if (args.scale_array_spec)
|
|
970
|
+
body.scale_array_spec = args.scale_array_spec;
|
|
971
|
+
if (args.scale_cpu_limit)
|
|
972
|
+
body.scale_cpu_limit = args.scale_cpu_limit;
|
|
973
|
+
if (args.scale_memory_limit)
|
|
974
|
+
body.scale_memory_limit = args.scale_memory_limit;
|
|
975
|
+
if (args.env_vars) {
|
|
976
|
+
body.run_env_variables = Object.entries(args.env_vars).map(([name, value]) => ({
|
|
977
|
+
type: 'literal', name, value,
|
|
978
|
+
}));
|
|
979
|
+
}
|
|
980
|
+
const response = await axios.post(`${base}/jobs`, body, { headers });
|
|
981
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
982
|
+
}
|
|
983
|
+
case 'ce_create_job_run': {
|
|
984
|
+
const token = await getIAMToken(getApiKey());
|
|
985
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
986
|
+
const body = { job_name: args.job_name };
|
|
987
|
+
if (args.name)
|
|
988
|
+
body.name = args.name;
|
|
989
|
+
const response = await axios.post(`${base}/job_runs`, body, { headers });
|
|
990
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
991
|
+
}
|
|
992
|
+
case 'ce_get_job': {
|
|
993
|
+
const token = await getIAMToken(getApiKey());
|
|
994
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
995
|
+
const response = await axios.get(`${base}/jobs/${args.job_name}`, { headers });
|
|
996
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
997
|
+
}
|
|
998
|
+
case 'ce_delete_job': {
|
|
999
|
+
const token = await getIAMToken(getApiKey());
|
|
1000
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1001
|
+
await axios.delete(`${base}/jobs/${args.job_name}`, { headers });
|
|
1002
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Job ${args.job_name} deleted` }, null, 2) }] };
|
|
1003
|
+
}
|
|
1004
|
+
case 'ce_list_job_runs': {
|
|
1005
|
+
const token = await getIAMToken(getApiKey());
|
|
1006
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1007
|
+
const params = {};
|
|
1008
|
+
if (args.job_name)
|
|
1009
|
+
params.job_name = args.job_name;
|
|
1010
|
+
if (args.limit)
|
|
1011
|
+
params.limit = args.limit;
|
|
1012
|
+
if (args.start)
|
|
1013
|
+
params.start = args.start;
|
|
1014
|
+
const response = await axios.get(`${base}/job_runs`, { headers, params });
|
|
1015
|
+
return { content: [{ type: 'text', text: JSON.stringify({ job_runs: response.data.job_runs || [], total: response.data.job_runs?.length || 0 }, null, 2) }] };
|
|
1016
|
+
}
|
|
1017
|
+
case 'ce_get_job_run': {
|
|
1018
|
+
const token = await getIAMToken(getApiKey());
|
|
1019
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1020
|
+
const response = await axios.get(`${base}/job_runs/${args.job_run_name}`, { headers });
|
|
1021
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
1022
|
+
}
|
|
1023
|
+
case 'ce_delete_job_run': {
|
|
1024
|
+
const token = await getIAMToken(getApiKey());
|
|
1025
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1026
|
+
await axios.delete(`${base}/job_runs/${args.job_run_name}`, { headers });
|
|
1027
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Job run ${args.job_run_name} deleted` }, null, 2) }] };
|
|
1028
|
+
}
|
|
1029
|
+
case 'ce_list_secrets': {
|
|
1030
|
+
const token = await getIAMToken(getApiKey());
|
|
1031
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1032
|
+
const response = await axios.get(`${base}/secrets`, { headers });
|
|
1033
|
+
// Omit secret data values from the listing for security
|
|
1034
|
+
const secrets = (response.data.secrets || []).map((s) => ({
|
|
1035
|
+
name: s.name, format: s.format, created_at: s.created_at,
|
|
1036
|
+
keys: s.data ? Object.keys(s.data) : [],
|
|
1037
|
+
}));
|
|
1038
|
+
return { content: [{ type: 'text', text: JSON.stringify({ secrets, total: secrets.length }, null, 2) }] };
|
|
1039
|
+
}
|
|
1040
|
+
case 'ce_create_secret': {
|
|
1041
|
+
const token = await getIAMToken(getApiKey());
|
|
1042
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1043
|
+
const body = { name: args.name, format: args.format, data: args.data };
|
|
1044
|
+
const response = await axios.post(`${base}/secrets`, body, { headers });
|
|
1045
|
+
return { content: [{ type: 'text', text: JSON.stringify({ name: response.data.name, format: response.data.format, created_at: response.data.created_at }, null, 2) }] };
|
|
1046
|
+
}
|
|
1047
|
+
case 'ce_get_secret': {
|
|
1048
|
+
const token = await getIAMToken(getApiKey());
|
|
1049
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1050
|
+
const response = await axios.get(`${base}/secrets/${args.secret_name}`, { headers });
|
|
1051
|
+
// Return metadata only — omit secret payload for security
|
|
1052
|
+
const s = response.data;
|
|
1053
|
+
return { content: [{ type: 'text', text: JSON.stringify({ name: s.name, format: s.format, created_at: s.created_at, keys: s.data ? Object.keys(s.data) : [] }, null, 2) }] };
|
|
1054
|
+
}
|
|
1055
|
+
case 'ce_delete_secret': {
|
|
1056
|
+
const token = await getIAMToken(getApiKey());
|
|
1057
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1058
|
+
await axios.delete(`${base}/secrets/${args.secret_name}`, { headers });
|
|
1059
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `Secret ${args.secret_name} deleted` }, null, 2) }] };
|
|
1060
|
+
}
|
|
1061
|
+
case 'ce_list_config_maps': {
|
|
1062
|
+
const token = await getIAMToken(getApiKey());
|
|
1063
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1064
|
+
const response = await axios.get(`${base}/config_maps`, { headers });
|
|
1065
|
+
return { content: [{ type: 'text', text: JSON.stringify({ config_maps: response.data.config_maps || [], total: response.data.config_maps?.length || 0 }, null, 2) }] };
|
|
1066
|
+
}
|
|
1067
|
+
case 'ce_create_config_map': {
|
|
1068
|
+
const token = await getIAMToken(getApiKey());
|
|
1069
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1070
|
+
const body = { name: args.name, data: args.data };
|
|
1071
|
+
const response = await axios.post(`${base}/config_maps`, body, { headers });
|
|
1072
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
1073
|
+
}
|
|
1074
|
+
case 'ce_get_config_map': {
|
|
1075
|
+
const token = await getIAMToken(getApiKey());
|
|
1076
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1077
|
+
const response = await axios.get(`${base}/config_maps/${args.config_map_name}`, { headers });
|
|
1078
|
+
return { content: [{ type: 'text', text: JSON.stringify(response.data, null, 2) }] };
|
|
1079
|
+
}
|
|
1080
|
+
case 'ce_delete_config_map': {
|
|
1081
|
+
const token = await getIAMToken(getApiKey());
|
|
1082
|
+
const { base, headers } = await ceApi(args.project_id, token);
|
|
1083
|
+
await axios.delete(`${base}/config_maps/${args.config_map_name}`, { headers });
|
|
1084
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, message: `ConfigMap ${args.config_map_name} deleted` }, null, 2) }] };
|
|
1085
|
+
}
|
|
1086
|
+
default:
|
|
1087
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
catch (error) {
|
|
1091
|
+
return {
|
|
1092
|
+
content: [
|
|
1093
|
+
{
|
|
1094
|
+
type: 'text',
|
|
1095
|
+
text: JSON.stringify({
|
|
1096
|
+
error: error.message,
|
|
1097
|
+
stderr: error.stderr,
|
|
1098
|
+
stdout: error.stdout
|
|
1099
|
+
}, null, 2),
|
|
1100
|
+
},
|
|
1101
|
+
],
|
|
1102
|
+
isError: true,
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
// Start server
|
|
1107
|
+
async function main() {
|
|
1108
|
+
const transport = new StdioServerTransport();
|
|
1109
|
+
await server.connect(transport);
|
|
1110
|
+
console.error('Code Engine MCP Server running on stdio');
|
|
1111
|
+
}
|
|
1112
|
+
main().catch((error) => {
|
|
1113
|
+
console.error('Server error:', error);
|
|
1114
|
+
process.exit(1);
|
|
1115
|
+
});
|
|
1116
|
+
// Made by MVK
|
|
1117
|
+
//# sourceMappingURL=index.js.map
|