@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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/dist/auth-header.d.ts +2 -0
  4. package/dist/auth-header.js +9 -0
  5. package/dist/auth.d.ts +6 -0
  6. package/dist/auth.js +148 -0
  7. package/dist/config.d.ts +12 -0
  8. package/dist/config.js +29 -0
  9. package/dist/ensure-auth.d.ts +4 -0
  10. package/dist/ensure-auth.js +56 -0
  11. package/dist/formio-client.d.ts +11 -0
  12. package/dist/formio-client.js +60 -0
  13. package/dist/mcp-responses.d.ts +13 -0
  14. package/dist/mcp-responses.js +12 -0
  15. package/dist/project-map.d.ts +5 -0
  16. package/dist/project-map.js +29 -0
  17. package/dist/project-resolver.d.ts +4 -0
  18. package/dist/project-resolver.js +35 -0
  19. package/dist/server.d.ts +3 -0
  20. package/dist/server.js +9 -0
  21. package/dist/stdio.d.ts +2 -0
  22. package/dist/stdio.js +8 -0
  23. package/dist/token-cache.d.ts +3 -0
  24. package/dist/token-cache.js +34 -0
  25. package/dist/token-validation.d.ts +2 -0
  26. package/dist/token-validation.js +6 -0
  27. package/dist/tools/action-schema.d.ts +39 -0
  28. package/dist/tools/action-schema.js +44 -0
  29. package/dist/tools/action_create.d.ts +3 -0
  30. package/dist/tools/action_create.js +37 -0
  31. package/dist/tools/action_delete.d.ts +3 -0
  32. package/dist/tools/action_delete.js +22 -0
  33. package/dist/tools/action_get.d.ts +3 -0
  34. package/dist/tools/action_get.js +20 -0
  35. package/dist/tools/action_list.d.ts +3 -0
  36. package/dist/tools/action_list.js +19 -0
  37. package/dist/tools/action_type_get.d.ts +3 -0
  38. package/dist/tools/action_type_get.js +29 -0
  39. package/dist/tools/action_types_list.d.ts +3 -0
  40. package/dist/tools/action_types_list.js +19 -0
  41. package/dist/tools/action_update.d.ts +3 -0
  42. package/dist/tools/action_update.js +25 -0
  43. package/dist/tools/form_create.d.ts +3 -0
  44. package/dist/tools/form_create.js +38 -0
  45. package/dist/tools/form_get.d.ts +3 -0
  46. package/dist/tools/form_get.js +25 -0
  47. package/dist/tools/form_list.d.ts +3 -0
  48. package/dist/tools/form_list.js +37 -0
  49. package/dist/tools/form_update.d.ts +3 -0
  50. package/dist/tools/form_update.js +39 -0
  51. package/dist/tools/hello.d.ts +2 -0
  52. package/dist/tools/hello.js +6 -0
  53. package/dist/tools/index.d.ts +6 -0
  54. package/dist/tools/index.js +44 -0
  55. package/dist/tools/project_export.d.ts +3 -0
  56. package/dist/tools/project_export.js +17 -0
  57. package/dist/tools/project_import.d.ts +3 -0
  58. package/dist/tools/project_import.js +23 -0
  59. package/dist/tools/project_set.d.ts +5 -0
  60. package/dist/tools/project_set.js +54 -0
  61. package/dist/tools/role-schema.d.ts +7 -0
  62. package/dist/tools/role-schema.js +10 -0
  63. package/dist/tools/role_create.d.ts +3 -0
  64. package/dist/tools/role_create.js +24 -0
  65. package/dist/tools/role_list.d.ts +3 -0
  66. package/dist/tools/role_list.js +20 -0
  67. package/dist/tools/role_update.d.ts +3 -0
  68. package/dist/tools/role_update.js +30 -0
  69. package/package.json +55 -0
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod';
2
+ import { readProjectEntry, writeProjectEntry } from '../project-map.js';
3
+ function normalizeProjectUrl(input) {
4
+ let parsed;
5
+ try {
6
+ parsed = new URL(input);
7
+ }
8
+ catch {
9
+ throw new Error(`projectUrl must be a valid URL, got: ${input}`);
10
+ }
11
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
12
+ throw new Error(`projectUrl must use http or https, got: ${parsed.protocol}`);
13
+ }
14
+ return input.replace(/\/+$/, '');
15
+ }
16
+ export function registerProjectSetTool(server, options = {}) {
17
+ const getServerCwd = options.cwd ?? (() => process.cwd());
18
+ server.tool('project_set', [
19
+ 'Set the active Form.io project for the given working directory by writing the URL to project.json file in formio configuration folder',
20
+ 'You MUST call this tool whenever the user asks to set, change, or switch the active Form.io project — do not merely acknowledge the request in text. Persisting the choice requires the tool call.',
21
+ "The chosen URL is persisted to project.json file in the formio configuration folder keyed by the cwd argument when provided (or the MCP server process cwd otherwise). Pass the `cwd` argument whenever you know the user's current working directory — the server process cwd is fixed at spawn and may not match where the user actually is.",
22
+ 'Every Form.io tool resolves its project URL from this map on each call, so the new mapping takes effect immediately for subsequent tool calls from the same cwd.',
23
+ ].join(' '), {
24
+ projectUrl: z
25
+ .url({ protocol: /^https?$/ })
26
+ .describe('Full URL of the Form.io project to activate, e.g. https://api.form.io/my-project'),
27
+ cwd: z
28
+ .string()
29
+ .optional()
30
+ .describe("User's current working directory to key the persisted mapping against. Pass whenever known (e.g. from UserPromptSubmit hook context). Falls back to the MCP server's process.cwd() when omitted."),
31
+ }, async ({ projectUrl, cwd }) => {
32
+ const normalized = normalizeProjectUrl(projectUrl);
33
+ const entryCwd = cwd ?? getServerCwd();
34
+ const existing = readProjectEntry(entryCwd);
35
+ const previousMapped = existing?.env.FORMIO_PROJECT_URL;
36
+ if (previousMapped === normalized) {
37
+ return {
38
+ content: [
39
+ {
40
+ type: 'text',
41
+ text: `Active project is already ${normalized} and persisted for ${entryCwd}; no change`,
42
+ },
43
+ ],
44
+ };
45
+ }
46
+ writeProjectEntry(entryCwd, { FORMIO_PROJECT_URL: normalized });
47
+ const message = previousMapped
48
+ ? `Active project set to ${normalized} (was ${previousMapped}; persisted for ${entryCwd})`
49
+ : `Active project set to ${normalized}; mapping persisted for ${entryCwd}`;
50
+ return {
51
+ content: [{ type: 'text', text: message }],
52
+ };
53
+ });
54
+ }
@@ -0,0 +1,7 @@
1
+ import { z } from 'zod';
2
+ export declare const roleFields: {
3
+ title: z.ZodOptional<z.ZodString>;
4
+ description: z.ZodOptional<z.ZodString>;
5
+ default: z.ZodOptional<z.ZodBoolean>;
6
+ admin: z.ZodOptional<z.ZodBoolean>;
7
+ };
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ export const roleFields = {
3
+ title: z.string().optional().describe('Role title'),
4
+ description: z.string().optional().describe('Role description'),
5
+ default: z
6
+ .boolean()
7
+ .optional()
8
+ .describe('When true, role is assigned to every new authenticated user'),
9
+ admin: z.boolean().optional().describe('When true, holders bypass access checks'),
10
+ };
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { FormioConfig } from '../config.js';
3
+ export declare function registerRoleCreateTool(server: McpServer, config: FormioConfig): void;
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ import { formioFetch } from '../formio-client.js';
3
+ import { toMcpTextResult, toMcpError } from '../mcp-responses.js';
4
+ import { roleFields } from './role-schema.js';
5
+ import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
6
+ export function registerRoleCreateTool(server, config) {
7
+ server.tool('role_create', "Create a new role in the Form.io project mapped to the user's current working directory.", {
8
+ cwd: cwdSchema,
9
+ ...roleFields,
10
+ title: z.string().describe('Role title'),
11
+ }, async ({ cwd, ...role }) => {
12
+ try {
13
+ const cfg = resolveProjectConfig(cwd, config);
14
+ const created = await formioFetch('role', {}, cfg, {
15
+ method: 'POST',
16
+ body: role,
17
+ });
18
+ return toMcpTextResult(created);
19
+ }
20
+ catch (error) {
21
+ return toMcpError(error);
22
+ }
23
+ });
24
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { FormioConfig } from '../config.js';
3
+ export declare function registerRoleListTool(server: McpServer, config: FormioConfig): void;
@@ -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 registerRoleListTool(server, config) {
6
+ server.tool('role_list', "List all roles defined in the Form.io project mapped to the user's current working directory.", {
7
+ cwd: cwdSchema,
8
+ select: z.string().optional().describe('Comma-separated fields to return'),
9
+ }, async ({ cwd, select }) => {
10
+ try {
11
+ const cfg = resolveProjectConfig(cwd, config);
12
+ const params = { select };
13
+ const roles = await formioFetch('role', params, cfg);
14
+ return toMcpTextResult(roles);
15
+ }
16
+ catch (error) {
17
+ return toMcpError(error);
18
+ }
19
+ });
20
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { FormioConfig } from '../config.js';
3
+ export declare function registerRoleUpdateTool(server: McpServer, config: FormioConfig): void;
@@ -0,0 +1,30 @@
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 { roleFields } from './role-schema.js';
5
+ import { cwdSchema, resolveProjectConfig } from '../project-resolver.js';
6
+ export function registerRoleUpdateTool(server, config) {
7
+ server.tool('role_update', "Update an existing role in the Form.io project mapped to the user's current working directory. This is a full replacement — include all fields you want to preserve.", {
8
+ cwd: cwdSchema,
9
+ roleId: z
10
+ .string()
11
+ .regex(MONGO_ID_PATTERN, 'Must be a 24-character MongoDB ObjectId')
12
+ .describe('The _id of the role to update'),
13
+ role: z
14
+ .object(roleFields)
15
+ .catchall(z.unknown())
16
+ .describe('Role document with updated fields'),
17
+ }, async ({ cwd, roleId, role }) => {
18
+ try {
19
+ const cfg = resolveProjectConfig(cwd, config);
20
+ const updated = await formioFetch(`role/${roleId}`, {}, cfg, {
21
+ method: 'PUT',
22
+ body: role,
23
+ });
24
+ return toMcpTextResult(updated);
25
+ }
26
+ catch (error) {
27
+ return toMcpError(error);
28
+ }
29
+ });
30
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@formio/mcp",
3
+ "version": "0.1.0",
4
+ "description": "Form.io MCP Server",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/formio/ai#readme",
8
+ "bugs": {
9
+ "url": "https://github.com/formio/ai/issues"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/formio/ai.git",
14
+ "directory": "packages/mcp-server"
15
+ },
16
+ "main": "./dist/server.js",
17
+ "bin": {
18
+ "formio-mcp": "./dist/stdio.js"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "engines": {
28
+ "node": ">=20"
29
+ },
30
+ "dependencies": {
31
+ "@modelcontextprotocol/sdk": "^1.10.2",
32
+ "express": "^4.21.2",
33
+ "zod": "^4.3.6"
34
+ },
35
+ "devDependencies": {
36
+ "@types/express": "^4.17.21",
37
+ "@types/node": "^22.19.17",
38
+ "fast-glob": "^3.3.3",
39
+ "gray-matter": "^4.0.3",
40
+ "tsx": "^4.19.3",
41
+ "typescript": "^5.8.2",
42
+ "vitest": "^3.1.1"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.build.json",
46
+ "build:dev": "tsc",
47
+ "dev": "tsx watch src/index.ts",
48
+ "start": "node dist/index.js",
49
+ "test": "vitest run",
50
+ "test:watch": "vitest",
51
+ "typecheck": "tsc --noEmit",
52
+ "lint": "eslint src && tsc --noEmit",
53
+ "clean": "rm -rf dist coverage"
54
+ }
55
+ }