This is page 1 of 2. Use http://codebase.md/cashfree/cashfree-mcp?format=md&lines=true&page={x} to view the full context.

# Directory Structure

```
├── .eslintrc.json
├── .github
│   └── workflows
│       ├── publish-mcp.yml
│       └── publish.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── scripts
│   └── copy-openapi-json.cjs
├── server.json
├── smithery.yaml
├── src
│   ├── cashien.ts
│   ├── config.readonly.ts
│   ├── config.ts
│   ├── connect.ts
│   ├── index.ts
│   ├── initialize.ts
│   ├── input-source-help.ts
│   ├── openapi
│   │   ├── helpers.ts
│   │   ├── index.ts
│   │   ├── openapi-PG.json
│   │   ├── openapi-PO.json
│   │   ├── openapi-VRS.json
│   │   └── zod.ts
│   ├── search.ts
│   ├── settings.ts
│   ├── types.ts
│   └── utils.ts
├── THIRD_PARTY_LICENSES
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "parser": "@typescript-eslint/parser",
3 |   "plugins": ["@typescript-eslint"],
4 |   "extends": ["plugin:@typescript-eslint/recommended"]
5 | }
6 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # These are some examples of commonly ignored file patterns.
 2 | # You should customize this list as applicable to your project.
 3 | # Learn more about .gitignore:
 4 | #     https://www.atlassian.com/git/tutorials/saving-changes/gitignore
 5 | 
 6 | # Node artifact files
 7 | node_modules/
 8 | dist/
 9 | 
10 | # Compiled Java class files
11 | *.class
12 | 
13 | # Compiled Python bytecode
14 | *.py[cod]
15 | 
16 | # Log files
17 | *.log
18 | 
19 | # Package files
20 | *.jar
21 | 
22 | # Maven
23 | target/
24 | dist/
25 | 
26 | # JetBrains IDE
27 | .idea/
28 | 
29 | # Unit test reports
30 | TEST*.xml
31 | 
32 | # Generated by MacOS
33 | .DS_Store
34 | 
35 | # Generated by Windows
36 | Thumbs.db
37 | 
38 | # Applications
39 | *.app
40 | *.exe
41 | *.war
42 | 
43 | # Large media files
44 | *.mp4
45 | *.tiff
46 | *.avi
47 | *.flv
48 | *.mov
49 | *.wmv
50 | 
51 | # env files
52 | .env.json
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/cashfree-cashfree-mcp-badge.png)](https://mseep.ai/app/cashfree-cashfree-mcp)
  2 | 
  3 | # Cashfree MCP Server
  4 | 
  5 | Cashfree MCP server allows AI tools and agents to integrate with [Cashfree](https://www.cashfree.com/) APIs (Payment Gateway, Payouts, and SecureID) using the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction).
  6 | 
  7 | ## Setup
  8 | 
  9 | ### Clone the Repository
 10 | 
 11 | ```bash
 12 | git clone https://github.com/cashfree/cashfree-mcp.git
 13 | cd cashfree-mcp
 14 | ```
 15 | 
 16 | ### Install Dependencies
 17 | 
 18 | Before installing, ensure you have **Node.js v14.x or higher** installed. If you're using `nvm` or `brew`, make sure the correct version is active:
 19 | 
 20 | ```bash
 21 | node -v
 22 | # Should output v14.x or higher
 23 | ```
 24 | 
 25 | #### Step 1: Install project dependencies
 26 | 
 27 | ```bash
 28 | npm install
 29 | ```
 30 | 
 31 | This will install all required packages listed in `package.json`.
 32 | 
 33 | > 💡 If you're using `Node.js >=18`, you might face peer dependency issues with packages like `undici`. In that case, upgrade Node.js to `>=20.18.1` or adjust the package version if needed.
 34 | 
 35 | #### Step 2: Build the project
 36 | 
 37 | ```bash
 38 | npm run build
 39 | ```
 40 | 
 41 | This compiles the source files to the `dist/` directory, which is required to run the MCP server.
 42 | 
 43 | > 🛠️ If you see errors related to missing files in `/dist`, ensure you've run the build step successfully.
 44 | 
 45 | 
 46 | ## Configuration
 47 | 
 48 | You will need a Cashfree account with API credentials (we support both sandbox and production keys). You can use Cashfree MCP in your favorite client, some sample configurations are shown below:
 49 | 
 50 | ### Claude
 51 | 
 52 | Add the following configuration block to your `claude_desktop_config.json`
 53 | 
 54 | ```json
 55 | {
 56 |   "mcpServers": {
 57 |     "cashfree": {
 58 |       "command": "node",
 59 |       "args": ["/path/to/cashfree-mcp/dist/index.js"],
 60 |       "env": {
 61 |         "PAYMENTS_APP_ID": "YOUR_PG_CLIENT_ID",
 62 |         "PAYMENTS_APP_SECRET": "YOUR_PG_CLIENT_SECRET",
 63 |         "PAYOUTS_APP_ID": "YOUR_PAYOUTS_CLIENT_ID",
 64 |         "PAYOUTS_APP_SECRET": "YOUR_PAYOUTS_CLIENT_SECRET",
 65 |         "TWO_FA_PUBLIC_KEY_PEM_PATH": "/path/to/public_key.pem",
 66 |         "SECUREID_APP_ID": "YOUR_SECUREID_CLIENT_ID",
 67 |         "SECUREID_APP_SECRET": "YOUR_SECUREID_CLIENT_SECRET",
 68 |         "TOOLS": "pg,payouts,secureid",
 69 |         "ENV": "sandbox",
 70 |         "ELICITATION_ENABLED": "true"
 71 |       }
 72 |     }
 73 |   }
 74 | }
 75 | ```
 76 | 
 77 | ### VS Code
 78 | 
 79 | Add the following configuration block to your VS Code settings
 80 | 
 81 | ```json
 82 | {
 83 |   "mcp": {
 84 |     "inputs": [],
 85 |     "servers": {
 86 |       "cashfree": {
 87 |         "command": "node",
 88 |         "args": ["/path/to/cashfree-mcp/dist/index.js"],
 89 |         "env": {
 90 |           "PAYMENTS_APP_ID": "YOUR_PG_CLIENT_ID",
 91 |           "PAYMENTS_APP_SECRET": "YOUR_PG_CLIENT_SECRET",
 92 |           "PAYOUTS_APP_ID": "YOUR_PAYOUTS_CLIENT_ID",
 93 |           "PAYOUTS_APP_SECRET": "YOUR_PAYOUTS_CLIENT_SECRET",
 94 |           "TWO_FA_PUBLIC_KEY_PEM_PATH": "/path/to/public_key.pem",
 95 |           "SECUREID_APP_ID": "YOUR_SECUREID_CLIENT_ID",
 96 |           "SECUREID_APP_SECRET": "YOUR_SECUREID_CLIENT_SECRET",
 97 |           "TOOLS": "pg,payouts,secureid",
 98 |           "ENV": "sandbox",
 99 |           "ELICITATION_ENABLED": "true"
100 |         }
101 |       }
102 |     }
103 |   }
104 | }
105 | ```
106 | 
107 | ### API Credentials
108 | 
109 | Set the following environment variables for each service:
110 | **Payment Gateway:**
111 | 
112 | - `PAYMENTS_APP_ID`: Your Payment Gateway client ID
113 | - `PAYMENTS_APP_SECRET`: Your Payment Gateway client secret
114 | 
115 | **Payouts:**
116 | 
117 | - `PAYOUTS_APP_ID`: Your Payouts client ID
118 | - `PAYOUTS_APP_SECRET`: Your Payouts client secret
119 | - `TWO_FA_PUBLIC_KEY_PEM_PATH`: Path to your 2FA public key (required only if 2FA is enabled)
120 | 
121 | **SecureID:**
122 | 
123 | - `SECUREID_APP_ID`: Your SecureID client ID
124 | - `SECUREID_APP_SECRET`: Your SecureID client secret
125 | - `TWO_FA_PUBLIC_KEY_PEM_PATH`: Path to your 2FA public key (required only if 2FA is enabled)
126 | 
127 | ### Environment
128 | 
129 | `ENV`: Set to `production` for production environment, `sandbox` for sandbox (default: `sandbox`)
130 | 
131 | ### Tools Configuration
132 | 
133 | `TOOLS`: Comma-separated list of modules to enable. Available options:
134 | 
135 | - `pg`: Payment Gateway APIs
136 | - `payouts`: Payouts APIs
137 | - `secureid`: SecureID APIs
138 | 
139 | ### Elicitation Configuration
140 | 
141 | `ELICITATION_ENABLED`: Set to `true` to enable interactive parameter elicitation, `false` to disable (default: `false`)
142 | 
143 | When enabled, the MCP server will prompt users for missing required parameters instead of failing with validation errors. This provides a more interactive experience by asking users to provide values for required fields that weren't initially supplied.
144 | 
145 | ## Tools
146 | 
147 | Cashfree MCP has the following tools available, grouped by the product category
148 | 
149 | ### Payment Gateway (PG)
150 | 
151 | | Tool Name                                                | Description                                                                                        |
152 | | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
153 | | **search**                                               | Search across the Cashfree Payments Developer Documentation.                                       |
154 | | **get-input-source-help**                               | Get comprehensive instructions for handling input source variable errors.                          |
155 | | **create-payment-link**                                  | Create a new payment link.                                                                         |
156 | | **fetch-payment-link-details**                           | View all details and status of a payment link.                                                     |
157 | | **cancel-payment-link**                                  | Cancel an active payment link. No further payments can be done against cancelled links             |
158 | | **get-orders-for-a-payment-link**                        | View all order details for a payment link.                                                         |
159 | | **create-order**                                         | Create orders with Cashfree to get a payment_sessions_id for transactions                          |
160 | | **get-order**                                            | Fetch order details using order_id                                                                 |
161 | | **get-order-extended**                                   | Get extended order data like address, cart, offers, customer details etc                           |
162 | | **get-eligible-payment-methods**                         | Get eligible payment methods for a given order amount and ID                                       |
163 | | **get-payments-for-an-order**                            | View all payment details for an order.                                                             |
164 | | **get-payment-by-id**                                    | View payment details of an order for a Payment ID.                                                 |
165 | | **create-refund**                                        | Initiate refunds.                                                                                  |
166 | | **get-all-refunds-for-an-order**                         | Fetch all refunds processed against an order.                                                      |
167 | | **get-refund**                                           | Fetch a specific refund processed on your Cashfree Account.                                        |
168 | | **get-all-settlements**                                  | Get all settlement details by specifying the settlement ID, settlement UTR, or date range.         |
169 | | **get-split-and-settlement-details-by-order-id-v2-0**    | Get split and settlement details, including settled/unsettled transactions for vendors in an order |
170 | | **get-settlements-by-order-id**                          | View all the settlements of a particular order.                                                    |
171 | | **get-disputes-by-order-id**                             | Get all dispute details by Order ID                                                                |
172 | | **get-disputes-by-payment-id**                           | Get all dispute details by Payment ID                                                              |
173 | | **get-disputes-by-dispute-id**                           | Get dispute details by Dispute ID                                                                  |
174 | | **accept-dispute-by-dispute-id**                         | Accept a dispute by its Dispute ID                                                                 |
175 | | **submit-evidence-to-contest-the-dispute-by-dispute-id** | Submit evidence to contest a dispute                                                               |
176 | | **simulate-payment**                                     | Simulate payment for testing. Requires prior order creation                                        |
177 | | **fetch-simulation**                                     | Fetch simulated payment details                                                                    |
178 | 
179 | ### Payouts
180 | 
181 | | Tool Name                        | Description                                                                      |
182 | | -------------------------------- | -------------------------------------------------------------------------------- |
183 | | **standard-transfer-v2**         | Initiate an amount transfer at Cashfree Payments.                                |
184 | | **get-transfer-status-v2**       | Get the status of an initiated transfer.                                         |
185 | | **batch-transfer-v2**            | Initiate a batch transfer request at Cashfree Payments.                          |
186 | | **get-batch-transfer-status-v2** | Get the status of an initiated batch transfer.                                   |
187 | | **authorize**                    | Authenticate with the Cashfree system and obtain the authorization bearer token. |
188 | | **create-cashgram**              | Create a Cashgram.                                                               |
189 | | **deactivate-cashgram**          | Deactivate a Cashgram.                                                           |
190 | | **get-cashgram-status**          | Get the status of a created Cashgram.                                            |
191 | 
192 | ### SecureID
193 | 
194 | | Tool Name                      | Description                                       |
195 | | ------------------------------ | ------------------------------------------------- |
196 | | **verify-name-match**          | Verify names with variations.                     |
197 | | **generate-kyc-link**          | Generate a verification form for KYC information. |
198 | | **get-kyc-link-status**        | Get the status of a KYC verification form.        |
199 | | **generate-static-kyc-link**   | Generate a static KYC link.                       |
200 | | **deactivate-static-kyc-link** | Deactivate a static KYC link.                     |
201 | 
202 | ## License
203 | 
204 | This project is licensed under the terms of the MIT open source license. Please refer to LICENSE for the full terms.
205 | 
206 | ## Documentation
207 | 
208 | For detailed API documentation, visit the [Cashfree API Documentation](https://docs.cashfree.com/reference/).
209 | 
210 | ## Support
211 | 
212 | For support, contact [care@cashfree.com](mailto:care@cashfree.com) or raise an issue in the [GitHub repository](https://github.com/cashfree/cashfree-mcp).
213 | 
```

