@formio/mcp 0.1.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 +146 -0
- package/dist/auth-header.d.ts +2 -0
- package/dist/auth-header.js +9 -0
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +148 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +29 -0
- package/dist/ensure-auth.d.ts +4 -0
- package/dist/ensure-auth.js +56 -0
- package/dist/formio-client.d.ts +11 -0
- package/dist/formio-client.js +60 -0
- package/dist/mcp-responses.d.ts +13 -0
- package/dist/mcp-responses.js +12 -0
- package/dist/project-map.d.ts +5 -0
- package/dist/project-map.js +29 -0
- package/dist/project-resolver.d.ts +4 -0
- package/dist/project-resolver.js +35 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +9 -0
- package/dist/stdio.d.ts +2 -0
- package/dist/stdio.js +8 -0
- package/dist/token-cache.d.ts +3 -0
- package/dist/token-cache.js +34 -0
- package/dist/token-validation.d.ts +2 -0
- package/dist/token-validation.js +6 -0
- package/dist/tools/action-schema.d.ts +39 -0
- package/dist/tools/action-schema.js +44 -0
- package/dist/tools/action_create.d.ts +3 -0
- package/dist/tools/action_create.js +37 -0
- package/dist/tools/action_delete.d.ts +3 -0
- package/dist/tools/action_delete.js +22 -0
- package/dist/tools/action_get.d.ts +3 -0
- package/dist/tools/action_get.js +20 -0
- package/dist/tools/action_list.d.ts +3 -0
- package/dist/tools/action_list.js +19 -0
- package/dist/tools/action_type_get.d.ts +3 -0
- package/dist/tools/action_type_get.js +29 -0
- package/dist/tools/action_types_list.d.ts +3 -0
- package/dist/tools/action_types_list.js +19 -0
- package/dist/tools/action_update.d.ts +3 -0
- package/dist/tools/action_update.js +25 -0
- package/dist/tools/form_create.d.ts +3 -0
- package/dist/tools/form_create.js +38 -0
- package/dist/tools/form_get.d.ts +3 -0
- package/dist/tools/form_get.js +25 -0
- package/dist/tools/form_list.d.ts +3 -0
- package/dist/tools/form_list.js +37 -0
- package/dist/tools/form_update.d.ts +3 -0
- package/dist/tools/form_update.js +39 -0
- package/dist/tools/hello.d.ts +2 -0
- package/dist/tools/hello.js +6 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.js +44 -0
- package/dist/tools/project_export.d.ts +3 -0
- package/dist/tools/project_export.js +17 -0
- package/dist/tools/project_import.d.ts +3 -0
- package/dist/tools/project_import.js +23 -0
- package/dist/tools/project_set.d.ts +5 -0
- package/dist/tools/project_set.js +54 -0
- package/dist/tools/role-schema.d.ts +7 -0
- package/dist/tools/role-schema.js +10 -0
- package/dist/tools/role_create.d.ts +3 -0
- package/dist/tools/role_create.js +24 -0
- package/dist/tools/role_list.d.ts +3 -0
- package/dist/tools/role_list.js +20 -0
- package/dist/tools/role_update.d.ts +3 -0
- package/dist/tools/role_update.js +30 -0
- package/package.json +55 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
const DEFAULT_CACHE_DIR = path.join(os.homedir(), '.formio');
|
|
5
|
+
const CACHE_FILE = 'mcp-tokens.json';
|
|
6
|
+
async function readCache(cacheDir) {
|
|
7
|
+
const filePath = path.join(cacheDir, CACHE_FILE);
|
|
8
|
+
try {
|
|
9
|
+
const contents = await fs.readFile(filePath, 'utf-8');
|
|
10
|
+
return JSON.parse(contents);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function writeCache(cacheDir, data) {
|
|
17
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
18
|
+
const filePath = path.join(cacheDir, CACHE_FILE);
|
|
19
|
+
await fs.writeFile(filePath, JSON.stringify(data), { mode: 0o600 });
|
|
20
|
+
}
|
|
21
|
+
export async function saveToken(baseUrl, jwt, cacheDir = DEFAULT_CACHE_DIR) {
|
|
22
|
+
const cache = await readCache(cacheDir);
|
|
23
|
+
cache[baseUrl] = jwt;
|
|
24
|
+
await writeCache(cacheDir, cache);
|
|
25
|
+
}
|
|
26
|
+
export async function readToken(baseUrl, cacheDir = DEFAULT_CACHE_DIR) {
|
|
27
|
+
const cache = await readCache(cacheDir);
|
|
28
|
+
return cache[baseUrl] ?? null;
|
|
29
|
+
}
|
|
30
|
+
export async function clearToken(baseUrl, cacheDir = DEFAULT_CACHE_DIR) {
|
|
31
|
+
const cache = await readCache(cacheDir);
|
|
32
|
+
delete cache[baseUrl];
|
|
33
|
+
await writeCache(cacheDir, cache);
|
|
34
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const actionDefinitionSchema: z.ZodObject<{
|
|
3
|
+
name: z.ZodString;
|
|
4
|
+
title: z.ZodString;
|
|
5
|
+
handler: z.ZodArray<z.ZodString>;
|
|
6
|
+
method: z.ZodArray<z.ZodString>;
|
|
7
|
+
settings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
8
|
+
condition: z.ZodOptional<z.ZodObject<{
|
|
9
|
+
conjunction: z.ZodEnum<{
|
|
10
|
+
all: "all";
|
|
11
|
+
any: "any";
|
|
12
|
+
}>;
|
|
13
|
+
conditions: z.ZodArray<z.ZodObject<{
|
|
14
|
+
component: z.ZodString;
|
|
15
|
+
operator: z.ZodEnum<{
|
|
16
|
+
includes: "includes";
|
|
17
|
+
endsWith: "endsWith";
|
|
18
|
+
startsWith: "startsWith";
|
|
19
|
+
isEqual: "isEqual";
|
|
20
|
+
isNotEqual: "isNotEqual";
|
|
21
|
+
isEmpty: "isEmpty";
|
|
22
|
+
isNotEmpty: "isNotEmpty";
|
|
23
|
+
greaterThan: "greaterThan";
|
|
24
|
+
greaterThanOrEqual: "greaterThanOrEqual";
|
|
25
|
+
lessThan: "lessThan";
|
|
26
|
+
lessThanOrEqual: "lessThanOrEqual";
|
|
27
|
+
notIncludes: "notIncludes";
|
|
28
|
+
isDateEqual: "isDateEqual";
|
|
29
|
+
isNotDateEqual: "isNotDateEqual";
|
|
30
|
+
dateGreaterThan: "dateGreaterThan";
|
|
31
|
+
dateGreaterThanOrEqual: "dateGreaterThanOrEqual";
|
|
32
|
+
dateLessThan: "dateLessThan";
|
|
33
|
+
dateLessThanOrEqual: "dateLessThanOrEqual";
|
|
34
|
+
}>;
|
|
35
|
+
value: z.ZodOptional<z.ZodUnknown>;
|
|
36
|
+
}, z.core.$strip>>;
|
|
37
|
+
}, z.core.$strip>>;
|
|
38
|
+
priority: z.ZodOptional<z.ZodNumber>;
|
|
39
|
+
}, z.core.$loose>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const conditionOperator = z.enum([
|
|
3
|
+
'isEqual',
|
|
4
|
+
'isNotEqual',
|
|
5
|
+
'isEmpty',
|
|
6
|
+
'isNotEmpty',
|
|
7
|
+
'greaterThan',
|
|
8
|
+
'greaterThanOrEqual',
|
|
9
|
+
'lessThan',
|
|
10
|
+
'lessThanOrEqual',
|
|
11
|
+
'startsWith',
|
|
12
|
+
'endsWith',
|
|
13
|
+
'includes',
|
|
14
|
+
'notIncludes',
|
|
15
|
+
'isDateEqual',
|
|
16
|
+
'isNotDateEqual',
|
|
17
|
+
'dateGreaterThan',
|
|
18
|
+
'dateGreaterThanOrEqual',
|
|
19
|
+
'dateLessThan',
|
|
20
|
+
'dateLessThanOrEqual',
|
|
21
|
+
]);
|
|
22
|
+
const actionCondition = z
|
|
23
|
+
.object({
|
|
24
|
+
conjunction: z.enum(['all', 'any']),
|
|
25
|
+
conditions: z.array(z.object({
|
|
26
|
+
component: z.string(),
|
|
27
|
+
operator: conditionOperator,
|
|
28
|
+
value: z.unknown().optional(),
|
|
29
|
+
})),
|
|
30
|
+
})
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Condition for when action runs');
|
|
33
|
+
export const actionDefinitionSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
name: z.string().describe('Action type name'),
|
|
36
|
+
title: z.string().describe('Action title'),
|
|
37
|
+
handler: z.array(z.string()).describe('Handler phases (e.g. ["before"], ["after"])'),
|
|
38
|
+
method: z.array(z.string()).describe('Methods (e.g. ["create"], ["update"])'),
|
|
39
|
+
settings: z.record(z.string(), z.unknown()).optional().describe('Action settings'),
|
|
40
|
+
condition: actionCondition,
|
|
41
|
+
priority: z.number().optional().describe('Action priority'),
|
|
42
|
+
})
|
|
43
|
+
.passthrough()
|
|
44
|
+
.describe('Action definition');
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { formioFetch } from '../formio-client.js';
|
|
2
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
3
|
+
import { actionDefinitionSchema } from './action-schema.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
export function registerActionCreateTool(server, config) {
|
|
7
|
+
server.tool('action_create', 'Create a new action on a form. Call action_type_get first to discover the required settings schema for the action type.', {
|
|
8
|
+
cwd: cwdSchema,
|
|
9
|
+
formId: z.string().describe('The form ID to attach the action to'),
|
|
10
|
+
action: actionDefinitionSchema,
|
|
11
|
+
}, async ({ cwd, formId, action }) => {
|
|
12
|
+
try {
|
|
13
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
14
|
+
const catalog = (await formioFetch(`form/${formId}/actions`, {}, cfg));
|
|
15
|
+
const availableNames = catalog.map((t) => t.name);
|
|
16
|
+
if (!availableNames.includes(action.name)) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: 'text',
|
|
21
|
+
text: `Action type '${action.name}' is not available on this server. Available types: ${availableNames.join(', ')}`,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
isError: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const created = await formioFetch(`form/${formId}/action`, {}, cfg, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
body: action,
|
|
30
|
+
});
|
|
31
|
+
return toMcpTextResult(created);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return toMcpError(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch } from '../formio-client.js';
|
|
3
|
+
import { toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
export function registerActionDeleteTool(server, config) {
|
|
6
|
+
server.tool('action_delete', 'Delete an action from a form.', {
|
|
7
|
+
cwd: cwdSchema,
|
|
8
|
+
formId: z.string().describe('The form ID the action belongs to'),
|
|
9
|
+
actionId: z.string().describe('The action ID to delete'),
|
|
10
|
+
}, async ({ cwd, formId, actionId }) => {
|
|
11
|
+
try {
|
|
12
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
13
|
+
await formioFetch(`form/${formId}/action/${actionId}`, {}, cfg, {
|
|
14
|
+
method: 'DELETE',
|
|
15
|
+
});
|
|
16
|
+
return { content: [{ type: 'text', text: 'OK' }] };
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return toMcpError(error);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch } from '../formio-client.js';
|
|
3
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
export function registerActionGetTool(server, config) {
|
|
6
|
+
server.tool('action_get', 'Get a single action by ID from a form.', {
|
|
7
|
+
cwd: cwdSchema,
|
|
8
|
+
formId: z.string().describe('The form ID the action belongs to'),
|
|
9
|
+
actionId: z.string().describe('The action ID to retrieve'),
|
|
10
|
+
}, async ({ cwd, formId, actionId }) => {
|
|
11
|
+
try {
|
|
12
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
13
|
+
const action = await formioFetch(`form/${formId}/action/${actionId}`, {}, cfg);
|
|
14
|
+
return toMcpTextResult(action);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
return toMcpError(error);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch } from '../formio-client.js';
|
|
3
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
export function registerActionListTool(server, config) {
|
|
6
|
+
server.tool('action_list', 'List actions configured on a form.', {
|
|
7
|
+
cwd: cwdSchema,
|
|
8
|
+
formId: z.string().describe('The form ID to list actions for'),
|
|
9
|
+
}, async ({ cwd, formId }) => {
|
|
10
|
+
try {
|
|
11
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
12
|
+
const actions = await formioFetch(`form/${formId}/action`, {}, cfg);
|
|
13
|
+
return toMcpTextResult(actions);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return toMcpError(error);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch } from '../formio-client.js';
|
|
3
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
export function registerActionTypeGetTool(server, config) {
|
|
6
|
+
server.tool('action_type_get', 'Get action type info and settings form schema. Call this before action_create to discover the required settings for the action type.', {
|
|
7
|
+
cwd: cwdSchema,
|
|
8
|
+
formId: z.string().describe('The form ID to get the action type for'),
|
|
9
|
+
actionName: z.string().describe('The action type name (e.g. "email", "save", "login")'),
|
|
10
|
+
}, async ({ cwd, formId, actionName }) => {
|
|
11
|
+
try {
|
|
12
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
13
|
+
try {
|
|
14
|
+
const typeInfo = await formioFetch(`form/${formId}/actions/${actionName}`, {}, cfg);
|
|
15
|
+
return toMcpTextResult(typeInfo);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const catalog = (await formioFetch(`form/${formId}/actions`, {}, cfg).catch(() => {
|
|
19
|
+
throw error;
|
|
20
|
+
}));
|
|
21
|
+
const availableTypes = catalog.map((t) => t.name).join(', ');
|
|
22
|
+
throw new Error(`Action type '${actionName}' is not available on this server. Available types: ${availableTypes}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return toMcpError(error);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch } from '../formio-client.js';
|
|
3
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
export function registerActionTypesListTool(server, config) {
|
|
6
|
+
server.tool('action_types_list', 'List available action types for a form. Returns the catalog of action types the server supports.', {
|
|
7
|
+
cwd: cwdSchema,
|
|
8
|
+
formId: z.string().describe('The form ID to list available action types for'),
|
|
9
|
+
}, async ({ cwd, formId }) => {
|
|
10
|
+
try {
|
|
11
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
12
|
+
const catalog = await formioFetch(`form/${formId}/actions`, {}, cfg);
|
|
13
|
+
return toMcpTextResult(catalog);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return toMcpError(error);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch } from '../formio-client.js';
|
|
3
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { actionDefinitionSchema } from './action-schema.js';
|
|
5
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
6
|
+
export function registerActionUpdateTool(server, config) {
|
|
7
|
+
server.tool('action_update', 'Update an existing action on a form.', {
|
|
8
|
+
cwd: cwdSchema,
|
|
9
|
+
formId: z.string().describe('The form ID the action belongs to'),
|
|
10
|
+
actionId: z.string().describe('The action ID to update'),
|
|
11
|
+
action: actionDefinitionSchema,
|
|
12
|
+
}, async ({ cwd, formId, actionId, action }) => {
|
|
13
|
+
try {
|
|
14
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
15
|
+
const updated = await formioFetch(`form/${formId}/action/${actionId}`, {}, cfg, {
|
|
16
|
+
method: 'PUT',
|
|
17
|
+
body: action,
|
|
18
|
+
});
|
|
19
|
+
return toMcpTextResult(updated);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return toMcpError(error);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch } from '../formio-client.js';
|
|
3
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
export function registerFormCreateTool(server, config) {
|
|
6
|
+
server.tool('form_create', "Create a new form in the Form.io project mapped to the user's current working directory. IMPORTANT: Before calling this tool, use the formio-form skill to construct a properly structured Form.io form JSON definition based on the user's requirements. The skill documents all component types, validation options, layout patterns, and conditional logic available in Form.io.", {
|
|
7
|
+
cwd: cwdSchema,
|
|
8
|
+
form: z
|
|
9
|
+
.looseObject({
|
|
10
|
+
title: z.string().describe('Human-readable form title'),
|
|
11
|
+
name: z.string().describe('Machine name for API references'),
|
|
12
|
+
path: z.string().describe('URL path segment for the form'),
|
|
13
|
+
components: z
|
|
14
|
+
.array(z.record(z.string(), z.unknown()))
|
|
15
|
+
.describe('Array of form components'),
|
|
16
|
+
type: z.enum(['form', 'resource']).optional().describe('Form type (default: "form")'),
|
|
17
|
+
display: z
|
|
18
|
+
.enum(['form', 'wizard', 'pdf'])
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Display mode (default: "form")'),
|
|
21
|
+
tags: z.array(z.string()).optional().describe('Tags for categorization'),
|
|
22
|
+
})
|
|
23
|
+
.catchall(z.unknown())
|
|
24
|
+
.describe('Form.io form JSON definition'),
|
|
25
|
+
}, async ({ cwd, form }) => {
|
|
26
|
+
try {
|
|
27
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
28
|
+
const created = await formioFetch('form', {}, cfg, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
body: form,
|
|
31
|
+
});
|
|
32
|
+
return toMcpTextResult(created);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
return toMcpError(error);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch, isMongoId } from '../formio-client.js';
|
|
3
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
export function registerFormGetTool(server, config) {
|
|
6
|
+
server.tool('form_get', "Fetch a single form definition from the Form.io project mapped to the user's current working directory, by form ID or path.", {
|
|
7
|
+
cwd: cwdSchema,
|
|
8
|
+
formIdOrPath: z.string().describe('Form ID (_id) or path (e.g. "user/login")'),
|
|
9
|
+
select: z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe('Comma-separated fields to return (omit for full form JSON)'),
|
|
13
|
+
}, async ({ cwd, formIdOrPath, select }) => {
|
|
14
|
+
try {
|
|
15
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
16
|
+
const params = { select };
|
|
17
|
+
const path = isMongoId(formIdOrPath) ? `form/${formIdOrPath}` : formIdOrPath;
|
|
18
|
+
const form = await formioFetch(path, params, cfg);
|
|
19
|
+
return toMcpTextResult(form);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return toMcpError(error);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch } from '../formio-client.js';
|
|
3
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
const DEFAULT_SELECT = '_id,title,name,path,type,tags';
|
|
6
|
+
const DEFAULT_LIMIT = 20;
|
|
7
|
+
export function registerFormListTool(server, config) {
|
|
8
|
+
server.tool('form_list', "List forms from the Form.io project mapped to the user's current working directory with optional filtering and pagination.", {
|
|
9
|
+
cwd: cwdSchema,
|
|
10
|
+
type: z.enum(['form', 'resource']).optional().describe('Filter by form type'),
|
|
11
|
+
limit: z.number().optional().describe('Maximum number of forms to return (default: 20)'),
|
|
12
|
+
skip: z.number().optional().describe('Number of forms to skip for pagination'),
|
|
13
|
+
sort: z.string().optional().describe('Sort field and direction (e.g. "-created")'),
|
|
14
|
+
select: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Comma-separated fields to return (default: _id,title,name,path,type,tags)'),
|
|
18
|
+
tags: z.array(z.string()).optional().describe('Filter by tags'),
|
|
19
|
+
}, async ({ cwd, type, limit, skip, sort, select, tags }) => {
|
|
20
|
+
try {
|
|
21
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
22
|
+
const params = {
|
|
23
|
+
select: select ?? DEFAULT_SELECT,
|
|
24
|
+
limit: String(limit ?? DEFAULT_LIMIT),
|
|
25
|
+
skip: skip !== undefined ? String(skip) : undefined,
|
|
26
|
+
sort,
|
|
27
|
+
type,
|
|
28
|
+
tags: tags?.join(','),
|
|
29
|
+
};
|
|
30
|
+
const forms = await formioFetch('form', params, cfg);
|
|
31
|
+
return toMcpTextResult(forms);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return toMcpError(error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch, MONGO_ID_PATTERN } from '../formio-client.js';
|
|
3
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
export function registerFormUpdateTool(server, config) {
|
|
6
|
+
server.tool('form_update', "Update an existing form in the Form.io project mapped to the user's current working directory. IMPORTANT: Before calling this tool, first use form_get to fetch the current form definition, then use the formio-form skill to apply the requested modifications (add, remove, or modify fields and settings), and finally call this tool with the complete updated form JSON.", {
|
|
7
|
+
cwd: cwdSchema,
|
|
8
|
+
formId: z
|
|
9
|
+
.string()
|
|
10
|
+
.regex(MONGO_ID_PATTERN, 'Must be a 24-character MongoDB ObjectId')
|
|
11
|
+
.describe('The _id of the form to update'),
|
|
12
|
+
form: z
|
|
13
|
+
.looseObject({
|
|
14
|
+
title: z.string().optional().describe('Human-readable form title'),
|
|
15
|
+
name: z.string().optional().describe('Machine name for API references'),
|
|
16
|
+
path: z.string().optional().describe('URL path segment for the form'),
|
|
17
|
+
components: z
|
|
18
|
+
.array(z.record(z.string(), z.unknown()))
|
|
19
|
+
.describe('Array of form components'),
|
|
20
|
+
type: z.enum(['form', 'resource']).optional().describe('Form type'),
|
|
21
|
+
display: z.enum(['form', 'wizard', 'pdf']).optional().describe('Display mode'),
|
|
22
|
+
tags: z.array(z.string()).optional().describe('Tags for categorization'),
|
|
23
|
+
})
|
|
24
|
+
.catchall(z.unknown())
|
|
25
|
+
.describe('Complete updated Form.io form JSON definition'),
|
|
26
|
+
}, async ({ cwd, formId, form }) => {
|
|
27
|
+
try {
|
|
28
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
29
|
+
const updated = await formioFetch(`form/${formId}`, {}, cfg, {
|
|
30
|
+
method: 'PUT',
|
|
31
|
+
body: form,
|
|
32
|
+
});
|
|
33
|
+
return toMcpTextResult(updated);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return toMcpError(error);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerHelloTool(server) {
|
|
3
|
+
server.tool('hello', 'Say hello — used to verify the server is working', { name: z.string().optional().describe('Name to greet') }, async ({ name }) => ({
|
|
4
|
+
content: [{ type: 'text', text: `Hello from formio-mcp, ${name ?? 'world'}!` }],
|
|
5
|
+
}));
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { FormioConfig } from '../config.js';
|
|
3
|
+
export interface RegisterAllToolsOptions {
|
|
4
|
+
cwd?: () => string;
|
|
5
|
+
}
|
|
6
|
+
export declare function registerAllTools(server: McpServer, config: FormioConfig, options?: RegisterAllToolsOptions): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { registerActionCreateTool } from './action_create.js';
|
|
2
|
+
import { registerActionDeleteTool } from './action_delete.js';
|
|
3
|
+
import { registerActionGetTool } from './action_get.js';
|
|
4
|
+
import { registerActionListTool } from './action_list.js';
|
|
5
|
+
import { registerActionTypeGetTool } from './action_type_get.js';
|
|
6
|
+
import { registerActionTypesListTool } from './action_types_list.js';
|
|
7
|
+
import { registerActionUpdateTool } from './action_update.js';
|
|
8
|
+
import { registerFormCreateTool } from './form_create.js';
|
|
9
|
+
import { registerFormGetTool } from './form_get.js';
|
|
10
|
+
import { registerFormListTool } from './form_list.js';
|
|
11
|
+
import { registerFormUpdateTool } from './form_update.js';
|
|
12
|
+
import { registerHelloTool } from './hello.js';
|
|
13
|
+
import { registerProjectExportTool } from './project_export.js';
|
|
14
|
+
import { registerProjectImportTool } from './project_import.js';
|
|
15
|
+
import { registerProjectSetTool } from './project_set.js';
|
|
16
|
+
import { registerRoleCreateTool } from './role_create.js';
|
|
17
|
+
import { registerRoleListTool } from './role_list.js';
|
|
18
|
+
import { registerRoleUpdateTool } from './role_update.js';
|
|
19
|
+
export function registerAllTools(server, config, options = {}) {
|
|
20
|
+
registerHelloTool(server);
|
|
21
|
+
registerFormCreateTool(server, config);
|
|
22
|
+
registerFormGetTool(server, config);
|
|
23
|
+
registerFormListTool(server, config);
|
|
24
|
+
registerFormUpdateTool(server, config);
|
|
25
|
+
registerProjectExportTool(server, config);
|
|
26
|
+
registerProjectImportTool(server, config);
|
|
27
|
+
// project_set is only useful when the SessionStart/PreToolUse hook drives
|
|
28
|
+
// per-cwd project mapping — i.e. plugin context. Standalone .mcp.json users
|
|
29
|
+
// bind the server to a project via FORMIO_PROJECT_URL instead, so exposing
|
|
30
|
+
// project_set there just invites drift between the env and the map.
|
|
31
|
+
if (process.env.FORMIO_PLUGIN_CONTEXT === '1') {
|
|
32
|
+
registerProjectSetTool(server, { cwd: options.cwd });
|
|
33
|
+
}
|
|
34
|
+
registerRoleCreateTool(server, config);
|
|
35
|
+
registerRoleListTool(server, config);
|
|
36
|
+
registerRoleUpdateTool(server, config);
|
|
37
|
+
registerActionTypesListTool(server, config);
|
|
38
|
+
registerActionTypeGetTool(server, config);
|
|
39
|
+
registerActionCreateTool(server, config);
|
|
40
|
+
registerActionListTool(server, config);
|
|
41
|
+
registerActionGetTool(server, config);
|
|
42
|
+
registerActionUpdateTool(server, config);
|
|
43
|
+
registerActionDeleteTool(server, config);
|
|
44
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { formioFetch } from '../formio-client.js';
|
|
2
|
+
import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
|
|
3
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
4
|
+
export function registerProjectExportTool(server, config) {
|
|
5
|
+
server.tool('project_export', "Export the complete template (roles, resources, forms, actions) of the Form.io project mapped to the user's current working directory as a portable JSON document. Use this to snapshot a project before importing changes.", {
|
|
6
|
+
cwd: cwdSchema,
|
|
7
|
+
}, async ({ cwd }) => {
|
|
8
|
+
try {
|
|
9
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
10
|
+
const template = await formioFetch('export', {}, cfg);
|
|
11
|
+
return toMcpTextResult(template);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
return toMcpError(error);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formioFetch } from '../formio-client.js';
|
|
3
|
+
import { toMcpError } from '../mcp-responses.js';
|
|
4
|
+
import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
|
|
5
|
+
export function registerProjectImportTool(server, config) {
|
|
6
|
+
server.tool('project_import', "Import a template JSON into the existing Form.io project mapped to the user's current working directory, merging roles, resources, forms, and actions in one call. Use the formio-resource-planner skill to construct the template before calling this tool. WARNING: import merges into the existing project — use project_export first to snapshot.", {
|
|
7
|
+
cwd: cwdSchema,
|
|
8
|
+
template: z.looseObject({}).describe('The template JSON object to import'),
|
|
9
|
+
}, async ({ cwd, template }) => {
|
|
10
|
+
try {
|
|
11
|
+
const cfg = resolveProjectConfig(cwd, config);
|
|
12
|
+
const result = await formioFetch('import', {}, cfg, {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
body: { template },
|
|
15
|
+
responseType: 'text',
|
|
16
|
+
});
|
|
17
|
+
return { content: [{ type: 'text', text: String(result) }] };
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return toMcpError(error);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|