@formio/uag 1.11.2 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,9 +6,16 @@ The Universal Agent Gateway (UAG) leverages the [Model Context Protocol (MCP)](h
6
6
 
7
7
  ![Diagram showing how the UAG provides an AI Agent the ability to read and understand a Form.io Form JSON. Similar to how the JavaScript renderer provides a Human the ability to visually see a rendered form.](./examples/images/uag-agent-rendering.png)
8
8
 
9
+ Official Documentation
10
+ --------------------------
11
+ For the latest documentation, release information, and guides, always refer to the official Form.io Help Documentation available here:
12
+
13
+ **[https://help.form.io](https://help.form.io/dev/ai/uag)**
14
+
15
+ --------------------------
9
16
  There are two primary scenarios that the UAG enables:
10
17
 
11
- - **Unstructured content conversion to deterministic structured data**: This capability uses the JSON schema of a Form.io form to produce a deterministic structured data object provided a Natural Language input.
18
+ - **Unstructured 'chat' input to deterministic form submission**: This capability uses the JSON schema of a Form.io form to produce a deterministic structured data object provided natural language conversational input.
12
19
 
13
20
  ![Conversion of natural language to structured data using UAG.](./examples/images/uag-natural-language.png)
14
21
 
@@ -20,19 +27,43 @@ There are two primary scenarios that the UAG enables:
20
27
 
21
28
  ---
22
29
 
23
- One of the many benefits that the UAG has to offer is the ability to inject **Dynamic Context** into an AI processing cycle. This is achieved from the dynamic nature of the Form JSON schemas that can easily be modified and deployed to any enviornment without the need to update your application or re-train the agents within the process. Because of this, dynamic context changes can be injected seamlessly into an existing running process which will automatically update the behaviors of the Agents utilzing the UAG.
30
+ One of the many benefits that the UAG has to offer is the ability to inject **Dynamic Context** into an AI processing cycle. This is achieved from the dynamic nature of the Form JSON schemas that can easily be modified and deployed to any environment without the need to update your application or re-train the agents within the process. Because of this, dynamic context changes can be injected seamlessly into an existing running process which will automatically update the behaviors of the Agents utilizing the UAG.
24
31
 
25
32
  ## Try it out!
26
- The quickest way to try out the UAG run the [Local Example](examples/local) which will spin up a local instance of Form.io Open Source and UAG. This can then be connected to an AI agent, which will have the ability to interact with the included example forms. The local example can be run entirely free, with no trial or license required.
33
+ There are three working examples in this repo, each spinning up a Form.io server alongside the UAG with Docker Compose. Pick the one that matches what you are building.
34
+
35
+ | Example | Pattern | Licensing |
36
+ |---------|---------|-----------|
37
+ | **[Custom Module](examples/custom-module)** | **Conversational, existing client.** Connect Claude Desktop to your forms and talk to them: "I would like to add a new customer." Built as a custom module, so roles and permissions decide what the agent may do. | Open Source — free |
38
+ | **[Conversation Form](examples/conversation-form)** | **Conversational, your own UI.** A chat application whose questions come entirely from a form. About 200 lines, no field names in the code — edit the form and the conversation changes. | Open Source — free |
39
+ | **[Agentic Workflow](examples/agentic-workflow)** | **Autonomous.** A submission triggers an agent that scores it against a rubric and writes its decision back, which in turn triggers a second agent. No human, no chat window. | Enterprise — the trigger needs a Webhook action that can set headers and transform its payload |
40
+
41
+ There is also a [Flow Viewer](examples/flow-viewer) — a small proxy that shows every leg of an agentic run live, which is the fastest way to see what an agent is actually doing.
42
+
43
+ **[Conversation »](examples/conversation-form)**  |  **[Claude Desktop »](examples/custom-module)**  |  **[Autonomous »](examples/agentic-workflow)**
44
+
45
+ ## Documentation in this repository
27
46
 
28
- **[Go to Local Example »](examples/local)**
47
+ This file is the main reference: what the UAG is, how to configure a form for it, how to deploy it, and how to troubleshoot it. The rest of the documentation lives next to the code it describes.
48
+
49
+ | Document | What it covers |
50
+ |----------|----------------|
51
+ | **README.md** (this file) | Concepts, [agentic workflow setup](#agentic-workflows), the [MCP tools](#pre-defined-mcp-tools-providing-dynamic-context), [deployment](#deploying-uag), [environment variables](#environment-variables), and [troubleshooting](#troubleshooting). |
52
+ | [module/Readme.md](./module/Readme.md) | Building a **custom module** — adding your own MCP tools, form actions, pre-defined forms and resources, and configuration overrides, then mounting it into the Docker container. |
53
+ | [integrations/claude/Readme.md](./integrations/claude/Readme.md) | The **Claude integration**: its API endpoints and commands, how to run it with Node or Docker, its environment variables, and how to trigger it from a Form.io Webhook action. |
54
+ | [examples/custom-module/Readme.md](./examples/custom-module/Readme.md) | Example — a custom module driven conversationally from **Claude Desktop**, where roles and permissions govern what the agent may do. |
55
+ | [examples/conversation-form/Readme.md](./examples/conversation-form/Readme.md) | Example — a **chat application of your own** whose questions come entirely from a form. |
56
+ | [examples/agentic-workflow/Readme.md](./examples/agentic-workflow/Readme.md) | Example — an **autonomous, webhook-triggered workflow** with two chained agent personas. |
57
+ | [examples/flow-viewer/Readme.md](./examples/flow-viewer/Readme.md) | A small proxy that shows **every leg of an agentic run live** — the token request, each tool call, and the write-back. The quickest way to see what an agent is doing. |
58
+ | [examples/custom-module/module/templates/Readme.md](./examples/custom-module/module/templates/Readme.md) | Overriding the **response templates** the UAG returns to the agent, using Lodash templates. |
59
+ | [test/e2e/Readme.md](./test/e2e/Readme.md) | The **end-to-end test suite** — its three layers, what each covers, and how to run it. |
29
60
 
30
61
  ---
31
62
 
32
63
  ## Agentic Workflows
33
64
  One of the more powerful features of the **UAG** is the `agent_provide_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.
34
65
 
35
- For example, let's suppose you wish to automate the backend administration behind a College Application Process. In this example, a potential student submits an application that consists of many different fields of data, such as Academnics, Extra curricular activities, Honors, Volunteer work, as well as possibly written Essays. Historically, these applications would be reviewed by an administrator in order to assess the candidates qualifications for acceptance. With the `agent_provide_data` tool, it is now possible to automate this process as the following diagram illustrates.
66
+ For example, let's suppose you wish to automate the backend administration behind a College Application Process. In this example, a potential student submits an application that consists of many different fields of data, such as Academics, Extra curricular activities, Honors, Volunteer work, as well as possibly written Essays. Historically, these applications would be reviewed by an administrator in order to assess the candidates qualifications for acceptance. With the `agent_provide_data` tool, it is now possible to automate this process as the following diagram illustrates.
36
67
 
37
68
  ![Process example of using the UAG `agent_provide_data` to achieve Agentic workflow automation](./examples/images/uag-agent-provide-data.png)
38
69
 
@@ -44,6 +75,24 @@ To achieve this feat, the `agent_provide_data` tool utilizes the following infor
44
75
 
45
76
  ### Setup
46
77
  To configure a form to use the `agent_provide_data` tool, you must first designate a section of your form that will be read and used by the AI Agent. This is similar to what you would see in a form that says "For Office Use Only", but instead of a Human contributing to the values of this section, it will be an automated AI Agent. There are two types of fields that can be added to a form to configure it for use by the `agent_provide_data` tool: **Criteria** and **Agent Fields**
78
+
79
+ A complete setup is four steps, and all four are required:
80
+
81
+ 1. **Tag the form `uag`** (described below) — without this the UAG will not register the form at all.
82
+ 2. Add the **Criteria** content component that teaches the agent what to do.
83
+ 3. Add the **Agent Fields** the agent is allowed to fill.
84
+ 4. Add a **Webhook action** to trigger the agent on submission — see the [Claude Integration](./integrations/claude/Readme.md#formio-webhook-setup). This step requires the Enterprise Server, because the action has to set a request header and transform its payload; on Community Edition, trigger the integration from your own code after creating the submission.
85
+
86
+ For a working reference containing all four, see the [Agentic Workflow example](./examples/agentic-workflow).
87
+
88
+ #### Tagging the form `uag`
89
+ The UAG only registers forms that carry the `uag` tag. Any form without it is invisible to every agent and to every tool, including `get_forms`. In the Form Builder this is set under **Form Settings → Tags**; in a form's JSON it looks like this:
90
+
91
+ ```json
92
+ "tags": ["uag"]
93
+ ```
94
+
95
+ This is worth checking first whenever an agent insists that a form does not exist, because a missing tag produces no error anywhere — the form simply is not there as far as the agent is concerned. Note also that newly tagged forms are picked up on the next project cache expiry, so allow up to `PROJECT_TTL` seconds (see [Project Cache and TTL](#project-cache-and-ttl)).
47
96
 
48
97
  #### Agent "Criteria" component
49
98
  The first thing that needs to be configured is a special **Content Component** that instructs the AI Agent how to interpret the data. For example, here is a Criteria content block that was written to instruct an AI Agent on how to assess the submission of a College Application Essay.
@@ -73,13 +122,47 @@ It is also possible to "flag" a collection of fields with the `uag` property. Th
73
122
  ### Multiple Personas per form
74
123
  It is also possible to achieve multiple personas per-form. This is very helpful if there are several isolated evaluations that need to occur within a process. For example, if we look at the flow chart diagram shown above, we can see a College Application Form. The first agentic evaluation occurs when the form is submitted by the applicant, where their application is assessed to be accepted or not. From that point, IF the applicant is accepted, a completely different evaluation needs to occur to award Financial Aid or even Scholarships to the applicant. These would require completely different criteria to assess the submission data differently from one another.
75
124
 
76
- To achieve this, you simply need to "flag" the components `uag` property value differently with the persona that applies for that field. For the example above, you would add a Content component to assess the if the applicant should be accepted, and then provide the following `uag` property to that content. `uag="application"`. Any fields that need to be filled out by the AI Agent for application approval would also be flagged with the property `uag="application"`. Then, in a different section of the form, you would create a separate Content component with the criteria for the financial admin AI Agent. This content would be flagged with the property `uag="finance"`. Any fields that the finance admin AI Agent need to fill out would also be flagged with `uag="finance"`. This provides a truely flexible and dynamic AI engagement where multiple generically trained agents can be "taught" dynamically how to read and interpret data that is only relevant to that part of the agentic process.
125
+ To achieve this, you simply need to "flag" the components `uag` property value differently with the persona that applies for that field. For the example above, you would add a Content component to assess if the applicant should be accepted, and then provide the following `uag` property to that content. `uag="application"`. Any fields that need to be filled out by the AI Agent for application approval would also be flagged with the property `uag="application"`. Then, in a different section of the form, you would create a separate Content component with the criteria for the financial admin AI Agent. This content would be flagged with the property `uag="finance"`. Any fields that the finance admin AI Agent need to fill out would also be flagged with `uag="finance"`. This provides a truly flexible and dynamic AI engagement where multiple generically trained agents can be "taught" dynamically how to read and interpret data that is only relevant to that part of the agentic process.
126
+
127
+ Agent fields do not have to sit next to their criteria, either. A `status` field in one panel can be flagged `uag="application"` while the field beside it is flagged `uag="finance"`, which is how a single workflow field can be owned by whichever persona is responsible for that stage.
128
+
129
+ #### Chaining personas into a workflow
130
+ Multiple personas on a form are only useful if something triggers the later ones. Because each persona writes to the submission, the natural trigger for the second persona is the *first* persona's write.
131
+
132
+ This is configured with a second Webhook action that fires on `update` rather than `create`, narrowed by a condition on a field that the first persona sets:
133
+
134
+ ```json
135
+ {
136
+ "name": "webhook", "handler": ["after"], "method": ["update"], "priority": 0,
137
+ "condition": {
138
+ "conjunction": "all",
139
+ "conditions": [
140
+ { "component": "status", "operator": "isEqual", "value": "scholarshipEvaluation" }
141
+ ]
142
+ },
143
+ "settings": {
144
+ "method": "post",
145
+ "url": "http://formio-uag-claude:3300/agent/claude/agent_provide_data",
146
+ "headers": [{ "header": "x-token", "value": "<your project API key>" }],
147
+ "transform": "payload = {formName: 'application', submissionId: payload.submission._id, persona: 'finance'};",
148
+ "block": false
149
+ }
150
+ }
151
+ ```
152
+
153
+ In the college application workflow, the admissions persona sets `status` to `scholarshipEvaluation` only for applicants on the scholarship track, so only those submissions wake the finance persona. Applicants who were simply accepted or declined never trigger it.
154
+
155
+ > **Avoid loops.** The second persona also writes to the submission, which fires the same `update` webhook again. Make sure each persona's own write cannot re-match the condition that triggered it — in the example above the finance persona sets `status` to `scholarshipGranted`, which no longer satisfies the condition, so the workflow terminates. A condition that stays true after the persona runs will loop indefinitely and consume API credits until you stop the container.
156
+
157
+ A complete, runnable version of this two-persona workflow is in the [Agentic Workflow example](./examples/agentic-workflow).
77
158
 
78
159
  ## Integrations
79
- In order to a aid in the "agentification" of the UAG, we have also provided some "integrations" that can be used to directly communicate with the Generically trained AI Agents to utilize the UAG to accomplish automated AI Agent behaviors. This involves providing a single API endpoint that is capable of triggering the AI Agent process along with the connections to your deployed UAG. These API endpoints can then be triggered using a simple Webhook action within a form to start an Agentic process as the result of a Form submission. Here is a diagram of how specific integrations can be used to achieve a single API call to accomplish an agentic task.
160
+ In order to aid in the "agentification" of the UAG, we have also provided some "integrations" that can be used to directly communicate with the Generically trained AI Agents to utilize the UAG to accomplish automated AI Agent behaviors. This involves providing a single API endpoint that is capable of triggering the AI Agent process along with the connections to your deployed UAG. These API endpoints can then be triggered using a simple Webhook action within a form to start an Agentic process as the result of a Form submission. Here is a diagram of how specific integrations can be used to achieve a single API call to accomplish an agentic task.
80
161
 
81
162
  ![Agentic process flow via single API call into Claude Integration](./examples/images/agent-flow.png)
82
163
 
164
+ Each integration runs its own MCP client and connects *out* to the UAG, so the UAG does not need to be publicly accessible for these to work — it only needs to be reachable from the integration, such as over a private Docker network or on `localhost`.
165
+
83
166
  These integrations can be found within the **integrations** folder. The following **integrations** are provided to enable automated Agentic workflows.
84
167
 
85
168
  - [Claude Integration](./integrations/claude)
@@ -128,7 +211,7 @@ While the UAG can be used as a stand-alone system to enable the interaction betw
128
211
  2. **Custom Actions**: Actions can be thought of as composable middleware functions that allow for configurable Express.js middleware functions to be attached to any Form and Resource, and then to be executed once the data being submitted by an AI agent has passed through the proper data validations and sanitization. These can be used to provide custom integrations to backend processes and workflows.
129
212
  3. **Custom Tools**: In addition to providing custom actions, a Module can also introduce custom MCP Tools to provide bespoke interaction capabilities between the UAG and the AI agents for any industry specific purposes.
130
213
  4. **Custom Authentication**: A module can also modify the "authentication" landing page to assist with any custom requirements that are needed around Authentication.
131
- 5. **Behavior Overrides**: A module also has the ability to "override" any default behavior from the UAG. This ensures that whomever is using a certain module can "tweak" certain tools and responses to achieve better results as it pertains their their industry specific needs.
214
+ 5. **Behavior Overrides**: A module also has the ability to "override" any default behavior from the UAG. This ensures that whomever is using a certain module can "tweak" certain tools and responses to achieve better results as it pertains to their industry specific needs.
132
215
 
133
216
  Form.io aims to enable developers to build and contribute their own custom modules to the broader community through GitHub and NPM.
134
217
 
@@ -165,10 +248,46 @@ The following sections describe the process of creating a running instance of UA
165
248
 
166
249
  This documentation focuses on using UAG with Form.io Open Source. For documentation on using UAG with Form.io Enterprise, refer to [https://help.form.io/uag](https://help.form.io/uag).
167
250
 
251
+ ### How Agents Connect
252
+ Before deploying, it is worth deciding **which side opens the connection** to the UAG's `/mcp` endpoint, because that single decision determines whether the UAG needs to be publicly accessible.
253
+
254
+ | Connection model | Who runs the MCP client | Does the UAG need a public domain? |
255
+ |------------------|-------------------------|------------------------------------|
256
+ | **Local MCP client** | Your own application or integration, running alongside the UAG | **No.** The UAG only needs to be reachable from that process — `http://localhost:3200` or a private Docker network address is fine. |
257
+ | **Remote MCP client** | A hosted agent that dials into your UAG (Claude.ai and Claude Desktop connectors, the Anthropic MCP connector, or any other remote MCP host) | **Yes.** The agent must be able to reach `/mcp` and the OAuth discovery endpoints over the public internet. |
258
+
259
+ With a **local MCP client**, your application connects *out* to the UAG, calls the tools itself, and passes the results on to whichever model it is using. Nothing dials back in, so the UAG can stay entirely inside your network. This is how the [Claude Integration](./integrations/claude) works, and it is the recommended model for server-to-server and webhook-triggered automation — including running the whole stack on a developer machine.
260
+
261
+ > If you are using the published Claude integration image, this requires **`formio/uag-claude:1.3.0` or newer**. Earlier images handed the MCP server URL to the Anthropic API as a *remote* connector, meaning the API dialed into your UAG and a public URL was mandatory. An older image pointed at a private address fails with a `400` from the Anthropic API reading `Connection error while communicating with MCP server`, which looks like a UAG fault but is not one.
262
+
263
+ With a **remote MCP client**, the agent lives outside your network and connects in, so `/mcp` and the `.well-known` OAuth discovery endpoints must be publicly reachable over HTTPS, and `BASE_URL` must be that public URL. See [Domain and URL Configuration](#domain-and-url-configuration).
264
+
265
+ Both models can be used against the same UAG deployment at the same time.
266
+
267
+ #### Authenticating a local MCP client
268
+ A remote agent authenticates through the browser-based OAuth (PKCE) flow. A local client has no browser, so it exchanges a server-side key for an access token using the `client_credentials` grant against `{{ BASE_URL }}/auth/token`, then sends that token as a `Bearer` token on its `/mcp` requests.
269
+
270
+ The `client_id` must be prefixed with the project name — which is `formio-oss` for Open Source deployments:
271
+
272
+ ```
273
+ POST /auth/token
274
+ Content-Type: application/json
275
+
276
+ {
277
+ "grant_type": "client_credentials",
278
+ "client_id": "formio-oss-x-admin-key",
279
+ "client_secret": "{{ ADMIN_KEY }}"
280
+ }
281
+ ```
282
+
283
+ For Enterprise, use the project name from your project endpoint together with the project API key — for example `"client_id": "myproject-x-token"` with `"client_secret": "{{ PROJECT_KEY }}"`.
284
+
285
+ The response contains an `access_token`, which is passed to the UAG as `Authorization: Bearer {{ access_token }}`. Tokens expire according to `JWT_EXPIRE_TIME`, so long-running clients should cache the token and refresh it before it expires. See [`integrations/claude/auth.js`](./integrations/claude/auth.js) for a working implementation.
286
+
168
287
  ### Runtime Environments
169
288
  There are currently two run-time environments that work with the UAG:
170
- -[**Docker**](#docker)
171
- -[**Node.js (Express)**](#nodejs-express)
289
+ - [**Docker**](#docker)
290
+ - [**Node.js (Express)**](#nodejs-express)
172
291
 
173
292
 
174
293
  ### Docker
@@ -179,16 +298,15 @@ formio/uag
179
298
  ```
180
299
 
181
300
  This container can be run in two ways:
182
- - [**Docker-Compose.yml**](#docker-compose-recommended) - The recommended method, deploys with of a Docker Compose multicontainer.
301
+ - [**Docker-Compose.yml**](#docker-compose-recommended) - The recommended method, deploys as a multi-container Docker Compose stack.
183
302
  - [**Docker Run**](#docker-run) - a standalone container using the common `docker run` command.
184
303
 
185
304
 
186
305
  #### Docker Compose (Recommended)
187
- The recommended way to launching the UAG is through Docker Compose. This enables you to orchestrate several of the containers to run within a single instance to provide a more seamless and simple way of managing your deployments. Here is a simple example of how to run both the Form.io UAG + Form.io OSS server on the same instance.
306
+ The recommended way to launch the UAG is through Docker Compose. This enables you to orchestrate several of the containers to run within a single instance to provide a more seamless and simple way of managing your deployments. Here is a simple example of how to run both the Form.io UAG + Form.io OSS server on the same instance.
188
307
 
189
308
  docker-compose.yml
190
309
  ```
191
- version: "3.8"
192
310
  services:
193
311
  mongo:
194
312
  image: mongo
@@ -228,8 +346,10 @@ services:
228
346
  ADMIN_KEY: CHANGEME
229
347
  JWT_SECRET: CHANGEME
230
348
  JWT_EXPIRE_TIME: 525600
231
- LOGIN_FORM: http://localhost:3000/employee/login
232
- BASE_URL: http://localhost:3000
349
+ LOGIN_FORM: http://localhost:3000/user/login
350
+ # The address clients reach the UAG at — this is the UAG's own port,
351
+ # not the Form.io server's. See the BASE_URL section below.
352
+ BASE_URL: http://localhost:3200
233
353
  ports:
234
354
  - "3200:3200"
235
355
  ```
@@ -259,7 +379,7 @@ docker run -d \
259
379
  formio/uag
260
380
  ```
261
381
 
262
- **UAG pointed to an Form.io Enterprise Server**
382
+ **UAG pointed to a Form.io Enterprise Server**
263
383
  ```
264
384
  docker run -d \
265
385
  -e "PROJECT=https://forms.mysite.com/myproject" \
@@ -281,7 +401,7 @@ Once it is running, you can then navigate to the following to access both the OS
281
401
  - http://localhost:3000: The Form.io OSS Server
282
402
  - http://localhost:3200: The UAG Server
283
403
 
284
- In both the Node.js runtime environmnt as well as Docker, the way to control the UAG is through the use of **Environment Variables** and **Modules**.
404
+ In both the Node.js runtime environment as well as Docker, the way to control the UAG is through the use of **Environment Variables** and **Modules**.
285
405
 
286
406
  ### Environment Variables
287
407
  This module can be configured in many ways. One of those ways is through the use of Environment Variables, which are documented as follows.
@@ -289,7 +409,7 @@ This module can be configured in many ways. One of those ways is through the use
289
409
  | Variable | Description | Example |
290
410
  |----------|-------------|---------|
291
411
  | PROJECT | The API Endpoint to either an Enterprise project endpoint, or the OSS server url. | http://localhost:3000 |
292
- | PROJECT_KEY | (Enterprise Only) Either a Project API Key (for Form.io Enterprise) or the ADMIN_KEY for Open Source. | CHANGEME |
412
+ | PROJECT_KEY | (Enterprise Only) The API Key of the project this UAG is bound to, found in **Project Settings &rarr; API Keys**. Open Source deployments use `ADMIN_KEY` instead. | CHANGEME |
293
413
  | ADMIN_KEY | (Open Source Only) Allows you to provide the ADMIN_KEY to install and connect to the OSS Server. | CHANGEME |
294
414
  | UAG_LICENSE | The license to run the UAG against a Form.io Enterprise Deployment. | |
295
415
  | PROJECT_TTL | The project cache TTL in seconds. This controls the amount of time to check for any "changes" that have been made to the project to refresh any forms and resources within the UAG. | 900 |
@@ -300,12 +420,13 @@ This module can be configured in many ways. One of those ways is through the use
300
420
  | JWT_EXPIRE_TIME | The expiration for the jwt secret. | 3600 |
301
421
  | MONGO | (Enterprise Only) Allows you to connect the UAG directly to a mongo database, rather than having to redirect the submissions to the Form.io Submission APIs. | |
302
422
  | MONGO_CONFIG | JSON configuration for the Node.js Mongo Driver. | |
303
- | BASE_URL | The public URL that the UAG is hosted on. This allows for proper OIDC authentication and allows for the authentication callbacks to point to the correct url. | https://forms.yoursite.com |
304
- | LOGIN_FORM | The public URL to the Login Form JSON endpoint. | https://mysite.com/project/user/login |
423
+ | BASE_URL | **Required.** The URL that the UAG is reachable at *by its clients*. It is published in the `.well-known` OIDC (PKCE) definitions and used for authentication callbacks. This must be a public URL when remote agents connect in; for a local MCP client, a private or localhost URL is fine. | https://forms.yoursite.com |
424
+ | LOGIN_FORM | The URL to the Login Form JSON endpoint, loaded by the `/auth/authorize` page. Only used by the browser-based login flow, so it must be reachable by that browser. | https://mysite.com/project/user/login |
305
425
  | CORS | The cors domain, or the JSON configuration to configure the "cors" node.js module cross domain resource sharing. | *.* |
426
+ | NODE_TLS_REJECT_UNAUTHORIZED | Standard Node.js variable. Set to `0` when `PROJECT` is an `https` URL secured by a self-signed or otherwise untrusted certificate, which is common for local and internal deployments — without it the UAG cannot connect to the server and every tool call fails. Never set this on a deployment reachable from outside your network, since it disables certificate verification for all outbound requests. | 0 |
306
427
 
307
428
  ### Project Cache and TTL
308
- By default, the UAG uses a TTL to "fetch" any of the project assets and register them and cache them into the UAG. What this means is that if you make any changes to your underlying project forms and resources (either directly or through a deployment), you need to wait a maximum of the TTL setting (in seconds) to see any updates that have been made. For example, if the TTL is set to 900 (or 15 minutes), and you make a change to any forms and resources, you must wait at most 15 minutes to see any of these changes take effect into the UAG. It should be noted that when a "refresh" occurs, an API call to the "export" method is called on the underlying project endpoint. This is a processor intensive API call, so it is not recommended to set the TTL to any value less than 60. A value of 0 is interpreted as "No TTL", which means the only way to refresh the project forms and resourcs, a UAG reboot is necessary.
429
+ By default, the UAG uses a TTL to "fetch" any of the project assets and register them and cache them into the UAG. What this means is that if you make any changes to your underlying project forms and resources (either directly or through a deployment), you need to wait a maximum of the TTL setting (in seconds) to see any updates that have been made. For example, if the TTL is set to 900 (or 15 minutes), and you make a change to any forms and resources, you must wait at most 15 minutes to see any of these changes take effect into the UAG. It should be noted that when a "refresh" occurs, an API call to the "export" method is called on the underlying project endpoint. This is a processor intensive API call, so it is not recommended to set the TTL to any value less than 60. A value of 0 is interpreted as "No TTL", which means the only way to refresh the project forms and resources, a UAG reboot is necessary.
309
430
 
310
431
  Here is a table explaining how this parameter can be used.
311
432
 
@@ -315,8 +436,16 @@ Here is a table explaining how this parameter can be used.
315
436
  | PROJECT_TTL | 60 | Check for changes every minute |
316
437
  | PROJECT_TTL | 3600 | Check for changes every hour |
317
438
 
318
- ### Running on Public Domain
319
- In order to run the UAG on a public domain, it is very important to provide the proper configurations so that any AI agent can properly authenticate. There are 3 different "domain" environment variables that matter, and it is important to understand how to configure them depending on your use case:
439
+ This cache is the reason a form edit does not appear to take effect immediately. Anything that changes what the agent knows — adding the `uag` tag, rewriting a **Criteria** content component, adding or removing **Agent Fields** — only reaches the agent after the next expiry. When you are actively iterating on criteria, restarting the UAG re-registers the project immediately instead of waiting:
440
+
441
+ ```bash
442
+ docker restart formio-uag
443
+ ```
444
+
445
+ ### Domain and URL Configuration
446
+ There are 3 different "domain" environment variables that matter, and it is important to understand how to configure them depending on your use case. Whether any of them need to be *public* depends on how agents connect — see [How Agents Connect](#how-agents-connect).
447
+
448
+ If a remote agent connects into your UAG, these must be configured correctly and publicly, or the agent will not be able to authenticate. If you only use a local MCP client, all three can point at private or localhost addresses.
320
449
 
321
450
  #### PROJECT
322
451
  The ```PROJECT``` environment variable is used to establish a connection from the UAG server to the project endpoint (for Enterprise) or OSS base url. This does NOT need to be a public DNS entry, but rather a URL that connects the UAG container to the Server container. The following examples illustrate how this would be configured.
@@ -326,7 +455,6 @@ If you are using a local connection, such as within a Docker Compose file, you c
326
455
 
327
456
  docker-compose.yml
328
457
  ```
329
- version: "3.8"
330
458
  services:
331
459
  formio:
332
460
  image: formio/formio:rc
@@ -353,7 +481,6 @@ If you are using the Enterprise Form.io server within a local environment to the
353
481
 
354
482
  docker-compose.yml
355
483
  ```
356
- version: "3.8"
357
484
  services:
358
485
  formio-enterprise:
359
486
  image: formio/formio-enterprise
@@ -377,7 +504,6 @@ If your project does not reside on the same network as the UAG, you can provide
377
504
 
378
505
  docker-compose.yml: Connected to Enterprise (formio/formio-enterprise)
379
506
  ```
380
- version: "3.8"
381
507
  services:
382
508
  formio-uag:
383
509
  image: formio/uag
@@ -389,7 +515,6 @@ services:
389
515
 
390
516
  docker-compose.yml: Connected to Open Source (formio/formio)
391
517
  ```
392
- version: "3.8"
393
518
  services:
394
519
  formio-uag:
395
520
  image: formio/uag
@@ -400,14 +525,21 @@ services:
400
525
  ```
401
526
 
402
527
  #### BASE_URL
403
- The ```BASE_URL``` is used to communicate to the AI agent the public domain that is hosting the UAG server. This value is provided within the ```.well-known``` definitions for the OIDC (PKCE) authentication. If this is not correct, then the AI agent will not be able to authenticate into the UAG.
528
+ The ```BASE_URL``` tells the UAG what URL it is reachable at. This value is published within the ```.well-known``` definitions for the OIDC (PKCE) authentication, and is used for the authentication callbacks.
529
+
530
+ **```BASE_URL``` is always required**, including for local clients. The UAG's authentication provider is only enabled when it is set, so if you omit it, ```/mcp``` returns a 401 for every request and the ```/auth/token``` and ```/.well-known/*``` endpoints do not exist at all (404). A 404 on ```/auth/token``` is the clearest signal that ```BASE_URL``` is missing.
531
+
532
+ What it needs to be set to depends on how agents connect:
404
533
 
405
- **This needs to be the publicly accessible domain that you are hosting your UAG.**
534
+ - **Remote agents connect in**: this must be the publicly accessible domain hosting your UAG, because that is the address the agent is told to authenticate against. For example, ```BASE_URL: https://forms.mysite.com```.
535
+ - **Local MCP client only**: set it to the address your client uses to reach the UAG. A private or localhost URL is fine — for example ```BASE_URL: http://localhost:3200``` or ```BASE_URL: http://formio-uag:3200``` within a Docker network.
406
536
 
407
- For example, ```BASE_URL: https://forms.mysite.com```.
537
+ If a remote agent cannot authenticate, a mismatched ```BASE_URL``` is the most common cause: the agent reads this value from the discovery document and will follow it, so if it points somewhere the agent cannot reach, authentication fails even though the UAG itself is running correctly.
408
538
 
409
539
  #### LOGIN_FORM
410
- This is the publicly accessible URL to the Login form of your Project or OSS deployment. This provides the URL that is loaded when the user navigates to ```{{ BASE_URL }}/auth/authorize```. If you navigate to this URL, and the page says that you cannot load the form, then this is because your LOGIN_FORM environment variable is not pointing to the correct form JSON endpoint of your project.
540
+ This is the URL to the Login form of your Project or OSS deployment. It provides the form that is loaded when a user navigates to ```{{ BASE_URL }}/auth/authorize```. If you navigate to this URL, and the page says that you cannot load the form, then this is because your LOGIN_FORM environment variable is not pointing to the correct form JSON endpoint of your project.
541
+
542
+ This is only used by the browser-based login flow, so it needs to be reachable by the browser performing that login. A local MCP client authenticating with the `client_credentials` grant never loads this page.
411
543
 
412
544
  For example:
413
545
  - Enterprise Example: ```LOGIN_FORM: https://forms.mysite.com/myproject/user/login```
@@ -465,3 +597,23 @@ In order to run the UAG against an Enterprise Server, you need to provide a few
465
597
 
466
598
  Once you have these environment variables in place, you should be able to run the UAG pointed to your Enterprise Project.
467
599
 
600
+ ## Troubleshooting
601
+ Most UAG problems are configuration, not code, and they tend to surface far from their cause — a missing tag looks like a missing form, and a missing `BASE_URL` looks like a permissions error. The table below maps the symptoms you are likely to see back to what actually produces them.
602
+
603
+ | Symptom | Likely cause |
604
+ |---------|--------------|
605
+ | The agent says a form does not exist, or `get_forms` does not list it | The form is not tagged `uag`, or the change has not survived the project cache yet. See [Tagging the form `uag`](#tagging-the-form-uag) and [Project Cache and TTL](#project-cache-and-ttl). |
606
+ | `/auth/token` returns `404`, and `/.well-known/*` is also missing | `BASE_URL` is not set. The authentication provider is only enabled when it is present, so those endpoints do not exist at all. See [BASE_URL](#base_url). |
607
+ | Every `/mcp` request returns `401` | Either `BASE_URL` is unset (as above), or the client is not sending `Authorization: Bearer <token>`. For a server-side client, see [Authenticating a local MCP client](#authenticating-a-local-mcp-client). |
608
+ | A remote agent (claude.ai, Claude Desktop connector) cannot authenticate | `BASE_URL` does not point at a publicly reachable HTTPS URL. The agent reads that value out of the discovery document and follows it, so it must be correct from the agent's perspective, not the server's. |
609
+ | The Claude integration returns `400 ... Connection error while communicating with MCP server` | The `formio/uag-claude` image predates `1.3.0` and is treating the UAG as a *remote* MCP server, so the Anthropic API is trying to dial into a private address. Upgrade to `1.3.0` or newer. See [How Agents Connect](#how-agents-connect). |
610
+ | The UAG cannot reach the server at all, and every tool fails | If `PROJECT` is an `https` URL with a self-signed certificate, set `NODE_TLS_REJECT_UNAUTHORIZED=0`. Otherwise check that `PROJECT` is resolvable *from inside the UAG container* — it is a container-to-container address, not a browser one. See [PROJECT](#project). |
611
+ | Nothing happens when a submission is created | The Webhook action did not fire. Confirm its `handler` is `after` and `method` includes `create`, and that the API key header matches the integration's `PROJECT_KEY` (Enterprise) or `ADMIN_KEY` (OSS). |
612
+ | A submission is created but the response hangs for a long time | The Webhook action has `block: true`. Agent runs take tens of seconds; set `block: false` so the submitter is not made to wait. |
613
+ | An agent runs continuously and burns API credits | A chained persona is re-triggering itself. Its own write still satisfies the condition on the `update` webhook that started it. See [Chaining personas into a workflow](#chaining-personas-into-a-workflow). |
614
+ | The agent fills in some fields but not others | Those fields are missing the `uag` property, or its value does not exactly match the persona on the **Criteria** component. Every field the agent may write must carry the same persona, either directly or by inheriting it from a parent component. |
615
+ | The agent writes values the form rejects | The **Criteria** is not specific enough about formats and allowed values. State units, rounding, and exact option values, and name the field keys verbatim. |
616
+ | A run fails and the logs are not enough to see where | Put the [Flow Viewer](./examples/flow-viewer) in front of the UAG. It shows the token request, `tools/list`, each tool call with arguments, and the write-back in order. |
617
+
618
+ If none of these apply, run one of the [examples](#try-it-out) to confirm the environment itself is sound, then compare its configuration against yours.
619
+
@@ -89,7 +89,7 @@ export declare class UAGFormInterface extends FormInterface {
89
89
  }[] | {
90
90
  data: {};
91
91
  } | {
92
- data?: undefined;
92
+ data?: {};
93
93
  } | null;
94
94
  /**
95
95
  * Override the process methods for the UAG.
@@ -13,22 +13,23 @@ export declare class UAGProjectInterface extends ProjectInterface {
13
13
  get config(): UAGConfig;
14
14
  constructor(endpoint?: string);
15
15
  initialize(): Promise<void>;
16
+ buildMcpServer(): Promise<McpServer>;
16
17
  addForm(form: Form, machineName: string): UAGFormInterface | undefined;
17
18
  uagRouter(): Router;
18
19
  authorizeRequest(req: SubmissionRequest, res: Response, next: NextFunction): Promise<Response<any, Record<string, any>> | undefined>;
19
20
  mcpJSONResponse(data?: object, isError?: boolean): {
20
- isError?: boolean | undefined;
21
21
  content: {
22
- type: "text";
22
+ type: 'text';
23
23
  text: string;
24
24
  }[];
25
25
  structuredContent: object;
26
+ isError?: boolean | undefined;
26
27
  };
27
28
  mcpResponse(templateName: ResponseTemplate, data?: object, isError?: boolean): {
28
- isError?: boolean | undefined;
29
29
  content: {
30
- type: "text";
30
+ type: 'text';
31
31
  text: string;
32
32
  }[];
33
+ isError?: boolean | undefined;
33
34
  };
34
35
  }
@@ -20,6 +20,12 @@ class UAGProjectInterface extends appserver_1.ProjectInterface {
20
20
  async initialize() {
21
21
  await super.initialize();
22
22
  this.uagTemplate = new template_1.UAGTemplate(this.config?.responseTemplates || {});
23
+ this.mcpServer = await this.buildMcpServer();
24
+ }
25
+ // Build a fresh McpServer with all tools registered. A new instance is created
26
+ // per request so concurrent requests never share (and overwrite) a transport.
27
+ async buildMcpServer() {
28
+ const server = new mcp_js_1.McpServer({ name: 'formio-uag', version: '1.0.0' });
23
29
  // Get the standard UAG tools.
24
30
  const tools = await (0, tools_1.getTools)(this);
25
31
  // Add the custom tools from config.
@@ -29,13 +35,14 @@ class UAGProjectInterface extends appserver_1.ProjectInterface {
29
35
  // Iterate and register all the tools.
30
36
  for (const tool of tools) {
31
37
  if (tool?.name) {
32
- this.mcpServer.registerTool(tool.name, {
38
+ server.registerTool(tool.name, {
33
39
  title: tool.title,
34
40
  description: tool.description,
35
41
  inputSchema: tool.inputSchema
36
42
  }, tool.execute);
37
43
  }
38
44
  }
45
+ return server;
39
46
  }
40
47
  addForm(form, machineName) {
41
48
  const formInterface = super.addForm(form, machineName);
package/lib/index.d.ts CHANGED
@@ -7,7 +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
+ router(): Promise<any>;
11
11
  }
12
12
  export * from '@formio/appserver';
13
13
  export * from './config';
package/lib/router.js CHANGED
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.UAGRouter = UAGRouter;
4
4
  const express_1 = require("express");
5
5
  const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
6
- const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
6
  // Helper function to create JSON RPC error responses
8
7
  const createJsonRpcErrorResponse = (code, message) => {
9
8
  return JSON.stringify({
@@ -19,6 +18,8 @@ const createJsonRpcErrorResponse = (code, message) => {
19
18
  * @returns True if the error was handled, false otherwise.
20
19
  */
21
20
  const handleResponseError = (error, res) => {
21
+ // A thrown fetch Response (the global Response, not Express's) carries the
22
+ // upstream status and headers that should be forwarded to the client.
22
23
  if (error instanceof Response) {
23
24
  const fixedHeaders = {};
24
25
  error.headers.forEach((value, key) => {
@@ -31,22 +32,29 @@ const handleResponseError = (error, res) => {
31
32
  res.status(500).json(JSON.parse(createJsonRpcErrorResponse(-32603, "Internal Server Error")));
32
33
  return false;
33
34
  };
35
+ /**
36
+ * Build a per-request McpServer + transport (stateless mode) and tear both down
37
+ * when the response closes. A fresh instance per request keeps concurrent requests
38
+ * from overwriting each other's transport.
39
+ */
40
+ const connectMcpTransport = async (project, res) => {
41
+ const server = await project.buildMcpServer();
42
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
43
+ sessionIdGenerator: undefined, // Use "undefined" to trigger stateless mode.
44
+ });
45
+ res.on('close', () => {
46
+ transport.close();
47
+ server.close();
48
+ });
49
+ await server.connect(transport);
50
+ return transport;
51
+ };
34
52
  function UAGRouter(project) {
35
53
  const router = (0, express_1.Router)();
36
54
  // Handles the MCP post requests.
37
55
  router.post('/', async (req, res) => {
38
56
  try {
39
- const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
40
- sessionIdGenerator: undefined, // Use "undefined" to trigger stateless mode.
41
- });
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);
45
- if (!transport) {
46
- res.status(500).json(JSON.parse(createJsonRpcErrorResponse(-32603, "Unable to create MCP transport.")));
47
- return;
48
- }
49
- (0, types_js_1.isInitializeRequest)(req.body);
57
+ const transport = await connectMcpTransport(project, res);
50
58
  await transport.handleRequest(req, res, req.body);
51
59
  }
52
60
  catch (error) {
@@ -56,16 +64,7 @@ function UAGRouter(project) {
56
64
  // Handles the MCP get requests (for health checks, etc).
57
65
  router.get('/', async (req, res) => {
58
66
  try {
59
- const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
60
- sessionIdGenerator: undefined, // Use "undefined" to trigger stateless mode.
61
- });
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);
65
- if (!transport) {
66
- res.status(500).json(JSON.parse(createJsonRpcErrorResponse(-32603, "Unable to create MCP transport.")));
67
- return;
68
- }
67
+ const transport = await connectMcpTransport(project, res);
69
68
  await transport.handleRequest(req, res);
70
69
  }
71
70
  catch (err) {
@@ -1,4 +1,4 @@
1
- Please read and understand the following **--- CRITERIA ---** section. This section provides instructions on how to analyze the **--- SUBMISSION ---** section that follows. Your objective is to provide values defined within the **--- REQUIRED FIELDS ---** section, and then to submit those values using the `agent_submit_data` tool.
1
+ Please read and understand the following **--- CRITERIA ---** section. This section provides instructions on how to analyze the **--- SUBMISSION ---** section that follows. Your objective is to provide values defined within the **--- REQUIRED FIELDS ---** section, and then to submit those values using the `submission_update` tool.
2
2
 
3
3
  --- CRITERIA ---
4
4
  <%= criteria %>
@@ -1,6 +1,6 @@
1
1
  There are some validation errors in the data collected:
2
2
  <% validationErrors.forEach(function(error) { %>
3
- - <% if (error.label && error.path) { %>**<%= error.label %> (<%= error.path %>)**: <% } %><%= error.message %><% }); %>
3
+ - <% if (error.label && error.path) { %>**<%= error.label %> (<%= error.path %>)**: <% } %><%= error.error || error.message %><% }); %>
4
4
 
5
5
  These validation errors are in one of the following format:
6
6
  - **Field Label (data_path)** - Error message: If the error is attributed to a specific field value.
@@ -21,13 +21,28 @@ const submitCompletedForm = async (project) => {
21
21
  try {
22
22
  let submission = form.convertToSubmission(form_data);
23
23
  const submitted = await form.submit(submission, extra.authInfo);
24
- if (submitted)
24
+ if (submitted) {
25
+ // The submission save proxies to the Form.io server, and a rejection
26
+ // (e.g. failed validation) can come back through the save pipeline as
27
+ // the error response body instead of a saved submission. A result
28
+ // without an _id was not persisted — report the failure to the agent
29
+ // instead of a false success.
30
+ const result = submitted;
31
+ if (!result._id) {
32
+ const errors = (result.name === 'ValidationError' && result.details)
33
+ ? form.convertToFormFieldErrors(result.details)
34
+ : [];
35
+ return project.mcpResponse(template_1.ResponseTemplate.submitValidationError, {
36
+ validationErrors: errors.length ? errors : [{
37
+ message: result.message || 'The submission was not saved by the server. Verify that all required fields have been collected and that every value conforms to its field rules, then try again.'
38
+ }]
39
+ }, true);
40
+ }
25
41
  submission = submitted;
26
- if (!submission) {
27
- return project.mcpResponse(template_1.ResponseTemplate.submitValidationError, {
28
- validationErrors: ['Unknown error during submission']
29
- }, true);
30
42
  }
43
+ // A null result is still a valid outcome for "action" forms that have no
44
+ // Save Submission action (e.g. email or webhook only) — the data was
45
+ // processed but intentionally not persisted, so no submission ID exists.
31
46
  debug(`Form submitted: ${form_name} with submission ID: ${submission._id}`);
32
47
  return project.mcpResponse(template_1.ResponseTemplate.formSubmitted, {
33
48
  form: form.form,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formio/uag",
3
- "version": "1.11.2",
3
+ "version": "1.12.1",
4
4
  "mcpName": "io.form/uag",
5
5
  "description": "The Form.io Universal Agent Gateway (UAG).",
6
6
  "main": "lib/index.js",
@@ -13,11 +13,12 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "clean": "rm -rf lib",
16
- "build:ts": "npx tsc",
16
+ "build:ts": "tsc",
17
17
  "build:copy-templates": "mkdir -p lib/templates && cp src/templates/*.md lib/templates/",
18
18
  "build": "npm run clean && npm run build:ts && npm run build:copy-templates",
19
19
  "build:docker": "docker build -t formio/uag$(node -p \"require('./package.json').version.includes('-rc.') ? ':rc' : ''\") --platform=linux/amd64 .",
20
- "test": "TS_NODE_PROJECT=tsconfig.test.json mocha --no-node-snapshot",
20
+ "test": "tsc --noEmit -p tsconfig.test.json && mocha --no-node-snapshot",
21
+ "test:e2e": "mocha --config .mocharc.e2e.json",
21
22
  "dev": "tsx --no-node-snapshot index.js",
22
23
  "start": "node --no-node-snapshot index.js",
23
24
  "claude": "node --no-node-snapshot ./integrations/claude/index.js"
@@ -29,18 +30,18 @@
29
30
  "author": "Form.io Developers",
30
31
  "license": "MIT",
31
32
  "dependencies": {
32
- "@formio/appserver": "^2.12.2",
33
- "@formio/core": "2.5.1-dev.291.6557e4e",
34
- "@modelcontextprotocol/sdk": "1.27.1",
33
+ "@formio/appserver": "^2.13.1",
34
+ "@formio/core": "2.8.0",
35
+ "@modelcontextprotocol/sdk": "1.30.0",
35
36
  "cors": "^2.8.6",
36
37
  "debug": "^4.4.3",
37
- "dotenv": "^17.3.1",
38
+ "dotenv": "^17.4.2",
38
39
  "express": "^5.2.1",
39
40
  "jsonwebtoken": "^9.0.3",
40
- "lodash": "^4.17.23",
41
+ "lodash": "^4.18.1",
41
42
  "node-cache": "^5.1.2",
42
43
  "node-html-markdown": "^2.0.0",
43
- "zod": "^4.3.6"
44
+ "zod": "^4.4.3"
44
45
  },
45
46
  "resolutions": {
46
47
  "tar": ">=7.5.12",
@@ -51,22 +52,23 @@
51
52
  "dompurify": ">=3.3.3"
52
53
  },
53
54
  "devDependencies": {
55
+ "@anthropic-ai/sdk": "^0.115.0",
54
56
  "@types/chai": "^5.2.3",
55
57
  "@types/cors": "^2.8.19",
56
- "@types/debug": "^4.1.12",
58
+ "@types/debug": "^4.1.13",
57
59
  "@types/dotenv": "^8.2.3",
58
60
  "@types/express": "^5.0.6",
59
61
  "@types/jsonwebtoken": "^9.0.10",
60
62
  "@types/lodash": "^4.17.24",
61
63
  "@types/mocha": "^10.0.10",
62
- "@types/node": "^25.5.0",
63
- "@types/supertest": "^7.2.0",
64
+ "@types/node": "^26.1.2",
65
+ "@types/supertest": "^7.2.1",
64
66
  "chai": "^6.2.2",
65
- "mocha": "^11.7.5",
67
+ "mocha": "^11.7.6",
66
68
  "supertest": "^7.2.2",
67
- "terser": "^5.46.1",
68
- "ts-node": "^10.9.2",
69
- "tsx": "^4.21.0",
70
- "typescript": "^5.9.3"
71
- }
69
+ "terser": "^5.49.0",
70
+ "tsx": "^4.23.1",
71
+ "typescript": "^7.0.2"
72
+ },
73
+ "packageManager": "yarn@4.17.1"
72
74
  }