--------------------------------------------------------------------------------
/src/settings.ts:
--------------------------------------------------------------------------------

```typescript
1 | export const SERVER_NAME: string = "cashfree-mcp";
2 | export const SERVER_VERSION: string = "1.0.0";
3 | 
```

--------------------------------------------------------------------------------
/src/config.readonly.ts:
--------------------------------------------------------------------------------

```typescript
1 | // read-only
2 | export const SUBDOMAIN: Readonly<string> = "cashfreepayments-d00050e9";
3 | export const SERVER_URL: Readonly<string> = "https://leaves.mintlify.com";
4 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/build/project-config
 2 | FROM node:lts-alpine
 3 | 
 4 | WORKDIR /app
 5 | 
 6 | # Install dependencies
 7 | COPY package*.json ./
 8 | RUN npm install --production
 9 | 
10 | # Copy source
11 | COPY . .
12 | 
13 | # Default command (override via MCP client)
14 | CMD ["node", "src/index.js"]
15 | 
```

--------------------------------------------------------------------------------
/src/initialize.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Initializes and returns the MCP server instance.
 3 |  */
 4 | 
 5 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 6 | import { SERVER_NAME, SERVER_VERSION } from "./settings.js";
 7 | import type { Config } from "./config.js";
 8 | 
 9 | export function initialize(config: Config): McpServer {
10 |   console.error("Initializing MCP Server...", config);
11 |   const server = new McpServer({
12 |     name: SERVER_NAME,
13 |     version: SERVER_VERSION,
14 |   });
15 |   return server;
16 | }
17 | 
```

--------------------------------------------------------------------------------
/scripts/copy-openapi-json.cjs:
--------------------------------------------------------------------------------

```
 1 | // Copy all JSON files from src/openapi to dist/openapi after tsc build
 2 | // Usage: node scripts/copy-openapi-json.cjs
 3 | 
 4 | const fs = require('fs');
 5 | const path = require('path');
 6 | 
 7 | const srcDir = path.join(__dirname, '../src/openapi');
 8 | const distDir = path.join(__dirname, '../dist/openapi');
 9 | 
10 | if (!fs.existsSync(distDir)) {
11 |   fs.mkdirSync(distDir, { recursive: true });
12 | }
13 | 
14 | const files = fs.readdirSync(srcDir);
15 | 
16 | for (const file of files) {
17 |   if (file.endsWith('.json')) {
18 |     const srcPath = path.join(srcDir, file);
19 |     const destPath = path.join(distDir, file);
20 |     fs.copyFileSync(srcPath, destPath);
21 |   }
22 | }
23 | 
```

--------------------------------------------------------------------------------
/src/connect.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 3 | 
 4 | /**
 5 |  * Connects the given MCP server to standard IO transport.
 6 |  * Logs server status and handles connection errors gracefully.
 7 |  *
 8 |  * @param server - An instance of McpServer to be connected.
 9 |  */
10 | export async function connectServer(server: McpServer): Promise<void> {
11 |   const transport = new StdioServerTransport();
12 |   try {
13 |     await server.connect(transport);
14 |   } catch (error: unknown) {
15 |     const errorMessage = error instanceof Error ? error.message : String(error);
16 |     console.error("Failed to connect MCP Server:", errorMessage);
17 |   }
18 | }
19 | 
```

--------------------------------------------------------------------------------
/.github/workflows/publish-mcp.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Publish to MCP Registry
 2 | 
 3 | on:
 4 |   push:
 5 |     tags: ["v*"]  # Triggers on version tags like v1.0.0
 6 | 
 7 | jobs:
 8 |   publish:
 9 |     runs-on: ubuntu-latest
10 |     permissions:
11 |       id-token: write  # Required for OIDC authentication
12 |       contents: read
13 | 
14 |     steps:
15 |       - name: Checkout code
16 |         uses: actions/checkout@v5
17 | 
18 |       - name: Install MCP Publisher
19 |         run: |
20 |           curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
21 | 
22 |       - name: Login to MCP Registry
23 |         run: ./mcp-publisher login github-oidc
24 | 
25 |       - name: Publish to MCP Registry
26 |         run: ./mcp-publisher publish
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "types": ["node"],
 4 |     "target": "es2020",
 5 |     "lib": ["es2020", "es2018", "dom", "dom.iterable", "esnext"],
 6 |     "allowJs": true,
 7 |     "skipLibCheck": true,
 8 |     "strict": true,
 9 |     "forceConsistentCasingInFileNames": true,
10 |     "esModuleInterop": true,
11 |     "module": "node16",
12 |     "moduleResolution": "node16",
13 |     "resolveJsonModule": true,
14 |     "isolatedModules": true,
15 |     "jsx": "react-jsx",
16 |     "declaration": true,
17 |     "declarationDir": "./dist",
18 |     "sourceMap": true,
19 |     "outDir": "./dist",
20 |     "baseUrl": ".",
21 |     "paths": {
22 |       "@/*": ["./src/*"]
23 |     },
24 |     "rootDir": "./src",
25 |     "typeRoots": ["./node_modules", "./node_modules/@types", "./src/@types"],
26 |     "noUnusedLocals": true,
27 |     "noUnusedParameters": true,
28 |     "noFallthroughCasesInSwitch": true,
29 |     "noEmitOnError": false
30 |   },
31 |   "include": ["./src/**/*.ts", "./src/*"],
32 |   "exclude": ["node_modules", "dist", "coverage", "package.json"]
33 | }
34 | 
```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { PrimitiveSchemaDefinition } from "@modelcontextprotocol/sdk/types.js";
 2 | 
 3 | export interface InitializationConfiguration {
 4 |   name: string;
 5 |   trieveApiKey: string;
 6 |   trieveDatasetId: string;
 7 | }
 8 | 
 9 | export interface SearchResult {
10 |   title: string;
11 |   content: string;
12 |   link: string;
13 | }
14 | 
15 | export interface Endpoint {
16 |   url?: string;
17 |   method: string;
18 |   path: string;
19 |   title?: string;
20 |   description?: string;
21 |   request: { [key: string]: any };
22 |   servers?: Array<{ url: string }> | { [key: string]: any };
23 |   operation: {
24 |     summary?: string;
25 |     description?: string;
26 |     tags?: string[];
27 |     "x-mcp"?: McpConfiguration;
28 |     operationId?: string;
29 |     deprecated?: boolean;
30 |     security?: any[];
31 |     parameters?: any[];
32 |     requestBody?: any;
33 |     responses?: any;
34 |     [key: string]: any;
35 |   };
36 | }
37 | 
38 | export interface McpConfiguration {
39 |   enabled: boolean;
40 |   config?: {
41 |     elicitation?: ElicitationConfiguration;
42 |   };
43 | }
44 | 
45 | // Elicitation configuration for OpenAPI endpoints
46 | export interface ElicitationConfiguration {
47 |   enabled: boolean;
48 |   fields: Record<string, ElicitationField>;
49 | }
50 | 
51 | export interface ElicitationField {
52 |   required: boolean;
53 |   message: string;
54 |   schema: PrimitiveSchemaDefinition;
55 |   mapping: {
56 |     target: string;
57 |     transform?: 'string' | 'number' | 'boolean' | 'array';
58 |   };
59 | }
60 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@cashfreepayments/cashfree-mcp",
 3 |   "mcpName": "io.github.cashfree/cashfree-mcp",
 4 |   "version": "1.0.1",
 5 |   "type": "module",
 6 |   "description": "Cashfree MCP server for cashfree docs and APIs",
 7 |   "engines": {
 8 |     "node": ">=18.0.0"
 9 |   },
10 |   "main": "dist/index.js",
11 |   "bin": {
12 |     "cashfree-mcp": "dist/index.js"
13 |   },
14 |   "files": [
15 |     "dist/**/*",
16 |     "README.md",
17 |     "LICENSE",
18 |     "THIRD_PARTY_LICENSES"
19 |   ],
20 |   "scripts": {
21 |     "start": "npm run build && node dist/index.js",
22 |     "dev": "ts-node src/index.ts",
23 |     "build": "tsc && node scripts/copy-openapi-json.cjs",
24 |     "lint": "echo 'No linting configured'",
25 |     "check": "tsc --noEmit"
26 |   },
27 |   "author": "Cashfree",
28 |   "reference": "Mintlify mcp",
29 |   "dependencies": {
30 |     "@mintlify/openapi-parser": "^0.0.7",
31 |     "@mintlify/openapi-types": "^0.0.0",
32 |     "@mintlify/validation": "^0.1.320",
33 |     "@modelcontextprotocol/sdk": "^1.20.0",
34 |     "axios": ">=1.12.0",
35 |     "dashify": "^2.0.0",
36 |     "js-yaml": "^4.1.0",
37 |     "semantic-ui-react": "^2.1.5",
38 |     "trieve-ts-sdk": "^0.0.62",
39 |     "uuid": "^11.1.0"
40 |   },
41 |   "devDependencies": {
42 |     "@types/dashify": "^1.0.3",
43 |     "@types/js-yaml": "^4.0.9",
44 |     "@types/node": "^22.18.9",
45 |     "@types/react": "^19.1.4",
46 |     "@types/uuid": "^10.0.0",
47 |     "@typescript-eslint/eslint-plugin": "^8.32.1",
48 |     "@typescript-eslint/parser": "^8.32.1",
49 |     "eslint": "^9.27.0",
50 |     "ts-node": "^10.9.2",
51 |     "typescript": "^5.8.3"
52 |   }
53 | }
54 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | /**
 3 |  * Entry point for Cashfree MCP server.
 4 |  * Initializes server, loads OpenAPI tools, and starts the server.
 5 |  */
 6 | 
 7 | import fs from "node:fs";
 8 | import path from "node:path";
 9 | import { fileURLToPath } from "node:url";
10 | import { connectServer } from "./connect.js";
11 | import { initialize } from "./initialize.js";
12 | import { createToolsFromOpenApi } from "./openapi/index.js";
13 | import { createSearchTool } from "./search.js";
14 | import { isMcpEnabled } from "./openapi/helpers.js";
15 | import { readConfig } from "./config.js";
16 | import { createCashienTool } from "./cashien.js";
17 | import { createCFContextInitializerTool } from "./input-source-help.js";
18 | 
19 | const __filename = fileURLToPath(import.meta.url);
20 | const __dirname = path.dirname(__filename);
21 | 
22 | async function main() {
23 |   const config = readConfig();
24 |   const server = initialize(config);
25 |   const existingTools: Set<string> = new Set();
26 | 
27 |   await createSearchTool(server);
28 |   await createCashienTool(server);
29 |   await createCFContextInitializerTool(server);
30 | 
31 |   // Dynamically load OpenAPI-based tools
32 |   const openApiDir = path.join(__dirname, "openapi");
33 |   if (!fs.existsSync(openApiDir)) {
34 |     throw new Error(`OpenAPI directory not found at path: ${openApiDir}`);
35 |   }
36 | 
37 |   const openApiFilePaths = fs
38 |     .readdirSync(openApiDir)
39 |     .filter((file) => file.startsWith("openapi-") && file.endsWith(".json"))
40 |     .filter((file) => isMcpEnabled(file));
41 | 
42 |   await Promise.all(
43 |     openApiFilePaths.map(async (openApiPath, index) => {
44 |       return createToolsFromOpenApi(
45 |         path.join(openApiDir, openApiPath),
46 |         index,
47 |         server,
48 |         existingTools
49 |       );
50 |     })
51 |   );
52 | 
53 |   await connectServer(server);
54 | }
55 | 
56 | main().catch((error: unknown) => {
57 |   console.error("Fatal error in trying to initialize MCP server:", error);
58 |   process.exit(1);
59 | });
60 | 
```

--------------------------------------------------------------------------------
/src/cashien.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Provides the Cashien tool for API integration queries.
 3 |  * This tool sends user queries to a specified endpoint and returns the response.
 4 |  */
 5 | 
 6 | import { z } from "zod";
 7 | import { formatErr } from "./utils.js";
 8 | import { v4 as uuidv4 } from "uuid";
 9 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10 | 
