@formio/uag 1.9.2 → 1.10.2

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 CHANGED
@@ -88,6 +88,7 @@ The following tools provided by the UAG can be described as follows:
88
88
  | find_submissions | Enables the agent to parse a user's natural language request into a query for a submission, or a specific field of a particular submission. |
89
89
  | submission_update | Provides the AI agent with the ability to update an existing submission, either by supplying unfilled fields or updating existing ones if allowed. Provides the AI agent with the context of the existing field values, allowing inline changes or edits. |
90
90
  | agent_provide_data | Enables an AI Agent to be able to analyze existing submission data according to rules defined with a configurable **Criteria**. It will then instruct the Agent to provide generated data for fields configured for a specific **persona** using the `submission_update` tool. Please read the [**Agentic Workflows**](#agentic-workflows) section for more information about this toolset. |
91
+ | fetch_external_data | This tool enables an AI Agent to pull external data into a form workflow through the use of either a Resource component, Select with URL, or a Data Source component. |
91
92
 
92
93
  ## Agentic Workflows
93
94
  One of the more powerful features of the **UAG** is the `agent_process_data` tool. This tool provides the ability to instruct a generically trained agent how to analyze existing submission data, and then produce its own data by following a configurable **Criteria**. This behavior historically could only be achieved using a specifically trained agent, which does not provide any benefits of dynamic configurability that the Form.io platform offers. This tool is able to achieve this goal by providing a generally trained agent with the necessary "context" it needs to accurately produce its own data as part of an automated workflow. This feature is particularly helpful if you wish to utilize the UAG within an Agentic Workflow, where the AI Agent is capable of understanding structured data, and then contribute its own data by following the configured **Criteria** "context" provided by the UAG.
@@ -91,6 +91,10 @@ export declare class UAGFormInterface extends FormInterface {
91
91
  } | {
92
92
  data?: undefined;
93
93
  } | null;
94
+ /**
95
+ * Override the process methods for the UAG.
96
+ */
97
+ process(submission: Submission, auth: AuthRequest, headers?: any, findQuery?: any, additional?: any[]): Promise<any>;
94
98
  /**
95
99
  * Get the relevant fields from the current form. This will return any non-nested input components whose
96
100
  * values have not already been set within the data model. This allows the agent to know what fields still need to be
@@ -74,10 +74,10 @@ class UAGFormInterface extends appserver_1.FormInterface {
74
74
  }
75
75
  if (component.type === 'select' || component.type === 'selectboxes') {
76
76
  if (component.dataSrc === 'url') {
77
- fieldInfo.options = [{ label: '** ANY VALUE IS ALLOWED **', value: 'Options are loaded from a URL.' }];
77
+ fieldInfo.options = [{ label: '** USE fetch_external_data TOOL **', value: `Call the \`fetch_external_data\` tool with \`field_path\`="${path}" to retrieve available options from the external URL.` }];
78
78
  }
79
79
  else if (component.dataSrc === 'resource') {
80
- fieldInfo.options = [{ label: '** ANY VALUE IS ALLOWED **', value: `Options are loaded from the Form.io resource (${component.data.resource}).` }];
80
+ fieldInfo.options = [{ label: '** USE fetch_external_data TOOL **', value: `Call the \`fetch_external_data\` tool with \`field_path\`="${path}" to retrieve available options from the Form.io resource (${component.data.resource}).` }];
81
81
  }
82
82
  else if (component.dataSrc === 'json') {
83
83
  fieldInfo.options = [{ label: '** ANY VALUE IS ALLOWED **', value: 'Options are not dynamically defined.' }];
@@ -95,6 +95,9 @@ class UAGFormInterface extends appserver_1.FormInterface {
95
95
  }
96
96
  }
97
97
  }
98
+ if (component.type === 'datasource') {
99
+ fieldInfo.options = [{ label: '** USE fetch_external_data TOOL **', value: `Call the \`fetch_external_data\` tool with \`field_path\`="${path}" to retrieve data from the external datasource.` }];
100
+ }
98
101
  fieldInfo.nested = this.isNestedComponent(component);
99
102
  return fieldInfo;
100
103
  }
@@ -203,6 +206,13 @@ class UAGFormInterface extends appserver_1.FormInterface {
203
206
  break;
204
207
  case 'selectboxes':
205
208
  case 'select':
209
+ if (component.dataSrc === 'url' || component.dataSrc === 'resource') {
210
+ rule += `You MUST first call the \`fetch_external_data\` tool with the \`field_path\` set to this component's \`data_path\` to retrieve the available options, then select ${this.isMultiple(component) ? 'one or more (as comma separated values)' : 'one'} of the returned options. Use the **value** (not the label) when setting this field's data.`;
211
+ }
212
+ else {
213
+ rule += `The value must be ${this.isMultiple(component) ? 'one or more (as comma separated values)' : 'one'} of the following options provided in the "**Options**" section of that component, formatted as " - Label (value)":`;
214
+ }
215
+ break;
206
216
  case 'radio':
207
217
  rule += `The value must be ${this.isMultiple(component) ? 'one or more (as comma separated values)' : 'one'} of the following options provided in the "**Options**" section of that component, formatted as " - Label (value)":`;
208
218
  break;
@@ -221,6 +231,20 @@ class UAGFormInterface extends appserver_1.FormInterface {
221
231
  case 'email':
222
232
  rule += 'The value must be a valid email address.';
223
233
  break;
234
+ case 'datasource':
235
+ rule += 'You MUST first call the `fetch_external_data` tool with the `field_path` set to this component\'s `data_path` to retrieve the external data. The value will be set automatically from the fetched data.';
236
+ break;
237
+ case 'survey':
238
+ rule += 'The value is an object of question/value pairs based on the following survey configuration.\n';
239
+ rule += ' **Keys**: The value `keys` will be the "value" property for each question in the survey configuration.';
240
+ component.questions.forEach((question, index) => {
241
+ rule += `\n - **${question.label}** (${question.value}): ${question.tooltip || ''}`;
242
+ });
243
+ rule += '\n\n **Values**: Only ONE of the the following values are allowed for each question.';
244
+ component.values.forEach((value) => {
245
+ rule += `\n - **${value.label}** (${value.value})`;
246
+ });
247
+ break;
224
248
  default:
225
249
  rule += '';
226
250
  }
@@ -276,6 +300,22 @@ class UAGFormInterface extends appserver_1.FormInterface {
276
300
  return null;
277
301
  }
278
302
  }
303
+ /**
304
+ * Override the process methods for the UAG.
305
+ */
306
+ async process(submission, auth, headers = {}, findQuery = {}, additional = []) {
307
+ const processors = [...core_1.Processors, ...additional];
308
+ // Remove the 'fetch' processor since UAG handles this with the fetch_external_data tool.
309
+ const fetchIndex = processors.findIndex((p) => p.name === 'fetch');
310
+ if (fetchIndex !== -1) {
311
+ processors.splice(fetchIndex, 1);
312
+ }
313
+ // Return the processed submission data.
314
+ const context = await super.process(submission, auth, headers, findQuery, processors);
315
+ // Filter the "required" validation since the UAG retrieves required fields separately.
316
+ context.scope.errors = context.scope.errors.filter((error) => (error.ruleName !== 'required'));
317
+ return context;
318
+ }
279
319
  /**
280
320
  * Get the relevant fields from the current form. This will return any non-nested input components whose
281
321
  * values have not already been set within the data model. This allows the agent to know what fields still need to be
@@ -302,8 +342,7 @@ class UAGFormInterface extends appserver_1.FormInterface {
302
342
  optional: { components: [], rules: {} }
303
343
  };
304
344
  const nestedPaths = [];
305
- const context = await this.process(submission, authInfo, null, null, null, [
306
- ...core_1.Processors,
345
+ const context = await this.process(submission, authInfo, null, null, [
307
346
  {
308
347
  name: 'getFields',
309
348
  shouldProcess: () => true,
@@ -379,11 +418,7 @@ class UAGFormInterface extends appserver_1.FormInterface {
379
418
  }
380
419
  ]);
381
420
  if (context.scope?.errors?.length) {
382
- // Filter the "required" validation since those are added to the fieldInfo separately.
383
- context.scope.errors = context.scope.errors.filter((error) => (error.ruleName !== 'required'));
384
- if (context.scope.errors?.length) {
385
- fieldInfo.errors = this.convertToFormFieldErrors((0, core_1.interpolateErrors)(context.scope.errors));
386
- }
421
+ fieldInfo.errors = this.convertToFormFieldErrors((0, core_1.interpolateErrors)(context.scope.errors));
387
422
  }
388
423
  return fieldInfo;
389
424
  }
@@ -455,16 +490,24 @@ class UAGFormInterface extends appserver_1.FormInterface {
455
490
  prefix += ' ';
456
491
  }
457
492
  else if (this.inputComponent(component) && (0, core_1.componentHasValue)(component, value)) {
458
- if (typeof value === 'string' &&
459
- ((component.editor === 'ckeditor') ||
460
- (component.editor === 'quill'))) {
461
- value = this.htmlToMarkdown(value);
493
+ const modelType = core_1.Utils.getModelType(component);
494
+ if ((0, lodash_1.isObjectLike)(value)) {
495
+ value = JSON.stringify(value);
496
+ }
497
+ else if (modelType === 'string' && (0, lodash_1.isString)(value)) {
498
+ const editor = component.editor;
499
+ if (editor === 'ckeditor' || editor === 'quill') {
500
+ value = this.htmlToMarkdown(value);
501
+ }
502
+ else {
503
+ value = `"${value.trim()}"`;
504
+ }
462
505
  }
463
506
  uagData.push({
464
507
  prefix,
465
508
  path,
466
509
  label: component.label || component.key || path,
467
- value: (0, lodash_1.isObjectLike)(value) ? JSON.stringify(value) : value
510
+ value
468
511
  });
469
512
  }
470
513
  }, false, false, undefined, undefined, undefined, (component, data) => {
@@ -1,7 +1,7 @@
1
1
  import { ProjectInterface, SubmissionRequest } from "@formio/appserver";
2
2
  import { UAGConfig } from "./config";
3
3
  import { Form } from "@formio/core";
4
- import { NextFunction, Response } from "express";
4
+ import { NextFunction, Response, Router } from "express";
5
5
  import { UAGFormInterface } from "./UAGFormInterface";
6
6
  import { ResponseTemplate, UAGTemplate } from "./template";
7
7
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
@@ -13,8 +13,8 @@ export declare class UAGProjectInterface extends ProjectInterface {
13
13
  get config(): UAGConfig;
14
14
  constructor(endpoint?: string);
15
15
  initialize(): Promise<void>;
16
- addForm(form: Form, key: string): UAGFormInterface | undefined;
17
- router(): Promise<any>;
16
+ addForm(form: Form, machineName: string): UAGFormInterface | undefined;
17
+ uagRouter(): Router;
18
18
  authorizeRequest(req: SubmissionRequest, res: Response, next: NextFunction): Promise<Response<any, Record<string, any>> | undefined>;
19
19
  mcpJSONResponse(data?: object, isError?: boolean): {
20
20
  isError?: boolean | undefined;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.UAGProjectInterface = void 0;
4
4
  const appserver_1 = require("@formio/appserver");
5
+ const express_1 = require("express");
5
6
  const tools_1 = require("./tools");
6
7
  const router_1 = require("./router");
7
8
  const template_1 = require("./template");
@@ -36,25 +37,25 @@ class UAGProjectInterface extends appserver_1.ProjectInterface {
36
37
  }
37
38
  }
38
39
  }
39
- addForm(form, key) {
40
- const formInterface = super.addForm(form, key);
40
+ addForm(form, machineName) {
41
+ const formInterface = super.addForm(form, machineName);
41
42
  if (!formInterface)
42
43
  return;
43
- key = key || formInterface?.form?.key;
44
+ machineName = formInterface?.form?.machineName;
44
45
  if (form.tags?.includes('uag')) {
45
- debug(`Registering UAG form: ${key}`);
46
+ debug(`Registering UAG form: ${machineName}`);
46
47
  formInterface.uag = {
47
- machineName: key,
48
- name: form.name || form.path || key,
49
- title: form.title || form.name || key,
48
+ machineName: machineName,
49
+ name: form.name || form.path || machineName,
50
+ title: form.title || form.name || machineName,
50
51
  description: form.properties?.description || `A form to submit new ${form.title} records.`
51
52
  };
52
53
  this.formNames.push(formInterface.uag.name);
53
54
  }
54
55
  return formInterface;
55
56
  }
56
- async router() {
57
- const router = await super.router();
57
+ uagRouter() {
58
+ const router = (0, express_1.Router)();
58
59
  const uagRouter = (0, router_1.UAGRouter)(this);
59
60
  router.use('/mcp', (req, res, next) => this.authorizeRequest(req, res, next), uagRouter);
60
61
  return router;
package/lib/config.d.ts CHANGED
@@ -44,6 +44,7 @@ export type UAGToolOverride = {
44
44
  submission_update?: ToolInfo;
45
45
  find_submissions?: ToolInfo;
46
46
  agent_provide_data?: ToolInfo;
47
+ fetch_external_data?: ToolInfo;
47
48
  };
48
49
  export interface UAGConfig extends ServerConfig {
49
50
  baseUrl?: string;
package/lib/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export type UAGModule = ServerModule & {
7
7
  };
8
8
  export declare class UAGServer extends Server {
9
9
  constructor(config?: UAGConfig);
10
+ router(): Promise<import("express-serve-static-core").Router>;
10
11
  }
11
12
  export * from '@formio/appserver';
12
13
  export * from './config';
package/lib/index.js CHANGED
@@ -27,6 +27,7 @@ class UAGServer extends appserver_1.Server {
27
27
  baseUrl: (0, lodash_1.get)(process.env, 'BASE_URL', '').toString(),
28
28
  license: (0, lodash_1.get)(process.env, 'UAG_LICENSE', '').toString(),
29
29
  submissionProxy: true,
30
+ processAsClient: true,
30
31
  auth: { pkce: true }
31
32
  }));
32
33
  this.use({
@@ -34,6 +35,12 @@ class UAGServer extends appserver_1.Server {
34
35
  FormInterface: UAGFormInterface_1.UAGFormInterface
35
36
  });
36
37
  }
38
+ async router() {
39
+ const router = await super.router();
40
+ const uagProject = Object.values(this.projectRouter?.projects || {})[0];
41
+ router.use(uagProject.uagRouter());
42
+ return router;
43
+ }
37
44
  }
38
45
  exports.UAGServer = UAGServer;
39
46
  __exportStar(require("@formio/appserver"), exports);
package/lib/router.js CHANGED
@@ -39,7 +39,9 @@ function UAGRouter(project) {
39
39
  const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
40
40
  sessionIdGenerator: undefined, // Use "undefined" to trigger stateless mode.
41
41
  });
42
- project.mcpServer.connect(transport);
42
+ // Close any existing connection before connecting to a new transport (required in SDK 1.26.0+)
43
+ await project.mcpServer.close();
44
+ await project.mcpServer.connect(transport);
43
45
  if (!transport) {
44
46
  res.status(500).json(JSON.parse(createJsonRpcErrorResponse(-32603, "Unable to create MCP transport.")));
45
47
  return;
@@ -57,7 +59,9 @@ function UAGRouter(project) {
57
59
  const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
58
60
  sessionIdGenerator: undefined, // Use "undefined" to trigger stateless mode.
59
61
  });
60
- project.mcpServer.connect(transport);
62
+ // Close any existing connection before connecting to a new transport (required in SDK 1.26.0+)
63
+ await project.mcpServer.close();
64
+ await project.mcpServer.connect(transport);
61
65
  if (!transport) {
62
66
  res.status(500).json(JSON.parse(createJsonRpcErrorResponse(-32603, "Unable to create MCP transport.")));
63
67
  return;
package/lib/template.d.ts CHANGED
@@ -5,7 +5,6 @@ import { UAGTemplateConfig } from './config';
5
5
  */
6
6
  export declare enum ResponseTemplate {
7
7
  collectedData = "collectedData",
8
- submittedData = "submittedData",
9
8
  fieldRules = "fieldRules",
10
9
  allFieldsCollected = "allFieldsCollected",
11
10
  getFormFieldsEmpty = "getFormFieldsEmpty",
@@ -19,6 +18,7 @@ export declare enum ResponseTemplate {
19
18
  formNotFound = "formNotFound",
20
19
  noSubmissionsFound = "noSubmissionsFound",
21
20
  submissionsFound = "submissionsFound",
21
+ submissionCount = "submissionCount",
22
22
  submissionSearchError = "submissionSearchError",
23
23
  submissionNotFound = "submissionNotFound",
24
24
  updateNotConfirmed = "updateNotConfirmed",
@@ -32,10 +32,13 @@ export declare enum ResponseTemplate {
32
32
  fields = "fields",
33
33
  fieldList = "fieldList",
34
34
  fieldValues = "fieldValues",
35
+ formData = "formData",
35
36
  getAvailableForms = "getAvailableForms",
36
37
  noFormsAvailable = "noFormsAvailable",
37
38
  uagComponentNotFound = "uagComponentNotFound",
38
- agentProcessData = "agentProcessData"
39
+ agentProcessData = "agentProcessData",
40
+ fetchExternalData = "fetchExternalData",
41
+ fetchExternalDataError = "fetchExternalDataError"
39
42
  }
40
43
  export declare class UAGTemplate {
41
44
  config: UAGTemplateConfig;
package/lib/template.js CHANGED
@@ -43,7 +43,6 @@ const lodash_1 = require("lodash");
43
43
  var ResponseTemplate;
44
44
  (function (ResponseTemplate) {
45
45
  ResponseTemplate["collectedData"] = "collectedData";
46
- ResponseTemplate["submittedData"] = "submittedData";
47
46
  ResponseTemplate["fieldRules"] = "fieldRules";
48
47
  ResponseTemplate["allFieldsCollected"] = "allFieldsCollected";
49
48
  ResponseTemplate["getFormFieldsEmpty"] = "getFormFieldsEmpty";
@@ -57,6 +56,7 @@ var ResponseTemplate;
57
56
  ResponseTemplate["formNotFound"] = "formNotFound";
58
57
  ResponseTemplate["noSubmissionsFound"] = "noSubmissionsFound";
59
58
  ResponseTemplate["submissionsFound"] = "submissionsFound";
59
+ ResponseTemplate["submissionCount"] = "submissionCount";
60
60
  ResponseTemplate["submissionSearchError"] = "submissionSearchError";
61
61
  ResponseTemplate["submissionNotFound"] = "submissionNotFound";
62
62
  ResponseTemplate["updateNotConfirmed"] = "updateNotConfirmed";
@@ -70,10 +70,13 @@ var ResponseTemplate;
70
70
  ResponseTemplate["fields"] = "fields";
71
71
  ResponseTemplate["fieldList"] = "fieldList";
72
72
  ResponseTemplate["fieldValues"] = "fieldValues";
73
+ ResponseTemplate["formData"] = "formData";
73
74
  ResponseTemplate["getAvailableForms"] = "getAvailableForms";
74
75
  ResponseTemplate["noFormsAvailable"] = "noFormsAvailable";
75
76
  ResponseTemplate["uagComponentNotFound"] = "uagComponentNotFound";
76
77
  ResponseTemplate["agentProcessData"] = "agentProcessData";
78
+ ResponseTemplate["fetchExternalData"] = "fetchExternalData";
79
+ ResponseTemplate["fetchExternalDataError"] = "fetchExternalDataError";
77
80
  })(ResponseTemplate || (exports.ResponseTemplate = ResponseTemplate = {}));
78
81
  class UAGTemplate {
79
82
  constructor(config = {}) {
@@ -1,5 +1,5 @@
1
1
  ## Collected Data
2
- IMPORTANT: When presenting the values to the user, it must appear exactly as it will be submitted; e.g "user@example.com", "(555) 123-4560"
2
+ Here is the data collected so far:
3
3
 
4
- <% data.forEach(function(item) { %><%= item.prefix %>- **<%= item.label %>**: <%= item.value %>
4
+ <% data.forEach(function(item) { %><%= item.prefix %>- **<%= item.label %>** (`data_path`: "<%= item.path %>"): <%= item.value %>
5
5
  <% }); %>
@@ -0,0 +1,10 @@
1
+ <% if (options) { %>Here are the available select options for **<%= label %>** (`data_path`: "<%= dataPath %>"):
2
+
3
+ <% options.forEach(function(option) { %>
4
+ - label: <%= option.label %>, value: `<%= typeof option.value === 'object' ? JSON.stringify(option.value) : option.value %>`<% }); %>
5
+
6
+ Use the **value** (not the label) when setting this field's data via `collect_field_data`.
7
+ <% } else if (formData) { %>The "fetched" value for **<%= label %>** (`data_path`: `<%= dataPath %>`) is included with following `form_data`:
8
+
9
+ <%= formData %>
10
+ <% } %>
@@ -0,0 +1,6 @@
1
+ Error fetching external data for field `<%= dataPath %>`: <%= error %>
2
+
3
+ Please verify:
4
+ - The form name and `data_path` are correct
5
+ - The field is a select component with `dataSrc` of "url" or "resource", or a datasource component
6
+ - The external data source is accessible
@@ -1,4 +1,4 @@
1
1
  <% data.forEach(function(item) { %><%= item.prefix %>
2
- Question: **<%= item.label %>**:
2
+ Question: **<%= item.label %>** (`data_path`="<%= item.path %>"):
3
3
  - Answer: `<%= item.value %>`
4
4
  <% }); %>
@@ -0,0 +1,5 @@
1
+ ## Current `form_data`
2
+ Here is the current value for the `form_data` property including all user provided, fetched, and calculated values.
3
+
4
+ <% data.forEach(function(item) { %> - **<%= item.path %>**: <%= item.value %>
5
+ <% }); %>
@@ -0,0 +1 @@
1
+ There are **<%= submissionCount %>** total submissions within the <%= form.title %> form.
@@ -1,16 +1,13 @@
1
- Found <%= resultCount %> submissions for <%= form.title %> matching "<%= searchQuery %>":
2
-
1
+ Found **<%= submissions.length %>** out of **<%= submissionCount %>** total submissions for <%= form.title %> matching the query "<%= searchQuery %>":
3
2
  <% submissions.forEach(function(submission, index) { %>
4
3
  **Submission <%= index + 1 %>**
5
- - **ID:** <%= submission._id %>
6
- - **Requested Data:**
4
+ - **ID:** <%= submission._id %>
5
+ - **Requested Data:**
7
6
  <% submission.data.forEach(function(field) { %>
8
7
  - <%= field.path %>: <%= field.value %>
9
- <% }); %>
10
- - **Partial ID (submission_id_partial):** <%= submission.partialId %>
11
- - **Created:** <%= submission.created %>
12
- - **Updated:** <%= submission.modified %>
13
- <% }); %>
8
+ <% }); %> - **Partial ID (submission_id_partial):** <%= submission.partialId %>
9
+ - **Created:** <%= submission.created %>
10
+ - **Updated:** <%= submission.modified %><% }); %>
14
11
  <% if (submissions.length > 1) { %>
15
12
  **Multiple submissions found. To proceed:**
16
13
  1. **Be more specific** in your search query to find just one record, OR
@@ -18,8 +15,5 @@ Found <%= resultCount %> submissions for <%= form.title %> matching "<%= searchQ
18
15
 
19
16
  Example: "Update the submission ending in **abc1**" or use `find_submissions` with `submission_id_partial: "abc1"`
20
17
 
21
- **Note:** All <%= fieldUpdates.length %> field update(s) will be applied to whichever submission you select.
22
- <% } else { %>
23
-
24
18
  Use the `confirm_submission_update` tool with the submission_id set to the full submission ID to submit the data.
25
19
  <% } %>
@@ -1,4 +1,4 @@
1
- Cannot submit the form due to the following reasons:
1
+ There are some validation errors in the data collected:
2
2
  <% validationErrors.forEach(function(error) { %>
3
3
  - <% if (error.label && error.path) { %>**<%= error.label %> (<%= error.path %>)**: <% } %><%= error.message %><% }); %>
4
4
 
@@ -83,9 +83,9 @@ export declare class SchemaBuilder {
83
83
  *
84
84
  * ```json
85
85
  * [
86
- * "email",
87
- * "firstName",
88
- * "lastName"
86
+ * "employee.email",
87
+ * "employee.firstName",
88
+ * "employee.lastName"
89
89
  * ]
90
90
  */
91
91
  fields_requested(): this;
@@ -113,6 +113,14 @@ export declare class SchemaBuilder {
113
113
  * @returns
114
114
  */
115
115
  persona(): this;
116
+ /**
117
+ * The data_path of a specific field within the form.
118
+ */
119
+ data_path(): this;
120
+ /**
121
+ * An optional search value to filter select options server-side.
122
+ */
123
+ search_value(): this;
116
124
  /**
117
125
  * An array of field updates to apply to the `form_data`.
118
126
  * Each update specifies the `data_path` and the complete new value.
@@ -120,11 +128,11 @@ export declare class SchemaBuilder {
120
128
  * ```json
121
129
  * [
122
130
  * {
123
- * "data_path": "firstName",
131
+ * "data_path": "customer.firstName",
124
132
  * "new_value": "John"
125
133
  * },
126
134
  * {
127
- * "data_path": "lastName",
135
+ * "data_path": "customer.lastName",
128
136
  * "new_value": "Doe"
129
137
  * }
130
138
  * ]
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.SchemaBuilder = void 0;
7
7
  const zod_1 = __importDefault(require("zod"));
8
+ const DATA_PATH_DESCRIPTION = `The \`data_path\` (or "full path") to a component's instance value within a form submission (e.g., "customer.firstName"). Use \`get_form_fields\` to find the \`data_path\` of this component.`;
8
9
  /**
9
10
  * Schema builder for tool input schemas.
10
11
  */
@@ -42,7 +43,7 @@ class SchemaBuilder {
42
43
  * ```
43
44
  */
44
45
  form_data() {
45
- this.schema.form_data = zod_1.default.record(zod_1.default.string().describe('The `data_path` of collected field.'), zod_1.default.any().describe(`The value collected for the field. ${this.valueRules}`)).optional().default({}).describe('The current data collected so far for the whole form without any updates applied. Include ALL previously collected field data to maintain session state.');
46
+ this.schema.form_data = zod_1.default.record(zod_1.default.string().describe(DATA_PATH_DESCRIPTION), zod_1.default.any().describe(`The value collected for the field. ${this.valueRules}`)).optional().default({}).describe('The current data collected so far for the whole form without any updates applied. Include ALL previously collected field data to maintain session state.');
46
47
  return this;
47
48
  }
48
49
  /**
@@ -63,7 +64,7 @@ class SchemaBuilder {
63
64
  * ```
64
65
  */
65
66
  field_paths() {
66
- this.schema.field_paths = zod_1.default.array(zod_1.default.string()).describe('An array of `data_path`s to get detailed information for specific fields. Each `data_path` within the array should correlate to the fields the user has provided values for. The `data_path`s can be found using the `get_form_fields` tool.');
67
+ this.schema.field_paths = zod_1.default.array(zod_1.default.string().describe(DATA_PATH_DESCRIPTION)).describe("An array of `data_path`(s) to get detailed information for specific fields.");
67
68
  return this;
68
69
  }
69
70
  /**
@@ -101,10 +102,23 @@ class SchemaBuilder {
101
102
  */
102
103
  search_query() {
103
104
  this.schema.search_query = zod_1.default.array(zod_1.default.object({
104
- data_path: zod_1.default.string().describe('The data path of the field used to search (e.g., "email", "customer.firstName", or "parent.email")'),
105
- operator: zod_1.default.enum(['equals', 'not_equals', 'contains', 'starts_with', 'ends_with', 'regex', 'in', 'nin', 'greater_than', 'greater_than_equal', 'less_than', 'less_than_equal']).optional().default('contains').describe('The operator to use for matching. "equals" for exact match, "contains" for substring match, "starts_with" or "ends_with" for prefix/suffix match, "regex" for regular expression match, "greater_than" or "less_than" only for numeric values, "in" if searching for multiple values as comma-separated values, "nin" if excluding multiple values as comma-separated values.'),
105
+ data_path: zod_1.default.string().describe(DATA_PATH_DESCRIPTION),
106
+ operator: zod_1.default.enum([
107
+ 'equals',
108
+ 'not_equals',
109
+ 'contains',
110
+ 'starts_with',
111
+ 'ends_with',
112
+ 'regex',
113
+ 'in',
114
+ 'nin',
115
+ 'greater_than',
116
+ 'greater_than_equal',
117
+ 'less_than',
118
+ 'less_than_equal'
119
+ ]).optional().default('contains').describe('The operator to use for matching. "equals" for exact match, "contains" for substring match, "starts_with" or "ends_with" for prefix/suffix match, "regex" for regular expression match, "greater_than" or "less_than" only for numeric values, "in" if searching for multiple values as comma-separated values, "nin" if excluding multiple values as comma-separated values.'),
106
120
  search_value: zod_1.default.string().describe('The value of the field to use when searching.')
107
- }).describe('Object containing the `data_path` and search value. Use `get_form_fields` to get all the field information to populate this search query object.')).describe('Array of search criteria to find matching submissions. All criteria must match (AND logic). Use `get_form_fields` to get `data_path`s.');
121
+ }).describe('Object containing the `data_path` and search value. Use `get_form_fields` to get all the field information to populate this search query object.')).optional().describe('Array of search criteria to find matching submissions. All criteria must match (AND logic). Do not provide this parameter to get the total count of submissions without retrieving the submission details.');
108
122
  return this;
109
123
  }
110
124
  /**
@@ -113,13 +127,13 @@ class SchemaBuilder {
113
127
  *
114
128
  * ```json
115
129
  * [
116
- * "email",
117
- * "firstName",
118
- * "lastName"
130
+ * "employee.email",
131
+ * "employee.firstName",
132
+ * "employee.lastName"
119
133
  * ]
120
134
  */
121
135
  fields_requested() {
122
- this.schema.fields_requested = zod_1.default.array(zod_1.default.string()).optional().describe('An array of "data_path"(s) for values you wish to include in the result. If not provided, only submission IDs and timestamps are returned. Use `get_form_fields` to get valid `data_path`s. (e.g., ["email"] - "What is the email address for the employee Joe Smith?")');
136
+ this.schema.fields_requested = zod_1.default.array(zod_1.default.string().describe(DATA_PATH_DESCRIPTION)).optional().describe("An array of `data_path`(s) for values you wish to include in the result. If not provided, only submission IDs and timestamps are returned. (e.g., [\"employee.email\"] - \"What is the email address for the employee Joe Smith?\")");
123
137
  return this;
124
138
  }
125
139
  /**
@@ -161,6 +175,20 @@ class SchemaBuilder {
161
175
  this.schema.persona = zod_1.default.string().optional().describe('The agent persona "type" to use when processing the existing submission data. The persona provides context to the agent on how to interpret and understand the data within the form submission. If not provided, the default persona defined within the form\'s UAG settings will be used.');
162
176
  return this;
163
177
  }
178
+ /**
179
+ * The data_path of a specific field within the form.
180
+ */
181
+ data_path() {
182
+ this.schema.data_path = zod_1.default.string().describe(DATA_PATH_DESCRIPTION);
183
+ return this;
184
+ }
185
+ /**
186
+ * An optional search value to filter select options server-side.
187
+ */
188
+ search_value() {
189
+ this.schema.search_value = zod_1.default.string().optional().describe('An optional search string to filter the select options server-side. The component must have a `searchField` configured for this to take effect.');
190
+ return this;
191
+ }
164
192
  /**
165
193
  * An array of field updates to apply to the `form_data`.
166
194
  * Each update specifies the `data_path` and the complete new value.
@@ -168,11 +196,11 @@ class SchemaBuilder {
168
196
  * ```json
169
197
  * [
170
198
  * {
171
- * "data_path": "firstName",
199
+ * "data_path": "customer.firstName",
172
200
  * "new_value": "John"
173
201
  * },
174
202
  * {
175
- * "data_path": "lastName",
203
+ * "data_path": "customer.lastName",
176
204
  * "new_value": "Doe"
177
205
  * }
178
206
  * ]
@@ -180,7 +208,7 @@ class SchemaBuilder {
180
208
  */
181
209
  updates() {
182
210
  this.schema.updates = zod_1.default.array(zod_1.default.object({
183
- data_path: zod_1.default.string().describe('The `data_path` of the field to update. To find the data_path, you can use the `get_form_fields` tool.'),
211
+ data_path: zod_1.default.string().describe(DATA_PATH_DESCRIPTION),
184
212
  new_value: zod_1.default.any().describe(`The complete new value for the field. ${this.valueRules}`)
185
213
  }).describe('A single field update including the `data_path` and new value.')).describe('An array of field updates to apply to the `form_data`. Each update specifies the `data_path` and the complete new value.');
186
214
  return this;
@@ -0,0 +1,3 @@
1
+ import { UAGProjectInterface } from "../UAGProjectInterface";
2
+ import { ToolInfo } from "./utils";
3
+ export declare const fetchExternalData: (project: UAGProjectInterface) => Promise<ToolInfo>;
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchExternalData = void 0;
4
+ const template_1 = require("../template");
5
+ const core_1 = require("@formio/core");
6
+ const SchemaBuilder_1 = require("./SchemaBuilder");
7
+ const lodash_1 = require("lodash");
8
+ const error = require('debug')('formio:uag:fetchExternalData:error');
9
+ /**
10
+ * Strip HTML tags from a string.
11
+ */
12
+ function stripHtml(str) {
13
+ return str.replace(/<[^>]*>/g, '').trim();
14
+ }
15
+ /**
16
+ * Extract label from an item using the component's template.
17
+ * For resource select items, the item is a full submission object with data nested under `item.data`.
18
+ * The template may reference `item.propertyName` which could be at `item.data.propertyName`.
19
+ */
20
+ function extractLabel(item, component, evalContext) {
21
+ if (component.template) {
22
+ evalContext.item = item; // Add the current item to the interpolation context for template evaluation
23
+ const label = core_1.Evaluator.interpolateString(component.template, evalContext);
24
+ if (label !== undefined && label !== null) {
25
+ return stripHtml(String(label));
26
+ }
27
+ }
28
+ // Fallback: try common label properties
29
+ return item.label || item.name || item.title || String(item);
30
+ }
31
+ /**
32
+ * Extract value from an item using the component's valueProperty.
33
+ */
34
+ function extractValue(item, component) {
35
+ if (component.valueProperty) {
36
+ return (0, lodash_1.get)(item, component.valueProperty);
37
+ }
38
+ return item;
39
+ }
40
+ /**
41
+ * Check if a component is a select-type component with external data (url or resource).
42
+ */
43
+ function isExternalSelect(component) {
44
+ if (component.type !== 'select' && component.type !== 'selectboxes')
45
+ return false;
46
+ const dataSrc = component.dataSrc;
47
+ return dataSrc === 'url' || dataSrc === 'resource';
48
+ }
49
+ /**
50
+ * Check if a component is a datasource component.
51
+ */
52
+ function isDatasource(component) {
53
+ return component.type === 'datasource';
54
+ }
55
+ /**
56
+ * Interpolate header key/value pairs, filtering out empty ones.
57
+ */
58
+ function interpolateHeaders(headers, context) {
59
+ const result = {};
60
+ if (!headers || !Array.isArray(headers))
61
+ return result;
62
+ for (const header of headers) {
63
+ const key = header.key ? core_1.Evaluator.interpolateString(header.key, context) : '';
64
+ const value = header.value ? core_1.Evaluator.interpolateString(header.value, context) : '';
65
+ if (key && value) {
66
+ result[key] = value;
67
+ }
68
+ }
69
+ return result;
70
+ }
71
+ const fetchExternalData = async (project) => {
72
+ return (0, lodash_1.defaultsDeep)(project.config?.toolOverrides?.fetch_external_data || {}, {
73
+ name: 'fetch_external_data',
74
+ title: 'Fetch External Data',
75
+ description: 'Fetch external data for a component that loads data from an external URL, a Form.io Resource, or a DataSource. Use this tool when `get_form_fields` indicates that a field requires calling `fetch_external_data` to retrieve its data. For select components, this returns the available options. For datasource components, this returns the fetched data. You can optionally provide a `search_value` to filter results server-side. If the component\'s URL or headers contain interpolation tokens (e.g. `{{ data.someField }}`), you MUST provide `form_data` with the current collected data so the tokens can be resolved.',
76
+ inputSchema: (new SchemaBuilder_1.SchemaBuilder(project))
77
+ .form_name()
78
+ .data_path()
79
+ .form_data()
80
+ .search_value().schema,
81
+ execute: async ({ form_name, data_path, form_data, search_value }, extra) => {
82
+ const form = await project.getForm(form_name);
83
+ if (!form) {
84
+ return project.mcpResponse(template_1.ResponseTemplate.formNotFound, { formName: form_name }, true);
85
+ }
86
+ try {
87
+ const component = form.getComponent(data_path);
88
+ if (!component) {
89
+ return project.mcpResponse(template_1.ResponseTemplate.fetchExternalDataError, {
90
+ dataPath: data_path,
91
+ error: `No component found at path "${data_path}".`
92
+ }, true);
93
+ }
94
+ // Build the interpolation context from the current form data.
95
+ const submission = form_data ? form.convertToSubmission(form_data) : { data: {} };
96
+ const evalContext = {
97
+ form: form.form,
98
+ component,
99
+ components: form.form.components,
100
+ data: submission.data || {},
101
+ row: submission.data || {},
102
+ submission
103
+ };
104
+ if (isDatasource(component)) {
105
+ const items = await fetchDatasourceData(component, extra.authInfo, evalContext);
106
+ (0, lodash_1.set)(submission.data || {}, data_path, items);
107
+ const errors = await form.validateData(submission, extra.authInfo);
108
+ if (errors?.length > 0) {
109
+ return project.mcpResponse(template_1.ResponseTemplate.fieldValidationErrors, { invalidFields: errors });
110
+ }
111
+ return project.mcpResponse(template_1.ResponseTemplate.fetchExternalData, {
112
+ label: component.label || component.key,
113
+ dataPath: data_path,
114
+ options: false,
115
+ formData: project.uagTemplate?.renderTemplate(template_1.ResponseTemplate.formData, {
116
+ data: form.formatData(submission.data)
117
+ })
118
+ });
119
+ }
120
+ if (!isExternalSelect(component)) {
121
+ return project.mcpResponse(template_1.ResponseTemplate.fetchExternalDataError, {
122
+ dataPath: data_path,
123
+ error: `Component at "${data_path}" is of type "${component.type}" and does not load external data. This tool only supports select/selectboxes components with dataSrc "url" or "resource", and datasource components.`
124
+ }, true);
125
+ }
126
+ const selectComponent = component;
127
+ const dataSrc = selectComponent.dataSrc;
128
+ if (dataSrc !== 'url' && dataSrc !== 'resource') {
129
+ return project.mcpResponse(template_1.ResponseTemplate.fetchExternalDataError, {
130
+ dataPath: data_path,
131
+ error: `Component at "${data_path}" has dataSrc "${dataSrc}". Only "url" and "resource" are supported by this tool.`
132
+ }, true);
133
+ }
134
+ let items = [];
135
+ if (dataSrc === 'url') {
136
+ items = await fetchUrlData(selectComponent, search_value, extra.authInfo, evalContext);
137
+ }
138
+ else if (dataSrc === 'resource') {
139
+ items = await fetchResourceData(selectComponent, search_value, extra.authInfo, project, evalContext);
140
+ }
141
+ const options = items.map(item => ({
142
+ label: extractLabel(item, selectComponent, evalContext),
143
+ value: extractValue(item, selectComponent)
144
+ }));
145
+ return project.mcpResponse(template_1.ResponseTemplate.fetchExternalData, {
146
+ label: selectComponent.label || selectComponent.key,
147
+ dataPath: data_path,
148
+ options,
149
+ formData: {}
150
+ });
151
+ }
152
+ catch (err) {
153
+ error('Error fetching external data:', err);
154
+ return project.mcpResponse(template_1.ResponseTemplate.fetchExternalDataError, {
155
+ dataPath: data_path,
156
+ error: err instanceof Error ? err.message : 'Unknown error'
157
+ }, true);
158
+ }
159
+ }
160
+ });
161
+ };
162
+ exports.fetchExternalData = fetchExternalData;
163
+ async function fetchDatasourceData(component, authInfo, context) {
164
+ const fetchConfig = component.fetch;
165
+ if (!fetchConfig?.url) {
166
+ throw new Error('No URL configured for this datasource component.');
167
+ }
168
+ // Interpolate the URL
169
+ const interpolatedUrl = core_1.Evaluator.interpolateString(fetchConfig.url, context);
170
+ const url = new URL(interpolatedUrl);
171
+ // Build and interpolate headers
172
+ const headers = interpolateHeaders(fetchConfig.headers, context);
173
+ // Add auth token if forwardHeaders or authenticate is enabled
174
+ if ((fetchConfig.forwardHeaders || fetchConfig.authenticate) && authInfo?.token) {
175
+ headers['x-jwt-token'] = authInfo.token;
176
+ }
177
+ const response = await fetch(url.toString(), {
178
+ method: (fetchConfig.method || 'get').toUpperCase(),
179
+ headers
180
+ });
181
+ if (!response.ok) {
182
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
183
+ }
184
+ let result = await response.json();
185
+ if (!Array.isArray(result)) {
186
+ // If the result has a 'results' property that is an array, use that
187
+ if (result.results && Array.isArray(result.results)) {
188
+ result = result.results;
189
+ }
190
+ else {
191
+ // Wrap single objects in an array
192
+ result = [result];
193
+ }
194
+ }
195
+ return result;
196
+ }
197
+ async function fetchUrlData(component, search_value, authInfo, context) {
198
+ const data = component.data;
199
+ const baseUrl = data?.url;
200
+ if (!baseUrl) {
201
+ throw new Error('No URL configured for this component.');
202
+ }
203
+ // Interpolate the URL
204
+ const interpolatedUrl = core_1.Evaluator.interpolateString(baseUrl, context);
205
+ const url = new URL(interpolatedUrl);
206
+ // Add filter query string if configured (interpolate the filter string)
207
+ if (component.filter) {
208
+ const interpolatedFilter = core_1.Evaluator.interpolateString(component.filter, context);
209
+ const filterParams = new URLSearchParams(interpolatedFilter);
210
+ filterParams.forEach((value, key) => {
211
+ url.searchParams.set(key, value);
212
+ });
213
+ }
214
+ // Add search parameter if provided
215
+ if (search_value && component.searchField) {
216
+ url.searchParams.set(component.searchField, search_value);
217
+ }
218
+ // Add limit
219
+ const limit = component.limit || 100;
220
+ url.searchParams.set('limit', String(limit));
221
+ // Build and interpolate headers
222
+ const headers = interpolateHeaders(data.headers, context);
223
+ // Add auth token if authenticate is enabled
224
+ if (component.authenticate && authInfo?.token) {
225
+ headers['x-jwt-token'] = authInfo.token;
226
+ }
227
+ const response = await fetch(url.toString(), { headers });
228
+ if (!response.ok) {
229
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
230
+ }
231
+ let result = await response.json();
232
+ // Navigate to selectValues path if configured
233
+ if (component.selectValues) {
234
+ result = (0, lodash_1.get)(result, component.selectValues, result);
235
+ }
236
+ if (!Array.isArray(result)) {
237
+ throw new Error('Response is not an array. Check the URL and selectValues configuration.');
238
+ }
239
+ return result;
240
+ }
241
+ async function fetchResourceData(component, search_value, authInfo, project, context) {
242
+ const data = component.data;
243
+ const resourceId = data?.resource;
244
+ if (!resourceId) {
245
+ throw new Error('No resource ID configured for this component.');
246
+ }
247
+ const resourceForm = await project.getFormById(resourceId);
248
+ if (!resourceForm) {
249
+ throw new Error(`Resource form with ID "${resourceId}" not found.`);
250
+ }
251
+ const query = {};
252
+ // Add search filter if provided
253
+ if (search_value && component.searchField) {
254
+ query[`data.${component.searchField}__regex`] = `/${search_value}/i`;
255
+ }
256
+ // Add component filter if configured (interpolate the filter string)
257
+ if (component.filter) {
258
+ const interpolatedFilter = core_1.Evaluator.interpolateString(component.filter, context);
259
+ const filterParams = new URLSearchParams(interpolatedFilter);
260
+ filterParams.forEach((value, key) => {
261
+ query[key] = value;
262
+ });
263
+ }
264
+ // Add limit
265
+ query.limit = component.limit || 100;
266
+ const result = await resourceForm.find(query, authInfo);
267
+ return result.items || [];
268
+ }
@@ -9,7 +9,7 @@ const findSubmission = async (project) => {
9
9
  return (0, lodash_1.defaultsDeep)(project.config?.toolOverrides?.find_submissions || {}, {
10
10
  name: 'find_submissions',
11
11
  title: 'Find submissions within a form',
12
- description: 'Find existing form submissions based on field values (search query). Use this to search for people, records, or data by name, email, phone, position, company, or other field values. Examples: find a contact by name, find someone who works at a company, find records with specific criteria.',
12
+ description: 'Find existing form submissions based on field values (search query). Use this to search for people, records, or data by name, email, phone, position, company, or other field values. Examples: find a contact by name, find someone who works at a company, find records with specific criteria. To retrieve the total count of records within a form, use this tool without providing a `search_query`.',
13
13
  inputSchema: (new SchemaBuilder_1.SchemaBuilder(project))
14
14
  .form_name()
15
15
  .search_query()
@@ -22,8 +22,9 @@ const findSubmission = async (project) => {
22
22
  if (!form) {
23
23
  return project.mcpResponse(template_1.ResponseTemplate.formNotFound, { formName: form_name }, true);
24
24
  }
25
+ search_query = search_query || [];
25
26
  const query = {};
26
- if (!submission_id) {
27
+ if (!submission_id && search_query.length > 0) {
27
28
  for (const criterion of search_query) {
28
29
  if (!criterion.data_path || !criterion.search_value) {
29
30
  return project.mcpResponse(template_1.ResponseTemplate.submissionSearchError, {
@@ -80,17 +81,29 @@ const findSubmission = async (project) => {
80
81
  }
81
82
  }
82
83
  try {
84
+ let submissionCount = 0;
83
85
  let submissions = [];
84
86
  if (submission_id) {
85
87
  const result = await form.loadSubmission(submission_id, extra.authInfo);
86
88
  if (result) {
87
89
  submissions = [result];
90
+ submissionCount = 1;
88
91
  }
89
92
  }
90
93
  else {
91
94
  query.limit = ((0, lodash_1.isNumber)(limit) && limit > 0) ? limit : 10;
92
- const result = await form.find(query, extra.authInfo);
93
- submissions = result.items;
95
+ if (search_query.length > 0) {
96
+ const result = await form.find(query, extra.authInfo);
97
+ submissions = result.items;
98
+ submissionCount = result.count;
99
+ }
100
+ else {
101
+ submissionCount = await form.count({}, extra.authInfo);
102
+ return project.mcpResponse(template_1.ResponseTemplate.submissionCount, {
103
+ form: form.form,
104
+ submissionCount
105
+ });
106
+ }
94
107
  }
95
108
  if (!submissions || submissions.length === 0) {
96
109
  return project.mcpResponse(template_1.ResponseTemplate.noSubmissionsFound, {
@@ -139,6 +152,7 @@ const findSubmission = async (project) => {
139
152
  return project.mcpResponse(template_1.ResponseTemplate.submissionsFound, {
140
153
  form: form.form,
141
154
  searchQuery: JSON.stringify(search_query),
155
+ submissionCount,
142
156
  submissions: submissions.map(sub => {
143
157
  return {
144
158
  _id: sub._id,
@@ -147,8 +161,7 @@ const findSubmission = async (project) => {
147
161
  data: getFieldValues(sub),
148
162
  partialId: sub._id ? sub._id.toString().slice(-4) : 'N/A'
149
163
  };
150
- }),
151
- resultCount: submissions.length
164
+ })
152
165
  });
153
166
  }
154
167
  catch (err) {
@@ -8,6 +8,7 @@ import { findSubmission } from "./findSubmission";
8
8
  import { submissionUpdate } from "./submissionUpdate";
9
9
  import { ToolInfo } from "./utils";
10
10
  import { UAGProjectInterface } from "../UAGProjectInterface";
11
+ import { fetchExternalData } from "./fetchExternalData";
11
12
  export * from './utils';
12
- export { getForms, getFormFields, getFieldInfo, collectData, confirmSubmission, submitCompletedForm, findSubmission, submissionUpdate };
13
+ export { getForms, getFormFields, getFieldInfo, collectData, confirmSubmission, submitCompletedForm, findSubmission, submissionUpdate, fetchExternalData };
13
14
  export declare const getTools: (project: UAGProjectInterface) => Promise<ToolInfo[]>;
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.getTools = exports.submissionUpdate = exports.findSubmission = exports.submitCompletedForm = exports.confirmSubmission = exports.collectData = exports.getFieldInfo = exports.getFormFields = exports.getForms = void 0;
17
+ exports.getTools = exports.fetchExternalData = exports.submissionUpdate = exports.findSubmission = exports.submitCompletedForm = exports.confirmSubmission = exports.collectData = exports.getFieldInfo = exports.getFormFields = exports.getForms = void 0;
18
18
  const getForms_1 = require("./getForms");
19
19
  Object.defineProperty(exports, "getForms", { enumerable: true, get: function () { return getForms_1.getForms; } });
20
20
  const getFormFields_1 = require("./getFormFields");
@@ -32,6 +32,8 @@ Object.defineProperty(exports, "findSubmission", { enumerable: true, get: functi
32
32
  const submissionUpdate_1 = require("./submissionUpdate");
33
33
  Object.defineProperty(exports, "submissionUpdate", { enumerable: true, get: function () { return submissionUpdate_1.submissionUpdate; } });
34
34
  const agentProvideData_1 = require("./agentProvideData");
35
+ const fetchExternalData_1 = require("./fetchExternalData");
36
+ Object.defineProperty(exports, "fetchExternalData", { enumerable: true, get: function () { return fetchExternalData_1.fetchExternalData; } });
35
37
  __exportStar(require("./utils"), exports);
36
38
  const getTools = async (project) => {
37
39
  return [
@@ -43,7 +45,8 @@ const getTools = async (project) => {
43
45
  await (0, submitForm_1.submitCompletedForm)(project),
44
46
  await (0, findSubmission_1.findSubmission)(project),
45
47
  await (0, submissionUpdate_1.submissionUpdate)(project),
46
- await (0, agentProvideData_1.agentProvideData)(project)
48
+ await (0, agentProvideData_1.agentProvideData)(project),
49
+ await (0, fetchExternalData_1.fetchExternalData)(project)
47
50
  ];
48
51
  };
49
52
  exports.getTools = getTools;
@@ -40,20 +40,12 @@ const submitCompletedForm = async (project) => {
40
40
  });
41
41
  }
42
42
  catch (err) {
43
- const errors = [];
43
+ let errors = [];
44
44
  if (err && err.name === 'ValidationError' && err.details) {
45
- for (const detail of err.details) {
46
- if (detail.message) {
47
- errors.push({
48
- label: detail.context?.label || 'Field',
49
- path: detail.context?.path,
50
- message: detail.message
51
- });
52
- }
53
- }
45
+ errors = form.convertToFormFieldErrors(err.details);
54
46
  }
55
47
  return project.mcpResponse(template_1.ResponseTemplate.submitValidationError, {
56
- validationErrors: errors.length ? errors : [{ message: err.message || 'Unknown error during submission' }]
48
+ validationErrors: errors.length ? errors : [{ message: err.message || err.toString() }]
57
49
  }, true);
58
50
  }
59
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formio/uag",
3
- "version": "1.9.2",
3
+ "version": "1.10.2",
4
4
  "description": "The Form.io Universal Agent Gateway (UAG).",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -15,7 +15,7 @@
15
15
  "build:ts": "npx tsc",
16
16
  "build:copy-templates": "mkdir -p lib/templates && cp src/templates/*.md lib/templates/",
17
17
  "build": "npm run clean && npm run build:ts && npm run build:copy-templates",
18
- "build:docker": "npm run build && docker build -t formio/uag --platform=linux/amd64 .",
18
+ "build:docker": "docker build -t formio/uag$(node -p \"require('./package.json').version.includes('-rc.') ? ':rc' : ''\") --platform=linux/amd64 .",
19
19
  "test": "TS_NODE_PROJECT=tsconfig.test.json mocha --no-node-snapshot",
20
20
  "dev": "tsx --no-node-snapshot index.js",
21
21
  "start": "node --no-node-snapshot index.js",
@@ -24,19 +24,25 @@
24
24
  "author": "",
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
- "@formio/appserver": "^2.11.2",
27
+ "@formio/appserver": "^2.12.2",
28
28
  "@formio/core": "2.5.1-dev.291.6557e4e",
29
- "@modelcontextprotocol/sdk": "^1.25.3",
29
+ "@modelcontextprotocol/sdk": "1.27.1",
30
30
  "cors": "^2.8.6",
31
- "debug": "^4.4.1",
32
- "dotenv": "^17.2.1",
31
+ "debug": "^4.4.3",
32
+ "dotenv": "^17.3.1",
33
33
  "express": "^5.2.1",
34
- "jsonwebtoken": "^9.0.2",
34
+ "jsonwebtoken": "^9.0.3",
35
35
  "lodash": "^4.17.23",
36
36
  "node-cache": "^5.1.2",
37
37
  "node-html-markdown": "^2.0.0",
38
38
  "zod": "^4.3.6"
39
39
  },
40
+ "resolutions": {
41
+ "tar": ">=7.5.7",
42
+ "minimatch": ">=10.2.1",
43
+ "brace-expansion": ">=5.0.1",
44
+ "diff": ">=8.0.3"
45
+ },
40
46
  "devDependencies": {
41
47
  "@types/chai": "^5.2.3",
42
48
  "@types/cors": "^2.8.19",
@@ -44,12 +50,12 @@
44
50
  "@types/dotenv": "^8.2.3",
45
51
  "@types/express": "^5.0.6",
46
52
  "@types/jsonwebtoken": "^9.0.10",
47
- "@types/lodash": "^4.17.23",
53
+ "@types/lodash": "^4.17.24",
48
54
  "@types/mocha": "^10.0.10",
49
- "@types/node": "^25.1.0",
50
- "@types/supertest": "^6.0.3",
55
+ "@types/node": "^25.3.2",
56
+ "@types/supertest": "^7.2.0",
51
57
  "chai": "^6.2.2",
52
- "mocha": "^11.3.0",
58
+ "mocha": "^11.7.5",
53
59
  "supertest": "^7.2.2",
54
60
  "terser": "^5.46.0",
55
61
  "ts-node": "^10.9.2",
@@ -1,4 +0,0 @@
1
- **Submission Data Summary:** (IMPORTANT NOTE: When presenting the values to the user, it must appear exactly as they are in the submission; e.g "user@example.com", "(555) 123-4560")
2
- <% data.forEach(function(item) { %>
3
- - **<%= item.label %>**: <%= item.value %>
4
- <% }); %>