@gavdi/cap-mcp 0.9.9-alpha.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -120,6 +120,7 @@ This plugin transforms your annotated CAP services into a fully functional MCP s
120
120
  - **🔧 Tools**: Convert CAP functions and actions into executable MCP tools
121
121
  - **🧩 Entity Wrappers (optional)**: Expose CAP entities as tools (`query`, `get`, and optionally `create`, `update`) for LLM tool use while keeping resources intact
122
122
  - **💡 Prompts**: Define reusable prompt templates for AI interactions
123
+ - **⚡ Elicitation**: Request user confirmation or input parameters before tool execution
123
124
  - **🔄 Auto-generation**: Automatically creates MCP server endpoints based on annotations
124
125
  - **⚙️ Flexible Configuration**: Support for custom parameter sets and descriptions
125
126
 
@@ -231,6 +232,52 @@ extend projection Books with actions {
231
232
  }
232
233
  ```
233
234
 
235
+ #### Tool Elicitation
236
+
237
+ Request user confirmation or input before tool execution using the `elicit` property:
238
+
239
+ ```cds
240
+ // Request user confirmation before execution
241
+ @mcp: {
242
+ name : 'book-recommendation',
243
+ description: 'Get a random book recommendation',
244
+ tool : true,
245
+ elicit : ['confirm']
246
+ }
247
+ function getBookRecommendation() returns String;
248
+
249
+ // Request user input for parameters
250
+ @mcp: {
251
+ name : 'get-author',
252
+ description: 'Gets the desired author',
253
+ tool : true,
254
+ elicit : ['input']
255
+ }
256
+ function getAuthor(id: String) returns String;
257
+
258
+ // Request both input and confirmation
259
+ @mcp: {
260
+ name : 'books-by-author',
261
+ description: 'Gets a list of books made by the author',
262
+ tool : true,
263
+ elicit : ['input', 'confirm']
264
+ }
265
+ function getBooksByAuthor(authorName: String) returns array of String;
266
+ ```
267
+
268
+ > NOTE: Elicitation is only available for direct tools at this moment. Wrapped entities are not covered by this.
269
+
270
+ **Elicit Types:**
271
+ - **`confirm`**: Requests user confirmation before executing the tool with a yes/no prompt
272
+ - **`input`**: Prompts the user to provide values for the tool's parameters
273
+ - **Combined**: Use both `['input', 'confirm']` to first collect parameters, then ask for confirmation
274
+
275
+ **User Experience:**
276
+ - **Confirmation**: "Please confirm that you want to perform action 'Get a random book recommendation'"
277
+ - **Input**: "Please fill out the required parameters" with a form for each parameter
278
+ - **User Actions**: Accept, decline, or cancel the elicitation request
279
+ - **Early Exit**: Tools return appropriate messages if declined or cancelled
280
+
234
281
  ### Prompt Templates
235
282
 
236
283
  Define reusable AI prompt templates:
@@ -550,24 +597,7 @@ npm test -- --testPathPattern=integration
550
597
  ## 🚨 Performance & Limitations
551
598
 
552
599
  ### Known Limitations
553
-
554
- #### No Interactive Authentication Support
555
- **The plugin currently does NOT support interactive OAuth flows** that allow end-users to log in through MCP clients like Claude Desktop, Cursor, or other consumer MCP applications.
556
-
557
- **What this means:**
558
- - ✅ Works with custom MCP clients that can inject pre-obtained bearer tokens
559
- - ✅ Works in development with `dummy` authentication
560
- - ❌ **Does NOT work with Claude Desktop, Cursor, or similar clients expecting OAuth login flows**
561
- - ❌ End-users cannot authenticate interactively when connecting
562
-
563
- **Technical Context:** This limitation exists due to architectural constraints in the current Model Context Protocol SDK. The MCP community is actively working on a solution that would enable proper interactive authentication flows, but no timeline has been announced. This is expected to be resolved in the second half of 2025.
564
-
565
- **Workarounds:**
566
- - **For Development**: Use `"auth": { "kind": "dummy" }` in your CAP configuration
567
- - **For Production**: Custom MCP clients must obtain valid bearer tokens through your CAP application's existing authentication flow and include them in requests as `Authorization: Bearer <token>`
568
-
569
- #### SDK Bug
570
- - **Dynamic Resource Queries**: Require all query parameters due to `@modelcontextprotocol/sdk` RFC template string issue
600
+ - **SDK Bug**: Dynamic resource queries require all query parameters due to `@modelcontextprotocol/sdk` RFC template string issue
571
601
 
572
602
  ### Performance Considerations
573
603
  - **Large Datasets**: Use `resource: ['top']` or similar constraints for entities with many records
@@ -27,6 +27,8 @@ exports.MCP_ANNOTATION_PROPS = {
27
27
  MCP_PROMPT: "@mcp.prompts",
28
28
  /** Wrapper configuration for exposing entities as tools */
29
29
  MCP_WRAP: "@mcp.wrap",
30
+ /** Elicited user input annotation for tools in CAP services */
31
+ MCP_ELICIT: "@mcp.elicit",
30
32
  };
31
33
  /**
32
34
  * Set of annotations used for CDS auth annotations
@@ -26,6 +26,13 @@ function parseDefinitions(model) {
26
26
  if (!parsedAnnotations || !(0, utils_1.containsRequiredAnnotations)(parsedAnnotations)) {
27
27
  continue; // This check must occur here, since we do want the bound operations even if the parent is not annotated
28
28
  }
29
+ // Set the target in annotations for error reporting
30
+ if (parsedAnnotations) {
31
+ parsedAnnotations.target = key;
32
+ }
33
+ if (!(0, utils_1.containsRequiredElicitedParams)(parsedAnnotations)) {
34
+ continue; // Really doesn't do anything as the method will throw if the implementation is invalid
35
+ }
29
36
  const verifiedAnnotations = parsedAnnotations;
30
37
  switch (def.kind) {
31
38
  case "entity":
@@ -97,6 +104,9 @@ function parseAnnotations(definition) {
97
104
  // Wrapper container to expose resources as tools
98
105
  annotations.wrap = v;
99
106
  continue;
107
+ case constants_1.MCP_ANNOTATION_PROPS.MCP_ELICIT:
108
+ annotations.elicit = v;
109
+ continue;
100
110
  case constants_1.CDS_AUTH_ANNOTATIONS.REQUIRES:
101
111
  annotations.requires = v;
102
112
  continue;
@@ -139,7 +149,7 @@ function constructToolAnnotation(serviceName, target, annotations, entityKey, ke
139
149
  return undefined;
140
150
  const { parameters, operationKind } = (0, utils_1.parseOperationElements)(annotations);
141
151
  const restrictions = (0, utils_1.parseCdsRestrictions)(annotations.restrict, annotations.requires);
142
- return new structures_1.McpToolAnnotation(annotations.name, annotations.description, target, serviceName, parameters, entityKey, operationKind, keyParams, restrictions);
152
+ return new structures_1.McpToolAnnotation(annotations.name, annotations.description, target, serviceName, parameters, entityKey, operationKind, keyParams, restrictions, annotations.elicit);
143
153
  }
144
154
  /**
145
155
  * Constructs a prompt annotation from parsed annotation data
@@ -172,7 +182,13 @@ function parseBoundOperations(serviceName, entityKey, definition, resultRef) {
172
182
  if (v.kind !== "function" && v.kind !== "action")
173
183
  continue;
174
184
  const parsedAnnotations = parseAnnotations(v);
175
- if (!parsedAnnotations || !(0, utils_1.containsRequiredAnnotations)(parsedAnnotations)) {
185
+ // Set the target in annotations for error reporting
186
+ if (parsedAnnotations) {
187
+ parsedAnnotations.target = k;
188
+ }
189
+ if (!parsedAnnotations ||
190
+ !(0, utils_1.containsRequiredAnnotations)(parsedAnnotations) ||
191
+ !(0, utils_1.containsRequiredElicitedParams)(parsedAnnotations)) {
176
192
  continue;
177
193
  }
178
194
  const verifiedAnnotations = parsedAnnotations;
@@ -142,6 +142,8 @@ class McpToolAnnotation extends McpAnnotation {
142
142
  _operationKind;
143
143
  /** Map of key field names to their types for bound operations */
144
144
  _keyTypeMap;
145
+ /** Elicited user input object */
146
+ _elicits;
145
147
  /**
146
148
  * Creates a new MCP tool annotation
147
149
  * @param name - Unique identifier for this tool
@@ -153,13 +155,15 @@ class McpToolAnnotation extends McpAnnotation {
153
155
  * @param operationKind - Optional operation type ('function' or 'action')
154
156
  * @param keyTypeMap - Optional map of key fields to types for bound operations
155
157
  * @param restrictions - Optional restrictions based on CDS roles
158
+ * @param elicits - Optional elicited input requirement
156
159
  */
157
- constructor(name, description, operation, serviceName, parameters, entityKey, operationKind, keyTypeMap, restrictions) {
160
+ constructor(name, description, operation, serviceName, parameters, entityKey, operationKind, keyTypeMap, restrictions, elicits) {
158
161
  super(name, description, operation, serviceName, restrictions ?? []);
159
162
  this._parameters = parameters;
160
163
  this._entityKey = entityKey;
161
164
  this._operationKind = operationKind;
162
165
  this._keyTypeMap = keyTypeMap;
166
+ this._elicits = elicits;
163
167
  }
164
168
  /**
165
169
  * Gets the map of function parameters to their CDS types
@@ -189,6 +193,13 @@ class McpToolAnnotation extends McpAnnotation {
189
193
  get keyTypeMap() {
190
194
  return this._keyTypeMap;
191
195
  }
196
+ /**
197
+ * Gets the elicited user input if any is required for the tool
198
+ * @returns Elicited user input object
199
+ */
200
+ get elicits() {
201
+ return this._elicits;
202
+ }
192
203
  }
193
204
  exports.McpToolAnnotation = McpToolAnnotation;
194
205
  /**
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.splitDefinitionName = splitDefinitionName;
4
4
  exports.containsMcpAnnotation = containsMcpAnnotation;
5
5
  exports.containsRequiredAnnotations = containsRequiredAnnotations;
6
+ exports.containsRequiredElicitedParams = containsRequiredElicitedParams;
6
7
  exports.isValidResourceAnnotation = isValidResourceAnnotation;
7
8
  exports.isValidToolAnnotation = isValidToolAnnotation;
8
9
  exports.isValidPromptsAnnotation = isValidPromptsAnnotation;
@@ -55,6 +56,21 @@ function containsRequiredAnnotations(annotations) {
55
56
  }
56
57
  return true;
57
58
  }
59
+ /**
60
+ * Validates that the required params for MCP elicited user input annotations are valid
61
+ * @param annotations - The annotation structure to validate
62
+ * @returns True if valid, throw error if invalid
63
+ * @throws Error if required annotations are missing
64
+ */
65
+ function containsRequiredElicitedParams(annotations) {
66
+ if (!annotations.elicit)
67
+ return true;
68
+ const param = annotations.elicit;
69
+ if (!param || param?.length <= 0) {
70
+ throw new Error(`Invalid annotation '${annotations.target}' - Incomplete elicited user input`);
71
+ }
72
+ return true;
73
+ }
58
74
  /**
59
75
  * Validates a resource annotation structure
60
76
  * @param annotations - The annotation structure to validate
@@ -2,11 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.authHandlerFactory = authHandlerFactory;
4
4
  exports.errorHandlerFactory = errorHandlerFactory;
5
- const logger_1 = require("../logger");
6
5
  /** JSON-RPC 2.0 error code for unauthorized requests */
7
6
  const RPC_UNAUTHORIZED = 10;
8
- /** HTTP Authenticate header **/
9
- const WWW_AUTHENTICATE = "WWW-Authenticate";
10
7
  /* @ts-ignore */
11
8
  const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
12
9
  /**
@@ -34,15 +31,9 @@ const cds = global.cds || require("@sap/cds"); // This is a work around for miss
34
31
  * @throws {500} When CAP context is not properly loaded
35
32
  */
36
33
  function authHandlerFactory() {
34
+ const authKind = cds.env.requires.auth.kind;
37
35
  return (req, res, next) => {
38
- const auth = cds.env.requires.auth;
39
- const authKind = auth.kind;
40
- const credentials = auth.credentials;
41
36
  if (!req.headers.authorization && authKind !== "dummy") {
42
- logger_1.LOGGER.warn("No valid authorization header provided");
43
- // We need to return a WWW-Authenticate response header here with .well-known metadata
44
- // Otherwise the MCP client will not be able to figure out the auth flow
45
- res.setHeader(WWW_AUTHENTICATE, `Bearer error='"invalid_token", resource_metadata="${credentials?.url}/.well-known/oauth-protected-resource"`);
46
37
  res.status(401).json({
47
38
  jsonrpc: "2.0",
48
39
  error: {
@@ -67,9 +58,6 @@ function authHandlerFactory() {
67
58
  }
68
59
  const user = ctx.user;
69
60
  if (!user || user === cds.User.anonymous) {
70
- // We need to return a WWW-Authenticate response header here with .well-known metadata
71
- // Otherwise the MCP client will not be able to figure out the auth flow
72
- res.setHeader(WWW_AUTHENTICATE, `Bearer error='"invalid_token", resource_metadata="${credentials?.url}/.well-known/oauth-protected-resource"`);
73
61
  res.status(401).json({
74
62
  jsonrpc: "2.0",
75
63
  error: {
package/lib/auth/utils.js CHANGED
@@ -6,10 +6,30 @@ exports.registerAuthMiddleware = registerAuthMiddleware;
6
6
  exports.hasToolOperationAccess = hasToolOperationAccess;
7
7
  exports.getWrapAccesses = getWrapAccesses;
8
8
  const handler_1 = require("./handler");
9
+ const proxyProvider_js_1 = require("@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js");
9
10
  const router_js_1 = require("@modelcontextprotocol/sdk/server/auth/router.js");
10
- const logger_1 = require("../logger");
11
+ /**
12
+ * @fileoverview Authentication utilities for MCP-CAP integration.
13
+ *
14
+ * This module provides utilities for integrating CAP authentication with MCP servers.
15
+ * It supports all standard CAP authentication types and provides functions for:
16
+ * - Determining authentication status
17
+ * - Managing user access rights
18
+ * - Registering authentication middleware
19
+ *
20
+ * Supported CAP authentication types:
21
+ * - 'dummy': No authentication (privileged access)
22
+ * - 'mocked': Mock users with predefined credentials
23
+ * - 'basic': HTTP Basic Authentication
24
+ * - 'jwt': Generic JWT token validation
25
+ * - 'xsuaa': SAP BTP XSUAA OAuth2/JWT authentication
26
+ * - 'ias': SAP Identity Authentication Service
27
+ * - Custom string types for user-defined authentication strategies
28
+ *
29
+ * Access CAP auth configuration via: cds.env.requires.auth.kind
30
+ */
11
31
  /* @ts-ignore */
12
- const cds = global.cds || require("@sap/cds"); // Use hosting app's CDS instance exclusively
32
+ const cds = global.cds || require("@sap/cds"); // This is a work around for missing cds context
13
33
  /**
14
34
  * Determines whether authentication is enabled for the MCP plugin.
15
35
  *
@@ -97,8 +117,7 @@ function getAccessRights(authEnabled) {
97
117
  * @since 1.0.0
98
118
  */
99
119
  function registerAuthMiddleware(expressApp) {
100
- logger_1.LOGGER.debug("Configuring auth middleware");
101
- const middlewares = cds.middlewares?.before || []; // Handle missing middlewares gracefully
120
+ const middlewares = cds.middlewares.before; // No types exists for this part of the CDS library
102
121
  // Build array of auth middleware to apply
103
122
  const authMiddleware = [];
104
123
  // Add CAP middleware
@@ -113,10 +132,8 @@ function registerAuthMiddleware(expressApp) {
113
132
  authMiddleware.push((0, handler_1.authHandlerFactory)());
114
133
  // Apply auth middleware to all /mcp routes EXCEPT health
115
134
  expressApp?.use(/^\/mcp(?!\/health).*/, ...authMiddleware);
116
- // Note: .well-known/oauth-authorization-server endpoint is automatically created by mcpAuthRouter
117
- // Configure OAuth proxy for enterprise authentication scenarios
135
+ // Then finally we add the oauth proxy to the xsuaa instance
118
136
  configureOAuthProxy(expressApp);
119
- logger_1.LOGGER.debug("Auth middleware configured");
120
137
  }
121
138
  /**
122
139
  * Configures OAuth proxy middleware for enterprise authentication scenarios.
@@ -129,7 +146,6 @@ function registerAuthMiddleware(expressApp) {
129
146
  * - Access token verification and validation
130
147
  * - Client credential management
131
148
  * - Integration with CAP authentication configuration
132
- * - Dynamic client registration for MCP clients
133
149
  *
134
150
  * The OAuth proxy is only configured for enterprise authentication types
135
151
  * (jwt, xsuaa, ias) and skips configuration for basic auth types.
@@ -159,86 +175,42 @@ function configureOAuthProxy(expressApp) {
159
175
  const config = cds.env.requires.auth;
160
176
  const kind = config.kind;
161
177
  const credentials = config.credentials;
162
- logger_1.LOGGER.debug("Running auth with configuration kind", kind);
163
178
  // Safety guard - skip OAuth proxy for basic auth types
164
- if (kind === "dummy" || kind === "mocked" || kind === "basic") {
165
- logger_1.LOGGER.debug("Skipping OAuth proxy for auth type:", kind);
179
+ if (kind === "dummy" || kind === "mocked" || kind === "basic")
166
180
  return;
167
- }
168
- if (!credentials ||
181
+ else if (!credentials ||
169
182
  !credentials.clientid ||
170
183
  !credentials.clientsecret ||
171
184
  !credentials.url) {
172
- logger_1.LOGGER.warn("OAuth proxy skipped - missing required XSUAA credentials");
173
- return; // Don't throw error, just skip OAuth proxy
185
+ throw new Error("Invalid security credentials");
174
186
  }
175
- logger_1.LOGGER.debug("Configuring OAuth proxy with XSUAA endpoints");
176
- const baseUrl = process.env.MCP_BASE_URL ||
177
- process.env.MCP_SERVER_URL ||
178
- "http://localhost:4004";
179
- // const proxyProvider = new ProxyOAuthServerProvider({
180
- // endpoints: {
181
- // authorizationUrl: `${credentials.url}/oauth/authorize`,
182
- // tokenUrl: `${credentials.url}/oauth/token`,
183
- // revocationUrl: `${credentials.url}/oauth/revoke`,
184
- // },
185
- // verifyAccessToken: async (token: string) => {
186
- // try {
187
- // LOGGER.debug("OAuth proxy: verifyAccessToken called");
188
- //
189
- // // Use CAP's built-in JWT verification for XSUAA
190
- // const decoded = await cds.auth.jwt.verify(token);
191
- // LOGGER.debug(
192
- // "Token decoded successfully for client:",
193
- // decoded.client_id || decoded.azp,
194
- // );
195
- //
196
- // return {
197
- // token,
198
- // clientId: decoded.client_id || decoded.azp,
199
- // scopes: decoded.scope?.split(" ") || [],
200
- // userId: decoded.sub,
201
- // expiresAt: decoded.exp, // Unix timestamp, not Date object
202
- // };
203
- // } catch (error) {
204
- // LOGGER.error("Token verification failed:", error);
205
- // throw new Error("Invalid access token");
206
- // }
207
- // },
208
- // getClient: async (client_id: string) => {
209
- // LOGGER.debug("OAuth proxy: Dynamic client registration requested");
210
- //
211
- // return {
212
- // client_secret: credentials.clientsecret as string,
213
- // client_id,
214
- // redirect_uris: [
215
- // `${baseUrl}/oauth/callback`,
216
- // `${baseUrl}/mcp/oauth/callback`,
217
- // "http://localhost:3000/callback", // Claude Desktop default
218
- // "http://localhost:3000/auth/callback", // Alternative format
219
- // ],
220
- // };
221
- // },
222
- // });
223
- expressApp.use((0, router_js_1.mcpAuthMetadataRouter)({
224
- oauthMetadata: {
225
- issuer: credentials.url,
226
- authorization_endpoint: `${credentials.url}/oauth/authorize`,
227
- token_endpoint: `${credentials.url}/oauth/token`,
228
- response_types_supported: ["code", "token"],
229
- grant_types_supported: [
230
- "authorization_code",
231
- "client_credentials",
232
- "urn:ietf:params:oauth:grant-type:jwt-bearer",
233
- "refresh_token"
234
- ],
235
- token_endpoint_auth_methods_supported: ["client_secret_post", "client_secret_basic"],
236
- code_challenge_methods_supported: ["S256"]
187
+ const proxyProvider = new proxyProvider_js_1.ProxyOAuthServerProvider({
188
+ endpoints: {
189
+ authorizationUrl: `${credentials.url}/oauth/authorize`,
190
+ tokenUrl: `${credentials.url}/oauth/token`,
191
+ revocationUrl: `${credentials.url}/oauth/revoke`,
192
+ },
193
+ verifyAccessToken: async (token) => {
194
+ return {
195
+ token,
196
+ clientId: credentials.clientid,
197
+ scopes: ["uaa.resource"],
198
+ };
237
199
  },
200
+ getClient: async (client_id) => {
201
+ return {
202
+ client_secret: credentials.clientsecret,
203
+ client_id,
204
+ redirect_uris: ["http://localhost:3000/callback"], // Temporary value for now
205
+ };
206
+ },
207
+ });
208
+ expressApp.use((0, router_js_1.mcpAuthRouter)({
209
+ provider: proxyProvider,
210
+ issuerUrl: new URL(credentials.url),
211
+ //baseUrl: new URL(""), // I have left this out for the time being due to the defaulting to issuer
238
212
  serviceDocumentationUrl: new URL("https://docs.cloudfoundry.org/api/uaa/version/77.34.0/index.html#authorization"),
239
- resourceServerUrl: new URL(baseUrl),
240
213
  }));
241
- logger_1.LOGGER.info("OAuth proxy configured successfully for XSUAA integration");
242
214
  }
243
215
  /**
244
216
  * Checks whether the requesting user's access matches that of the roles required
@@ -61,7 +61,11 @@ function registerDescribeModelTool(server) {
61
61
  }));
62
62
  const keys = elements.filter((e) => e.key).map((e) => e.name);
63
63
  const sampleTop = 5;
64
- const shortFields = elements.slice(0, 5).map((e) => e.name);
64
+ // Prefer scalar fields for sample selects; exclude associations
65
+ const scalarFields = elements
66
+ .filter((e) => String(e.type).toLowerCase() !== "cds.association")
67
+ .map((e) => e.name);
68
+ const shortFields = scalarFields.slice(0, 5);
65
69
  // Match wrapper tool naming: Service_Entity_mode
66
70
  const entName = String(ent?.name || "entity");
67
71
  const svcPart = service || entName.split(".")[0] || "Service";
@@ -75,7 +79,7 @@ function registerDescribeModelTool(server) {
75
79
  fields: elements,
76
80
  usage: {
77
81
  rationale: "Entity wrapper tools expose CRUD-like operations for LLMs. Prefer query/get globally; create/update must be explicitly enabled by the developer.",
78
- guidance: "Use the *_query tool for retrieval with filters and projections; use *_get with keys for a single record; use *_create/*_update only if enabled and necessary.",
82
+ guidance: "Use the *_query tool for retrieval with filters and projections. All fields in select/where are consistent. For associations, use foreign key fields (e.g., author_ID not author). Use *_get with keys for a single record; use *_create/*_update only if enabled and necessary.",
79
83
  },
80
84
  examples: {
81
85
  list_tool: listName,
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isElicitInput = isElicitInput;
4
+ exports.constructElicitationFunctions = constructElicitationFunctions;
5
+ exports.handleElicitationRequests = handleElicitationRequests;
6
+ const zod_1 = require("zod");
7
+ /**
8
+ * Message displayed to users when requesting input parameters
9
+ */
10
+ const INPUT_MSG = "Please fill out the required parameters";
11
+ /**
12
+ * Checks if the elicited input array contains an 'input' requirement
13
+ * @param elicits - Array of elicit types or undefined
14
+ * @returns True if 'input' elicitation is required, false otherwise
15
+ */
16
+ function isElicitInput(elicits) {
17
+ return elicits ? elicits.includes("input") : false;
18
+ }
19
+ /**
20
+ * Constructs elicitation request parameters based on the tool annotation's elicit requirements
21
+ * @param model - MCP tool annotation containing elicit configuration
22
+ * @param params - Parameter definitions for the tool
23
+ * @returns Array of elicit request parameters for MCP server
24
+ * @throws Error if invalid elicitation type is encountered
25
+ */
26
+ function constructElicitationFunctions(model, params) {
27
+ const result = [];
28
+ for (const el of model.elicits ?? []) {
29
+ switch (el) {
30
+ case "input":
31
+ result.push(constructElicitInput(params));
32
+ continue;
33
+ case "confirm":
34
+ result.push(contructElicitConfirm(model));
35
+ continue;
36
+ default:
37
+ throw new Error("Invalid elicitation type");
38
+ }
39
+ }
40
+ return result;
41
+ }
42
+ /**
43
+ * Processes multiple elicitation requests sequentially and handles user responses
44
+ * @param requests - Array of elicit request parameters or undefined
45
+ * @param server - MCP server instance for making elicit calls
46
+ * @returns Promise resolving to elicitation response with early exit or data
47
+ */
48
+ async function handleElicitationRequests(requests, server) {
49
+ if (!requests || requests.length <= 0) {
50
+ return { earlyResponse: undefined };
51
+ }
52
+ let data = undefined;
53
+ for (const req of requests) {
54
+ const res = await server.server.elicitInput(req);
55
+ const earlyResponse = handleElicitResponse(res);
56
+ if (earlyResponse) {
57
+ return { earlyResponse };
58
+ }
59
+ if (req.message === INPUT_MSG) {
60
+ data = res.content;
61
+ }
62
+ }
63
+ return {
64
+ earlyResponse: undefined,
65
+ data,
66
+ };
67
+ }
68
+ /**
69
+ * Converts elicit response action into appropriate MCP result
70
+ * @param elicitResponse - Result from MCP server elicit input call
71
+ * @returns MCP result for decline/cancel actions, undefined for accept
72
+ * @throws Error if invalid response action is received
73
+ */
74
+ function handleElicitResponse(elicitResponse) {
75
+ switch (elicitResponse.action) {
76
+ case "accept":
77
+ return undefined;
78
+ case "decline":
79
+ return {
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: "Action was declined.",
84
+ },
85
+ ],
86
+ };
87
+ case "cancel":
88
+ return {
89
+ content: [
90
+ {
91
+ type: "text",
92
+ text: "Action was cancelled",
93
+ },
94
+ ],
95
+ };
96
+ default:
97
+ throw new Error("Invalid elicit response received");
98
+ }
99
+ }
100
+ /**
101
+ * Determines the schema type for elicit input based on Zod parameter type
102
+ * @param param - Zod schema parameter to analyze
103
+ * @returns Corresponding elicit schema type string
104
+ * @throws Error if parameter type is not supported for elicitation
105
+ */
106
+ function determineSchemaType(param) {
107
+ if (param instanceof zod_1.z.ZodBoolean) {
108
+ return "boolean";
109
+ }
110
+ else if (param instanceof zod_1.z.ZodString) {
111
+ return "string";
112
+ }
113
+ else if (param instanceof zod_1.z.ZodNumber) {
114
+ return "number";
115
+ }
116
+ throw new Error("Unsupported elicitation input type");
117
+ }
118
+ /**
119
+ * Constructs confirmation elicit request for tool execution
120
+ * @param model - MCP annotation model containing tool description
121
+ * @returns Elicit request parameters for user confirmation
122
+ */
123
+ function contructElicitConfirm(model) {
124
+ return {
125
+ message: `Please confirm that you want to perform action '${model.description}'`,
126
+ requestedSchema: {
127
+ type: "object",
128
+ properties: {
129
+ confirm: {
130
+ type: "boolean",
131
+ title: "Confirmation",
132
+ description: "Please confirm the action",
133
+ },
134
+ },
135
+ required: ["confirm"],
136
+ },
137
+ };
138
+ }
139
+ /**
140
+ * Constructs input elicit request for tool parameters
141
+ * @param params - Tool parameters definition with Zod schemas
142
+ * @returns Elicit request parameters for user input collection
143
+ */
144
+ function constructElicitInput(params) {
145
+ const elicitSpec = {
146
+ message: INPUT_MSG,
147
+ requestedSchema: {
148
+ type: "object",
149
+ properties: {},
150
+ required: [],
151
+ },
152
+ };
153
+ for (const [key, zodType] of Object.entries(params)) {
154
+ elicitSpec.requestedSchema.required?.push(key);
155
+ elicitSpec.requestedSchema.properties[key] = {
156
+ type: determineSchemaType(zodType),
157
+ title: key,
158
+ description: key,
159
+ };
160
+ }
161
+ return elicitSpec;
162
+ }