11 | // Constants
12 | const CASHIEN_API_URL =
13 |   "https://receiver.cashfree.com/pgnextgenconsumer/cashien/external/chat/message";
14 | const GENERIC_ERR_MSG =
15 |   "Unable to process your request. Please try again after some time.";
16 | 
17 | // Types
18 | interface CashienPayload {
19 |   conversationId: string;
20 |   userId: string;
21 |   messageId: string;
22 |   message: string;
23 | }
24 | 
25 | interface CashienResponse {
26 |   status?: string;
27 |   message?: string;
28 | }
29 | 
30 | // Tool definition
31 | export function createCashienTool(server: McpServer) {
32 |   return server.tool(
33 |     "cashien",
34 |     "Use this tool to write code to integrate Cashfree APIs and SDKs. Supports both backend and frontend.",
35 |     { query: z.string() },
36 |     async ({ query }) => {
37 |       try {
38 |         const response = await sendMessageToChatbot(query);
39 |         return {
40 |           content: [
41 |             {
42 |               type: "text",
43 |               text: response,
44 |             },
45 |           ],
46 |         };
47 |       } catch (err) {
48 |         console.error("Error in createCashienTool:", err);
49 |         throw new Error(formatErr(err));
50 |       }
51 |     }
52 |   );
53 | }
54 | 
55 | // Chatbot message handler
56 | export async function sendMessageToChatbot(message: string): Promise<string> {
57 |   const payload: CashienPayload = {
58 |     conversationId: uuidv4(),
59 |     userId: uuidv4(),
60 |     messageId: uuidv4(),
61 |     message,
62 |   };
63 |   try {
64 |     const response = await fetch(`${CASHIEN_API_URL}`, {
65 |       method: "POST",
66 |       headers: {
67 |         "Content-Type": "application/json",
68 |       },
69 |       body: JSON.stringify(payload),
70 |     });
71 |     const finalResp: CashienResponse = await response.json();
72 |     if (finalResp.status === undefined || finalResp.status === "ERROR") {
73 |       return GENERIC_ERR_MSG;
74 |     }
75 |     return finalResp.message ?? GENERIC_ERR_MSG;
76 |   } catch (error) {
77 |     console.error("Error in sendMessageToChatbot:", error);
78 |     return GENERIC_ERR_MSG;
79 |   }
80 | }
81 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/build/project-config
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   commandFunction:
 6 |     # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
 7 |     |-
 8 |     (config) => ({ command: 'node', args: ['src/index.js'], env: { PAYMENTS_APP_ID: config.paymentsAppId, PAYMENTS_APP_SECRET: config.paymentsAppSecret, PAYOUTS_APP_ID: config.payoutsAppId, PAYOUTS_APP_SECRET: config.payoutsAppSecret, SECUREID_APP_ID: config.secureidAppId, SECUREID_APP_SECRET: config.secureidAppSecret, TWO_FA_PUBLIC_KEY_PEM_PATH: config.twoFaPublicKeyPemPath, TOOLS: config.tools, ENV: config.env } })
 9 |   configSchema:
10 |     # JSON Schema defining the configuration options for the MCP.
11 |     type: object
12 |     required:
13 |       - paymentsAppId
14 |       - paymentsAppSecret
15 |       - payoutsAppId
16 |       - payoutsAppSecret
17 |       - secureidAppId
18 |       - secureidAppSecret
19 |       - tools
20 |     properties:
21 |       paymentsAppId:
22 |         type: string
23 |         description: Client ID for Payment Gateway
24 |       paymentsAppSecret:
25 |         type: string
26 |         description: Client Secret for Payment Gateway
27 |       payoutsAppId:
28 |         type: string
29 |         description: Client ID for Payouts
30 |       payoutsAppSecret:
31 |         type: string
32 |         description: Client Secret for Payouts
33 |       secureidAppId:
34 |         type: string
35 |         description: Client ID for Secure ID
36 |       secureidAppSecret:
37 |         type: string
38 |         description: Client Secret for Secure ID
39 |       twoFaPublicKeyPemPath:
40 |         type: string
41 |         description: Path to 2FA public key PEM file
42 |       tools:
43 |         type: string
44 |         description: Comma-separated list of tools to enable (pg,payouts,secureid)
45 |       env:
46 |         type: string
47 |         default: sandbox
48 |         description: "Environment: sandbox or production"
49 |   exampleConfig:
50 |     paymentsAppId: test_pg_id
51 |     paymentsAppSecret: test_pg_secret
52 |     payoutsAppId: test_payouts_id
53 |     payoutsAppSecret: test_payouts_secret
54 |     secureidAppId: test_secureid_id
55 |     secureidAppSecret: test_secureid_secret
56 |     twoFaPublicKeyPemPath: /path/to/public_key.pem
57 |     tools: pg,payouts,secureid
58 |     env: sandbox
59 | 
```

--------------------------------------------------------------------------------
/server.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
 3 |   "name": "io.github.cashfree/cashfree-mcp",
 4 |   "description": "Cashfree MCP server for cashfree docs and APIs",
 5 |   "repository": {
 6 |     "url": "https://github.com/cashfree/cashfree-mcp",
 7 |     "source": "github"
 8 |   },
 9 |   "version": "1.0.0",
10 |   "packages": [
11 |     {
12 |       "registryType": "npm",
13 |       "registryBaseUrl": "https://registry.npmjs.org",
14 |       "identifier": "@cashfreepayments/cashfree-mcp",
15 |       "version": "1.0.0",
16 |       "transport": {
17 |         "type": "stdio"
18 |       },
19 |       "environmentVariables": [
20 |         {
21 |           "description": "Payment Gateway App Id",
22 |           "format": "string",
23 |           "isSecret": true,
24 |           "name": "PAYMENTS_APP_ID"
25 |         },
26 |         {
27 |           "description": "Payment Gateway App Secret",
28 |           "format": "string",
29 |           "isSecret": true,
30 |           "name": "PAYMENTS_APP_SECRET"
31 |         },
32 |         {
33 |           "description": "Payouts App Id",
34 |           "format": "string",
35 |           "isSecret": true,
36 |           "name": "PAYOUTS_APP_ID"
37 |         },
38 |         {
39 |           "description": "Payouts App Secret",
40 |           "format": "string",
41 |           "isSecret": true,
42 |           "name": "PAYOUTS_APP_SECRET"
43 |         },
44 |         {
45 |           "description": "Path to the PEM file containing the public key for verifying 2FA signatures",
46 |           "format": "string",
47 |           "isSecret": false,
48 |           "name": "TWO_FA_PUBLIC_KEY_PEM_PATH"
49 |         },
50 |         {
51 |           "description": "SecureId App Id",
52 |           "format": "string",
53 |           "isSecret": true,
54 |           "name": "SECUREID_APP_ID"
55 |         },
56 |         {
57 |           "description": "SecureId App Secret",
58 |           "format": "string",
59 |           "isSecret": true,
60 |           "name": "SECUREID_APP_SECRET"
61 |         },
62 |         {
63 |           "description": "Tools (Comma-separated list of modules to enable. Available options: pg, payouts, secureid)",
64 |           "format": "string",
65 |           "isSecret": false,
66 |           "isRequired": true,
67 |           "name": "TOOLS"
68 |         },
69 |         {
70 |           "description": "Environment (production/sandbox default: sandbox)",
71 |           "format": "string",
72 |           "isSecret": false,
73 |           "isRequired": true,
74 |           "name": "ENV"
75 |         }
76 |       ]
77 |     }
78 |   ]
79 | }
80 | 
```

--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import axios, { AxiosResponse } from "axios";
 2 | import { OpenAPI } from "@mintlify/openapi-types";
 3 | 
 4 | // A recursive type representing nested objects or strings
 5 | export type NestedRecord = string | { [key: string]: NestedRecord };
 6 | 
 7 | export type SimpleRecord = Record<string, NestedRecord>;
 8 | 
 9 | /**
10 |  * Initializes an object along a given path, creating nested objects if needed.
11 |  */
12 | export function initializeObject(
13 |   obj: SimpleRecord,
14 |   path: string[]
15 | ): SimpleRecord {
16 |   let current: NestedRecord = obj;
17 | 
18 |   for (const key of path) {
19 |     if (!current[key] || typeof current[key] !== "object") {
20 |       current[key] = {};
21 |     }
22 |     current = current[key];
23 |   }
24 |   return current;
25 | }
26 | 
27 | /**
28 |  * Gets a unique file ID based on spec title and version.
29 |  */
30 | export function getFileId(
31 |   spec: OpenAPI.Document,
32 |   index: number
33 | ): string | number {
34 |   var _a;
35 |   return ((_a = spec.info) === null || _a === void 0 ? void 0 : _a.title) &&
36 |     spec?.info?.version
37 |     ? `${spec?.info?.title} - ${spec?.info?.version}`
38 |     : index;
39 | }
40 | 
41 | /**
42 |  * Throws an error if the Axios response indicates a failure.
43 |  */
44 | export function throwOnAxiosError(
45 |   response: AxiosResponse,
46 |   errMsg: string
47 | ): void {
48 |   var _a, _b;
49 |   if (response.status !== 200) {
50 |     if (
51 |       ((_a = response.headers["content-type"]) === null || _a === void 0
52 |         ? void 0
53 |         : _a.includes("application/json")) &&
54 |       ((_b = response.data) === null || _b === void 0 ? void 0 : _b.error)
55 |     ) {
56 |       throw new Error(`${errMsg}: ${response.data.error}`);
57 |     } else {
58 |       throw new Error(
59 |         `${errMsg}: ${response.status} ${response.statusText || ""}`
60 |       );
61 |     }
62 |   }
63 |   if (!response.data) {
64 |     throw new Error(`${errMsg}: ${response.status} ${response.statusText}`);
65 |   }
66 | }
67 | 
68 | /**
69 |  * Formats various types of errors into human-readable strings.
70 |  */
71 | export function formatErr(err: unknown) {
72 |   var _a, _b;
73 |   if (axios.isAxiosError(err)) {
74 |     if (err.message) {
75 |       return err.message;
76 |     } else if (err.response) {
77 |       return (_b =
78 |         (_a = err.response.data) === null || _a === void 0
79 |           ? void 0
80 |           : _a.error) !== null && _b !== void 0
81 |         ? _b
82 |         : `${err.response.status} ${err.response.statusText}`;
83 |     } else if (err.request) {
84 |       return "No response received from server";
85 |     } else {
86 |       err = "An unknown error occurred";
87 |     }
88 |   } else if (err instanceof Error) {
89 |     return err.message;
90 |   } else {
91 |     return JSON.stringify(err, undefined, 2);
92 |   }
93 | }
94 | 
```

--------------------------------------------------------------------------------
/src/search.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Provides search functionality for documentation using Trieve.
  3 |  * Exports functions to fetch search configuration and register the search tool.
  4 |  */
  5 | 
  6 | import axios, { AxiosResponse } from "axios";
  7 | import { TrieveSDK } from "trieve-ts-sdk";
  8 | import { z } from "zod";
  9 | import { SUBDOMAIN, SERVER_URL } from "./config.readonly.js";
 10 | import { formatErr, throwOnAxiosError } from "./utils.js";
 11 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 12 | import { InitializationConfiguration } from "./types.js";
 13 | 
 14 | const DEFAULT_BASE_URL = "https://api.mintlifytrieve.com";
 15 | 
 16 | /**
 17 |  * Fetches search configuration and OpenAPI data for a given subdomain.
 18 |  * @param subdomain - Subdomain to fetch config for.
 19 |  * @returns The initialization configuration.
 20 |  */
 21 | export async function fetchSearchConfigurationAndOpenApi(
 22 |   subdomain: string
 23 | ): Promise<InitializationConfiguration> {
 24 |   try {
 25 |     const response: AxiosResponse<InitializationConfiguration> =
 26 |       await axios.get(`${SERVER_URL}/api/mcp/config/${subdomain}`, {
 27 |         validateStatus: () => true,
 28 |       });
 29 | 
 30 |     throwOnAxiosError(response, "Failed to fetch MCP config");
 31 |     return response.data;
 32 |   } catch (err) {
 33 |     const friendlyError = formatErr(err).replace(
 34 |       "Request failed with status code 404",
 35 |       `${subdomain} not found`
 36 |     );
 37 |     throw new Error(friendlyError);
 38 |   }
 39 | }
 40 | 
 41 | interface TrieveChunk {
 42 |   chunk: {
 43 |     metadata: {
 44 |       title: string;
 45 |     };
 46 |     chunk_html: string;
 47 |     link: string;
 48 |   };
 49 | }
 50 | 
 51 | interface SearchResult {
 52 |   title: string;
 53 |   content: string;
 54 |   link: string;
 55 | }
 56 | 
 57 | /**
 58 |  * Queries Trieve and returns formatted search results.
 59 |  * @param query - The search string.
 60 |  * @param config - Trieve configuration values.
 61 |  * @returns Array of search results.
 62 |  */
 63 | async function search(
 64 |   query: string,
 65 |   config: InitializationConfiguration
 66 | ): Promise<SearchResult[]> {
 67 |   const trieve = new TrieveSDK({
 68 |     apiKey: config.trieveApiKey,
 69 |     datasetId: config.trieveDatasetId,
 70 |     baseUrl: DEFAULT_BASE_URL,
 71 |   });
 72 | 
 73 |   const data = await trieve.autocomplete({
 74 |     page_size: 10,
 75 |     query,
 76 |     search_type: "fulltext",
 77 |     extend_results: true,
 78 |     score_threshold: 1,
 79 |   });
 80 | 
 81 |   if (!data?.chunks?.length) {
 82 |     throw new Error("No results found");
 83 |   }
 84 | 
 85 |   return data.chunks.map(({ chunk }: TrieveChunk) => ({
 86 |     title: chunk.metadata.title,
 87 |     content: chunk.chunk_html,
 88 |     link: chunk.link,
 89 |   }));
 90 | }
 91 | 
 92 | /**
 93 |  * Registers the "search" tool to the MCP server for querying documentation.
 94 |  * @param server - The MCP server instance.
 95 |  */
 96 | export async function createSearchTool(server: McpServer): Promise<void> {
 97 |   const config = await fetchSearchConfigurationAndOpenApi(SUBDOMAIN);
 98 | 
 99 |   server.tool(
100 |     "search",
101 |     `Search across the ${config.name} documentation to fetch relevant context for a given query.`,
102 |     { query: z.string() },
103 |     async ({ query }: { query: string }) => {
104 |       const results = await search(query, config);
105 | 
106 |       const content = results.map(({ title, content, link }) => ({
107 |         type: "text" as const,
108 |         text: `Title: ${title}\nContent: ${content}\nLink: ${link}`,
109 |       }));
110 | 
111 |       return { content };
112 |     }
113 |   );
114 | }
115 | 
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Reads and prepares configuration for Cashfree APIs (Payment, Payout, Verification).
  3 |  * Handles environment-based base URLs and API credentials.
  4 |  */
  5 | 
  6 | import { getPublicKeyFromPath } from "./openapi/helpers.js";
  7 | export interface ApiConfig {
  8 |   base_url?: string;
  9 |   header: {
 10 |     "x-client-id"?: string;
 11 |     "x-client-secret"?: string;
 12 |   };
 13 |   TWO_FA_PUBLIC_KEY?: string;
 14 | }
 15 | 
 16 | export type Config = Record<
 17 |   typeof PAYMENT_API_KEY | typeof PAYOUT_API_KEY | typeof VERIFICATION_API_KEY,
 18 |   ApiConfig
 19 | >;
 20 | 
 21 | const BASE_URLS = {
 22 |   sandbox: "https://sandbox.cashfree.com",
 23 |   production: "https://api.cashfree.com",
 24 | };
 25 | 
 26 | // API Keys / Identifiers
 27 | export const PAYMENT_API_KEY = "Cashfree Payment Gateway APIs - 2025-01-01";
 28 | export const PAYOUT_API_KEY = "Cashfree Payout APIs - 2024-01-01";
 29 | export const VERIFICATION_API_KEY = "Cashfree Verification API's. - 2023-12-18";
 30 | 
 31 | // Default config structure
 32 | const DEFAULT_CONFIG: Config = {
 33 |   [PAYMENT_API_KEY]: {
 34 |     base_url: `${BASE_URLS.sandbox}/pg`,
 35 |     header: {},
 36 |   },
 37 |   [PAYOUT_API_KEY]: {
 38 |     base_url: `${BASE_URLS.sandbox}/payout`,
 39 |     header: {},
 40 |   },
 41 |   [VERIFICATION_API_KEY]: {
 42 |     base_url: `${BASE_URLS.sandbox}/verification`,
 43 |     header: {},
 44 |   },
 45 | };
 46 | 
 47 | export function readConfig(): Config {
 48 |   const config: Config = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
 49 |   const isProduction = process.env.ENV === "production";
 50 | 
 51 |   // Adjust base_url for sandbox vs production
 52 |   const baseUrl = isProduction ? BASE_URLS.production : BASE_URLS.sandbox;
 53 |   (
 54 |     Object.keys(config) as Array<
 55 |       | typeof PAYMENT_API_KEY
 56 |       | typeof PAYOUT_API_KEY
 57 |       | typeof VERIFICATION_API_KEY
 58 |     >
 59 |   ).forEach((api) => {
 60 |     config[api].base_url = `${baseUrl}${
 61 |       config[api].base_url!.split(BASE_URLS.sandbox)[1]
 62 |     }`;
 63 |   });
 64 | 
 65 |   // Helper to configure API credentials
 66 |   const configureApiCredentials = ({
 67 |     key,
 68 |     idVar,
 69 |     secretVar,
 70 |     pubKeyVar,
 71 |   }: {
 72 |     key:
 73 |       | typeof PAYMENT_API_KEY
 74 |       | typeof PAYOUT_API_KEY
 75 |       | typeof VERIFICATION_API_KEY;
 76 |     idVar: string;
 77 |     secretVar: string;
 78 |     pubKeyVar?: string;
 79 |   }) => {
 80 |     const appId = process.env[idVar];
 81 |     const appSecret = process.env[secretVar];
 82 |     if (appId && appSecret) {
 83 |       config[key].header = {
 84 |         "x-client-id": appId,
 85 |         "x-client-secret": appSecret,
 86 |       };
 87 |     }
 88 |     if (pubKeyVar && process.env[pubKeyVar]) {
 89 |       const publicKey = getPublicKeyFromPath(process.env[pubKeyVar]);
 90 |       if (publicKey !== null && publicKey !== undefined) {
 91 |         config[key].TWO_FA_PUBLIC_KEY = publicKey;
 92 |       }
 93 |     }
 94 |   };
 95 | 
 96 |   // Apply credentials for each API
 97 |   (
 98 |     [
 99 |       {
100 |         key: PAYMENT_API_KEY,
101 |         idVar: "PAYMENTS_APP_ID",
102 |         secretVar: "PAYMENTS_APP_SECRET",
103 |       },
104 |       {
105 |         key: PAYOUT_API_KEY,
106 |         idVar: "PAYOUTS_APP_ID",
107 |         secretVar: "PAYOUTS_APP_SECRET",
108 |         pubKeyVar: "TWO_FA_PUBLIC_KEY_PEM_PATH",
109 |       },
110 |       {
111 |         key: VERIFICATION_API_KEY,
112 |         idVar: "SECUREID_APP_ID",
113 |         secretVar: "SECUREID_APP_SECRET",
114 |         pubKeyVar: "TWO_FA_PUBLIC_KEY_PEM_PATH",
115 |       },
116 |     ] as {
117 |       key:
118 |         | typeof PAYMENT_API_KEY
119 |         | typeof PAYOUT_API_KEY
120 |         | typeof VERIFICATION_API_KEY;
121 |       idVar: string;
122 |       secretVar: string;
123 |       pubKeyVar?: string;
124 |     }[]
125 |   ).forEach(configureApiCredentials);
126 | 
127 |   return config;
128 | }
129 | 
```

