@vibetools/dokploy-mcp 2.1.1 → 2.2.1
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 +102 -12
- package/dist/codemode/context/execute-context.d.ts +77 -572
- package/dist/codemode/context/execute-context.js +28 -1
- package/dist/codemode/context/search-context.d.ts +9 -13831
- package/dist/codemode/context/search-context.js +45 -9
- package/dist/codemode/gateway/api-gateway.js +96 -6
- package/dist/codemode/overrides/catalog-overrides.d.ts +10 -0
- package/dist/codemode/overrides/catalog-overrides.js +79 -0
- package/dist/codemode/overrides/procedure-overrides.d.ts +22166 -0
- package/dist/codemode/overrides/procedure-overrides.js +308 -0
- package/dist/codemode/overrides/virtual-procedures.d.ts +29 -0
- package/dist/codemode/overrides/virtual-procedures.js +336 -0
- package/dist/codemode/sandbox/worker-entry.js +23 -6
- package/dist/codemode/tools/execute.d.ts +1 -572
- package/dist/codemode/tools/execute.js +1 -0
- package/dist/codemode/tools/search.js +2 -1
- package/package.json +1 -1
|
@@ -1,29 +1,61 @@
|
|
|
1
|
-
import { dokployCatalog
|
|
1
|
+
import { dokployCatalog } from '../../generated/dokploy-catalog.js';
|
|
2
2
|
import { procedureSchemas } from '../../generated/dokploy-schemas.js';
|
|
3
|
+
import { applyCatalogResponseHints, getCatalogResponseHints, } from '../overrides/catalog-overrides.js';
|
|
4
|
+
import { applyProcedureInputMetadata, getEffectiveProcedureSchema, } from '../overrides/procedure-overrides.js';
|
|
5
|
+
import { getVirtualCatalogEndpoints, getVirtualProcedureSchema, } from '../overrides/virtual-procedures.js';
|
|
6
|
+
function createCatalogEndpointView(endpoint) {
|
|
7
|
+
return applyCatalogResponseHints(applyProcedureInputMetadata(endpoint));
|
|
8
|
+
}
|
|
9
|
+
function createCatalogIndexes(endpoints) {
|
|
10
|
+
const byTag = {};
|
|
11
|
+
const byProcedure = {};
|
|
12
|
+
const byPath = {};
|
|
13
|
+
for (const [index, endpoint] of endpoints.entries()) {
|
|
14
|
+
byProcedure[endpoint.procedure] = index;
|
|
15
|
+
byPath[endpoint.path] = index;
|
|
16
|
+
const tagIndexes = byTag[endpoint.tag] ?? [];
|
|
17
|
+
tagIndexes.push(index);
|
|
18
|
+
byTag[endpoint.tag] = tagIndexes;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
byTag,
|
|
22
|
+
byProcedure,
|
|
23
|
+
byPath,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
3
26
|
export function createSearchCatalogView() {
|
|
27
|
+
const endpoints = [
|
|
28
|
+
...dokployCatalog.endpoints.map(createCatalogEndpointView),
|
|
29
|
+
...getVirtualCatalogEndpoints().map(applyCatalogResponseHints),
|
|
30
|
+
];
|
|
31
|
+
const indexes = createCatalogIndexes(endpoints);
|
|
4
32
|
return {
|
|
5
|
-
endpoints
|
|
6
|
-
byTag:
|
|
7
|
-
byProcedure:
|
|
8
|
-
byPath:
|
|
33
|
+
endpoints,
|
|
34
|
+
byTag: indexes.byTag,
|
|
35
|
+
byProcedure: indexes.byProcedure,
|
|
36
|
+
byPath: indexes.byPath,
|
|
9
37
|
get: (id) => {
|
|
10
|
-
const
|
|
38
|
+
const index = indexes.byProcedure[id] ?? indexes.byPath[id];
|
|
39
|
+
const endpoint = index === undefined ? null : endpoints[index];
|
|
11
40
|
if (!endpoint)
|
|
12
41
|
return null;
|
|
13
42
|
const procedure = endpoint.procedure;
|
|
14
|
-
const schema =
|
|
43
|
+
const schema = getVirtualProcedureSchema(procedure) ??
|
|
44
|
+
getEffectiveProcedureSchema(procedure) ??
|
|
45
|
+
procedureSchemas[procedure];
|
|
15
46
|
return {
|
|
16
47
|
...endpoint,
|
|
17
48
|
inputSchema: schema?.inputSchema ?? null,
|
|
18
49
|
outputSchema: schema?.outputSchema ?? null,
|
|
19
50
|
};
|
|
20
51
|
},
|
|
21
|
-
getByTag: (tag) =>
|
|
52
|
+
getByTag: (tag) => (indexes.byTag[tag] ?? []).map((index) => endpoints[index]).filter(Boolean),
|
|
22
53
|
searchText: (query) => {
|
|
23
54
|
const normalized = query.trim().toLowerCase();
|
|
24
55
|
if (normalized.length === 0)
|
|
25
56
|
return [];
|
|
26
|
-
return
|
|
57
|
+
return endpoints.filter((endpoint) => {
|
|
58
|
+
const hints = getCatalogResponseHints(endpoint.procedure);
|
|
27
59
|
const haystack = [
|
|
28
60
|
endpoint.procedure,
|
|
29
61
|
endpoint.path,
|
|
@@ -32,6 +64,10 @@ export function createSearchCatalogView() {
|
|
|
32
64
|
endpoint.description ?? '',
|
|
33
65
|
...endpoint.requiredInputs,
|
|
34
66
|
...endpoint.optionalInputs,
|
|
67
|
+
...(hints?.commonResponseFields ?? []),
|
|
68
|
+
...(hints?.responseHints ?? []),
|
|
69
|
+
...(hints?.examples ?? []),
|
|
70
|
+
...(hints?.notes ?? []),
|
|
35
71
|
]
|
|
36
72
|
.join(' ')
|
|
37
73
|
.toLowerCase();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ApiError, api } from '../../api/client.js';
|
|
2
2
|
import { procedureSchemas } from '../../generated/dokploy-schemas.js';
|
|
3
|
+
import { getEffectiveProcedureSchema, mapProcedureInput, transformProcedureResponse, validateProcedureInput, } from '../overrides/procedure-overrides.js';
|
|
3
4
|
import { formatGatewayError } from './error-format.js';
|
|
4
5
|
import { finishTrace, startTrace } from './trace.js';
|
|
5
6
|
const RETRYABLE_STATUS_CODES = new Set([429, 502, 503, 504]);
|
|
@@ -50,10 +51,11 @@ function validateTypedSchema(value, schemaObject, path) {
|
|
|
50
51
|
case 'array':
|
|
51
52
|
return validateArraySchema(value, schemaObject, path);
|
|
52
53
|
case 'string':
|
|
53
|
-
return
|
|
54
|
+
return validateStringSchema(value, schemaObject, path);
|
|
54
55
|
case 'number':
|
|
56
|
+
return validateNumberSchema(value, schemaObject, path);
|
|
55
57
|
case 'integer':
|
|
56
|
-
return
|
|
58
|
+
return validateIntegerSchema(value, schemaObject, path);
|
|
57
59
|
case 'boolean':
|
|
58
60
|
return validatePrimitive(value, 'boolean', path);
|
|
59
61
|
case 'null':
|
|
@@ -69,12 +71,36 @@ function validateObjectSchema(value, schemaObject, path) {
|
|
|
69
71
|
const objectValue = value;
|
|
70
72
|
const properties = schemaObject.properties ?? {};
|
|
71
73
|
const required = schemaObject.required ?? [];
|
|
74
|
+
const additionalProperties = schemaObject.additionalProperties;
|
|
75
|
+
return [
|
|
76
|
+
...validateRequiredObjectKeys(objectValue, required, path),
|
|
77
|
+
...validateUnexpectedObjectKeys(objectValue, properties, additionalProperties, path),
|
|
78
|
+
...validateObjectProperties(objectValue, properties, path),
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
function validateRequiredObjectKeys(objectValue, required, path) {
|
|
72
82
|
const errors = [];
|
|
73
83
|
for (const key of required) {
|
|
74
84
|
if (!(key in objectValue) || objectValue[key] == null) {
|
|
75
85
|
errors.push(`${path ? `${path}.` : ''}${key} is required`);
|
|
76
86
|
}
|
|
77
87
|
}
|
|
88
|
+
return errors;
|
|
89
|
+
}
|
|
90
|
+
function validateUnexpectedObjectKeys(objectValue, properties, additionalProperties, path) {
|
|
91
|
+
if (additionalProperties !== false) {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
const errors = [];
|
|
95
|
+
for (const key of Object.keys(objectValue)) {
|
|
96
|
+
if (!(key in properties)) {
|
|
97
|
+
errors.push(`${path ? `${path}.` : ''}${key} is not allowed`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return errors;
|
|
101
|
+
}
|
|
102
|
+
function validateObjectProperties(objectValue, properties, path) {
|
|
103
|
+
const errors = [];
|
|
78
104
|
for (const [key, propertySchema] of Object.entries(properties)) {
|
|
79
105
|
if (key in objectValue) {
|
|
80
106
|
errors.push(...validateAgainstSchema(objectValue[key], propertySchema, path ? `${path}.${key}` : key));
|
|
@@ -88,6 +114,14 @@ function validateArraySchema(value, schemaObject, path) {
|
|
|
88
114
|
}
|
|
89
115
|
const itemSchema = schemaObject.items;
|
|
90
116
|
const errors = [];
|
|
117
|
+
const minItems = typeof schemaObject.minItems === 'number' ? schemaObject.minItems : undefined;
|
|
118
|
+
const maxItems = typeof schemaObject.maxItems === 'number' ? schemaObject.maxItems : undefined;
|
|
119
|
+
if (minItems !== undefined && value.length < minItems) {
|
|
120
|
+
errors.push(`${path || 'value'} must contain at least ${minItems} items`);
|
|
121
|
+
}
|
|
122
|
+
if (maxItems !== undefined && value.length > maxItems) {
|
|
123
|
+
errors.push(`${path || 'value'} must contain at most ${maxItems} items`);
|
|
124
|
+
}
|
|
91
125
|
for (const [index, entry] of value.entries()) {
|
|
92
126
|
errors.push(...validateAgainstSchema(entry, itemSchema, `${path || 'value'}[${index}]`));
|
|
93
127
|
}
|
|
@@ -96,8 +130,61 @@ function validateArraySchema(value, schemaObject, path) {
|
|
|
96
130
|
function validatePrimitive(value, type, path) {
|
|
97
131
|
return typeof value === type ? [] : [`${path || 'value'} must be a ${type}`];
|
|
98
132
|
}
|
|
133
|
+
function validateStringSchema(value, schemaObject, path) {
|
|
134
|
+
const primitiveErrors = validatePrimitive(value, 'string', path);
|
|
135
|
+
if (primitiveErrors.length > 0) {
|
|
136
|
+
return primitiveErrors;
|
|
137
|
+
}
|
|
138
|
+
const stringValue = value;
|
|
139
|
+
const errors = [];
|
|
140
|
+
const minLength = typeof schemaObject.minLength === 'number' ? schemaObject.minLength : undefined;
|
|
141
|
+
const maxLength = typeof schemaObject.maxLength === 'number' ? schemaObject.maxLength : undefined;
|
|
142
|
+
const pattern = typeof schemaObject.pattern === 'string' ? schemaObject.pattern : undefined;
|
|
143
|
+
if (minLength !== undefined && stringValue.length < minLength) {
|
|
144
|
+
errors.push(`${path || 'value'} must have length >= ${minLength}`);
|
|
145
|
+
}
|
|
146
|
+
if (maxLength !== undefined && stringValue.length > maxLength) {
|
|
147
|
+
errors.push(`${path || 'value'} must have length <= ${maxLength}`);
|
|
148
|
+
}
|
|
149
|
+
if (pattern && !new RegExp(pattern).test(stringValue)) {
|
|
150
|
+
errors.push(`${path || 'value'} must match pattern ${pattern}`);
|
|
151
|
+
}
|
|
152
|
+
return errors;
|
|
153
|
+
}
|
|
154
|
+
function validateNumberSchema(value, schemaObject, path) {
|
|
155
|
+
const primitiveErrors = validatePrimitive(value, 'number', path);
|
|
156
|
+
if (primitiveErrors.length > 0) {
|
|
157
|
+
return primitiveErrors;
|
|
158
|
+
}
|
|
159
|
+
return validateNumericBounds(value, schemaObject, path);
|
|
160
|
+
}
|
|
161
|
+
function validateIntegerSchema(value, schemaObject, path) {
|
|
162
|
+
const primitiveErrors = validatePrimitive(value, 'number', path);
|
|
163
|
+
if (primitiveErrors.length > 0) {
|
|
164
|
+
return primitiveErrors;
|
|
165
|
+
}
|
|
166
|
+
const numberValue = value;
|
|
167
|
+
const errors = [];
|
|
168
|
+
if (!Number.isInteger(numberValue)) {
|
|
169
|
+
errors.push(`${path || 'value'} must be an integer`);
|
|
170
|
+
}
|
|
171
|
+
errors.push(...validateNumericBounds(numberValue, schemaObject, path));
|
|
172
|
+
return errors;
|
|
173
|
+
}
|
|
174
|
+
function validateNumericBounds(value, schemaObject, path) {
|
|
175
|
+
const errors = [];
|
|
176
|
+
const minimum = typeof schemaObject.minimum === 'number' ? schemaObject.minimum : undefined;
|
|
177
|
+
const maximum = typeof schemaObject.maximum === 'number' ? schemaObject.maximum : undefined;
|
|
178
|
+
if (minimum !== undefined && value < minimum) {
|
|
179
|
+
errors.push(`${path || 'value'} must be >= ${minimum}`);
|
|
180
|
+
}
|
|
181
|
+
if (maximum !== undefined && value > maximum) {
|
|
182
|
+
errors.push(`${path || 'value'} must be <= ${maximum}`);
|
|
183
|
+
}
|
|
184
|
+
return errors;
|
|
185
|
+
}
|
|
99
186
|
export async function invokeProcedureWithApi(procedure, input = {}, requestApi = api) {
|
|
100
|
-
const schema = procedureSchemas[procedure];
|
|
187
|
+
const schema = getEffectiveProcedureSchema(procedure) ?? procedureSchemas[procedure];
|
|
101
188
|
if (!schema) {
|
|
102
189
|
throw formatGatewayError({
|
|
103
190
|
type: 'validation_error',
|
|
@@ -106,6 +193,7 @@ export async function invokeProcedureWithApi(procedure, input = {}, requestApi =
|
|
|
106
193
|
});
|
|
107
194
|
}
|
|
108
195
|
const validationErrors = validateAgainstSchema(input, schema.inputSchema);
|
|
196
|
+
validationErrors.push(...validateProcedureInput(procedure, input));
|
|
109
197
|
if (validationErrors.length > 0) {
|
|
110
198
|
throw formatGatewayError({
|
|
111
199
|
type: 'validation_error',
|
|
@@ -115,13 +203,15 @@ export async function invokeProcedureWithApi(procedure, input = {}, requestApi =
|
|
|
115
203
|
}
|
|
116
204
|
const trace = startTrace(procedure, schema.method);
|
|
117
205
|
const maxRetries = resolveGatewayRetryCount();
|
|
206
|
+
const requestInput = mapProcedureInput(procedure, input);
|
|
118
207
|
try {
|
|
119
208
|
let attempt = 0;
|
|
120
209
|
while (true) {
|
|
121
210
|
try {
|
|
122
|
-
const
|
|
123
|
-
? await requestApi.get(schema.path,
|
|
124
|
-
: await requestApi.post(schema.path,
|
|
211
|
+
const response = schema.method === 'GET'
|
|
212
|
+
? await requestApi.get(schema.path, requestInput)
|
|
213
|
+
: await requestApi.post(schema.path, requestInput);
|
|
214
|
+
const data = transformProcedureResponse(procedure, input, response);
|
|
125
215
|
return {
|
|
126
216
|
data,
|
|
127
217
|
trace: finishTrace(trace),
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CatalogEndpoint } from '../../generated/dokploy-catalog.js';
|
|
2
|
+
export interface CatalogResponseHints {
|
|
3
|
+
commonResponseFields?: string[];
|
|
4
|
+
responseHints?: string[];
|
|
5
|
+
examples?: string[];
|
|
6
|
+
notes?: string[];
|
|
7
|
+
}
|
|
8
|
+
export type CatalogEndpointWithHints = CatalogEndpoint & CatalogResponseHints;
|
|
9
|
+
export declare function getCatalogResponseHints(procedure: string): CatalogResponseHints | null;
|
|
10
|
+
export declare function applyCatalogResponseHints(endpoint: CatalogEndpoint): CatalogEndpointWithHints;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const catalogResponseHints = {
|
|
2
|
+
'application.one': {
|
|
3
|
+
commonResponseFields: [
|
|
4
|
+
'name',
|
|
5
|
+
'appName',
|
|
6
|
+
'applicationStatus',
|
|
7
|
+
'mounts',
|
|
8
|
+
'watchPaths',
|
|
9
|
+
'domains',
|
|
10
|
+
'deployments',
|
|
11
|
+
],
|
|
12
|
+
responseHints: [
|
|
13
|
+
'Heavy detail endpoint for application config, runtime status, mounts, domains, and deployment history.',
|
|
14
|
+
'Deployment history can dominate token usage because entries may include long commit messages.',
|
|
15
|
+
],
|
|
16
|
+
examples: [
|
|
17
|
+
'await dokploy.application.one({ applicationId: "app-123" })',
|
|
18
|
+
'await dokploy.application.one({ applicationId: "app-123", select: ["name", "watchPaths"], deploymentLimit: 1 })',
|
|
19
|
+
'catalog.get("application.one")',
|
|
20
|
+
],
|
|
21
|
+
notes: [
|
|
22
|
+
'Generated OpenAPI output schema is currently incomplete for this endpoint, so these are common observed fields rather than a full contract.',
|
|
23
|
+
'MCP adds optional shaping inputs for this endpoint: select, includeDeployments, and deploymentLimit.',
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
'application.many': {
|
|
27
|
+
commonResponseFields: ['items', 'total'],
|
|
28
|
+
responseHints: [
|
|
29
|
+
'MCP-only virtual helper that reads several applications by delegating to application.one.',
|
|
30
|
+
'Preserves input order and supports the same shaping inputs as application.one.',
|
|
31
|
+
],
|
|
32
|
+
examples: [
|
|
33
|
+
'await dokploy.application.many({ applicationIds: ["app-1", "app-2"], select: ["name", "watchPaths"] })',
|
|
34
|
+
],
|
|
35
|
+
notes: [
|
|
36
|
+
'This helper is available in execute workflows and is not backed by a Dokploy HTTP endpoint.',
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
'project.all': {
|
|
40
|
+
commonResponseFields: ['projectId', 'name', 'environments'],
|
|
41
|
+
responseHints: ['Commonly returns projects with nested environments and service references.'],
|
|
42
|
+
examples: ['await dokploy.project.all({})'],
|
|
43
|
+
},
|
|
44
|
+
'project.overview': {
|
|
45
|
+
commonResponseFields: ['projectId', 'name', 'environments'],
|
|
46
|
+
responseHints: [
|
|
47
|
+
'MCP-only virtual helper that returns a compact per-environment and per-application project state view.',
|
|
48
|
+
'Per application it focuses on applicationId, name, appName, applicationStatus, domains, mounts, watchPaths, and lastDeployment.',
|
|
49
|
+
],
|
|
50
|
+
examples: ['await dokploy.project.overview({ projectId: "project-1" })'],
|
|
51
|
+
notes: [
|
|
52
|
+
'This helper is available in execute workflows and is not backed by a Dokploy HTTP endpoint.',
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
'project.one': {
|
|
56
|
+
commonResponseFields: ['projectId', 'name', 'description', 'environments'],
|
|
57
|
+
responseHints: ['Project detail endpoint used to inspect one project and its environments.'],
|
|
58
|
+
notes: [
|
|
59
|
+
'Generated OpenAPI output schema is currently incomplete for this endpoint, so nested service details may not be visible from the schema alone.',
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
'deployment.all': {
|
|
63
|
+
commonResponseFields: ['deploymentId', 'title', 'status', 'createdAt'],
|
|
64
|
+
responseHints: ['Returns deployment history entries ordered for inspection workflows.'],
|
|
65
|
+
examples: ['await dokploy.deployment.all({ applicationId: "app-123" })'],
|
|
66
|
+
},
|
|
67
|
+
'compose.search': {
|
|
68
|
+
commonResponseFields: ['items', 'total'],
|
|
69
|
+
responseHints: ['Search endpoints commonly return paginated results with items and total.'],
|
|
70
|
+
examples: ['await dokploy.compose.search({ name: "wordpress", limit: 5 })'],
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
export function getCatalogResponseHints(procedure) {
|
|
74
|
+
return catalogResponseHints[procedure] ?? null;
|
|
75
|
+
}
|
|
76
|
+
export function applyCatalogResponseHints(endpoint) {
|
|
77
|
+
const hints = getCatalogResponseHints(endpoint.procedure);
|
|
78
|
+
return hints ? { ...endpoint, ...hints } : endpoint;
|
|
79
|
+
}
|