@vibetools/dokploy-mcp 2.1.1 → 2.2.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.
@@ -1,29 +1,61 @@
1
- import { dokployCatalog, getCatalogEndpoint, getCatalogEndpointsByTag, } from '../../generated/dokploy-catalog.js';
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: dokployCatalog.endpoints,
6
- byTag: dokployCatalog.byTag,
7
- byProcedure: dokployCatalog.byProcedure,
8
- byPath: dokployCatalog.byPath,
33
+ endpoints,
34
+ byTag: indexes.byTag,
35
+ byProcedure: indexes.byProcedure,
36
+ byPath: indexes.byPath,
9
37
  get: (id) => {
10
- const endpoint = getCatalogEndpoint(id);
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 = procedureSchemas[procedure];
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) => getCatalogEndpointsByTag(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 dokployCatalog.endpoints.filter((endpoint) => {
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 validatePrimitive(value, 'string', path);
54
+ return validateStringSchema(value, schemaObject, path);
54
55
  case 'number':
56
+ return validateNumberSchema(value, schemaObject, path);
55
57
  case 'integer':
56
- return validatePrimitive(value, 'number', path);
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 data = schema.method === 'GET'
123
- ? await requestApi.get(schema.path, input)
124
- : await requestApi.post(schema.path, input);
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
+ }