--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: Publish Package to npm
  2 | 
  3 | on:
  4 |     release:
  5 |         types: [created]
  6 | 
  7 | jobs:
  8 |     test:
  9 |         runs-on: ubuntu-latest
 10 |         steps:
 11 |             - uses: actions/checkout@v3
 12 | 
 13 |             - name: Setup Node.js
 14 |               uses: actions/setup-node@v3
 15 |               with:
 16 |                   node-version: "18.x"
 17 |                   cache: "npm"
 18 | 
 19 |             - name: Install dependencies
 20 |               run: npm ci
 21 | 
 22 |             # No test step required as indicated in package.json
 23 | 
 24 |     build:
 25 |         needs: test
 26 |         runs-on: ubuntu-latest
 27 |         steps:
 28 |             - uses: actions/checkout@v3
 29 | 
 30 |             - name: Setup Node.js
 31 |               uses: actions/setup-node@v3
 32 |               with:
 33 |                   node-version: "18.x"
 34 |                   cache: "npm"
 35 | 
 36 |             - name: Install dependencies
 37 |               run: npm ci
 38 | 
 39 |             - name: Build package
 40 |               run: npm run build
 41 | 
 42 |             - name: Upload build artifacts
 43 |               uses: actions/upload-artifact@v4
 44 |               with:
 45 |                   name: dist
 46 |                   path: |
 47 |                       src/
 48 |                       package.json
 49 |                       README.md
 50 |                       LICENSE
 51 |                       THIRD_PARTY_LICENSES
 52 | 
 53 |     publish:
 54 |         needs: build
 55 |         runs-on: ubuntu-latest
 56 |         steps:
 57 |             - name: Download build artifacts
 58 |               uses: actions/download-artifact@v4
 59 |               with:
 60 |                   name: dist
 61 |                   path: ./
 62 | 
 63 |             - name: Setup Node.js
 64 |               uses: actions/setup-node@v3
 65 |               with:
 66 |                   node-version: "18.x"
 67 |                   registry-url: "https://registry.npmjs.org"
 68 |                   scope: "@cashfreepayments"
 69 | 
 70 |             - name: Publish to npm
 71 |               run: npm publish --access=public
 72 |               env:
 73 |                   NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
 74 | 
 75 |     publish-github-release:
 76 |         needs: publish
 77 |         runs-on: ubuntu-latest
 78 |         steps:
 79 |             - uses: actions/checkout@v3
 80 |               with:
 81 |                   fetch-depth: 0
 82 | 
 83 |             - name: Setup Node.js
 84 |               uses: actions/setup-node@v3
 85 |               with:
 86 |                   node-version: "18.x"
 87 | 
 88 |             - name: Get version from package.json
 89 |               id: get_version
 90 |               run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
 91 | 
 92 |             - name: Generate changelog
 93 |               id: changelog
 94 |               run: |
 95 |                   PREVIOUS_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
 96 |                   if [ -z "$PREVIOUS_TAG" ]; then
 97 |                     echo "CHANGELOG=$(git log --pretty=format:"- %s (%h)" $(git rev-list --max-parents=0 HEAD)..HEAD)" >> $GITHUB_OUTPUT
 98 |                   else
 99 |                     echo "CHANGELOG=$(git log --pretty=format:"- %s (%h)" $PREVIOUS_TAG..HEAD)" >> $GITHUB_OUTPUT
