From 7db52a86170c0c225f64985172225fc55c3f7237 Mon Sep 17 00:00:00 2001 From: Srujan Gurram Date: Wed, 28 Jan 2026 01:59:38 +0530 Subject: [PATCH] feat: Standardize README structure and add auto-generation workflow - Update README to follow standardized template with badges, sections for Overview, Features, Installation, Configuration, Usage Examples, etc. - Add AUTO-GENERATED TOOLS markers for dynamic tool documentation - Copy sync-tools.yml workflow for auto-syncing MCP tool docs - Add generate-tools.mjs action for tool documentation generation - Add zod-to-json-schema devDependency for schema conversion Co-Authored-By: Claude Opus 4.5 --- .../generate-mcp-tools/generate-tools.mjs | 156 +++++++++++ .github/workflows/sync-tools.yml | 47 ++++ README.md | 242 ++++++++---------- package.json | 13 +- pnpm-lock.yaml | 21 +- 5 files changed, 320 insertions(+), 159 deletions(-) create mode 100644 .github/actions/generate-mcp-tools/generate-tools.mjs create mode 100644 .github/workflows/sync-tools.yml diff --git a/.github/actions/generate-mcp-tools/generate-tools.mjs b/.github/actions/generate-mcp-tools/generate-tools.mjs new file mode 100644 index 0000000..20ace7e --- /dev/null +++ b/.github/actions/generate-mcp-tools/generate-tools.mjs @@ -0,0 +1,156 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { zodToJsonSchema } from "zod-to-json-schema"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, "../../.."); +const README_PATH = path.join(ROOT, "README.md"); +const TOOLS_DIR = path.join(ROOT, "src", "tools"); + +const START = ""; +const END = ""; + +/** + * Load MCP tools + * Scans for any export that looks like an MCP tool: + * { + * name: string, + * description: string, + * parameters?: ZodSchema + * schema?: JSONSchema + * } + */ +async function loadTools() { + const files = fs + .readdirSync(TOOLS_DIR) + .filter((f) => f.endsWith(".ts") && f !== "index.ts"); + + const toolPromises = files.map(async (file) => { + const mod = await import(path.join(TOOLS_DIR, file)); + + const matches = Object.values(mod).filter( + (exp) => + exp && + typeof exp === "object" && + typeof exp.name === "string" && + typeof exp.description === "string" && + (exp.parameters || exp.schema), + ); + + if (matches.length === 0) { + return null; + } + + if (matches.length > 1) { + console.warn( + `Warning: ${file} exports multiple MCP-like tools. Using the first one.`, + ); + } + + return matches[0]; + }); + + const loadedTools = await Promise.all(toolPromises); + const tools = loadedTools.filter(Boolean); + + return tools.sort((a, b) => a.name.localeCompare(b.name)); +} + +function renderSchema(schema) { + if (!schema) { + return "_No parameters_"; + } + + // If this is a Zod schema, convert it to JSON Schema + const jsonSchema = + typeof schema.safeParse === "function" ? zodToJsonSchema(schema) : schema; + + const properties = jsonSchema.properties ?? {}; + const required = new Set(jsonSchema.required ?? []); + + if (Object.keys(properties).length === 0) { + return "_No parameters_"; + } + + // Check if any param has a default value to determine table columns + const hasDefaults = Object.values(properties).some( + (prop) => prop.default !== undefined, + ); + + // Build table header + let table = hasDefaults + ? "| Parameter | Type | Required | Default | Description |\n|-----------|------|----------|---------|-------------|\n" + : "| Parameter | Type | Required | Description |\n|-----------|------|----------|-------------|\n"; + + // Build table rows + for (const [key, prop] of Object.entries(properties)) { + const type = Array.isArray(prop.type) + ? prop.type.join(" | ") + : (prop.type ?? "unknown"); + + const requiredStr = required.has(key) ? "✅" : ""; + const description = prop.description ?? ""; + const defaultVal = + prop.default !== undefined ? JSON.stringify(prop.default) : ""; + + if (hasDefaults) { + table += `| \`${key}\` | ${type} | ${requiredStr} | ${defaultVal} | ${description} |\n`; + } else { + table += `| \`${key}\` | ${type} | ${requiredStr} | ${description} |\n`; + } + } + + return table.trim(); +} + +function renderMarkdown(tools) { + let md = ""; + + for (const tool of tools) { + const schema = tool.parameters || tool.schema; + + md += `### \`${tool.name}\`\n`; + md += `${tool.description}\n\n`; + md += `${renderSchema(schema)}\n\n`; + } + + return md.trim(); +} + +function updateReadme({ readme, tools }) { + if (!readme.includes(START) || !readme.includes(END)) { + throw new Error("README missing AUTO-GENERATED TOOLS markers"); + } + + const toolsMd = renderMarkdown(tools); + + return readme.replace( + new RegExp(`${START}[\\s\\S]*?${END}`, "m"), + `${START}\n\n${toolsMd}\n\n${END}`, + ); +} + +async function main() { + try { + const readme = fs.readFileSync(README_PATH, "utf8"); + const tools = await loadTools(); + + if (tools.length === 0) { + console.warn("Warning: No tools found!"); + } + + const updated = updateReadme({ readme, tools }); + + fs.writeFileSync(README_PATH, updated); + console.log(`Synced ${tools.length} MCP tools to README.md`); + } catch (error) { + console.error("Error updating README:", error); + process.exit(1); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/.github/workflows/sync-tools.yml b/.github/workflows/sync-tools.yml new file mode 100644 index 0000000..81f62f4 --- /dev/null +++ b/.github/workflows/sync-tools.yml @@ -0,0 +1,47 @@ +name: Sync MCP Tool Docs + +on: + push: + branches: + - main + +permissions: + contents: write + +jobs: + sync-tools: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: | + if [ -f pnpm-lock.yaml ]; then + corepack enable + pnpm install --frozen-lockfile + else + npm install + fi + + - name: Generate MCP tool documentation + run: npx tsx .github/actions/generate-mcp-tools/generate-tools.mjs + + - name: Commit README changes + run: | + if git diff --quiet; then + echo "No README changes" + exit 0 + fi + + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add README.md + git commit -m "chore: auto-sync MCP tool documentation" + git push diff --git a/README.md b/README.md index df5726f..13cfc68 100644 --- a/README.md +++ b/README.md @@ -1,203 +1,163 @@ -# MCP-ABI: Model Context Protocol Server for Smart Contract ABI Interactions +# 🔗 ABI MCP Server -This project implements a Model Context Protocol (MCP) server to interact with Ethereum-compatible smart contracts using their ABI (Application Binary Interface). It allows MCP-compatible clients (like AI assistants, IDE extensions, or custom applications) to dynamically generate and execute smart contract interactions based on contract ABIs. +[![npm version](https://img.shields.io/npm/v/@iqai/mcp-abi.svg)](https://www.npmjs.com/package/@iqai/mcp-abi) +[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC) -This server is built using TypeScript and `fastmcp`. +## 📖 Overview -## Features (MCP Tools) +The ABI MCP Server enables AI agents to interact with any Ethereum-compatible smart contract using its ABI (Application Binary Interface). This server dynamically generates MCP tools from contract ABIs, allowing seamless interaction with any smart contract without requiring custom tool implementations. -The server dynamically exposes tools based on the provided contract ABI. Each function in the ABI becomes an available MCP tool: +By implementing the Model Context Protocol (MCP), this server allows Large Language Models (LLMs) to read contract state, execute transactions, and interact with decentralized applications directly through their context window. -- **Read Functions (view/pure)**: Query contract state without sending transactions - - Example: `CONTRACT_BALANCE_OF`, `CONTRACT_TOTAL_SUPPLY`, `CONTRACT_NAME` - - Parameters: Function-specific parameters as defined in the ABI -- **Write Functions**: Execute state-changing transactions on the contract - - Example: `CONTRACT_TRANSFER`, `CONTRACT_APPROVE`, `CONTRACT_MINT` - - Parameters: Function-specific parameters as defined in the ABI - - Requires `WALLET_PRIVATE_KEY` in the environment +## ✨ Features -All tools are automatically prefixed with the contract name (e.g., `ERC20_TRANSFER` for an ERC20 contract). +* **Dynamic Tool Generation**: Automatically creates MCP tools from any contract ABI at runtime. +* **Read Functions**: Query contract state (view/pure functions) without sending transactions. +* **Write Functions**: Execute state-changing transactions with wallet signing support. +* **Multi-Chain Support**: Works with any EVM-compatible blockchain via configurable RPC endpoints. +* **Type-Safe Interactions**: Validates function arguments against ABI specifications. -## Prerequisites +## 📦 Installation -- Node.js (v18 or newer recommended) -- pnpm (See ) +### 🚀 Using npx (Recommended) -## Installation - -There are a few ways to use `mcp-abi`: - -**1. Using `pnpm dlx` (Recommended for most MCP client setups):** - -You can run the server directly using `pnpm dlx` without needing a global installation. `pnpm dlx` is a command that allows you to run packages without installing them globally. This is often the easiest way to integrate with MCP clients. See the "Running the Server with an MCP Client" section for examples. -(`pnpm dlx` is pnpm's equivalent of `npx`) - -**2. Global Installation from npm (via pnpm):** - -Install the package globally to make the `mcp-abi` command available system-wide: +To use this server without installing it globally: ```bash -pnpm add -g @iqai/mcp-abi +npx @iqai/mcp-abi ``` -If you install globally, you may need to set up environment variables for the `mcp-abi` command to be recognized system-wide. - -**3. Building from Source (for development or custom modifications):** - -1. **Clone the repository:** - - ```bash - git clone https://github.com/IQAIcom/mcp-abi.git - cd mcp-abi - ``` - -2. **Install dependencies:** - - ```bash - pnpm install - ``` - -3. **Build the server:** - This compiles the TypeScript code to JavaScript in the `dist` directory. - - ```bash - pnpm run build - ``` - - The `prepare` script also runs `pnpm run build`, so dependencies are built upon installation if you clone and run `pnpm install`. - -## Configuration (Environment Variables) - -This MCP server requires certain environment variables to be set by the MCP client that runs it. These are typically configured in the client's MCP server definition (e.g., in a `mcp.json` file for Cursor, or similar for other clients). - -- **`WALLET_PRIVATE_KEY`**: (Required for write functions) - - - The private key of the wallet to be used for signing and sending transactions to the blockchain. - - **Security Note:** Handle this private key with extreme care. Ensure it is stored securely and only provided to trusted MCP client configurations. - -- **`CONTRACT_ABI`**: (Required) - - - The JSON string representation of the contract's ABI. - - This defines which functions will be available as MCP tools. - -- **`CONTRACT_ADDRESS`**: (Required) - - - The deployed contract address on the blockchain. - -- **`CONTRACT_NAME`**: (Optional, defaults to "CONTRACT") - - - A friendly name for the contract, used as a prefix for generated tool names. - -- **`CHAIN_ID`**: (Optional, defaults to Fraxtal (252)) +### 🔧 Build from Source - - The blockchain network chain ID to interact with. - -- **`RPC_URL`**: (Optional) - - Custom RPC endpoint URL. If not provided, uses default RPC for the specified chain. +```bash +git clone https://github.com/IQAIcom/mcp-abi.git +cd mcp-abi +pnpm install +pnpm run build +``` -## Running the Server with an MCP Client +## ⚡ Running with an MCP Client -MCP clients (like AI assistants, IDE extensions, etc.) will run this server as a background process. You need to configure the client to tell it how to start your server. +Add the following configuration to your MCP client settings (e.g., `claude_desktop_config.json`). -Below is an example configuration snippet that an MCP client might use (e.g., in a `mcp_servers.json` or similar configuration file). This example shows how to run the server using the published npm package via `pnpm dlx`. This configuration defines an MCP server named "smart-contract-abi" that executes the `pnpm dlx @iqai/mcp-abi` command with the specified environment variables. +### 📋 Minimal Configuration ```json { "mcpServers": { "smart-contract-abi": { - "command": "pnpm", - "args": ["dlx", "@iqai/mcp-abi"], + "command": "npx", + "args": ["-y", "@iqai/mcp-abi"], "env": { - "WALLET_PRIVATE_KEY": "your_wallet_private_key_here", - "CONTRACT_ABI": "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + "CONTRACT_ABI": "[{\"inputs\":[{\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", "CONTRACT_ADDRESS": "0xaB195B090Cc60C1EFd4d1cEE94Bf441F5931C01b", - "CONTRACT_NAME": "ERC20", - "CHAIN_ID": "252", - "RPC_URL": "https://rpc.frax.com" + "CONTRACT_NAME": "ERC20" } } } } ``` -**Alternative if Globally Installed:** - -If you have installed `mcp-abi` globally (`pnpm add -g @iqai/mcp-abi`), you can simplify the `command` and `args`. This configuration defines an MCP server named "smart-contract-abi" that executes the `mcp-abi` command with the specified environment variables. +### ⚙️ Advanced Configuration (Local Build) ```json { "mcpServers": { "smart-contract-abi": { - "command": "mcp-abi", - "args": [], + "command": "node", + "args": ["/absolute/path/to/mcp-abi/dist/index.js"], "env": { "WALLET_PRIVATE_KEY": "your_wallet_private_key_here", - "CONTRACT_ABI": "[{\"inputs\":[{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + "CONTRACT_ABI": "[{\"inputs\":[{\"name\":\"to\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", "CONTRACT_ADDRESS": "0xaB195B090Cc60C1EFd4d1cEE94Bf441F5931C01b", "CONTRACT_NAME": "ERC20", - "CHAIN_ID": "252" + "CHAIN_ID": "252", + "RPC_URL": "https://rpc.frax.com" } } } } ``` -- **`command`**: The executable to run. - - For `pnpm dlx`: `"pnpm"` (with `"dlx"` as the first arg) - - For global install: `"mcp-abi"` -- **`args`**: An array of arguments to pass to the command. - - For `pnpm dlx`: `["dlx", "@iqai/mcp-abi"]` - - For global install: `[]` -- **`env`**: An object containing environment variables to be set when the server process starts. This is where you provide `WALLET_PRIVATE_KEY`, `CONTRACT_ABI`, `CONTRACT_ADDRESS`, and other configuration options. +## 🔐 Configuration (Environment Variables) -## Example Usage Scenarios +| Variable | Required | Description | Default | +| :--- | :--- | :--- | :--- | +| `CONTRACT_ABI` | Yes | JSON string representation of the contract's ABI | - | +| `CONTRACT_ADDRESS` | Yes | The deployed contract address on the blockchain | - | +| `CONTRACT_NAME` | No | Friendly name for the contract (used as tool name prefix) | `CONTRACT` | +| `WALLET_PRIVATE_KEY` | For writes | Private key for signing transactions (required for write functions) | - | +| `CHAIN_ID` | No | Blockchain network chain ID | `252` (Fraxtal) | +| `RPC_URL` | No | Custom RPC endpoint URL | Default for chain | -**ERC20 Token Contract:** +## 💡 Usage Examples -- Check token balance: Use `ERC20_BALANCE_OF` tool with an address parameter -- Transfer tokens: Use `ERC20_TRANSFER` tool with recipient address and amount -- Check allowances: Use `ERC20_ALLOWANCE` tool with owner and spender addresses -- Approve spending: Use `ERC20_APPROVE` tool with spender address and amount +### 🔍 Read Contract State +* "Check the token balance of wallet 0xabc..." +* "What is the total supply of this token?" +* "Get the allowance for spender 0x123... from owner 0x456..." -**NFT Contract (ERC721):** +### 📝 Execute Transactions +* "Transfer 100 tokens to address 0xabc..." +* "Approve 0x123... to spend 1000 tokens" +* "Mint a new NFT to wallet 0xdef..." -- Check token ownership: Use `NFT_OWNER_OF` tool with token ID -- Transfer NFT: Use `NFT_TRANSFER_FROM` tool with from address, to address, and token ID -- Mint new token: Use `NFT_MINT` tool with recipient address and token metadata +### 📊 Contract Analysis +* "What functions are available on this contract?" +* "Show me all the read functions I can call" +* "What parameters does the transfer function require?" -## Response Format +## 🛠️ MCP Tools -**Read Functions:** +Tools are dynamically generated based on the provided contract ABI. Each function in the ABI becomes an MCP tool: -``` -✅ Successfully called balanceOf -Result: "1000000000000000000" -``` +* **Read Functions (view/pure)**: Tools prefixed with contract name (e.g., `erc20_balanceOf`, `erc20_totalSupply`) +* **Write Functions**: Tools for state-changing operations (e.g., `erc20_transfer`, `erc20_approve`) -**Write Functions:** +Example tool names for an ERC20 contract with `CONTRACT_NAME=ERC20`: +* `erc20_balanceOf` - Query token balance +* `erc20_transfer` - Transfer tokens +* `erc20_approve` - Approve spending +* `erc20_allowance` - Check allowance + + + + +## 👨‍💻 Development + +### 🏗️ Build Project +```bash +pnpm run build ``` -✅ Successfully executed transfer -Transaction hash: 0x123abc... -You can view this transaction on the blockchain explorer. + +### 👁️ Development Mode (Watch) +```bash +pnpm run watch ``` -## Error Handling +### ✅ Linting & Formatting +```bash +pnpm run lint +pnpm run format +``` + +### 📁 Project Structure +* `src/tools/`: Tool generation logic +* `src/services/`: Contract interaction service +* `src/lib/`: Shared utilities +* `src/index.ts`: Server entry point + +## 📚 Resources + +* [Model Context Protocol (MCP)](https://modelcontextprotocol.io) +* [Ethereum ABI Specification](https://docs.soliditylang.org/en/latest/abi-spec.html) +* [Viem Documentation](https://viem.sh) -The server provides comprehensive error handling for various blockchain interaction scenarios: +## ⚠️ Disclaimer -- 🚨 **Invalid function arguments** - "❌ Error parsing arguments: [specific error]" -- 🔄 **Transaction failures** - "❌ Error with [function]: [error message]" -- 🔒 **Access control errors** - "❌ Error with [function]: execution reverted" -- 🌐 **Network errors** - "❌ Error with [function]: network connection failed" -- 💲 **Insufficient funds** - "❌ Error with [function]: insufficient funds for gas" -- 📄 **ABI parsing errors** - "❌ Invalid ABI format: [specific error]" -- 🏠 **Contract address errors** - "❌ Invalid contract address: [address]" +This tool interacts with blockchain smart contracts and can execute real transactions. Store private keys securely and never commit them to version control. Always test interactions on testnets before mainnet deployment. Trading and interacting with smart contracts involves risk. -## Security Considerations +## 📄 License -- Store private keys securely and never commit them to version control -- Use environment variables for sensitive configuration -- Validate all contract interactions before execution -- Consider using hardware wallets or secure key management solutions for production use -- Always test interactions on testnets before mainnet deployment +[ISC](LICENSE) diff --git a/package.json b/package.json index 5f6b4ea..8d8ccd0 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,7 @@ "bin": { "mcp-abi": "dist/index.js" }, - "files": [ - "dist" - ], + "files": ["dist"], "scripts": { "build": "tsc && shx chmod +x dist/index.js", "prepare": "husky", @@ -37,11 +35,7 @@ "type": "git", "url": "git+https://github.com/IQAIcom/mcp-abi.git" }, - "keywords": [ - "mcp", - "ABI", - "iqagents" - ], + "keywords": ["mcp", "ABI", "iqagents"], "author": "IQAI", "license": "ISC", "bugs": { @@ -55,7 +49,8 @@ "husky": "^9.1.7", "lint-staged": "^15.0.0", "shx": "^0.3.4", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "zod-to-json-schema": "^3.25.1" }, "dependencies": { "@biomejs/biome": "*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f04884a..e3f1a7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,9 @@ importers: typescript: specifier: ^5.8.3 version: 5.8.3 + zod-to-json-schema: + specifier: ^3.25.1 + version: 3.25.1(zod@3.25.28) packages: @@ -1339,10 +1342,10 @@ packages: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} - zod-to-json-schema@3.24.5: - resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: - zod: ^3.24.1 + zod: ^3.25 || ^4 zod@3.25.28: resolution: {integrity: sha512-/nt/67WYKnr5by3YS7LroZJbtcCBurDKKPBPWWzaxvVCGuG/NOsiKkrjoOhI8mJ+SQUXEbUzeB3S+6XDUEEj7Q==} @@ -1585,7 +1588,7 @@ snapshots: pkce-challenge: 5.0.0 raw-body: 3.0.0 zod: 3.25.28 - zod-to-json-schema: 3.24.5(zod@3.25.28) + zod-to-json-schema: 3.25.1(zod@3.25.28) transitivePeerDependencies: - supports-color @@ -1935,10 +1938,10 @@ snapshots: strict-event-emitter-types: 2.0.0 undici: 7.10.0 uri-templates: 0.2.0 - xsschema: 0.2.0-beta.3(zod-to-json-schema@3.24.5(zod@3.25.28)) + xsschema: 0.2.0-beta.3(zod-to-json-schema@3.25.1(zod@3.25.28)) yargs: 17.7.2 zod: 3.25.28 - zod-to-json-schema: 3.24.5(zod@3.25.28) + zod-to-json-schema: 3.25.1(zod@3.25.28) transitivePeerDependencies: - '@valibot/to-json-schema' - arktype @@ -2655,9 +2658,9 @@ snapshots: ws@8.18.2: {} - xsschema@0.2.0-beta.3(zod-to-json-schema@3.24.5(zod@3.25.28)): + xsschema@0.2.0-beta.3(zod-to-json-schema@3.25.1(zod@3.25.28)): optionalDependencies: - zod-to-json-schema: 3.24.5(zod@3.25.28) + zod-to-json-schema: 3.25.1(zod@3.25.28) y18n@5.0.8: {} @@ -2677,7 +2680,7 @@ snapshots: yoctocolors@2.1.1: {} - zod-to-json-schema@3.24.5(zod@3.25.28): + zod-to-json-schema@3.25.1(zod@3.25.28): dependencies: zod: 3.25.28