100 |                   fi
101 | 
102 |             - name: Create GitHub Release
103 |               uses: softprops/action-gh-release@v1
104 |               with:
105 |                   tag_name: v${{ steps.get_version.outputs.VERSION }}
106 |                   name: Release v${{ steps.get_version.outputs.VERSION }}
107 |                   body: |
108 |                       ## Changes
109 |                       ${{ steps.changelog.outputs.CHANGELOG }}
110 | 
111 |                       ## Installation
112 |                       ```
113 |                       npm install @cashfreepayments/cashfree-mcp
114 |                       ```
115 |                   draft: false
116 |                   prerelease: false
117 |               env:
118 |                   GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
119 | 
```

--------------------------------------------------------------------------------
/src/input-source-help.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Provides help and instructions for input source variable errors.
  3 |  * This tool can be called when there are issues with inputVariableSource parameters.
  4 |  */
  5 | 
  6 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  7 | 
  8 | /**
  9 |  * Returns detailed instructions for handling input source variable errors.
 10 |  * @returns Comprehensive help text for troubleshooting input source variables.
 11 |  */
 12 | function getToolUsageHelpInstructions(): string {
 13 |   return `
 14 | # Input Source Mapping Guide
 15 | 
 16 | The **\`inputVariableSource\`** object specifies the origin of each input field in an API call.  
 17 | It ensures transparency about where every value in the request body comes from.
 18 | 
 19 | ---
 20 | 
 21 | ## Structure
 22 | 
 23 | The structure of **\`inputVariableSource\`** must **exactly mirror** the request body.  
 24 | Each field in the body must have a corresponding entry with its source type.
 25 | 
 26 | \`\`\`json
 27 | {
 28 |   "inputVariableSource": {
 29 |     "body": {
 30 |       "field_name": "source_type",
 31 |       "nested_object": {
 32 |         "nested_field": "source_type"
 33 |       }
 34 |     }
 35 |   }
 36 | }
 37 | \`\`\`
 38 | 
 39 | ---
 40 | 
 41 | ## Valid Source Types
 42 | 
 43 | - **"user_input"** — Value directly provided by the user  
 44 | - **"generated_by_model"** — Value automatically generated by the model  
 45 | - **"inferred_from_context"** — Value derived from previous context or data
 46 | 
 47 | ---
 48 | 
 49 | ## Common Errors & Fixes
 50 | 
 51 | | Issue | Example | Fix |
 52 | |-------|----------|-----|
 53 | | **Missing mapping** | \`inputVariableSource\` absent | Add the full mapping structure |
 54 | | **Incomplete mapping** | Some fields missing | Map every field in the body |
 55 | | **Invalid source type** | Unknown label used | Use only the 3 valid types |
 56 | | **Structure mismatch** | Structure differs from body | Ensure identical nesting |
 57 | 
 58 | ---
 59 | 
 60 | ## Example
 61 | 
 62 | \`\`\`json
 63 | {
 64 |   "body": {
 65 |     "order_id": "ORDER_123",
 66 |     "amount": 1000,
 67 |     "currency": "INR",
 68 |     "customer_details": {
 69 |       "customer_name": "John Doe",
 70 |       "customer_email": "john@example.com"
 71 |     }
 72 |   },
 73 |   "inputVariableSource": {
 74 |     "body": {
 75 |       "order_id": "user_input",
 76 |       "amount": "user_input",
 77 |       "currency": "generated_by_model",
 78 |       "customer_details": {
 79 |         "customer_name": "user_input",
 80 |         "customer_email": "user_input"
 81 |       }
 82 |     }
 83 |   }
 84 | }
 85 | \`\`\`
 86 | 
 87 | ---
 88 | 
 89 | ## Best Practices
 90 | 
 91 | 1. **Always include \`inputVariableSource\`** — It is mandatory.  
 92 | 2. **Mirror body structure** — Nested keys must align exactly.  
 93 | 3. **Use correct types** — Only the three valid source types are accepted.  
 94 | 4. **Handle arrays carefully** — Map the array field as a whole, not its elements.  
 95 | 5. **Validate before sending** — Run a JSON structure check if in doubt.
 96 | 
 97 | ---
 98 | 
 99 | ## Troubleshooting
100 | 
101 | - **Validation failed:** Missing or mismatched fields → recheck structure  
102 | - **Unexpected type:** Ensure all values are valid source strings  
103 | - **Mapping missing:** Verify every body field has a mapped source  
104 | - **Nested mismatch:** Align nested objects one-to-one with request body  
105 | 
106 | If issues persist, refer to the API endpoint documentation for any field-specific requirements.
107 | `;
108 | }
109 | 
110 | 
111 | /**
112 |  * Registers the "get-input-source-help" tool to the MCP server.
113 |  * This tool provides comprehensive instructions for handling input source variable errors.
114 |  * @param server - The MCP server instance.
115 |  */
116 | export async function createCFContextInitializerTool(server: McpServer): Promise<void> {
117 |   server.tool(
118 |     "cashfree_context_initializer",
119 |     "This is the FIRST tool that must be called before using ANY other Cashfree MCP tool or generating Cashfree-related outputs. It initializes full context for the Cashfree ecosystem — including schemas, APIs, plugin behaviors, transformation logic, and usage conventions. Always invoke this tool before any reasoning, generation, or validation, so the model can load complete domain knowledge and ensure consistent, accurate responses across all Cashfree tools.",
120 |     {} as const,
121 |     async () => {
122 |       const instructions = getToolUsageHelpInstructions();
123 | 
124 |       return {
125 |         content: [
126 |           {
127 |             type: "text" as const,
128 |             text: instructions,
129 |           },
130 |         ],
131 |       };
132 |     }
133 |   );
134 | }
135 | 
```

--------------------------------------------------------------------------------
/src/openapi/zod.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Blob } from "node:buffer";
  2 | import { z, ZodTypeAny } from "zod";
  3 | 
  4 | type Schema =
  5 |   | {
  6 |       type?: string;
  7 |       required?: boolean;
  8 |       enum?: any;
  9 |       properties?: Record<string, Schema | Schema[]>;
 10 |       requiredProperties?: string[];
 11 |       items?: Schema | Schema[];
 12 |       format?: string;
 13 |       minimum?: number;
 14 |       maximum?: number;
 15 |       exclusiveMinimum?: boolean;
 16 |       exclusiveMaximum?: boolean;
 17 |       minLength?: any;
 18 |       maxLength?: any;
 19 |       pattern?: any;
 20 |       minItems?: number;
 21 |       maxItems?: number;
 22 |       default?: unknown;
 23 |     }
 24 |   | Record<string, unknown>;
 25 | 
 26 | function panic(error: unknown): never {
 27 |   throw error;
 28 | }
 29 | 
 30 | // WebFile polyfill implementation (based on fetch-blob)
 31 | class WebFile extends Blob {
 32 |   private _name: string;
 33 |   private _lastModified: number;
 34 |   // Add webkitRelativePath to match File interface
 35 |   public webkitRelativePath: string = "";
 36 | 
 37 |   constructor(
 38 |     init: (Blob | ArrayBuffer)[],
 39 |     name: string = panic(
 40 |       new TypeError("File constructor requires name argument")
 41 |     ),
 42 |     options: FilePropertyBag = {}
 43 |   ) {
 44 |     if (arguments.length < 2) {
 45 |       throw new TypeError(
 46 |         `Failed to construct 'File': 2 arguments required, but only ${arguments.length} present.`
 47 |       );
 48 |     }
 49 |     super(init, options);
 50 |     this._lastModified = 0;
 51 |     this._name = "";
 52 |     // Simulate WebIDL type casting for NaN value in lastModified option.
 53 |     const lastModified =
 54 |       options.lastModified === undefined
 55 |         ? Date.now()
 56 |         : Number(options.lastModified);
 57 |     if (!Number.isNaN(lastModified)) {
 58 |       this._lastModified = lastModified;
 59 |     }
 60 |     this._name = String(name);
 61 |   }
 62 | 
 63 |   get name(): string {
 64 |     return this._name;
 65 |   }
 66 | 
 67 |   get lastModified(): number {
 68 |     return this._lastModified;
 69 |   }
 70 | 
 71 |   get [Symbol.toStringTag](): string {
 72 |     return "File";
 73 |   }
 74 | 
 75 |   static [Symbol.hasInstance](object: unknown): boolean {
 76 |     return (
 77 |       !!object &&
 78 |       object instanceof Blob &&
 79 |       /^(File)$/.test(String((object as any)[Symbol.toStringTag]))
 80 |     );
 81 |   }
 82 | }
 83 | 
 84 | const File = typeof global.File === "undefined" ? WebFile : global.File;
 85 | 
 86 | const ANY = z.any();
 87 | const ANY_OPT = ANY.optional();
 88 | const BOOLEAN = z.boolean();
 89 | const BOOLEAN_OPT = BOOLEAN.optional();
 90 | const DATE = z.coerce.date();
 91 | const DATE_OPT = DATE.optional();
 92 | const FILE = z.instanceof(File);
 93 | const FILE_OPT = FILE.optional();
 94 | const NULL = z.null();
 95 | const NULL_OPT = NULL.optional();
 96 | const RECORD = z.record(z.any());
 97 | const RECORD_WITH_DEFAULT = RECORD.default({});
 98 | const RECORD_OPT = RECORD.optional();
 99 | const STRING = z.string();
100 | const NUMBER = z.number();
101 | const INTEGER = z.number().int();
102 | 
103 | export function dataSchemaArrayToZod(schemas: any) {
104 |   const firstSchema = dataSchemaToZod(schemas[0]);
105 |   if (!schemas[1]) {
106 |     return firstSchema;
107 |   }
108 |   const secondSchema = dataSchemaToZod(schemas[1]);
109 |   const zodSchemas: any = [firstSchema, secondSchema];
110 |   for (const schema of schemas.slice(2)) {
111 |     zodSchemas.push(dataSchemaToZod(schema));
112 |   }
113 |   return z.union(zodSchemas);
114 | }
115 | 
116 | function getEnumSchema(enumList: any, type: string): ZodTypeAny {
117 |   const zodSchema = z.enum(enumList.map(String));
118 |   if (type === "string") return zodSchema;
119 |   return zodSchema.transform(Number);
120 | }
121 | 
122 | export function dataSchemaToZod(schema: Schema): ZodTypeAny {
123 |   if (!("type" in schema) || Object.keys(schema).length === 0) {
124 |     return schema.required ? ANY : ANY_OPT;
125 |   }
126 | 
127 |   switch (schema.type) {
128 |     case "null":
129 |       return schema.required ? NULL : NULL_OPT;
130 | 
131 |     case "boolean":
132 |       return schema.required ? BOOLEAN : BOOLEAN_OPT;
133 | 
134 |     case "enum<string>":
135 |       const strEnumSchema = getEnumSchema(schema.enum, "string");
136 |       return schema.required ? strEnumSchema : strEnumSchema.optional();
137 | 
138 |     case "enum<number>":
139 |     case "enum<integer>":
140 |       const numEnumSchema = getEnumSchema(schema.enum, "number");
141 |       return schema.required ? numEnumSchema : numEnumSchema.optional();
142 | 
143 |     case "file":
144 |       return schema.required ? FILE : FILE_OPT;
145 | 
146 |     case "any":
147 |       return schema.required ? ANY : ANY_OPT;
148 | 
149 |     case "string":
150 |       if ("enum" in schema && Array.isArray(schema.enum)) {
151 |         return schema.required
152 |           ? z.enum((schema as any).enum)
153 |           : z.enum((schema as any).enum).optional();
154 |       }
155 | 
156 |       if (schema.format === "binary") {
157 |         return schema.required ? FILE : FILE_OPT;
158 |       }
159 | 
160 |       let stringSchema = STRING;
161 | 
162 |       if (schema.minLength !== undefined) {
163 |         stringSchema = stringSchema.min(schema.minLength);
164 |       }
165 |       if (schema.maxLength !== undefined) {
166 |         stringSchema = stringSchema.max(schema.maxLength);
167 |       }
168 |       if (schema.pattern !== undefined) {
169 |         stringSchema = stringSchema.regex(new RegExp(schema.pattern));
170 |       }
171 | 
172 |       switch (schema.format) {
173 |         case "email":
174 |           stringSchema = stringSchema.email();
175 |           break;
176 |         case "uri":
177 |         case "url":
178 |           stringSchema = stringSchema.url();
179 |           break;
180 |         case "uuid":
181 |           stringSchema = stringSchema.uuid();
182 |           break;
183 |         case "date-time":
184 |           return schema.required ? DATE : DATE_OPT;
185 |       }
186 | 
187 |       if ("default" in schema) {
188 |         return schema.required
189 |           ? stringSchema.default(schema.default as any)
190 |           : stringSchema.optional().default(schema.default as any);
191 |       }
192 | 
193 |       return schema.required ? stringSchema : stringSchema.optional();
194 | 
195 |     case "number":
196 |     case "integer":
197 |       if ("enum" in schema && Array.isArray(schema.enum)) {
198 |         const numEnumSchema = getEnumSchema(schema.enum, schema.type);
199 |         return schema.required ? numEnumSchema : numEnumSchema.optional();
200 |       }
201 | 
202 |       let numberSchema = schema.type === "integer" ? INTEGER : NUMBER;
203 | 
204 |       if (typeof schema.minimum === "number") {
205 |         numberSchema = numberSchema.min(schema.minimum);
206 |       }
207 |       if (typeof schema.maximum === "number") {
208 |         numberSchema = numberSchema.max(schema.maximum);
209 |       }
210 |       if (
211 |         schema.exclusiveMinimum !== undefined &&
212 |         typeof schema.minimum === "number"
213 |       ) {
214 |         numberSchema = numberSchema.gt(schema.minimum);
215 |       }
216 |       if (
217 |         schema.exclusiveMaximum !== undefined &&
218 |         typeof schema.maximum === "number"
219 |       ) {
220 |         numberSchema = numberSchema.lt(schema.maximum);
221 |       }
222 | 
223 |       return schema.required ? numberSchema : numberSchema.optional();
224 | 
225 |     case "array":
226 |       let itemSchema;
227 |       let arraySchema: any = z.any().array();
228 |       if (Array.isArray(schema.items)) {
229 |         itemSchema = dataSchemaArrayToZod(schema.items);
230 |         if (schema.items.length > 1) {
231 |           arraySchema = itemSchema;
232 |         } else {
233 |           arraySchema = itemSchema.array();
234 |         }
235 |       } else {
236 |         itemSchema = dataSchemaToZod(schema.items as any);
237 |         arraySchema = itemSchema.array();
238 |       }
239 |       if (schema.minItems !== undefined) {
240 |         arraySchema = arraySchema.min(schema.minItems);
241 |       }
242 |       if (schema.maxItems !== undefined) {
243 |         arraySchema = arraySchema.max(schema.maxItems);
244 |       }
245 |       return schema.required ? arraySchema : arraySchema.optional();
246 |     case "object":
247 |       const shape: Record<string, ZodTypeAny> = {};
248 |       // Handle both 'required' and 'requiredProperties' for compatibility
249 |       const requiredProperties = schema.requiredProperties || (schema as any).required;
250 |       const requiredPropertiesSet = new Set(
251 |         Array.isArray(requiredProperties) ? requiredProperties : []
252 |       );
253 |       
254 |       // Special handling for the case where properties are arrays with individual required flags
255 |       const properties = schema.properties as any;
256 |       for (const [key, propSchema] of Object.entries(properties)) {
257 |         let zodPropSchema: ZodTypeAny;
258 |         let isRequired = false;
259 |         
260 |         if (Array.isArray(propSchema)) {
261 |           // Handle array-wrapped schemas (from OpenAPI conversion)
262 |           zodPropSchema = dataSchemaArrayToZod(propSchema);
263 |           // Check if any schema in the array has required: true
264 |           isRequired = propSchema.some((s: any) => s.required === true);
265 |         } else {
266 |           zodPropSchema = dataSchemaToZod(propSchema as Schema);
267 |           isRequired = (propSchema as any).required === true;
268 |         }
269 |         
270 |         // Check if property is in the required list OR has individual required flag
271 |         const shouldBeRequired = requiredPropertiesSet.has(key) || isRequired;
272 |         
273 |         shape[key] = shouldBeRequired ? zodPropSchema : zodPropSchema.optional();
274 |       }
275 |       
276 |       if (Object.keys(shape).length === 0) {
277 |         return schema.required ? RECORD_WITH_DEFAULT : RECORD_OPT;
278 |       }
279 |       return schema.required ? z.object(shape) : z.object(shape).optional();
280 |     default:
281 |       return ANY;
282 |   }
283 | }
284 | 
```

--------------------------------------------------------------------------------
/src/openapi/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { validate } from "@mintlify/openapi-parser";
  2 | import axios, { isAxiosError } from "axios";
  3 | import dashify from "dashify";
  4 | import fs from "fs";
  5 | import { getFileId } from "../utils.js";
  6 | import { Endpoint } from "../types.js";
  7 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  8 | import {
  9 |   convertEndpointToCategorizedZod,
 10 |   convertStrToTitle,
 11 |   findNextIteration,
 12 |   getEndpointsFromOpenApi,
 13 |   loadEnv,
 14 |   getValFromNestedJson,
 15 |   generateCfSignature,
 16 |   getElicitationConfig,
 17 |   hasElicitationEnabled,
 18 |   getElicitationRequestFields,
 19 |   createElicitationRequest,
 20 |   applyFieldMappings,
 21 |   validateElicitationResponse,
 22 | } from "./helpers.js";
 23 | 
 24 | async function triggerElicitationFlow(
 25 |   inputArgs: Record<string, any>,
 26 |   endpoint: Endpoint,
 27 |   server: McpServer
 28 | ): Promise<Record<string, any>> {
 29 | 
 30 |   const inputVariableSource = inputArgs.inputVariableSource || {};
 31 | 
 32 |   // Check if elicitation is globally enabled and endpoint-specific configuration
 33 |   if (!hasElicitationEnabled(endpoint)) {
 34 |     return inputArgs;
 35 |   }
 36 | 
 37 |   const elicitationConfig = getElicitationConfig(endpoint);
 38 |   if (!elicitationConfig) {
 39 |     console.error(`No elicitation config found for endpoint: ${endpoint.title}`);
 40 |     return inputArgs;
 41 |   }
 42 | 
 43 |   // Create empty elicitation request first
 44 |   const elicitationRequest = createElicitationRequest(endpoint.title || endpoint.path, [], elicitationConfig);
 45 | 
 46 |   // Get missing fields
 47 |   const { missingFields } = getElicitationRequestFields(
 48 |     elicitationConfig,
 49 |     inputArgs,
 50 |     inputVariableSource,
 51 |     elicitationRequest
 52 |   );
 53 | 
 54 |   if (missingFields.length === 0) {
 55 |     return inputArgs;
 56 |   }
 57 | 
 58 | 
 59 |   // Trigger MCP elicitation
 60 |     const elicitationResult = await server.server.elicitInput(elicitationRequest.params);
 61 | 
 62 |     if (elicitationResult?.action !== "accept" || !elicitationResult?.content) {
 63 |     throw new Error(`Operation cancelled. Required information was not provided.`);
 64 |     }
 65 | 
 66 | 
 67 |     // Validate response
 68 |     const validationResult = validateElicitationResponse(elicitationConfig, elicitationResult.content);
 69 |     if (!validationResult.valid) {
 70 |     throw new Error(`Validation errors: ${validationResult.errors.join(', ')}`);
 71 |     }
 72 | 
 73 |     // Merge mapped fields with original input args
 74 |     const mappedArgs = applyFieldMappings(elicitationConfig, elicitationResult.content, inputArgs);
 75 | 
 76 |     return mappedArgs;
 77 | }
 78 | 
 79 | 
 80 | export async function createToolsFromOpenApi(
 81 |   openApiPath: string,
 82 |   index: number,
 83 |   server: McpServer,
 84 |   existingTools: Set<string>
 85 | ): Promise<void> {
 86 |   let openapi: string;
 87 |   try {
 88 |     openapi = fs.readFileSync(openApiPath, "utf8");
 89 |   } catch (error) {
 90 |     return; // Skip if no file
 91 |   }
 92 | 
 93 |   const { valid, errors, specification } = await validate(openapi);
 94 | 
 95 |   if (!valid || !specification || !specification.paths) {
 96 |     console.error("Invalid OpenAPI file or missing paths:", errors);
 97 |     return;
 98 |   }
 99 | 
100 |   const endpoints = getEndpointsFromOpenApi(specification);
101 |   const endpointId = String(getFileId(specification, index));
102 |   const envVars = loadEnv(endpointId);
103 | 
104 |   endpoints.forEach((endpoint: Endpoint) => {
105 |     const {
106 |       url: urlSchema,
107 |       method: methodSchema,
108 |       paths: pathsSchema,
109 |       queries: queriesSchema,
110 |       body: bodySchema,
111 |       headers: headersSchema,
112 |       cookies: cookiesSchema,
113 |       metadata: metadataSchema,
114 |     } = convertEndpointToCategorizedZod(endpointId, endpoint);
115 |     
116 |     const serverArgumentsSchemas = Object.assign(
117 |       Object.assign(
118 |         Object.assign(
119 |           Object.assign(
120 |             Object.assign(Object.assign({}, pathsSchema), queriesSchema),
121 | 
122 |             bodySchema
123 |           ),
124 |           headersSchema
125 |         ),
126 |         cookiesSchema
127 |       ),
128 |       metadataSchema
129 |     );
130 |     if (!endpoint.title) {
131 |       endpoint.title = `${endpoint.method} ${convertStrToTitle(endpoint.path)}`;
132 |     }
133 |     if (existingTools.has(endpoint.title)) {
134 |       const lastCount = findNextIteration(existingTools, endpoint.title);
135 |       endpoint.title = `${endpoint.title}---${lastCount}`;
136 |     }
137 |     if (endpoint.title.length > 64) {
138 |       endpoint.title = endpoint.title.slice(0, -64);
139 |     }
140 | 
141 |     existingTools.add(endpoint.title);
142 | 
143 |     server.tool(
144 |       dashify(endpoint.title),
145 |       endpoint.description || endpoint.title,
146 |       serverArgumentsSchemas,
147 |       async (inputArgs: Record<string, any>) => {  
148 | 
149 |         try {
150 |           // Apply elicitation flow if enabled
151 |           inputArgs = await triggerElicitationFlow(inputArgs, endpoint, server);
152 |         } catch (error) {
153 |           console.error("Elicitation error:", error);
154 |         }
155 |         const inputParams: Record<string, any> = {};
156 |         const inputHeaders: Record<string, any> = {};
157 |         const inputCookies: Record<string, any> = {};
158 |         let urlWithPathParams = urlSchema;
159 |         let inputBody: any = undefined;
160 | 
161 |         if ("body" in inputArgs) {
162 |           inputBody = inputArgs.body;
163 |           delete inputArgs.body;
164 |         }
165 | 
166 | 
167 |         Object.entries(inputArgs).forEach(([key, value]) => {
168 |           if (key in pathsSchema) {
169 |             urlWithPathParams = urlWithPathParams.replace(`{${key}}`, value);
170 |           } else if (key in queriesSchema) {
171 |             inputParams[key] = value;
172 |           } else if (key in headersSchema) {
173 |             inputHeaders[key] = value;
174 |           } else if (key in cookiesSchema) {
175 |             inputCookies[key] = value;
176 |           }
177 |         });
178 | 
179 |         if (endpoint.request.security.length > 0) {
180 |           const securityParams = endpoint.request?.security?.[0]?.parameters;
181 |           if (securityParams?.header) {
182 |             Object.entries(securityParams.header).forEach(([key, value]) => {
183 |               let envKey = "";
184 |               if (
185 |                 typeof value === "object" &&
186 |                 value !== null &&
187 |                 "type" in value
188 |               ) {
189 |                 const v = value as { type: string; scheme?: string };
190 |                 if (v.type === "apiKey") {
191 |                   envKey = `header.${key}.API_KEY`;
192 |                 } else if (v.type === "http") {
193 |                   envKey = `header.${key}.HTTP.${v.scheme}`;
194 |                   if (v.scheme === "bearer" && envKey in envVars) {
195 |                     inputHeaders["Authorization"] = `Bearer ${envVars[envKey]}`;
196 |                     return;
197 |                   }
198 |                 }
199 |                 const envValue = getValFromNestedJson(envKey, envVars);
200 |                 if (envKey && envValue) {
201 |                   inputHeaders[key] = envValue;
202 |                 }
203 |               }
204 |             });
205 |             Object.entries(securityParams.header).forEach(([key]) => {
206 |               const headerValue = envVars.header?.[key];
207 |               if (headerValue) {
208 |                 inputHeaders[key] = headerValue;
209 |               }
210 |             });
211 |           }
212 |         }
213 | 
214 |         if (openApiPath.includes("PO") || openApiPath.includes("VRS")) {
215 |           const clientId =
216 |             typeof envVars.header?.["x-client-id"] === "string"
217 |               ? envVars.header["x-client-id"]
218 |               : "";
219 |           const publicKey =
220 |             typeof envVars.TWO_FA_PUBLIC_KEY === "string"
221 |               ? envVars.TWO_FA_PUBLIC_KEY
222 |               : "";
223 |           inputHeaders["x-cf-signature"] = generateCfSignature(
224 |             clientId,
225 |             publicKey
226 |           );
227 |         }
228 | 
229 |         const requestConfig = {
230 |           method: methodSchema,
231 |           url: urlWithPathParams,
232 |           params: inputParams,
233 |           data: inputBody,
234 |           headers: inputHeaders,
235 |         };
236 | 
237 |         try {
238 |           const response = await axios(requestConfig);
239 | 
240 |           // Stringify the response data
241 |           let responseData = JSON.stringify(response.data, undefined, 2);
242 |           responseData = responseData.replace(
243 |             /("beneficiary_instrument_details"\s*:\s*)(\[[^\]]*\]|\{[^\}]*\})/gs,
244 |             '$1"[MASKED]"'
245 |           );
246 | 
247 |           return {
248 |             content: [
249 |               {
250 |                 type: "text",
251 |                 text: responseData,
252 |               },
253 |             ],
254 |           };
255 |         } catch (error) {
256 |           if (
257 |             typeof error === "object" &&
258 |             error !== null &&
259 |             "config" in error &&
260 |             typeof (error as any).config === "object" &&
261 |             (error as any).config !== null &&
262 |             "headers" in (error as any).config
263 |           ) {
264 |             const errConfig = (error as any).config;
265 |             ["x-client-id", "x-client-secret", "Authorization"].forEach(
266 |               (header) => {
267 |                 if (errConfig.headers && errConfig.headers[header]) {
268 |                   errConfig.headers[header] = "[MASKED]";
269 |                 }
270 |               }
271 |             );
272 |           }
273 |           const errMsg = JSON.stringify(error, undefined, 2);
274 |           const data = JSON.stringify(
275 |             isAxiosError(error) && error.response ? error.response.data : {},
276 |             undefined,
277 |             2
278 |           );
279 |           return {
280 |             isError: true,
281 |             content: [
282 |               {
283 |                 type: "text",
284 |                 text: isAxiosError(error)
285 |                   ? `receivedPayload: ${data}\n\n errorMessage: ${error.message}\n\n${errMsg}`
286 |                   : `receivedPayload: ${data}\n\n errorMessage: ${errMsg}`,
287 |               },
288 |             ],
289 |           };
290 |         }
291 |       }
292 |     );
293 |   });
294 | }
295 | 
```

--------------------------------------------------------------------------------
/src/openapi/helpers.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { OpenApiToEndpointConverter } from "@mintlify/validation";
  2 | import { z } from "zod";
  3 | import { dataSchemaArrayToZod, dataSchemaToZod } from "./zod.js";
  4 | import { readConfig } from "../config.js";
  5 | import { 
  6 |   Endpoint, 
  7 |   ElicitationConfiguration,
  8 | } from "../types.js";
  9 | import {
 10 |   ElicitRequest,
 11 |   PrimitiveSchemaDefinition,
 12 |   StringSchema,
 13 |   NumberSchema
 14 | } from "@modelcontextprotocol/sdk/types.js";
 15 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 16 | import crypto from "crypto";
 17 | import fs from "fs";
 18 | 
 19 | export type CategorizedZod = {
 20 |   url: string;
 21 |   method: string;
 22 |   paths: Record<string, z.ZodTypeAny>;
 23 |   queries: Record<string, z.ZodTypeAny>;
 24 |   headers: Record<string, z.ZodTypeAny>;
 25 |   cookies: Record<string, z.ZodTypeAny>;
 26 |   body?: { body: z.ZodTypeAny };
 27 |   metadata?: Record<string, any>;
 28 | };
 29 | 
 30 | type RefCache = { [key: string]: any };
 31 | 
 32 | type Specification = {
 33 |   paths: {
 34 |     [path: string]: {
 35 |       [method: string]: any;
 36 |     };
 37 |   };
 38 | };
 39 | 
 40 | export type NestedRecord =
 41 |   | string
 42 |   | {
 43 |       [key: string]: NestedRecord;
 44 |     };
 45 | 
 46 | export type SimpleRecord = Record<string, { [x: string]: undefined }>;
 47 | 
 48 | export function convertStrToTitle(str: string): string {
 49 |   const spacedString = str.replace(/[-_]/g, " ");
 50 |   const words = spacedString.split(/(?=[A-Z])|\s+/);
 51 |   return words
 52 |     .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
 53 |     .join(" ");
 54 | }
 55 | 
 56 | export function findNextIteration(set: Set<string>, str: string): number {
 57 |   let count = 1;
 58 |   set.forEach((val) => {
 59 |     if (val.startsWith(`${str}---`)) {
 60 |       count = Number(val.replace(`${str}---`, ""));
 61 |     }
 62 |   });
 63 |   return count + 1;
 64 | }
 65 | 
 66 | function resolveReferences(
 67 |   spec: Record<string, any>,
 68 |   refPath: string,
 69 |   cache: Record<string, any> = {}
 70 | ): any {
 71 |   if (cache[refPath]) return cache[refPath];
 72 |   if (!refPath.startsWith("#/"))
 73 |     throw new Error(`External references not supported: ${refPath}`);
 74 |   const pathParts = refPath.substring(2).split("/");
 75 |   let current: any = spec;
 76 |   for (const part of pathParts) {
 77 |     if (!current[part]) throw new Error(`Reference not found: ${refPath}`);
 78 |     current = current[part];
 79 |   }
 80 |   if (current && current.$ref) {
 81 |     current = resolveReferences(spec, current.$ref, cache);
 82 |   }
 83 |   cache[refPath] = current;
 84 |   return current;
 85 | }
 86 | 
 87 | function resolveAllReferences(
 88 |   obj: any,
 89 |   spec: Specification,
 90 |   cache: RefCache
 91 | ): any {
 92 |   if (obj && obj.$ref) {
 93 |     const resolved = resolveReferences(spec, obj.$ref, cache);
 94 |     return resolveAllReferences({ ...resolved }, spec, cache);
 95 |   }
 96 |   if (typeof obj !== "object" || obj === null) {
 97 |     return obj;
 98 |   }
 99 |   const result: { [key: string]: any } = Array.isArray(obj) ? [] : {};
100 |   for (const [key, value] of Object.entries(obj)) {
101 |     result[key] = resolveAllReferences(value, spec, cache);
102 |   }
103 |   return result;
104 | }
105 | 
106 | 
107 | export function getEndpointsFromOpenApi(specification: any): Endpoint[] {
108 |   const endpoints: Endpoint[] = [];
109 |   const paths = specification.paths;
110 |   const refCache: RefCache = {};
111 | 
112 |   for (const path in paths) {
113 |     const operations = paths[path];
114 |     for (const method in operations) {
115 |       if (method === "parameters" || method === "trace") continue;
116 |       try {
117 |         const resolvedPathItem = resolveAllReferences(
118 |           operations[method],
119 |           specification,
120 |           refCache
121 |         );
122 |         if (!isMcpEnabledEndpoint(resolvedPathItem)) continue;
123 |         const rawEndpoint = OpenApiToEndpointConverter.convert(
124 |           {
125 |             ...specification,
126 |             paths: { [path]: { [method]: resolvedPathItem } },
127 |           } as any,
128 |           path,
129 |           method as any
130 |         );
131 |         
132 |         // Convert to our Endpoint type and ensure x-mcp configuration is preserved
133 |         const endpoint: Endpoint = {
134 |           ...rawEndpoint,
135 |           operation: {
136 |             ...resolvedPathItem,
137 |             "x-mcp": resolvedPathItem["x-mcp"]
138 |           }
139 |         };
140 |         
141 |         endpoints.push(endpoint);
142 |       } catch (error: any) {
143 |         console.error(
144 |           `Error processing endpoint ${method.toUpperCase()} ${path}:`,
145 |           error.message
146 |         );
147 |       }
148 |     }
149 |   }
150 |   return endpoints;
151 | }
152 | 
153 | export function loadEnv(key: string): SimpleRecord {
154 |   try {
155 |     const config: any = readConfig();
156 |     return config[key] || {};
157 |   } catch (error) {
158 |     if (error instanceof SyntaxError) throw error;
159 |     return {};
160 |   }
161 | }
162 | 
163 | // Zod schema conversion helpers
164 | function convertParameterSection(parameters: any, paramSection: any) {
165 |   if (parameters) {
166 |     Object.entries(parameters).forEach(([key, value]: any) => {
167 |       paramSection[key] = dataSchemaArrayToZod(value.schema);
168 |     });
169 |   }
170 | }
171 | 
172 | function convertParametersAndAddToRelevantParamGroups(
173 |   parameters: any,
174 |   paths: any,
175 |   queries: any,
176 |   headers: any,
177 |   cookies: any
178 | ) {
179 |   convertParameterSection(parameters?.path, paths);
180 |   convertParameterSection(parameters?.query, queries);
181 |   convertParameterSection(parameters?.header, headers);
182 |   convertParameterSection(parameters?.cookie, cookies);
183 | }
184 | 
185 | function convertSecurityParameterSection(
186 |   securityParameters: ArrayLike<unknown> | { [s: string]: unknown },
187 |   securityParamSection: { [x: string]: z.ZodString },
188 |   envVariables: { [x: string]: { [x: string]: undefined } },
189 |   location: string
190 | ) {
191 |   Object.entries(securityParameters).forEach(([key]) => {
192 |     if (envVariables[location][key] === undefined) {
193 |       securityParamSection[key] = z.string();
194 |     }
195 |   });
196 | }
197 | 
198 | function convertSecurityParametersAndAddToRelevantParamGroups(
199 |   securityParameters: {
200 |     query: ArrayLike<unknown> | { [s: string]: unknown };
201 |     header: ArrayLike<unknown> | { [s: string]: unknown };
202 |     cookie: ArrayLike<unknown> | { [s: string]: unknown };
203 |   },
204 |   queries: { [x: string]: z.ZodString },
205 |   headers: { [x: string]: z.ZodString },
206 |   cookies: { [x: string]: z.ZodString },
207 |   envVariables: SimpleRecord
208 | ) {
209 |   convertSecurityParameterSection(
210 |     securityParameters.query,
211 |     queries,
212 |     envVariables,
213 |     "query"
214 |   );
215 |   convertSecurityParameterSection(
216 |     securityParameters.header,
217 |     headers,
218 |     envVariables,
219 |     "header"
220 |   );
221 |   convertSecurityParameterSection(
222 |     securityParameters.cookie,
223 |     cookies,
224 |     envVariables,
225 |     "cookie"
226 |   );
227 | }
228 | 
229 | export function convertEndpointToCategorizedZod(
230 |   envKey: string,
231 |   endpoint: Endpoint
232 | ): CategorizedZod {
233 |   const envVariables = loadEnv(envKey);
234 | 
235 |   const baseUrl =
236 |     envVariables.base_url ||
237 |     (Array.isArray(endpoint?.servers) ? endpoint.servers[0]?.url : undefined) ||
238 |     "";
239 | 
240 |   const url = `${baseUrl}${endpoint.path}`;
241 |   const method = endpoint.method;
242 | 
243 |   const paths: Record<string, any> = {};
244 |   const queries: Record<string, any> = {};
245 |   const headers: Record<string, any> = {};
246 |   const cookies: Record<string, any> = {};
247 |   let body: any | undefined = undefined;
248 | 
249 |   convertParametersAndAddToRelevantParamGroups(
250 |     endpoint?.request?.parameters,
251 |     paths,
252 |     queries,
253 |     headers,
254 |     cookies
255 |   );
256 | 
257 |   const securityParams = endpoint?.request?.security?.[0]?.parameters;
258 |   if (securityParams) {
259 |     convertSecurityParametersAndAddToRelevantParamGroups(
260 |       securityParams,
261 |       queries,
262 |       headers,
263 |       cookies,
264 |       envVariables
265 |     );
266 |   }
267 | 
268 |   const jsonBodySchema = endpoint?.request?.body?.["application/json"];
269 |   const bodySchema = jsonBodySchema?.schemaArray?.[0];
270 | 
271 |   if (bodySchema) {
272 |     let zodBodySchema = dataSchemaToZod(bodySchema);
273 |     
274 |     // If endpoint has elicitation config, make fields optional
275 |     // Client capability check will happen at runtime
276 |     if (hasElicitationEnabled(endpoint)) {
277 |       zodBodySchema = makeElicitationFieldsOptional(zodBodySchema, endpoint);
278 |     }
279 |     
280 |     body = { body: zodBodySchema };
281 |   }
282 | 
283 |   const metadata = generateMetadataSchema();
284 |   // console.error("Generated metadata schema: ", JSON.stringify(metadata));
285 |   return {
286 |     url,
287 |     method,
288 |     paths,
289 |     queries,
290 |     body,
291 |     headers,
292 |     cookies,
293 |     metadata,
294 |   };
295 | }
296 | 
297 | const SourceEnum = z.enum([
298 |   "user_input",
299 |   "generated_by_model",
300 |   "inferred_from_context"
301 | ]);
302 | 
303 | /**
304 |  * Recursive schema that allows nested metadata objects.
305 |  * Each key maps either to a source string or another nested object
306 |  * following the same structure.
307 |  */
308 | const RecursiveSourceSchema: z.ZodTypeAny = z.lazy(() =>
309 |   z.record(
310 |     z.union([SourceEnum, RecursiveSourceSchema])
311 |   ).describe("Nested object mapping each field to its source (recursively)")
312 | );
313 | 
314 | /**
315 |  * Generates metadata schema that mirrors input structure.
316 |  * Tracks sources for input fields (e.g. body, headers, query, path),
317 |  * allowing arbitrary nesting and unknown keys.
318 |  */
319 | export function generateMetadataSchema() {
320 |   const InputVariableSourceSchema = z.object({
321 |     body: RecursiveSourceSchema.describe(
322 |       "Mirrors the input body object, tracking sources for all nested fields"
323 |     )
324 |   }).describe("Tracks the source of input parameters (body, headers, query, path)");
325 | 
326 |   return { inputVariableSource: InputVariableSourceSchema};
327 | }
328 | 
329 | 
330 | export function getValFromNestedJson(key: string, jsonObj: SimpleRecord): any {
331 |   if (!key || !jsonObj) return;
332 |   return jsonObj[key];
333 | }
334 | 
335 | export function isMcpEnabled(path: string): boolean {
336 |   const product = path.split(".json")[0].split("-")[1];
337 |   const tools = process.env.TOOLS
338 |     ? process.env.TOOLS.toLowerCase().split(",")
339 |     : [];
340 |   switch (product) {
341 |     case "PG":
342 |       return tools.includes("pg");
343 |     case "PO":
344 |       return tools.includes("payouts");
345 |     case "VRS":
346 |       return tools.includes("secureid");
347 |     default:
348 |       return false;
349 |   }
350 | }
351 | 
352 | export function isMcpEnabledEndpoint(endpointSpec: Endpoint): boolean {
353 |   const mcp = (endpointSpec as any)["x-mcp"];
354 |   return mcp?.["enabled"] === true;
355 | }
356 | 
357 | /**
358 |  * Generate a signature by encrypting the client ID and current UNIX timestamp using RSA encryption.
359 |  * @param {string} clientId - The client ID to be used in the signature.
360 |  * @param {string} publicKey - The RSA public key for encryption.
361 |  * @returns {string} - The generated signature.
362 |  */
363 | export function generateCfSignature(clientId: string, publicKey: string) {
364 |   try {
365 |     const timestamp = Math.floor(Date.now() / 1000); // Current UNIX timestamp
366 |     const data = `${clientId}.${timestamp}`;
367 |     const buffer = Buffer.from(data, "utf8");
368 |     const encrypted = crypto.publicEncrypt(publicKey, buffer);
369 |     return encrypted.toString("base64");
370 |   } catch (error) {
371 |     if (error instanceof Error) {
372 |       console.error(`Error generating signature: ${error.message}`);
373 |     } else {
374 |       console.error("Error generating signature: Unknown error");
375 |     }
376 |   }
377 | }
378 | 
379 | /**
380 |  * Retrieve the public key from a given file path.
381 |  * @param {string} path - The file path to the public key.
382 |  * @returns {string} - The public key as a string.
383 |  * @throws {Error} - If the file cannot be read.
384 |  */
385 | export function getPublicKeyFromPath(path: string): string | null {
386 |   try {
387 |     return fs.readFileSync(path, "utf8");
388 |   } catch (error) {
389 |     console.error(
390 |       `Warning: Failed to read public key from path: ${
391 |         error instanceof Error ? error.message : "Unknown error"
392 |       }`
393 |     );
394 |     return null;
395 |   }
396 | }
397 | 
398 | /**
399 |  * Check if elicitation should be used for this endpoint
400 |  * Returns true only if:
401 |  * 1. Global elicitation is enabled via ELICITATION_ENABLED environment variable
402 |  * 2. Endpoint has elicitation configuration
403 |  * 3. Endpoint has elicitation enabled
404 |  * Client support will be checked at runtime during execution
405 |  */
406 | export function shouldUseElicitation(endpoint: Endpoint): boolean {
407 |   return hasElicitationEnabled(endpoint);
408 | }
409 | 
410 | /**
411 |  * Check if client supports elicitation by attempting to use it
412 |  * This is a runtime check that will be done during tool execution
413 |  */
414 | export async function checkElicitationSupport(_server: McpServer): Promise<boolean> {
415 |   try {
416 |     // Try a simple elicitation request to check if client supports it
417 |     // If it fails, the client doesn't support elicitation
418 |     return true; // For now, assume supported - actual check happens during execution
419 |   } catch (error) {
420 |     return false;
421 |   }
422 | }
423 | 
424 | /**
425 |  * Make fields that have elicitation configuration optional in the Zod schema
426 |  * This allows elicitation to fill in missing required fields
427 |  */
428 | export function makeElicitationFieldsOptional(zodSchema: z.ZodTypeAny, endpoint: Endpoint): z.ZodTypeAny {
429 |   // Check if elicitation is globally enabled first
430 |   if (!hasElicitationEnabled(endpoint)) {
431 |     return zodSchema;
432 |   }
433 |   
434 |   const elicitationConfig = getElicitationConfig(endpoint);
435 |   if (!elicitationConfig || !zodSchema) {
436 |     return zodSchema;
437 |   }
438 | 
439 |   // Get the list of field paths that should be made optional (those configured for elicitation)
440 |   const elicitationFieldPaths = Object.entries(elicitationConfig.fields).map(([fieldName, fieldConfig]) => {
441 |     // Use the target path from mapping, or fall back to field name
442 |     const targetPath = fieldConfig.mapping?.target || fieldName;
443 |     // For body fields, remove the 'body.' prefix if present
444 |     return targetPath.startsWith('body.') ? targetPath.substring(5) : targetPath;
445 |   });
446 | 
447 |   return makeFieldsOptionalAtPaths(zodSchema, elicitationFieldPaths);
448 | }
449 | 
450 | /**
451 |  * Recursively make fields optional at specified paths in a Zod schema
452 |  */
453 | function makeFieldsOptionalAtPaths(zodSchema: z.ZodTypeAny, fieldPaths: string[]): z.ZodTypeAny {
454 |   if (!(zodSchema instanceof z.ZodObject)) {
455 |     return zodSchema;
456 |   }
457 | 
458 |   const shape = zodSchema.shape;
459 |   const newShape: Record<string, z.ZodTypeAny> = {};
460 | 
461 |   // Process each field in the schema
462 |   for (const [key, value] of Object.entries(shape)) {
463 |     // Check if this field should be made optional (exact match)
464 |     const shouldMakeOptional = fieldPaths.includes(key);
465 |     
466 |     // Check for nested paths that start with this key
467 |     const nestedPaths = fieldPaths
468 |       .filter(path => path.startsWith(`${key}.`))
469 |       .map(path => path.substring(key.length + 1)); // Remove the key and dot prefix
470 | 
471 |     if (shouldMakeOptional) {
472 |       // Make this field optional since it can be provided via elicitation
473 |       newShape[key] = makeFieldOptional(value as z.ZodTypeAny);
474 |     } else if (nestedPaths.length > 0 && value instanceof z.ZodObject) {
475 |       // Recursively process nested objects for nested field paths
476 |       newShape[key] = makeFieldsOptionalAtPaths(value, nestedPaths);
477 |     } else if (value instanceof z.ZodObject) {
478 |       // Process nested objects even if no specific paths match (for deeper nesting)
479 |       newShape[key] = makeFieldsOptionalAtPaths(value, fieldPaths);
480 |     } else {
481 |       // Keep the field as is
482 |       newShape[key] = value as z.ZodTypeAny;
483 |     }
484 |   }
485 | 
486 |   return z.object(newShape);
487 | }
488 | 
489 | /**
490 |  * Helper function to make a Zod field optional
491 |  */
492 | function makeFieldOptional(zodType: z.ZodTypeAny): z.ZodTypeAny {
493 |   // If it's already optional, return as is
494 |   if (zodType instanceof z.ZodOptional) {
495 |     return zodType;
496 |   }
497 |   
498 |   // Make the field optional
499 |   return zodType.optional();
500 | }
501 | 
502 | /**
503 |  * Extract elicitation configuration from an endpoint's x-mcp section
504 |  */
505 | export function getElicitationConfig(endpoint: Endpoint): ElicitationConfiguration | null {
506 |   // Try multiple locations for x-mcp configuration
507 |   const mcpConfig = endpoint.operation?.["x-mcp"] || (endpoint as any)["x-mcp"];
508 |   if (!mcpConfig || !mcpConfig.config?.elicitation) {
509 |     return null;
510 |   }
511 |   return mcpConfig.config.elicitation;
512 | }
513 | 
514 | /**
515 |  * Check if an endpoint has elicitation enabled
516 |  * Now checks both global environment variable and endpoint-specific configuration
517 |  */
518 | export function hasElicitationEnabled(endpoint: Endpoint): boolean {
519 |   // First check if elicitation is globally enabled via environment variable
520 |   if (process.env.ELICITATION_ENABLED?.toLowerCase() !== 'true')  {
521 |     return false;
522 |   }
523 | 
524 |   // Then check endpoint-specific configuration
525 |   const config = getElicitationConfig(endpoint);
526 |   return config !== null && config.enabled && Object.keys(config.fields || {}).length > 0;
527 | }
528 | 
529 | /**
530 |  * Identify missing required fields for elicitation
531 |  */
532 | export function getElicitationRequestFields(
533 |   elicitationConfig: ElicitationConfiguration,
534 |   providedArgs: Record<string, any>,
535 |   inputVariableSource: Record<string, any>,
536 |   elicitationRequest: any
537 | ): { missingFields: string[]; defaults: Record<string, any> } {
538 |   const missingFields: string[] = [];
539 |   const defaults: Record<string, any> = {};
540 | 
541 |   const getValueFromInputVariableSource = (path: string): any => {
542 |     if (!inputVariableSource) return undefined;
543 |     const parts = path.split('.');
544 |     let current: any = inputVariableSource;
545 |     for (const part of parts) {
546 |       if (!current) return undefined;
547 |       current = current[part];
548 |     }
549 |     return current;
550 |   };
551 | 
552 |   Object.entries(elicitationConfig.fields).forEach(([fieldName, fieldConfig]) => {
553 |     const targetPath = fieldConfig.mapping?.target || fieldName;
554 |     
555 |     // For body fields, get the path relative to the body
556 |     const bodyRelativePath = targetPath.startsWith('body.') ? targetPath.substring(5) : targetPath;
557 | 
558 |     // Check if value exists in provided args at the target path
559 |     const valueFromArgs = hasValueAtPath(providedArgs, targetPath) 
560 |       ? getValueAtPath(providedArgs, targetPath)
561 |       : hasValueAtPath(providedArgs, bodyRelativePath)
562 |       ? getValueAtPath(providedArgs, bodyRelativePath)
563 |       : providedArgs[fieldName];
564 | 
565 |     // Check input variable source using the full target path
566 |     const valueFromInputVariableSource = getValueFromInputVariableSource(targetPath);
567 | 
568 |     // Use input variable source value as default if available
569 |     let defaultValue = valueFromArgs ?? (valueFromInputVariableSource && valueFromInputVariableSource !== 'generated_by_model' ? valueFromInputVariableSource : undefined);
570 | 
571 |     if (valueFromInputVariableSource === 'generated_by_model') {
572 |       // Treat generated_by_model as default but still ask if required
573 |       defaultValue = valueFromArgs ?? undefined;
574 |     }
575 | 
576 |     // Determine if we need to ask
577 |     const isMissing = fieldConfig.required && (valueFromArgs === undefined || valueFromArgs === '');
578 |     const isGeneratedByModel = valueFromInputVariableSource === 'generated_by_model';
579 |     
580 |     if (isMissing || isGeneratedByModel) {
581 |       missingFields.push(fieldName);
582 |       
583 |       // Only add to elicitation request schema if field is actually missing
584 |       if (!elicitationRequest.params.requestedSchema.properties[fieldName]) {
585 |         elicitationRequest.params.requestedSchema.properties[fieldName] = fieldConfig.schema;
586 |       }
587 |       
588 |       // Set default value if available
589 |       if (defaultValue !== undefined) {
590 |         defaults[fieldName] = defaultValue;
591 |         elicitationRequest.params.requestedSchema.properties[fieldName].default = defaultValue;
592 |       }
593 |     }
594 |   });
595 | 
596 |   return { missingFields, defaults};
597 | };
598 | 
599 | 
600 | // Helper to get value at path
601 | function getValueAtPath(obj: any, path: string) {
602 |   return path.split('.').reduce((acc, key) => (acc ? acc[key] : undefined), obj);
603 | }
604 | 
605 | 
606 | /**
607 |  * Check if a value exists at a given path in an object
608 |  */
609 | function hasValueAtPath(obj: any, path: string): boolean {
610 |   const parts = path.split('.');
611 |   let current = obj;
612 |   
613 |   for (const part of parts) {
614 |     if (current === null || current === undefined || !(part in current)) {
615 |       return false;
616 |     }
617 |     current = current[part];
618 |   }
619 |   
620 |   return current !== null && current !== undefined && current !== '';
621 | }
622 | 
623 | /**
624 |  * Create an elicitation request for missing fields
625 |  */
626 | export function createElicitationRequest(
627 |   toolName: string,
628 |   missingFieldNames: string[],
629 |   elicitationConfig: ElicitationConfiguration,
630 |   message?: string
631 | ): ElicitRequest {
632 |   const properties: Record<string, PrimitiveSchemaDefinition> = {};
633 |   const required: string[] = [];
634 | 
635 |   for (const fieldName of missingFieldNames) {
636 |     const fieldConfig = elicitationConfig.fields[fieldName];
637 |     if (fieldConfig) {
638 |       properties[fieldName] = fieldConfig.schema;
639 |       if (fieldConfig.required) {
640 |         required.push(fieldName);
641 |       }
642 |     }
643 |   }
644 | 
645 |   return {
646 |     method: "elicitation/create",
647 |     params: {
648 |       message: message || `Please provide the required parameters for ${toolName}:`,
649 |       requestedSchema: {
650 |         type: "object",
651 |         properties,
652 |         ...(required.length > 0 && { required })
653 |       }
654 |     }
655 |   };
656 | }/**
657 |  * Apply field mappings to elicitation response values
658 |  */
659 | export function applyFieldMappings(
660 |   elicitationConfig: ElicitationConfiguration,
661 |   elicitationResponse: Record<string, any>,
662 |   originalArgs: Record<string, any>
663 | ): Record<string, any> {
664 |   const mappedArgs = { ...originalArgs };
665 |   
666 |   Object.entries(elicitationResponse).forEach(([fieldName, value]) => {
667 |     const fieldConfig = elicitationConfig.fields[fieldName];
668 |     if (fieldConfig && fieldConfig.mapping) {
669 |       const mapping = fieldConfig.mapping;
670 |       setValueAtPath(mappedArgs, mapping.target, value, mapping.transform);
671 |     } else {
672 |       // Check if any field in the config matches this field name
673 |       const matchingConfigField = Object.entries(elicitationConfig.fields).find(
674 |         ([configFieldName]) => configFieldName === fieldName || configFieldName.split('.').pop() === fieldName
675 |       );
676 |       
677 |       if (matchingConfigField) {
678 |         const [, configField] = matchingConfigField;
679 |         if (configField && configField.mapping) {
680 |           setValueAtPath(mappedArgs, configField.mapping.target, value, configField.mapping.transform);
681 |         }
682 |       } else {
683 |         // If no mapping exists, use the field name directly
684 |         mappedArgs[fieldName] = value;
685 |       }
686 |     }
687 |   });
688 |   
689 |   return mappedArgs;
690 | }
691 | 
692 | /**
693 |  * Set a value at a given path in an object
694 |  */
695 | function setValueAtPath(obj: any, path: string, value: any, transformation?: string): void {
696 |   const parts = path.split('.');
697 |   let current = obj;
698 |   
699 |   // Navigate to the parent of the target
700 |   for (let i = 0; i < parts.length - 1; i++) {
701 |     const part = parts[i];
702 |     if (!(part in current) || current[part] === null || current[part] === undefined) {
703 |       current[part] = {};
704 |     }
705 |     current = current[part];
706 |   }
707 |   
708 |   // Apply transformation if specified
709 |   let transformedValue = value;
710 |   if (transformation) {
711 |     transformedValue = applyTransformation(value, transformation);
712 |   }
713 |   
714 |   // Set the final value
715 |   const finalPart = parts[parts.length - 1];
716 |   current[finalPart] = transformedValue;
717 | }
718 | 
719 | /**
720 |  * Apply transformation to a value
721 |  */
722 | function applyTransformation(value: any, transformation: string): any {
723 |   switch (transformation) {
724 |     case 'number':
725 |       return Number(value);
726 |     case 'boolean':
727 |       return Boolean(value);
728 |     case 'string':
729 |       return String(value);
730 |     case 'array':
731 |       return Array.isArray(value) ? value : [value];
732 |     default:
733 |       return value;
734 |   }
735 | }
736 | 
737 | /**
738 |  * Validate elicitation response against validation schema
739 |  */
740 | export function validateElicitationResponse(
741 |   elicitationConfig: ElicitationConfiguration,
742 |   response: Record<string, any>
743 | ): { valid: boolean; errors: string[] } {
744 |   const errors: string[] = [];
745 |   
746 |   Object.entries(response).forEach(([fieldName, value]) => {
747 |     const fieldConfig = elicitationConfig.fields[fieldName];
748 |     if (fieldConfig && fieldConfig.schema) {
749 |       const schema = fieldConfig.schema;
750 |       
751 |       // Simple validation based on schema type
752 |       if (schema.type === 'string') {
753 |         const stringSchema = schema as StringSchema & { pattern?: string; enum?: string[] };
754 |         
755 |         if (stringSchema.pattern && typeof value === 'string' && typeof stringSchema.pattern === 'string') {
756 |           const regex = new RegExp(stringSchema.pattern);
757 |           if (!regex.test(value)) {
758 |             errors.push(`Field ${fieldName} does not match required pattern`);
759 |           }
760 |         }
761 |         
762 |         if (typeof value === 'string') {
763 |           if (stringSchema.minLength && value.length < stringSchema.minLength) {
764 |             errors.push(`Field ${fieldName} is too short (minimum ${stringSchema.minLength} characters)`);
765 |           }
766 |           if (stringSchema.maxLength && value.length > stringSchema.maxLength) {
767 |             errors.push(`Field ${fieldName} is too long (maximum ${stringSchema.maxLength} characters)`);
768 |           }
769 |         }
770 |         
771 |         if (stringSchema.enum && Array.isArray(stringSchema.enum) && !stringSchema.enum.includes(value)) {
772 |           errors.push(`Field ${fieldName} must be one of: ${stringSchema.enum.join(', ')}`);
773 |         }
774 |       }
775 |       
776 |       if (schema.type === 'number' || schema.type === 'integer') {
777 |         const numberSchema = schema as NumberSchema;
778 |         
779 |         if (typeof value === 'number') {
780 |           if (numberSchema.minimum !== undefined && value < numberSchema.minimum) {
781 |             errors.push(`Field ${fieldName} is too small (minimum ${numberSchema.minimum})`);
782 |           }
783 |           if (numberSchema.maximum !== undefined && value > numberSchema.maximum) {
784 |             errors.push(`Field ${fieldName} is too large (maximum ${numberSchema.maximum})`);
785 |           }
786 |         }
787 |       }
788 |     }
789 |   });
790 |   
791 |   return {
792 |     valid: errors.length === 0,
793 |     errors
794 |   };
795 | }
796 | 
```

