diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 78baf5b..d4f6f29 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.5.0" + ".": "3.0.0" } diff --git a/.stats.yml b/.stats.yml index f25cd48..99e1a9a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 84 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/coingecko%2Fcoingecko-a23494dedfc230f769076f4022de83a327d642db68ceadedf998921c66ff9310.yml -openapi_spec_hash: 0bcc6962d4ee8f39880019986e87e433 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/coingecko%2Fcoingecko-eb98e136b67fc13d5c247829bb39e6ca26343d39801258f26f92ea25d3f4e3ef.yml +openapi_spec_hash: 04a096ec27eeb369d224c00bccc3bb9b config_hash: f101f417dba7f9352f7573639dd5938f diff --git a/CHANGELOG.md b/CHANGELOG.md index 7608eba..3d3cb83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 3.0.0 (2025-12-23) + +Full Changelog: [v2.5.0...v3.0.0](https://github.com/coingecko/coingecko-typescript/compare/v2.5.0...v3.0.0) + +### ⚠ BREAKING CHANGES + +* **mcp:** remove deprecated tool schemes +* **mcp:** **Migration:** To migrate, simply modify the command used to invoke the MCP server. Currently, the only supported tool scheme is code mode. Now, starting the server with just `node /path/to/mcp/server` or `npx package-name` will invoke code tools: changing your command to one of these is likely all you will need to do. + +### Features + +* **api:** api update ([006ad17](https://github.com/coingecko/coingecko-typescript/commit/006ad17f169ca009dac4fc387f75efc22d44c7b0)) + + +### Chores + +* **mcp:** remove deprecated tool schemes ([b20be48](https://github.com/coingecko/coingecko-typescript/commit/b20be485120dd8f34ed3fcf8c5fe15a6ff85838f)) + ## 2.5.0 (2025-12-18) Full Changelog: [v2.4.0...v2.5.0](https://github.com/coingecko/coingecko-typescript/compare/v2.4.0...v2.5.0) diff --git a/package.json b/package.json index 022b09c..cf96be4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@coingecko/coingecko-typescript", - "version": "2.5.0", + "version": "3.0.0", "description": "The official TypeScript library for the Coingecko API", "author": "Coingecko ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 0564973..d824743 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -27,7 +27,7 @@ For clients with a configuration JSON, it might look something like this: "mcpServers": { "coingecko_api": { "command": "npx", - "args": ["-y", "@coingecko/coingecko-mcp", "--client=claude", "--tools=dynamic"], + "args": ["-y", "@coingecko/coingecko-mcp"], "env": { "COINGECKO_PRO_API_KEY": "My Pro API Key", "COINGECKO_DEMO_API_KEY": "My Demo API Key", @@ -61,110 +61,22 @@ environment variables in Claude Code's `.claude.json`, which can be found in you claude mcp add --transport stdio coingecko_coingecko_typescript_api --env COINGECKO_PRO_API_KEY="Your COINGECKO_PRO_API_KEY here." COINGECKO_DEMO_API_KEY="Your COINGECKO_DEMO_API_KEY here." -- npx -y @coingecko/coingecko-mcp ``` -## Exposing endpoints to your MCP Client +## Code Mode -There are three ways to expose endpoints as tools in the MCP server: +This MCP server is built on the "Code Mode" tool scheme. In this MCP Server, +your agent will write code against the TypeScript SDK, which will then be executed in an +isolated sandbox. To accomplish this, the server will expose two tools to your agent: -1. Exposing one tool per endpoint, and filtering as necessary -2. Exposing a set of tools to dynamically discover and invoke endpoints from the API -3. Exposing a docs search tool and a code execution tool, allowing the client to write code to be executed against the TypeScript client +- The first tool is a docs search tool, which can be used to generically query for + documentation about your API/SDK. -### Filtering endpoints and tools +- The second tool is a code tool, where the agent can write code against the TypeScript SDK. + The code will be executed in a sandbox environment without web or filesystem access. Then, + anything the code returns or prints will be returned to the agent as the result of the + tool call. -You can run the package on the command line to discover and filter the set of tools that are exposed by the -MCP Server. This can be helpful for large APIs where including all endpoints at once is too much for your AI's -context window. - -You can filter by multiple aspects: - -- `--tool` includes a specific tool by name -- `--resource` includes all tools under a specific resource, and can have wildcards, e.g. `my.resource*` -- `--operation` includes just read (get/list) or just write operations - -### Dynamic tools - -If you specify `--tools=dynamic` to the MCP server, instead of exposing one tool per endpoint in the API, it will -expose the following tools: - -1. `list_api_endpoints` - Discovers available endpoints, with optional filtering by search query -2. `get_api_endpoint_schema` - Gets detailed schema information for a specific endpoint -3. `invoke_api_endpoint` - Executes any endpoint with the appropriate parameters - -This allows you to have the full set of API endpoints available to your MCP Client, while not requiring that all -of their schemas be loaded into context at once. Instead, the LLM will automatically use these tools together to -search for, look up, and invoke endpoints dynamically. However, due to the indirect nature of the schemas, it -can struggle to provide the correct properties a bit more than when tools are imported explicitly. Therefore, -you can opt-in to explicit tools, the dynamic tools, or both. - -See more information with `--help`. - -All of these command-line options can be repeated, combined together, and have corresponding exclusion versions (e.g. `--no-tool`). - -Use `--list` to see the list of available tools, or see below. - -### Code execution - -If you specify `--tools=code` to the MCP server, it will expose just two tools: - -- `search_docs` - Searches the API documentation and returns a list of markdown results -- `execute` - Runs code against the TypeScript client - -This allows the LLM to implement more complex logic by chaining together many API calls without loading -intermediary results into its context window. - -The code execution itself happens in a Deno sandbox that has network access only to the base URL for the API. - -### Specifying the MCP Client - -Different clients have varying abilities to handle arbitrary tools and schemas. - -You can specify the client you are using with the `--client` argument, and the MCP server will automatically -serve tools and schemas that are more compatible with that client. - -- `--client=`: Set all capabilities based on a known MCP client - - - Valid values: `openai-agents`, `claude`, `claude-code`, `cursor` - - Example: `--client=cursor` - -Additionally, if you have a client not on the above list, or the client has gotten better -over time, you can manually enable or disable certain capabilities: - -- `--capability=`: Specify individual client capabilities - - Available capabilities: - - `top-level-unions`: Enable support for top-level unions in tool schemas - - `valid-json`: Enable JSON string parsing for arguments - - `refs`: Enable support for $ref pointers in schemas - - `unions`: Enable support for union types (anyOf) in schemas - - `formats`: Enable support for format validations in schemas (e.g. date-time, email) - - `tool-name-length=N`: Set maximum tool name length to N characters - - Example: `--capability=top-level-unions --capability=tool-name-length=40` - - Example: `--capability=top-level-unions,tool-name-length=40` - -### Examples - -1. Filter for read operations on cards: - -```bash ---resource=cards --operation=read -``` - -2. Exclude specific tools while including others: - -```bash ---resource=cards --no-tool=create_cards -``` - -3. Configure for Cursor client with custom max tool name length: - -```bash ---client=cursor --capability=tool-name-length=40 -``` - -4. Complex filtering with multiple criteria: - -```bash ---resource=cards,accounts --operation=read --tag=kyc --no-tool=create_cards -``` +Using this scheme, agents are capable of performing very complex tasks deterministically +and repeatably. ## Running remotely @@ -190,233 +102,3 @@ A configuration JSON for this server might look like this, assuming the server i } } ``` - -The command-line arguments for filtering tools and specifying clients can also be used as query parameters in the URL. -For example, to exclude specific tools while including others, use the URL: - -``` -http://localhost:3000?resource=cards&resource=accounts&no_tool=create_cards -``` - -Or, to configure for the Cursor client, with a custom max tool name length, use the URL: - -``` -http://localhost:3000?client=cursor&capability=tool-name-length%3D40 -``` - -## Importing the tools and server individually - -```js -// Import the server, generated endpoints, or the init function -import { server, endpoints, init } from "@coingecko/coingecko-mcp/server"; - -// import a specific tool -import getAssetPlatforms from "@coingecko/coingecko-mcp/tools/asset-platforms/get-asset-platforms"; - -// initialize the server and all endpoints -init({ server, endpoints }); - -// manually start server -const transport = new StdioServerTransport(); -await server.connect(transport); - -// or initialize your own server with specific tools -const myServer = new McpServer(...); - -// define your own endpoint -const myCustomEndpoint = { - tool: { - name: 'my_custom_tool', - description: 'My custom tool', - inputSchema: zodToJsonSchema(z.object({ a_property: z.string() })), - }, - handler: async (client: client, args: any) => { - return { myResponse: 'Hello world!' }; - }) -}; - -// initialize the server with your custom endpoints -init({ server: myServer, endpoints: [getAssetPlatforms, myCustomEndpoint] }); -``` - -## Available Tools - -The following tools are available in this MCP server. - -### Resource `asset_platforms`: - -- `get_asset_platforms` (`read`): This endpoint allows you to **query all the asset platforms on CoinGecko** - -### Resource `coins`: - -- `get_id_coins` (`read`): This endpoint allows you to **query all the metadata (image, websites, socials, description, contract address, etc.) and market data (price, ATH, exchange tickers, etc.) of a coin from the CoinGecko coin page based on a particular coin ID** - -### Resource `coins.categories`: - -- `get_list_coins_categories` (`read`): This endpoint allows you to **query all the coins categories on CoinGecko** - -### Resource `coins.list`: - -- `get_new_coins_list` (`read`): This endpoint allows you to **query the latest 200 coins that recently listed on CoinGecko** - -### Resource `coins.markets`: - -- `get_coins_markets` (`read`): This endpoint allows you to **query all the supported coins with price, market cap, volume and market related data** - -### Resource `coins.top_gainers_losers`: - -- `get_coins_top_gainers_losers` (`read`): This endpoint allows you to **query the top 30 coins with largest price gain and loss by a specific time duration** - -### Resource `coins.contract`: - -- `get_coins_contract` (`read`): This endpoint allows you to **query all the metadata (image, websites, socials, description, contract address, etc.) and market data (price, ATH, exchange tickers, etc.) of a coin from the CoinGecko coin page based on an asset platform and a particular token contract address** - -### Resource `coins.contract.market_chart`: - -- `get_range_contract_coins_market_chart` (`read`): This endpoint allows you to **get the historical chart data within certain time range in UNIX along with price, market cap and 24hr volume based on asset platform and particular token contract address** - -### Resource `coins.history`: - -- `get_coins_history` (`read`): This endpoint allows you to **query the historical data (price, market cap, 24hrs volume, ...) at a given date for a coin based on a particular coin ID** - -### Resource `coins.market_chart`: - -- `get_range_coins_market_chart` (`read`): This endpoint allows you to **get the historical chart data of a coin within certain time range in UNIX along with price, market cap and 24hr volume based on particular coin ID** - -### Resource `coins.ohlc`: - -- `get_range_coins_ohlc` (`read`): This endpoint allows you to **get the OHLC chart (Open, High, Low, Close) of a coin within a range of timestamp based on particular coin ID** - -### Resource `exchanges`: - -- `get_id_exchanges` (`read`): This endpoint allows you to **query exchange's data (name, year established, country, ...), exchange volume in BTC and top 100 tickers based on exchange's ID** -- `get_list_exchanges` (`read`): This endpoint allows you to **query all the exchanges with ID and name** - -### Resource `exchanges.tickers`: - -- `get_exchanges_tickers` (`read`): This endpoint allows you to **query exchange's tickers based on exchange's ID** - -### Resource `exchanges.volume_chart`: - -- `get_range_exchanges_volume_chart` (`read`): This endpoint allows you to **query the historical volume chart data in BTC by specifying date range in UNIX based on exchange's ID** - -### Resource `global`: - -- `get_global` (`read`): This endpoint allows you **query cryptocurrency global data including active cryptocurrencies, markets, total crypto market cap and etc** - -### Resource `nfts`: - -- `get_id_nfts` (`read`): This endpoint allows you to **query all the NFT data (name, floor price, 24hr volume ...) based on the NFT collection ID** -- `get_list_nfts` (`read`): This endpoint allows you to **query all supported NFTs with ID, contract address, name, asset platform ID and symbol on CoinGecko** -- `get_markets_nfts` (`read`): This endpoint allows you to **query all the supported NFT collections with floor price, market cap, volume and market related data on CoinGecko** - -### Resource `nfts.market_chart`: - -- `get_nfts_market_chart` (`read`): This endpoint allows you **query historical market data of a NFT collection, including floor price, market cap, and 24hr volume, by number of days away from now** - -### Resource `onchain.categories`: - -- `get_onchain_categories` (`read`): This endpoint allows you to **query all the supported categories on GeckoTerminal** -- `get_pools_onchain_categories` (`read`): This endpoint allows you to **query all the pools based on the provided category ID** - -### Resource `onchain.networks`: - -- `get_onchain_networks` (`read`): This endpoint allows you to **query all the supported networks on GeckoTerminal** - -### Resource `onchain.networks.new_pools`: - -- `get_networks_onchain_new_pools` (`read`): This endpoint allows you to **query all the latest pools across all networks on GeckoTerminal** -- `get_network_networks_onchain_new_pools` (`read`): This endpoint allows you to **query all the latest pools based on provided network** - -### Resource `onchain.networks.dexes`: - -- `get_networks_onchain_dexes` (`read`): This endpoint allows you to **query all the supported decentralized exchanges (DEXs) based on the provided network on GeckoTerminal** - -### Resource `onchain.networks.pools.multi`: - -- `get_addresses_pools_networks_onchain_multi` (`read`): This endpoint allows you to **query multiple pools based on the provided network and pool address** - -### Resource `onchain.networks.pools.info`: - -- `get_pools_networks_onchain_info` (`read`): This endpoint allows you to **query pool metadata (base and quote token details, image, socials, websites, description, contract address, etc.) based on a provided pool contract address on a network** - -### Resource `onchain.networks.pools.ohlcv`: - -- `get_timeframe_pools_networks_onchain_ohlcv` (`read`): This endpoint allows you to **get the OHLCV chart (Open, High, Low, Close, Volume) of a pool based on the provided pool address on a network** - -### Resource `onchain.networks.pools.trades`: - -- `get_pools_networks_onchain_trades` (`read`): This endpoint allows you to **query the last 300 trades in the past 24 hours based on the provided pool address** - -### Resource `onchain.networks.tokens.multi`: - -- `get_addresses_tokens_networks_onchain_multi` (`read`): This endpoint allows you to **query multiple tokens data based on the provided token contract addresses on a network** - -### Resource `onchain.networks.tokens.info`: - -- `get_tokens_networks_onchain_info` (`read`): This endpoint allows you to **query token metadata (name, symbol, CoinGecko ID, image, socials, websites, description, etc.) based on a provided token contract address on a network** - -### Resource `onchain.networks.tokens.top_holders`: - -- `get_tokens_networks_onchain_top_holders` (`read`): This endpoint allows you to **query top token holders based on the provided token contract address on a network** - -### Resource `onchain.networks.tokens.holders_chart`: - -- `get_tokens_networks_onchain_holders_chart` (`read`): This endpoint allows you to **get the historical token holders chart based on the provided token contract address on a network** - -### Resource `onchain.networks.tokens.ohlcv`: - -- `get_timeframe_tokens_networks_onchain_ohlcv` (`read`): This endpoint allows you to **get the OHLCV chart (Open, High, Low, Close, Volume) of a token based on the provided token address on a network** - -### Resource `onchain.networks.tokens.pools`: - -- `get_tokens_networks_onchain_pools` (`read`): This endpoint allows you to **query top pools based on the provided token contract address on a network** - -### Resource `onchain.networks.tokens.trades`: - -- `get_tokens_networks_onchain_trades` (`read`): This endpoint allows you to **query the last 300 trades in the past 24 hours, across all pools, based on the provided token contract address on a network** - -### Resource `onchain.networks.tokens.top_traders`: - -- `get_tokens_networks_onchain_top_traders` (`read`): This endpoint allows you to **query top token traders based on the provided token contract address on a network** - -### Resource `onchain.pools.megafilter`: - -- `get_pools_onchain_megafilter` (`read`): This endpoint allows you to **query pools based on various filters across all networks on GeckoTerminal** - -### Resource `onchain.pools.trending_search`: - -- `get_pools_onchain_trending_search` (`read`): This endpoint allows you to **query all the trending search pools across all networks on GeckoTerminal** - -### Resource `onchain.search.pools`: - -- `get_search_onchain_pools` (`read`): This endpoint allows you to **search for pools on a network** - -### Resource `onchain.simple.networks.token_price`: - -- `get_addresses_networks_simple_onchain_token_price` (`read`): This endpoint allows you to **get token price based on the provided token contract address on a network** - -### Resource `public_treasury`: - -- `get_holding_chart_public_treasury` (`read`): This endpoint allows you to **query historical cryptocurrency holdings chart of public companies & governments** by Entity ID and Coin ID -- `get_transaction_history_public_treasury` (`read`): This endpoint allows you **query public companies & governments' cryptocurrency transaction history** by Entity ID - -### Resource `search`: - -- `get_search` (`read`): This endpoint allows you to **search for coins, categories and markets listed on CoinGecko** - -### Resource `search.trending`: - -- `get_search_trending` (`read`): This endpoint allows you **query trending search coins, NFTs and categories on CoinGecko in the last 24 hours** - -### Resource `simple.price`: - -- `get_simple_price` (`read`): This endpoint allows you to **query the prices of one or more coins by using their unique Coin API IDs** - -### Resource `simple.supported_vs_currencies`: - -- `get_simple_supported_vs_currencies` (`read`): This endpoint allows you to **query all the supported currencies on CoinGecko** - -### Resource `simple.token_price`: - -- `get_id_simple_token_price` (`read`): This endpoint allows you to **query one or more token prices using their token contract addresses** diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 2141656..3ab9b52 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@coingecko/coingecko-mcp", - "version": "2.5.0", + "version": "3.0.0", "description": "The official MCP Server for the Coingecko API", "author": "Coingecko ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index be9903d..c3dd4f1 100644 --- a/packages/mcp-server/src/code-tool.ts +++ b/packages/mcp-server/src/code-tool.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, ToolCallResult, asTextContentResult } from './tools/types'; +import { McpTool, Metadata, ToolCallResult, asTextContentResult } from './types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { readEnv } from './server'; import { WorkerSuccess } from './code-tool-types'; @@ -13,7 +13,7 @@ import { WorkerSuccess } from './code-tool-types'; * * @param endpoints - The endpoints to include in the list. */ -export async function codeTool() { +export function codeTool(): McpTool { const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] }; const tool: Tool = { name: 'execute', diff --git a/packages/mcp-server/src/compat.ts b/packages/mcp-server/src/compat.ts deleted file mode 100644 index f84053c..0000000 --- a/packages/mcp-server/src/compat.ts +++ /dev/null @@ -1,483 +0,0 @@ -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import { z } from 'zod'; -import { Endpoint } from './tools'; - -export interface ClientCapabilities { - topLevelUnions: boolean; - validJson: boolean; - refs: boolean; - unions: boolean; - formats: boolean; - toolNameLength: number | undefined; -} - -export const defaultClientCapabilities: ClientCapabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, -}; - -export const ClientType = z.enum(['openai-agents', 'claude', 'claude-code', 'cursor', 'infer']); -export type ClientType = z.infer; - -// Client presets for compatibility -// Note that these could change over time as models get better, so this is -// a best effort. -export const knownClients: Record, ClientCapabilities> = { - 'openai-agents': { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - claude: { - topLevelUnions: true, - validJson: false, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - 'claude-code': { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - cursor: { - topLevelUnions: false, - validJson: true, - refs: false, - unions: false, - formats: false, - toolNameLength: 50, - }, -}; - -/** - * Attempts to parse strings into JSON objects - */ -export function parseEmbeddedJSON(args: Record, schema: Record) { - let updated = false; - const newArgs: Record = Object.assign({}, args); - - for (const [key, value] of Object.entries(newArgs)) { - if (typeof value === 'string') { - try { - const parsed = JSON.parse(value); - // Only parse if result is a plain object (not array, null, or primitive) - if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { - newArgs[key] = parsed; - updated = true; - } - } catch (e) { - // Not valid JSON, leave as is - } - } - } - - if (updated) { - return newArgs; - } - - return args; -} - -export type JSONSchema = { - type?: string; - properties?: Record; - required?: string[]; - anyOf?: JSONSchema[]; - $ref?: string; - $defs?: Record; - [key: string]: any; -}; - -/** - * Truncates tool names to the specified length while ensuring uniqueness. - * If truncation would cause duplicate names, appends a number to make them unique. - */ -export function truncateToolNames(names: string[], maxLength: number): Map { - if (maxLength <= 0) { - return new Map(); - } - - const renameMap = new Map(); - const usedNames = new Set(); - - const toTruncate = names.filter((name) => name.length > maxLength); - - if (toTruncate.length === 0) { - return renameMap; - } - - const willCollide = - new Set(toTruncate.map((name) => name.slice(0, maxLength - 1))).size < toTruncate.length; - - if (!willCollide) { - for (const name of toTruncate) { - const truncatedName = name.slice(0, maxLength); - renameMap.set(name, truncatedName); - } - } else { - const baseLength = maxLength - 1; - - for (const name of toTruncate) { - const baseName = name.slice(0, baseLength); - let counter = 1; - - while (usedNames.has(baseName + counter)) { - counter++; - } - - const finalName = baseName + counter; - renameMap.set(name, finalName); - usedNames.add(finalName); - } - } - - return renameMap; -} - -/** - * Removes top-level unions from a tool by splitting it into multiple tools, - * one for each variant in the union. - */ -export function removeTopLevelUnions(tool: Tool): Tool[] { - const inputSchema = tool.inputSchema as JSONSchema; - const variants = inputSchema.anyOf; - - if (!variants || !Array.isArray(variants) || variants.length === 0) { - return [tool]; - } - - const defs = inputSchema.$defs || {}; - - return variants.map((variant, index) => { - const variantSchema: JSONSchema = { - ...inputSchema, - ...variant, - type: 'object', - properties: { - ...(inputSchema.properties || {}), - ...(variant.properties || {}), - }, - }; - - delete variantSchema.anyOf; - - if (!variantSchema['description']) { - variantSchema['description'] = tool.description; - } - - const usedDefs = findUsedDefs(variant, defs); - if (Object.keys(usedDefs).length > 0) { - variantSchema.$defs = usedDefs; - } else { - delete variantSchema.$defs; - } - - return { - ...tool, - name: `${tool.name}_${toSnakeCase(variant['title'] || `variant${index + 1}`)}`, - description: variant['description'] || tool.description, - inputSchema: variantSchema, - } as Tool; - }); -} - -function findUsedDefs( - schema: JSONSchema, - defs: Record, - visited: Set = new Set(), -): Record { - const usedDefs: Record = {}; - - if (typeof schema !== 'object' || schema === null) { - return usedDefs; - } - - if (schema.$ref) { - const refParts = schema.$ref.split('/'); - if (refParts[0] === '#' && refParts[1] === '$defs' && refParts[2]) { - const defName = refParts[2]; - const def = defs[defName]; - if (def && !visited.has(schema.$ref)) { - usedDefs[defName] = def; - visited.add(schema.$ref); - Object.assign(usedDefs, findUsedDefs(def, defs, visited)); - visited.delete(schema.$ref); - } - } - return usedDefs; - } - - for (const key in schema) { - if (key !== '$defs' && typeof schema[key] === 'object' && schema[key] !== null) { - Object.assign(usedDefs, findUsedDefs(schema[key] as JSONSchema, defs, visited)); - } - } - - return usedDefs; -} - -// Export for testing -export { findUsedDefs }; - -/** - * Inlines all $refs in a schema, eliminating $defs. - * If a circular reference is detected, the circular property is removed. - */ -export function inlineRefs(schema: JSONSchema): JSONSchema { - if (!schema || typeof schema !== 'object') { - return schema; - } - - const clonedSchema = { ...schema }; - const defs: Record = schema.$defs || {}; - - delete clonedSchema.$defs; - - const result = inlineRefsRecursive(clonedSchema, defs, new Set()); - // The top level can never be null - return result === null ? {} : result; -} - -function inlineRefsRecursive( - schema: JSONSchema, - defs: Record, - refPath: Set, -): JSONSchema | null { - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => { - const processed = inlineRefsRecursive(item, defs, refPath); - return processed === null ? {} : processed; - }) as JSONSchema; - } - - const result = { ...schema }; - - if ('$ref' in result && typeof result.$ref === 'string') { - if (result.$ref.startsWith('#/$defs/')) { - const refName = result.$ref.split('/').pop() as string; - const def = defs[refName]; - - // If we've already seen this ref in our path, we have a circular reference - if (refPath.has(result.$ref)) { - // For circular references, we completely remove the property - // by returning null. The parent will remove it. - return null; - } - - if (def) { - const newRefPath = new Set(refPath); - newRefPath.add(result.$ref); - - const inlinedDef = inlineRefsRecursive({ ...def }, defs, newRefPath); - - if (inlinedDef === null) { - return { ...result }; - } - - // Merge the inlined definition with the original schema's properties - // but preserve things like description, etc. - const { $ref, ...rest } = result; - return { ...inlinedDef, ...rest }; - } - } - - // Keep external refs as-is - return result; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - const processed = inlineRefsRecursive(result[key] as JSONSchema, defs, refPath); - if (processed === null) { - // Remove properties that would cause circular references - delete result[key]; - } else { - result[key] = processed; - } - } - } - - return result; -} - -/** - * Removes anyOf fields from a schema, using only the first variant. - */ -export function removeAnyOf(schema: JSONSchema): JSONSchema { - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => removeAnyOf(item)) as JSONSchema; - } - - const result = { ...schema }; - - if ('anyOf' in result && Array.isArray(result.anyOf) && result.anyOf.length > 0) { - const firstVariant = result.anyOf[0]; - - if (firstVariant && typeof firstVariant === 'object') { - // Special handling for properties to ensure deep merge - if (firstVariant.properties && result.properties) { - result.properties = { - ...result.properties, - ...(firstVariant.properties as Record), - }; - } else if (firstVariant.properties) { - result.properties = { ...firstVariant.properties }; - } - - for (const key in firstVariant) { - if (key !== 'properties') { - result[key] = firstVariant[key]; - } - } - } - - delete result.anyOf; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - result[key] = removeAnyOf(result[key] as JSONSchema); - } - } - - return result; -} - -/** - * Removes format fields from a schema and appends them to the description. - */ -export function removeFormats(schema: JSONSchema, formatsCapability: boolean): JSONSchema { - if (formatsCapability) { - return schema; - } - - if (!schema || typeof schema !== 'object') { - return schema; - } - - if (Array.isArray(schema)) { - return schema.map((item) => removeFormats(item, formatsCapability)) as JSONSchema; - } - - const result = { ...schema }; - - if ('format' in result && typeof result['format'] === 'string') { - const formatStr = `(format: "${result['format']}")`; - - if ('description' in result && typeof result['description'] === 'string') { - result['description'] = `${result['description']} ${formatStr}`; - } else { - result['description'] = formatStr; - } - - delete result['format']; - } - - for (const key in result) { - if (result[key] && typeof result[key] === 'object') { - result[key] = removeFormats(result[key] as JSONSchema, formatsCapability); - } - } - - return result; -} - -/** - * Applies all compatibility transformations to the endpoints based on the provided capabilities. - */ -export function applyCompatibilityTransformations( - endpoints: Endpoint[], - capabilities: ClientCapabilities, -): Endpoint[] { - let transformedEndpoints = [...endpoints]; - - // Handle top-level unions first as this changes tool names - if (!capabilities.topLevelUnions) { - const newEndpoints: Endpoint[] = []; - - for (const endpoint of transformedEndpoints) { - const variantTools = removeTopLevelUnions(endpoint.tool); - - if (variantTools.length === 1) { - newEndpoints.push(endpoint); - } else { - for (const variantTool of variantTools) { - newEndpoints.push({ - ...endpoint, - tool: variantTool, - }); - } - } - } - - transformedEndpoints = newEndpoints; - } - - if (capabilities.toolNameLength) { - const toolNames = transformedEndpoints.map((endpoint) => endpoint.tool.name); - const renameMap = truncateToolNames(toolNames, capabilities.toolNameLength); - - transformedEndpoints = transformedEndpoints.map((endpoint) => ({ - ...endpoint, - tool: { - ...endpoint.tool, - name: renameMap.get(endpoint.tool.name) ?? endpoint.tool.name, - }, - })); - } - - if (!capabilities.refs || !capabilities.unions || !capabilities.formats) { - transformedEndpoints = transformedEndpoints.map((endpoint) => { - let schema = endpoint.tool.inputSchema as JSONSchema; - - if (!capabilities.refs) { - schema = inlineRefs(schema); - } - - if (!capabilities.unions) { - schema = removeAnyOf(schema); - } - - if (!capabilities.formats) { - schema = removeFormats(schema, capabilities.formats); - } - - return { - ...endpoint, - tool: { - ...endpoint.tool, - inputSchema: schema as typeof endpoint.tool.inputSchema, - }, - }; - }); - } - - return transformedEndpoints; -} - -function toSnakeCase(str: string): string { - return str - .replace(/\s+/g, '_') - .replace(/([a-z])([A-Z])/g, '$1_$2') - .toLowerCase(); -} diff --git a/packages/mcp-server/src/docs-search-tool.ts b/packages/mcp-server/src/docs-search-tool.ts index 4e401b3..d61461a 100644 --- a/packages/mcp-server/src/docs-search-tool.ts +++ b/packages/mcp-server/src/docs-search-tool.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { Metadata, asTextContentResult } from './tools/types'; +import { Metadata, asTextContentResult } from './types'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; diff --git a/packages/mcp-server/src/dynamic-tools.ts b/packages/mcp-server/src/dynamic-tools.ts deleted file mode 100644 index da9d3b9..0000000 --- a/packages/mcp-server/src/dynamic-tools.ts +++ /dev/null @@ -1,159 +0,0 @@ -import Coingecko from '@coingecko/coingecko-typescript'; -import { Endpoint, asTextContentResult, ToolCallResult } from './tools/types'; -import { zodToJsonSchema } from 'zod-to-json-schema'; -import { z } from 'zod'; -import { Cabidela } from '@cloudflare/cabidela'; - -function zodToInputSchema(schema: z.ZodSchema) { - return { - type: 'object' as const, - ...(zodToJsonSchema(schema) as any), - }; -} - -/** - * A list of tools that expose all the endpoints in the API dynamically. - * - * Instead of exposing every endpoint as its own tool, which uses up too many tokens for LLMs to use at once, - * we expose a single tool that can be used to search for endpoints by name, resource, operation, or tag, and then - * a generic endpoint that can be used to invoke any endpoint with the provided arguments. - * - * @param endpoints - The endpoints to include in the list. - */ -export function dynamicTools(endpoints: Endpoint[]): Endpoint[] { - const listEndpointsSchema = z.object({ - search_query: z - .string() - .optional() - .describe( - 'An optional search query to filter the endpoints by. Provide a partial name, resource, operation, or tag to filter the endpoints returned.', - ), - }); - - const listEndpointsTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'read' as const, - tags: [], - }, - tool: { - name: 'list_api_endpoints', - description: 'List or search for all endpoints in the Coingecko TypeScript API', - inputSchema: zodToInputSchema(listEndpointsSchema), - }, - handler: async ( - client: Coingecko, - args: Record | undefined, - ): Promise => { - const query = args && listEndpointsSchema.parse(args).search_query?.trim(); - - const filteredEndpoints = - query && query.length > 0 ? - endpoints.filter((endpoint) => { - const fieldsToMatch = [ - endpoint.tool.name, - endpoint.tool.description, - endpoint.metadata.resource, - endpoint.metadata.operation, - ...endpoint.metadata.tags, - ]; - return fieldsToMatch.some((field) => field && field.toLowerCase().includes(query.toLowerCase())); - }) - : endpoints; - - return asTextContentResult({ - tools: filteredEndpoints.map(({ tool, metadata }) => ({ - name: tool.name, - description: tool.description, - resource: metadata.resource, - operation: metadata.operation, - tags: metadata.tags, - })), - }); - }, - }; - - const getEndpointSchema = z.object({ - endpoint: z.string().describe('The name of the endpoint to get the schema for.'), - }); - const getEndpointTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'read' as const, - tags: [], - }, - tool: { - name: 'get_api_endpoint_schema', - description: - 'Get the schema for an endpoint in the Coingecko TypeScript API. You can use the schema returned by this tool to invoke an endpoint with the `invoke_api_endpoint` tool.', - inputSchema: zodToInputSchema(getEndpointSchema), - }, - handler: async (client: Coingecko, args: Record | undefined) => { - if (!args) { - throw new Error('No endpoint provided'); - } - const endpointName = getEndpointSchema.parse(args).endpoint; - - const endpoint = endpoints.find((e) => e.tool.name === endpointName); - if (!endpoint) { - throw new Error(`Endpoint ${endpointName} not found`); - } - return asTextContentResult(endpoint.tool); - }, - }; - - const invokeEndpointSchema = z.object({ - endpoint_name: z.string().describe('The name of the endpoint to invoke.'), - args: z - .record(z.string(), z.any()) - .describe( - 'The arguments to pass to the endpoint. This must match the schema returned by the `get_api_endpoint_schema` tool.', - ), - }); - - const invokeEndpointTool = { - metadata: { - resource: 'dynamic_tools', - operation: 'write' as const, - tags: [], - }, - tool: { - name: 'invoke_api_endpoint', - description: - 'Invoke an endpoint in the Coingecko TypeScript API. Note: use the `list_api_endpoints` tool to get the list of endpoints and `get_api_endpoint_schema` tool to get the schema for an endpoint.', - inputSchema: zodToInputSchema(invokeEndpointSchema), - }, - handler: async ( - client: Coingecko, - args: Record | undefined, - ): Promise => { - if (!args) { - throw new Error('No endpoint provided'); - } - const { success, data, error } = invokeEndpointSchema.safeParse(args); - if (!success) { - throw new Error(`Invalid arguments for endpoint. ${error?.format()}`); - } - const { endpoint_name, args: endpointArgs } = data; - - const endpoint = endpoints.find((e) => e.tool.name === endpoint_name); - if (!endpoint) { - throw new Error( - `Endpoint ${endpoint_name} not found. Use the \`list_api_endpoints\` tool to get the list of available endpoints.`, - ); - } - - try { - // Try to validate the arguments for a better error message - const cabidela = new Cabidela(endpoint.tool.inputSchema, { fullErrors: true }); - cabidela.validate(endpointArgs); - } catch (error) { - throw new Error(`Invalid arguments for endpoint ${endpoint_name}:\n${error}`); - } - - return await endpoint.handler(client, endpointArgs); - }, - }; - - return [getEndpointTool, listEndpointsTool, invokeEndpointTool]; -} diff --git a/packages/mcp-server/src/filtering.ts b/packages/mcp-server/src/filtering.ts deleted file mode 100644 index eaae0fc..0000000 --- a/packages/mcp-server/src/filtering.ts +++ /dev/null @@ -1,18 +0,0 @@ -// @ts-nocheck -import initJq from 'jq-web'; - -export async function maybeFilter(jqFilter: unknown | undefined, response: any): Promise { - if (jqFilter && typeof jqFilter === 'string') { - return await jq(response, jqFilter); - } else { - return response; - } -} - -async function jq(json: any, jqFilter: string) { - return (await initJq).json(json, jqFilter); -} - -export function isJqError(error: any): error is Error { - return error instanceof Error && 'stderr' in error; -} diff --git a/packages/mcp-server/src/http.ts b/packages/mcp-server/src/http.ts index 8451700..2366d8f 100644 --- a/packages/mcp-server/src/http.ts +++ b/packages/mcp-server/src/http.ts @@ -4,38 +4,21 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import express from 'express'; -import { fromError } from 'zod-validation-error/v3'; -import { McpOptions, parseQueryOptions } from './options'; +import { McpOptions } from './options'; import { ClientOptions, initMcpServer, newMcpServer } from './server'; import { parseAuthHeaders } from './headers'; const newServer = ({ clientOptions, - mcpOptions: defaultMcpOptions, req, res, }: { clientOptions: ClientOptions; - mcpOptions: McpOptions; req: express.Request; res: express.Response; }): McpServer | null => { const server = newMcpServer(); - let mcpOptions: McpOptions; - try { - mcpOptions = parseQueryOptions(defaultMcpOptions, req.query); - } catch (error) { - res.status(400).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: `Invalid request: ${fromError(error)}`, - }, - }); - return null; - } - try { const authOptions = parseAuthHeaders(req); initMcpServer({ @@ -44,7 +27,6 @@ const newServer = ({ ...clientOptions, ...authOptions, }, - mcpOptions, }); } catch (error) { res.status(401).json({ diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 4850a0e..0f6dd42 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -1,20 +1,15 @@ #!/usr/bin/env node import { selectTools } from './server'; -import { Endpoint, endpoints } from './tools'; import { McpOptions, parseCLIOptions } from './options'; import { launchStdioServer } from './stdio'; import { launchStreamableHTTPServer } from './http'; +import type { McpTool } from './types'; async function main() { const options = parseOptionsOrError(); - if (options.list) { - listAllTools(); - return; - } - - const selectedTools = await selectToolsOrError(endpoints, options); + const selectedTools = await selectToolsOrError(options); console.error( `MCP Server starting with ${selectedTools.length} tools:`, @@ -23,7 +18,7 @@ async function main() { switch (options.transport) { case 'stdio': - await launchStdioServer(options); + await launchStdioServer(); break; case 'http': await launchStreamableHTTPServer(options, options.port ?? options.socket); @@ -47,9 +42,9 @@ function parseOptionsOrError() { } } -async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): Promise { +async function selectToolsOrError(options: McpOptions): Promise { try { - const includedTools = await selectTools(endpoints, options); + const includedTools = selectTools(options); if (includedTools.length === 0) { console.error('No tools match the provided filters.'); process.exit(1); @@ -64,45 +59,3 @@ async function selectToolsOrError(endpoints: Endpoint[], options: McpOptions): P process.exit(1); } } - -function listAllTools() { - if (endpoints.length === 0) { - console.log('No tools available.'); - return; - } - console.log('Available tools:\n'); - - // Group endpoints by resource - const resourceGroups = new Map(); - - for (const endpoint of endpoints) { - const resource = endpoint.metadata.resource; - if (!resourceGroups.has(resource)) { - resourceGroups.set(resource, []); - } - resourceGroups.get(resource)!.push(endpoint); - } - - // Sort resources alphabetically - const sortedResources = Array.from(resourceGroups.keys()).sort(); - - // Display hierarchically by resource - for (const resource of sortedResources) { - console.log(`Resource: ${resource}`); - - const resourceEndpoints = resourceGroups.get(resource)!; - // Sort endpoints by tool name - resourceEndpoints.sort((a, b) => a.tool.name.localeCompare(b.tool.name)); - - for (const endpoint of resourceEndpoints) { - const { - tool, - metadata: { operation, tags }, - } = endpoint; - - console.log(` - ${tool.name} (${operation}) ${tags.length > 0 ? `tags: ${tags.join(', ')}` : ''}`); - console.log(` Description: ${tool.description}`); - } - console.log(''); - } -} diff --git a/packages/mcp-server/src/options.ts b/packages/mcp-server/src/options.ts index b6ff597..6c8bb8d 100644 --- a/packages/mcp-server/src/options.ts +++ b/packages/mcp-server/src/options.ts @@ -2,140 +2,31 @@ import qs from 'qs'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import z from 'zod'; -import { endpoints, Filter } from './tools'; -import { ClientCapabilities, knownClients, ClientType } from './compat'; export type CLIOptions = McpOptions & { - list: boolean; transport: 'stdio' | 'http'; port: number | undefined; socket: string | undefined; }; export type McpOptions = { - client?: ClientType | undefined; - includeDynamicTools?: boolean | undefined; - includeAllTools?: boolean | undefined; - includeCodeTools?: boolean | undefined; includeDocsTools?: boolean | undefined; - filters?: Filter[] | undefined; - capabilities?: Partial | undefined; }; -const CAPABILITY_CHOICES = [ - 'top-level-unions', - 'valid-json', - 'refs', - 'unions', - 'formats', - 'tool-name-length', -] as const; - -type Capability = (typeof CAPABILITY_CHOICES)[number]; - -function parseCapabilityValue(cap: string): { name: Capability; value?: number } { - if (cap.startsWith('tool-name-length=')) { - const parts = cap.split('='); - if (parts.length === 2) { - const length = parseInt(parts[1]!, 10); - if (!isNaN(length)) { - return { name: 'tool-name-length', value: length }; - } - throw new Error(`Invalid tool-name-length value: ${parts[1]}. Expected a number.`); - } - throw new Error(`Invalid format for tool-name-length. Expected tool-name-length=N.`); - } - if (!CAPABILITY_CHOICES.includes(cap as Capability)) { - throw new Error(`Unknown capability: ${cap}. Valid capabilities are: ${CAPABILITY_CHOICES.join(', ')}`); - } - return { name: cap as Capability }; -} - export function parseCLIOptions(): CLIOptions { const opts = yargs(hideBin(process.argv)) .option('tools', { type: 'string', array: true, - choices: ['dynamic', 'all', 'code', 'docs'], + choices: ['code', 'docs'], description: 'Use dynamic tools or all tools', }) .option('no-tools', { type: 'string', array: true, - choices: ['dynamic', 'all', 'code', 'docs'], + choices: ['code', 'docs'], description: 'Do not use any dynamic or all tools', }) - .option('tool', { - type: 'string', - array: true, - description: 'Include tools matching the specified names', - }) - .option('resource', { - type: 'string', - array: true, - description: 'Include tools matching the specified resources', - }) - .option('operation', { - type: 'string', - array: true, - choices: ['read', 'write'], - description: 'Include tools matching the specified operations', - }) - .option('tag', { - type: 'string', - array: true, - description: 'Include tools with the specified tags', - }) - .option('no-tool', { - type: 'string', - array: true, - description: 'Exclude tools matching the specified names', - }) - .option('no-resource', { - type: 'string', - array: true, - description: 'Exclude tools matching the specified resources', - }) - .option('no-operation', { - type: 'string', - array: true, - description: 'Exclude tools matching the specified operations', - }) - .option('no-tag', { - type: 'string', - array: true, - description: 'Exclude tools with the specified tags', - }) - .option('list', { - type: 'boolean', - description: 'List all tools and exit', - }) - .option('client', { - type: 'string', - choices: Object.keys(knownClients), - description: 'Specify the MCP client being used', - }) - .option('capability', { - type: 'string', - array: true, - description: 'Specify client capabilities', - coerce: (values: string[]) => { - return values.flatMap((v) => v.split(',')); - }, - }) - .option('no-capability', { - type: 'string', - array: true, - description: 'Unset client capabilities', - choices: CAPABILITY_CHOICES, - coerce: (values: string[]) => { - return values.flatMap((v) => v.split(',')); - }, - }) - .option('describe-capabilities', { - type: 'boolean', - description: 'Print detailed explanation of client capabilities and exit', - }) .option('transport', { type: 'string', choices: ['stdio', 'http'], @@ -152,122 +43,19 @@ export function parseCLIOptions(): CLIOptions { }) .help(); - for (const [command, desc] of examples()) { - opts.example(command, desc); - } - const argv = opts.parseSync(); - // Handle describe-capabilities flag - if (argv.describeCapabilities) { - console.log(getCapabilitiesExplanation()); - process.exit(0); - } - - const filters: Filter[] = []; - - // Helper function to support comma-separated values - const splitValues = (values: string[] | undefined): string[] => { - if (!values) return []; - return values.flatMap((v) => v.split(',')); - }; - - for (const tag of splitValues(argv.tag)) { - filters.push({ type: 'tag', op: 'include', value: tag }); - } - - for (const tag of splitValues(argv.noTag)) { - filters.push({ type: 'tag', op: 'exclude', value: tag }); - } - - for (const resource of splitValues(argv.resource)) { - filters.push({ type: 'resource', op: 'include', value: resource }); - } - - for (const resource of splitValues(argv.noResource)) { - filters.push({ type: 'resource', op: 'exclude', value: resource }); - } - - for (const tool of splitValues(argv.tool)) { - filters.push({ type: 'tool', op: 'include', value: tool }); - } - - for (const tool of splitValues(argv.noTool)) { - filters.push({ type: 'tool', op: 'exclude', value: tool }); - } - - for (const operation of splitValues(argv.operation)) { - filters.push({ type: 'operation', op: 'include', value: operation }); - } - - for (const operation of splitValues(argv.noOperation)) { - filters.push({ type: 'operation', op: 'exclude', value: operation }); - } - - // Parse client capabilities - const clientCapabilities: Partial = {}; - - // Apply individual capability overrides - if (Array.isArray(argv.capability)) { - for (const cap of argv.capability) { - const parsedCap = parseCapabilityValue(cap); - if (parsedCap.name === 'top-level-unions') { - clientCapabilities.topLevelUnions = true; - } else if (parsedCap.name === 'valid-json') { - clientCapabilities.validJson = true; - } else if (parsedCap.name === 'refs') { - clientCapabilities.refs = true; - } else if (parsedCap.name === 'unions') { - clientCapabilities.unions = true; - } else if (parsedCap.name === 'formats') { - clientCapabilities.formats = true; - } else if (parsedCap.name === 'tool-name-length') { - clientCapabilities.toolNameLength = parsedCap.value; - } - } - } - - // Handle no-capability options to unset capabilities - if (Array.isArray(argv.noCapability)) { - for (const cap of argv.noCapability) { - if (cap === 'top-level-unions') { - clientCapabilities.topLevelUnions = false; - } else if (cap === 'valid-json') { - clientCapabilities.validJson = false; - } else if (cap === 'refs') { - clientCapabilities.refs = false; - } else if (cap === 'unions') { - clientCapabilities.unions = false; - } else if (cap === 'formats') { - clientCapabilities.formats = false; - } else if (cap === 'tool-name-length') { - clientCapabilities.toolNameLength = undefined; - } - } - } - - const shouldIncludeToolType = (toolType: 'dynamic' | 'all' | 'code' | 'docs') => + const shouldIncludeToolType = (toolType: 'code' | 'docs') => argv.noTools?.includes(toolType) ? false : argv.tools?.includes(toolType) ? true : undefined; - const includeDynamicTools = shouldIncludeToolType('dynamic'); - const includeAllTools = shouldIncludeToolType('all'); - const includeCodeTools = shouldIncludeToolType('code'); const includeDocsTools = shouldIncludeToolType('docs'); const transport = argv.transport as 'stdio' | 'http'; - const client = argv.client as ClientType; return { - client: client && client !== 'infer' && knownClients[client] ? client : undefined, - includeDynamicTools, - includeAllTools, - includeCodeTools, includeDocsTools, - filters, - capabilities: clientCapabilities, - list: argv.list || false, transport, port: argv.port, socket: argv.socket, @@ -284,190 +72,21 @@ const coerceArray = (zodType: T) => ); const QueryOptions = z.object({ - tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe('Specify which MCP tools to use'), - no_tools: coerceArray(z.enum(['dynamic', 'all', 'code', 'docs'])).describe( - 'Specify which MCP tools to not use.', - ), + tools: coerceArray(z.enum(['code', 'docs'])).describe('Specify which MCP tools to use'), + no_tools: coerceArray(z.enum(['code', 'docs'])).describe('Specify which MCP tools to not use.'), tool: coerceArray(z.string()).describe('Include tools matching the specified names'), - resource: coerceArray(z.string()).describe('Include tools matching the specified resources'), - operation: coerceArray(z.enum(['read', 'write'])).describe( - 'Include tools matching the specified operations', - ), - tag: coerceArray(z.string()).describe('Include tools with the specified tags'), - no_tool: coerceArray(z.string()).describe('Exclude tools matching the specified names'), - no_resource: coerceArray(z.string()).describe('Exclude tools matching the specified resources'), - no_operation: coerceArray(z.enum(['read', 'write'])).describe( - 'Exclude tools matching the specified operations', - ), - no_tag: coerceArray(z.string()).describe('Exclude tools with the specified tags'), - client: ClientType.optional().describe('Specify the MCP client being used'), - capability: coerceArray(z.string()).describe('Specify client capabilities'), - no_capability: coerceArray(z.enum(CAPABILITY_CHOICES)).describe('Unset client capabilities'), }); export function parseQueryOptions(defaultOptions: McpOptions, query: unknown): McpOptions { const queryObject = typeof query === 'string' ? qs.parse(query) : query; const queryOptions = QueryOptions.parse(queryObject); - const filters: Filter[] = [...(defaultOptions.filters ?? [])]; - - for (const resource of queryOptions.resource || []) { - filters.push({ type: 'resource', op: 'include', value: resource }); - } - for (const operation of queryOptions.operation || []) { - filters.push({ type: 'operation', op: 'include', value: operation }); - } - for (const tag of queryOptions.tag || []) { - filters.push({ type: 'tag', op: 'include', value: tag }); - } - for (const tool of queryOptions.tool || []) { - filters.push({ type: 'tool', op: 'include', value: tool }); - } - for (const resource of queryOptions.no_resource || []) { - filters.push({ type: 'resource', op: 'exclude', value: resource }); - } - for (const operation of queryOptions.no_operation || []) { - filters.push({ type: 'operation', op: 'exclude', value: operation }); - } - for (const tag of queryOptions.no_tag || []) { - filters.push({ type: 'tag', op: 'exclude', value: tag }); - } - for (const tool of queryOptions.no_tool || []) { - filters.push({ type: 'tool', op: 'exclude', value: tool }); - } - - // Parse client capabilities - const clientCapabilities: Partial = { ...defaultOptions.capabilities }; - - for (const cap of queryOptions.capability || []) { - const parsed = parseCapabilityValue(cap); - if (parsed.name === 'top-level-unions') { - clientCapabilities.topLevelUnions = true; - } else if (parsed.name === 'valid-json') { - clientCapabilities.validJson = true; - } else if (parsed.name === 'refs') { - clientCapabilities.refs = true; - } else if (parsed.name === 'unions') { - clientCapabilities.unions = true; - } else if (parsed.name === 'formats') { - clientCapabilities.formats = true; - } else if (parsed.name === 'tool-name-length') { - clientCapabilities.toolNameLength = parsed.value; - } - } - - for (const cap of queryOptions.no_capability || []) { - if (cap === 'top-level-unions') { - clientCapabilities.topLevelUnions = false; - } else if (cap === 'valid-json') { - clientCapabilities.validJson = false; - } else if (cap === 'refs') { - clientCapabilities.refs = false; - } else if (cap === 'unions') { - clientCapabilities.unions = false; - } else if (cap === 'formats') { - clientCapabilities.formats = false; - } else if (cap === 'tool-name-length') { - clientCapabilities.toolNameLength = undefined; - } - } - - let dynamicTools: boolean | undefined = - queryOptions.no_tools && queryOptions.no_tools?.includes('dynamic') ? false - : queryOptions.tools?.includes('dynamic') ? true - : defaultOptions.includeDynamicTools; - - let allTools: boolean | undefined = - queryOptions.no_tools && queryOptions.no_tools?.includes('all') ? false - : queryOptions.tools?.includes('all') ? true - : defaultOptions.includeAllTools; - let docsTools: boolean | undefined = queryOptions.no_tools && queryOptions.no_tools?.includes('docs') ? false : queryOptions.tools?.includes('docs') ? true : defaultOptions.includeDocsTools; - let codeTools: boolean | undefined = - queryOptions.no_tools && queryOptions.no_tools?.includes('code') ? false - : queryOptions.tools?.includes('code') && defaultOptions.includeCodeTools ? true - : defaultOptions.includeCodeTools; - return { - client: queryOptions.client ?? defaultOptions.client, - includeDynamicTools: dynamicTools, - includeAllTools: allTools, - includeCodeTools: codeTools, includeDocsTools: docsTools, - filters, - capabilities: clientCapabilities, }; } - -function getCapabilitiesExplanation(): string { - return ` -Client Capabilities Explanation: - -Different Language Models (LLMs) and the MCP clients that use them have varying limitations in how they handle tool schemas. Capability flags allow you to inform the MCP server about these limitations. - -When a capability flag is set to false, the MCP server will automatically adjust the tool schemas to work around that limitation, ensuring broader compatibility. - -Available Capabilities: - -# top-level-unions -Some clients/LLMs do not support JSON schemas with a union type (anyOf) at the root level. If a client lacks this capability, the MCP server splits tools with top-level unions into multiple separate tools, one for each variant in the union. - -# refs -Some clients/LLMs do not support $ref pointers for schema reuse. If a client lacks this capability, the MCP server automatically inlines all references ($defs) directly into the schema. Properties that would cause circular references are removed during this process. - -# valid-json -Some clients/LLMs may incorrectly send arguments as a JSON-encoded string instead of a proper JSON object. If a client *has* this capability, the MCP server will attempt to parse string values as JSON if the initial validation against the schema fails. - -# unions -Some clients/LLMs do not support union types (anyOf) in JSON schemas. If a client lacks this capability, the MCP server removes all anyOf fields and uses only the first variant as the schema. - -# formats -Some clients/LLMs do not support the 'format' keyword in JSON Schema specifications. If a client lacks this capability, the MCP server removes all format fields and appends the format information to the field's description in parentheses. - -# tool-name-length=N -Some clients/LLMs impose a maximum length on tool names. If this capability is set, the MCP server will automatically truncate tool names exceeding the specified length (N), ensuring uniqueness by appending numbers if necessary. - -Client Presets (--client): -Presets like '--client=openai-agents' or '--client=cursor' automatically configure these capabilities based on current known limitations of those clients, simplifying setup. - -Current presets: -${JSON.stringify(knownClients, null, 2)} - `; -} - -function examples(): [string, string][] { - const firstEndpoint = endpoints[0]!; - const secondEndpoint = - endpoints.find((e) => e.metadata.resource !== firstEndpoint.metadata.resource) || endpoints[1]; - const tag = endpoints.find((e) => e.metadata.tags.length > 0)?.metadata.tags[0]; - const otherEndpoint = secondEndpoint || firstEndpoint; - - return [ - [ - `--tool="${firstEndpoint.tool.name}" ${secondEndpoint ? `--tool="${secondEndpoint.tool.name}"` : ''}`, - 'Include tools by name', - ], - [ - `--resource="${firstEndpoint.metadata.resource}" --operation="read"`, - 'Filter by resource and operation', - ], - [ - `--resource="${otherEndpoint.metadata.resource}*" --no-tool="${otherEndpoint.tool.name}"`, - 'Use resource wildcards and exclusions', - ], - [`--client="cursor"`, 'Adjust schemas to be more compatible with Cursor'], - [ - `--capability="top-level-unions" --capability="tool-name-length=40"`, - 'Specify individual client capabilities', - ], - [ - `--client="cursor" --no-capability="tool-name-length"`, - 'Use cursor client preset but remove tool name length limit', - ], - ...(tag ? [[`--tag="${tag}"`, 'Filter based on tags'] as [string, string]] : []), - ]; -} diff --git a/packages/mcp-server/src/server.ts b/packages/mcp-server/src/server.ts index 231073b..94b4beb 100644 --- a/packages/mcp-server/src/server.ts +++ b/packages/mcp-server/src/server.ts @@ -2,39 +2,26 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { Endpoint, endpoints, HandlerFunction, query } from './tools'; import { CallToolRequestSchema, ListToolsRequestSchema, SetLevelRequestSchema, - Implementation, - Tool, } from '@modelcontextprotocol/sdk/types.js'; import { ClientOptions } from '@coingecko/coingecko-typescript'; import Coingecko from '@coingecko/coingecko-typescript'; -import { - applyCompatibilityTransformations, - ClientCapabilities, - defaultClientCapabilities, - knownClients, - parseEmbeddedJSON, -} from './compat'; -import { dynamicTools } from './dynamic-tools'; import { codeTool } from './code-tool'; import docsSearchTool from './docs-search-tool'; import { McpOptions } from './options'; +import { HandlerFunction, McpTool } from './types'; export { McpOptions } from './options'; -export { ClientType } from './compat'; -export { Filter } from './tools'; export { ClientOptions } from '@coingecko/coingecko-typescript'; -export { endpoints } from './tools'; export const newMcpServer = () => new McpServer( { name: 'coingecko_coingecko_typescript_api', - version: '2.5.0', + version: '3.0.0', }, { capabilities: { tools: {}, logging: {} } }, ); @@ -52,25 +39,6 @@ export function initMcpServer(params: { mcpOptions?: McpOptions; }) { const server = params.server instanceof McpServer ? params.server.server : params.server; - const mcpOptions = params.mcpOptions ?? {}; - - let providedEndpoints: Endpoint[] | null = null; - let endpointMap: Record | null = null; - - const initTools = async (implementation?: Implementation) => { - if (implementation && (!mcpOptions.client || mcpOptions.client === 'infer')) { - mcpOptions.client = - implementation.name.toLowerCase().includes('claude') ? 'claude' - : implementation.name.toLowerCase().includes('cursor') ? 'cursor' - : undefined; - mcpOptions.capabilities = { - ...(mcpOptions.client && knownClients[mcpOptions.client]), - ...mcpOptions.capabilities, - }; - } - providedEndpoints ??= await selectTools(endpoints, mcpOptions); - endpointMap ??= Object.fromEntries(providedEndpoints.map((endpoint) => [endpoint.tool.name, endpoint])); - }; const logAtLevel = (level: 'debug' | 'info' | 'warning' | 'error') => @@ -97,26 +65,23 @@ export function initMcpServer(params: { }, }); + const providedTools = selectTools(params.mcpOptions); + const toolMap = Object.fromEntries(providedTools.map((mcpTool) => [mcpTool.tool.name, mcpTool])); + server.setRequestHandler(ListToolsRequestSchema, async () => { - if (providedEndpoints === null) { - await initTools(server.getClientVersion()); - } return { - tools: providedEndpoints!.map((endpoint) => endpoint.tool), + tools: providedTools.map((mcpTool) => mcpTool.tool), }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (endpointMap === null) { - await initTools(server.getClientVersion()); - } const { name, arguments: args } = request.params; - const endpoint = endpointMap![name]; - if (!endpoint) { + const mcpTool = toolMap[name]; + if (!mcpTool) { throw new Error(`Unknown tool: ${name}`); } - return executeHandler(endpoint.tool, endpoint.handler, client, args, mcpOptions.capabilities); + return executeHandler(mcpTool.handler, client, args); }); server.setRequestHandler(SetLevelRequestSchema, async (request) => { @@ -146,47 +111,22 @@ export function initMcpServer(params: { /** * Selects the tools to include in the MCP Server based on the provided options. */ -export async function selectTools(endpoints: Endpoint[], options?: McpOptions): Promise { - const filteredEndpoints = query(options?.filters ?? [], endpoints); - - let includedTools = filteredEndpoints.slice(); - - if (includedTools.length > 0) { - if (options?.includeDynamicTools) { - includedTools = dynamicTools(includedTools); - } - } else { - if (options?.includeAllTools) { - includedTools = endpoints.slice(); - } else if (options?.includeDynamicTools) { - includedTools = dynamicTools(endpoints); - } else if (options?.includeCodeTools) { - includedTools = [await codeTool()]; - } else { - includedTools = endpoints.slice(); - } - } +export function selectTools(options?: McpOptions): McpTool[] { + const includedTools = [codeTool()]; if (options?.includeDocsTools ?? true) { includedTools.push(docsSearchTool); } - const capabilities = { ...defaultClientCapabilities, ...options?.capabilities }; - return applyCompatibilityTransformations(includedTools, capabilities); + return includedTools; } /** * Runs the provided handler with the given client and arguments. */ export async function executeHandler( - tool: Tool, handler: HandlerFunction, client: Coingecko, args: Record | undefined, - compatibilityOptions?: Partial, ) { - const options = { ...defaultClientCapabilities, ...compatibilityOptions }; - if (!options.validJson && args) { - args = parseEmbeddedJSON(args, tool.inputSchema); - } return await handler(client, args || {}); } diff --git a/packages/mcp-server/src/stdio.ts b/packages/mcp-server/src/stdio.ts index d902a5b..f07696f 100644 --- a/packages/mcp-server/src/stdio.ts +++ b/packages/mcp-server/src/stdio.ts @@ -1,11 +1,10 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { initMcpServer, newMcpServer } from './server'; -import { McpOptions } from './options'; -export const launchStdioServer = async (options: McpOptions) => { +export const launchStdioServer = async () => { const server = newMcpServer(); - initMcpServer({ server, mcpOptions: options }); + initMcpServer({ server }); const transport = new StdioServerTransport(); await server.connect(transport); diff --git a/packages/mcp-server/src/tools.ts b/packages/mcp-server/src/tools.ts deleted file mode 100644 index 7e516de..0000000 --- a/packages/mcp-server/src/tools.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tools/index'; diff --git a/packages/mcp-server/src/tools/asset-platforms/get-asset-platforms.ts b/packages/mcp-server/src/tools/asset-platforms/get-asset-platforms.ts deleted file mode 100644 index f944bd0..0000000 --- a/packages/mcp-server/src/tools/asset-platforms/get-asset-platforms.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'asset_platforms', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/asset_platforms', - operationId: 'asset-platforms-list', -}; - -export const tool: Tool = { - name: 'get_asset_platforms', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the asset platforms on CoinGecko**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/asset_platform_get_response',\n $defs: {\n asset_platform_get_response: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'asset platform ID'\n },\n chain_identifier: {\n type: 'number',\n description: 'chainlist\\'s chain ID'\n },\n image: {\n type: 'object',\n description: 'image of the asset platform',\n properties: {\n large: {\n type: 'string'\n },\n small: {\n type: 'string'\n },\n thumb: {\n type: 'string'\n }\n }\n },\n name: {\n type: 'string',\n description: 'chain name'\n },\n native_coin_id: {\n type: 'string',\n description: 'chain native coin ID'\n },\n shortname: {\n type: 'string',\n description: 'chain shortname'\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - filter: { - type: 'string', - description: 'apply relevant filters to results', - enum: ['nft'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.assetPlatforms.get(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/coins/categories/get-list-coins-categories.ts b/packages/mcp-server/src/tools/coins/categories/get-list-coins-categories.ts deleted file mode 100644 index 5566e53..0000000 --- a/packages/mcp-server/src/tools/coins/categories/get-list-coins-categories.ts +++ /dev/null @@ -1,51 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'coins.categories', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/coins/categories/list', - operationId: 'coins-categories-list', -}; - -export const tool: Tool = { - name: 'get_list_coins_categories', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the coins categories on CoinGecko**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/category_get_list_response',\n $defs: {\n category_get_list_response: {\n type: 'object',\n properties: {\n category_id: {\n type: 'string',\n description: 'category ID'\n },\n name: {\n type: 'string',\n description: 'category name'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.coins.categories.getList())); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/coins/contract/get-coins-contract.ts b/packages/mcp-server/src/tools/coins/contract/get-coins-contract.ts deleted file mode 100644 index 723d1ba..0000000 --- a/packages/mcp-server/src/tools/coins/contract/get-coins-contract.ts +++ /dev/null @@ -1,50 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'coins.contract', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/coins/{id}/contract/{contract_address}', - operationId: 'coins-contract-address', -}; - -export const tool: Tool = { - name: 'get_coins_contract', - description: - 'This endpoint allows you to **query all the metadata (image, websites, socials, description, contract address, etc.) and market data (price, ATH, exchange tickers, etc.) of a coin from the CoinGecko coin page based on an asset platform and a particular token contract address**', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - contract_address: { - type: 'string', - }, - }, - required: ['id', 'contract_address'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { contract_address, ...body } = args as any; - try { - return asTextContentResult(await client.coins.contract.get(contract_address, body)); - } catch (error) { - if (error instanceof Coingecko.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/coins/contract/market-chart/get-range-contract-coins-market-chart.ts b/packages/mcp-server/src/tools/coins/contract/market-chart/get-range-contract-coins-market-chart.ts deleted file mode 100644 index 0f9b0a8..0000000 --- a/packages/mcp-server/src/tools/coins/contract/market-chart/get-range-contract-coins-market-chart.ts +++ /dev/null @@ -1,105 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'coins.contract.market_chart', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/coins/{id}/contract/{contract_address}/market_chart/range', - operationId: 'contract-address-market-chart-range', -}; - -export const tool: Tool = { - name: 'get_range_contract_coins_market_chart', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **get the historical chart data within certain time range in UNIX along with price, market cap and 24hr volume based on asset platform and particular token contract address**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/market_chart_get_range_response',\n $defs: {\n market_chart_get_range_response: {\n type: 'object',\n properties: {\n market_caps: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n },\n prices: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n },\n total_volumes: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - contract_address: { - type: 'string', - }, - from: { - type: 'string', - description: - 'starting date in ISO date string (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM`) or UNIX timestamp. \n **use ISO date string for best compatibility**', - }, - to: { - type: 'string', - description: - 'ending date in ISO date string (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM`) or UNIX timestamp. \n **use ISO date string for best compatibility**', - }, - vs_currency: { - type: 'string', - description: - 'target currency of market data \n *refers to [`/simple/supported_vs_currencies`](/reference/simple-supported-currencies).', - }, - interval: { - type: 'string', - description: 'data interval, leave empty for auto granularity', - enum: ['5m', 'hourly', 'daily'], - }, - precision: { - type: 'string', - description: 'decimal place for currency price value', - enum: [ - 'full', - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '10', - '11', - '12', - '13', - '14', - '15', - '16', - '17', - '18', - ], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id', 'contract_address', 'from', 'to', 'vs_currency'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { contract_address, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.coins.contract.marketChart.getRange(contract_address, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/coins/get-id-coins.ts b/packages/mcp-server/src/tools/coins/get-id-coins.ts deleted file mode 100644 index e5e4a36..0000000 --- a/packages/mcp-server/src/tools/coins/get-id-coins.ts +++ /dev/null @@ -1,81 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'coins', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/coins/{id}', - operationId: 'coins-id', -}; - -export const tool: Tool = { - name: 'get_id_coins', - description: - 'This endpoint allows you to **query all the metadata (image, websites, socials, description, contract address, etc.) and market data (price, ATH, exchange tickers, etc.) of a coin from the CoinGecko coin page based on a particular coin ID**', - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - community_data: { - type: 'boolean', - description: 'include community data, default: true', - }, - developer_data: { - type: 'boolean', - description: 'include developer data, default: true', - }, - dex_pair_format: { - type: 'string', - description: - 'set to `symbol` to display DEX pair base and target as symbols, default: `contract_address`', - enum: ['contract_address', 'symbol'], - }, - include_categories_details: { - type: 'boolean', - description: 'include categories details, default: false', - }, - localization: { - type: 'boolean', - description: 'include all the localized languages in the response, default: true', - }, - market_data: { - type: 'boolean', - description: 'include market data, default: true', - }, - sparkline: { - type: 'boolean', - description: 'include sparkline 7 days data, default: false', - }, - tickers: { - type: 'boolean', - description: 'include tickers data, default: true', - }, - }, - required: ['id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { id, ...body } = args as any; - try { - return asTextContentResult(await client.coins.getID(id, body)); - } catch (error) { - if (error instanceof Coingecko.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/coins/history/get-coins-history.ts b/packages/mcp-server/src/tools/coins/history/get-coins-history.ts deleted file mode 100644 index b6732d8..0000000 --- a/packages/mcp-server/src/tools/coins/history/get-coins-history.ts +++ /dev/null @@ -1,62 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'coins.history', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/coins/{id}/history', - operationId: 'coins-id-history', -}; - -export const tool: Tool = { - name: 'get_coins_history', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query the historical data (price, market cap, 24hrs volume, ...) at a given date for a coin based on a particular coin ID**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/history_get_response',\n $defs: {\n history_get_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'coin ID'\n },\n community_data: {\n type: 'object',\n description: 'coin community data',\n properties: {\n facebook_likes: {\n type: 'number',\n description: 'coin facebook likes'\n },\n reddit_accounts_active_48h: {\n type: 'number',\n description: 'coin reddit accounts active 48h'\n },\n reddit_average_comments_48h: {\n type: 'number',\n description: 'coin reddit average comments 48h'\n },\n reddit_average_posts_48h: {\n type: 'number',\n description: 'coin reddit average posts 48h'\n },\n reddit_subscribers: {\n type: 'number',\n description: 'coin reddit subscribers'\n }\n }\n },\n developer_data: {\n type: 'object',\n description: 'coin developer data',\n properties: {\n closed_issues: {\n type: 'number',\n description: 'coin repository closed issues'\n },\n code_additions_deletions_4_weeks: {\n type: 'object',\n description: 'coin code additions deletions 4 weeks',\n properties: {\n additions: {\n type: 'number'\n },\n deletions: {\n type: 'number'\n }\n }\n },\n commit_count_4_weeks: {\n type: 'number',\n description: 'coin commit count 4 weeks'\n },\n forks: {\n type: 'number',\n description: 'coin repository forks'\n },\n pull_request_contributors: {\n type: 'number',\n description: 'coin repository pull request contributors'\n },\n pull_requests_merged: {\n type: 'number',\n description: 'coin repository pull requests merged'\n },\n stars: {\n type: 'number',\n description: 'coin repository stars'\n },\n subscribers: {\n type: 'number',\n description: 'coin repository subscribers'\n },\n total_issues: {\n type: 'number',\n description: 'coin repository total issues'\n }\n }\n },\n image: {\n type: 'object',\n description: 'coin image url',\n properties: {\n small: {\n type: 'string'\n },\n thumb: {\n type: 'string'\n }\n }\n },\n localization: {\n type: 'object',\n description: 'coin localization',\n additionalProperties: true\n },\n market_data: {\n type: 'object',\n description: 'coin market data',\n properties: {\n current_price: {\n type: 'object',\n description: 'coin current price',\n properties: {\n btc: {\n type: 'number'\n },\n eur: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n market_cap: {\n type: 'object',\n description: 'coin market cap',\n properties: {\n btc: {\n type: 'number'\n },\n eur: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n total_volume: {\n type: 'object',\n description: 'coin total volume',\n properties: {\n btc: {\n type: 'number'\n },\n eur: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n }\n }\n },\n name: {\n type: 'string',\n description: 'coin name'\n },\n public_interest_stats: {\n type: 'object',\n description: 'coin public interest stats',\n properties: {\n alexa_rank: {\n type: 'number',\n description: 'coin alexa rank'\n },\n bing_matches: {\n type: 'number',\n description: 'coin bing matches'\n }\n }\n },\n symbol: {\n type: 'string',\n description: 'coin symbol'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - date: { - type: 'string', - description: 'date of data snapshot (`YYYY-MM-DD`)', - }, - localization: { - type: 'boolean', - description: 'include all the localized languages in response, default: true', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id', 'date'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.coins.history.get(id, body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/coins/list/get-new-coins-list.ts b/packages/mcp-server/src/tools/coins/list/get-new-coins-list.ts deleted file mode 100644 index c68bcc4..0000000 --- a/packages/mcp-server/src/tools/coins/list/get-new-coins-list.ts +++ /dev/null @@ -1,51 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'coins.list', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/coins/list/new', - operationId: 'coins-list-new', -}; - -export const tool: Tool = { - name: 'get_new_coins_list', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query the latest 200 coins that recently listed on CoinGecko**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/list_get_new_response',\n $defs: {\n list_get_new_response: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'coin ID'\n },\n activated_at: {\n type: 'number',\n description: 'timestamp when coin was activated on CoinGecko'\n },\n name: {\n type: 'string',\n description: 'coin name'\n },\n symbol: {\n type: 'string',\n description: 'coin symbol'\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.coins.list.getNew())); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/coins/market-chart/get-range-coins-market-chart.ts b/packages/mcp-server/src/tools/coins/market-chart/get-range-coins-market-chart.ts deleted file mode 100644 index 8d32490..0000000 --- a/packages/mcp-server/src/tools/coins/market-chart/get-range-coins-market-chart.ts +++ /dev/null @@ -1,102 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'coins.market_chart', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/coins/{id}/market_chart/range', - operationId: 'coins-id-market-chart-range', -}; - -export const tool: Tool = { - name: 'get_range_coins_market_chart', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **get the historical chart data of a coin within certain time range in UNIX along with price, market cap and 24hr volume based on particular coin ID**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/market_chart_get_range_response',\n $defs: {\n market_chart_get_range_response: {\n type: 'object',\n properties: {\n market_caps: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n },\n prices: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n },\n total_volumes: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - from: { - type: 'string', - description: - 'starting date in ISO date string (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM`) or UNIX timestamp. \n **use ISO date string for best compatibility**', - }, - to: { - type: 'string', - description: - 'ending date in ISO date string (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM`) or UNIX timestamp. \n **use ISO date string for best compatibility**', - }, - vs_currency: { - type: 'string', - description: - 'target currency of market data \n *refers to [`/simple/supported_vs_currencies`](/reference/simple-supported-currencies).', - }, - interval: { - type: 'string', - description: 'data interval, leave empty for auto granularity', - enum: ['5m', 'hourly', 'daily'], - }, - precision: { - type: 'string', - description: 'decimal place for currency price value', - enum: [ - 'full', - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '10', - '11', - '12', - '13', - '14', - '15', - '16', - '17', - '18', - ], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id', 'from', 'to', 'vs_currency'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.coins.marketChart.getRange(id, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/coins/markets/get-coins-markets.ts b/packages/mcp-server/src/tools/coins/markets/get-coins-markets.ts deleted file mode 100644 index c5bd0f2..0000000 --- a/packages/mcp-server/src/tools/coins/markets/get-coins-markets.ts +++ /dev/null @@ -1,168 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'coins.markets', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/coins/markets', - operationId: 'coins-markets', -}; - -export const tool: Tool = { - name: 'get_coins_markets', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the supported coins with price, market cap, volume and market related data**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/market_get_response',\n $defs: {\n market_get_response: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'coin ID'\n },\n ath: {\n type: 'number',\n description: 'coin all time high (ATH) in currency'\n },\n ath_change_percentage: {\n type: 'number',\n description: 'coin all time high (ATH) change in percentage'\n },\n ath_date: {\n type: 'string',\n description: 'coin all time high (ATH) date',\n format: 'date-time'\n },\n atl: {\n type: 'number',\n description: 'coin all time low (atl) in currency'\n },\n atl_change_percentage: {\n type: 'number',\n description: 'coin all time low (atl) change in percentage'\n },\n atl_date: {\n type: 'string',\n description: 'coin all time low (atl) date',\n format: 'date-time'\n },\n circulating_supply: {\n type: 'number',\n description: 'coin circulating supply'\n },\n current_price: {\n type: 'number',\n description: 'coin current price in currency'\n },\n fully_diluted_valuation: {\n type: 'number',\n description: 'coin fully diluted valuation (fdv) in currency'\n },\n high_24h: {\n type: 'number',\n description: 'coin 24hr price high in currency'\n },\n image: {\n type: 'string',\n description: 'coin image url'\n },\n last_updated: {\n type: 'string',\n description: 'coin last updated timestamp',\n format: 'date-time'\n },\n low_24h: {\n type: 'number',\n description: 'coin 24hr price low in currency'\n },\n market_cap: {\n type: 'number',\n description: 'coin market cap in currency'\n },\n market_cap_change_24h: {\n type: 'number',\n description: 'coin 24hr market cap change in currency'\n },\n market_cap_change_percentage_24h: {\n type: 'number',\n description: 'coin 24hr market cap change in percentage'\n },\n market_cap_rank: {\n type: 'number',\n description: 'coin rank by market cap'\n },\n max_supply: {\n type: 'number',\n description: 'coin max supply'\n },\n name: {\n type: 'string',\n description: 'coin name'\n },\n price_change_24h: {\n type: 'number',\n description: 'coin 24hr price change in currency'\n },\n price_change_percentage_24h: {\n type: 'number',\n description: 'coin 24hr price change in percentage'\n },\n roi: {\n type: 'object',\n description: 'return on investment data',\n properties: {\n currency: {\n type: 'string',\n description: 'ROI currency'\n },\n percentage: {\n type: 'number',\n description: 'ROI percentage'\n },\n times: {\n type: 'number',\n description: 'ROI multiplier'\n }\n },\n required: [ 'currency',\n 'percentage',\n 'times'\n ]\n },\n symbol: {\n type: 'string',\n description: 'coin symbol'\n },\n total_supply: {\n type: 'number',\n description: 'coin total supply'\n },\n total_volume: {\n type: 'number',\n description: 'coin total trading volume in currency'\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - vs_currency: { - type: 'string', - description: - 'target currency of coins and market data \n *refers to [`/simple/supported_vs_currencies`](/reference/simple-supported-currencies).', - }, - category: { - type: 'string', - description: - "filter based on coins' category \n *refers to [`/coins/categories/list`](/reference/coins-categories-list).", - }, - ids: { - type: 'string', - description: - "coins' IDs, comma-separated if querying more than 1 coin. \n *refers to [`/coins/list`](/reference/coins-list).", - }, - include_tokens: { - type: 'string', - description: - 'for `symbols` lookups, specify `all` to include all matching tokens \n Default `top` returns top-ranked tokens (by market cap or volume)', - enum: ['top', 'all'], - }, - locale: { - type: 'string', - description: 'language background, default: en', - enum: [ - 'ar', - 'bg', - 'cs', - 'da', - 'de', - 'el', - 'en', - 'es', - 'fi', - 'fr', - 'he', - 'hi', - 'hr', - 'hu', - 'id', - 'it', - 'ja', - 'ko', - 'lt', - 'nl', - 'no', - 'pl', - 'pt', - 'ro', - 'ru', - 'sk', - 'sl', - 'sv', - 'th', - 'tr', - 'uk', - 'vi', - 'zh', - 'zh-tw', - ], - }, - names: { - type: 'string', - description: "coins' names, comma-separated if querying more than 1 coin.", - }, - order: { - type: 'string', - description: 'sort result by field, default: market_cap_desc', - enum: ['market_cap_asc', 'market_cap_desc', 'volume_asc', 'volume_desc', 'id_asc', 'id_desc'], - }, - page: { - type: 'number', - description: 'page through results, default: 1', - }, - per_page: { - type: 'number', - description: 'total results per page, default: 100 \n Valid values: 1...250', - }, - precision: { - type: 'string', - description: 'decimal place for currency price value', - enum: [ - 'full', - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '10', - '11', - '12', - '13', - '14', - '15', - '16', - '17', - '18', - ], - }, - price_change_percentage: { - type: 'string', - description: - 'include price change percentage timeframe, comma-separated if query more than 1 timeframe \n Valid values: 1h, 24h, 7d, 14d, 30d, 200d, 1y', - }, - sparkline: { - type: 'boolean', - description: 'include sparkline 7 days data, default: false', - }, - symbols: { - type: 'string', - description: "coins' symbols, comma-separated if querying more than 1 coin.", - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['vs_currency'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.coins.markets.get(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/coins/ohlc/get-range-coins-ohlc.ts b/packages/mcp-server/src/tools/coins/ohlc/get-range-coins-ohlc.ts deleted file mode 100644 index ceb267b..0000000 --- a/packages/mcp-server/src/tools/coins/ohlc/get-range-coins-ohlc.ts +++ /dev/null @@ -1,74 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'coins.ohlc', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/coins/{id}/ohlc/range', - operationId: 'coins-id-ohlc-range', -}; - -export const tool: Tool = { - name: 'get_range_coins_ohlc', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **get the OHLC chart (Open, High, Low, Close) of a coin within a range of timestamp based on particular coin ID**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/ohlc_get_range_response',\n $defs: {\n ohlc_get_range_response: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - from: { - type: 'string', - description: - 'starting date in ISO date string (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM`) or UNIX timestamp. \n **use ISO date string for best compatibility**', - }, - interval: { - type: 'string', - description: 'data interval', - enum: ['daily', 'hourly'], - }, - to: { - type: 'string', - description: - 'ending date in ISO date string (`YYYY-MM-DD` or `YYYY-MM-DDTHH:MM`) or UNIX timestamp. \n **use ISO date string for best compatibility**', - }, - vs_currency: { - type: 'string', - description: - 'target currency of price data \n *refers to [`/simple/supported_vs_currencies`](/reference/simple-supported-currencies).', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id', 'from', 'interval', 'to', 'vs_currency'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.coins.ohlc.getRange(id, body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/coins/top-gainers-losers/get-coins-top-gainers-losers.ts b/packages/mcp-server/src/tools/coins/top-gainers-losers/get-coins-top-gainers-losers.ts deleted file mode 100644 index cd4e26f..0000000 --- a/packages/mcp-server/src/tools/coins/top-gainers-losers/get-coins-top-gainers-losers.ts +++ /dev/null @@ -1,72 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'coins.top_gainers_losers', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/coins/top_gainers_losers', - operationId: 'coins-top-gainers-losers', -}; - -export const tool: Tool = { - name: 'get_coins_top_gainers_losers', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query the top 30 coins with largest price gain and loss by a specific time duration**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/top_gainers_loser_get_response',\n $defs: {\n top_gainers_loser_get_response: {\n type: 'object',\n properties: {\n top_gainers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'coin ID'\n },\n image: {\n type: 'string',\n description: 'coin image url'\n },\n market_cap_rank: {\n type: 'number',\n description: 'coin rank by market cap'\n },\n name: {\n type: 'string',\n description: 'coin name'\n },\n symbol: {\n type: 'string',\n description: 'coin symbol'\n },\n usd: {\n type: 'number',\n description: 'coin price in USD'\n },\n usd_14d_change: {\n type: 'number',\n description: 'coin 14 day change percentage in USD'\n },\n usd_1h_change: {\n type: 'number',\n description: 'coin 1hr change percentage in USD'\n },\n usd_1y_change: {\n type: 'number',\n description: 'coin 1 year change percentage in USD'\n },\n usd_200d_change: {\n type: 'number',\n description: 'coin 200 day change percentage in USD'\n },\n usd_24h_change: {\n type: 'number',\n description: 'coin 24hr change percentage in USD'\n },\n usd_24h_vol: {\n type: 'number',\n description: 'coin 24hr volume in USD'\n },\n usd_30d_change: {\n type: 'number',\n description: 'coin 30 day change percentage in USD'\n },\n usd_7d_change: {\n type: 'number',\n description: 'coin 7 day change percentage in USD'\n }\n }\n }\n },\n top_losers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'coin ID'\n },\n image: {\n type: 'string',\n description: 'coin image url'\n },\n market_cap_rank: {\n type: 'number',\n description: 'coin rank by market cap'\n },\n name: {\n type: 'string',\n description: 'coin name'\n },\n symbol: {\n type: 'string',\n description: 'coin symbol'\n },\n usd: {\n type: 'number',\n description: 'coin price in USD'\n },\n usd_14d_change: {\n type: 'number',\n description: 'coin 14 day change percentage in USD'\n },\n usd_1h_change: {\n type: 'number',\n description: 'coin 1hr change percentage in USD'\n },\n usd_1y_change: {\n type: 'number',\n description: 'coin 1 year change percentage in USD'\n },\n usd_200d_change: {\n type: 'number',\n description: 'coin 200 day change percentage in USD'\n },\n usd_24h_change: {\n type: 'number',\n description: 'coin 24hr change percentage in USD'\n },\n usd_24h_vol: {\n type: 'number',\n description: 'coin 24hr volume in USD'\n },\n usd_30d_change: {\n type: 'number',\n description: 'coin 30 day change percentage in USD'\n },\n usd_7d_change: {\n type: 'number',\n description: 'coin 7 day change percentage in USD'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - vs_currency: { - type: 'string', - description: - 'target currency of coins \n *refers to [`/simple/supported_vs_currencies`](/reference/simple-supported-currencies).', - }, - duration: { - type: 'string', - description: 'filter result by time range \n Default value: `24h`', - enum: ['1h', '24h', '7d', '14d', '30d', '60d', '1y'], - }, - price_change_percentage: { - type: 'string', - description: - 'include price change percentage timeframe, comma-separated if query more than 1 price change percentage timeframe \n Valid values: 1h, 24h, 7d, 14d, 30d, 200d, 1y', - }, - top_coins: { - type: 'string', - description: - 'filter result by market cap ranking (top 300 to 1000) or all coins (including coins that do not have market cap) \n Default value: `1000`', - enum: ['300', '500', '1000', 'all'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['vs_currency'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.coins.topGainersLosers.get(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/exchanges/get-id-exchanges.ts b/packages/mcp-server/src/tools/exchanges/get-id-exchanges.ts deleted file mode 100644 index dd30a73..0000000 --- a/packages/mcp-server/src/tools/exchanges/get-id-exchanges.ts +++ /dev/null @@ -1,60 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'exchanges', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/exchanges/{id}', - operationId: 'exchanges-id', -}; - -export const tool: Tool = { - name: 'get_id_exchanges', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query exchange's data (name, year established, country, ...), exchange volume in BTC and top 100 tickers based on exchange's ID**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/exchange_get_id_response',\n $defs: {\n exchange_get_id_response: {\n type: 'object',\n properties: {\n alert_notice: {\n type: 'string',\n description: 'alert notice for exchange'\n },\n centralized: {\n type: 'boolean',\n description: 'exchange type (true for centralized, false for decentralized)'\n },\n coins: {\n type: 'number',\n description: 'number of coins listed on the exchange'\n },\n country: {\n type: 'string',\n description: 'exchange incorporated country'\n },\n description: {\n type: 'string',\n description: 'exchange description'\n },\n facebook_url: {\n type: 'string',\n description: 'exchange facebook url'\n },\n has_trading_incentive: {\n type: 'boolean',\n description: 'exchange trading incentive'\n },\n image: {\n type: 'string',\n description: 'exchange image url'\n },\n name: {\n type: 'string',\n description: 'exchange name'\n },\n other_url_1: {\n type: 'string'\n },\n other_url_2: {\n type: 'string'\n },\n pairs: {\n type: 'number',\n description: 'number of trading pairs on the exchange'\n },\n public_notice: {\n type: 'string',\n description: 'public notice for exchange'\n },\n reddit_url: {\n type: 'string',\n description: 'exchange reddit url'\n },\n slack_url: {\n type: 'string',\n description: 'exchange slack url'\n },\n telegram_url: {\n type: 'string',\n description: 'exchange telegram url'\n },\n tickers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description: 'coin name'\n },\n tickers: {\n type: 'array',\n description: 'list of tickers',\n items: {\n type: 'object',\n properties: {\n base: {\n type: 'string',\n description: 'coin ticker base currency'\n },\n bid_ask_spread_percentage: {\n type: 'number',\n description: 'coin ticker bid ask spread percentage'\n },\n coin_id: {\n type: 'string',\n description: 'coin ticker base currency coin ID'\n },\n converted_last: {\n type: 'object',\n description: 'coin ticker converted last price',\n properties: {\n btc: {\n type: 'number'\n },\n eth: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n converted_volume: {\n type: 'object',\n description: 'coin ticker converted volume',\n properties: {\n btc: {\n type: 'number'\n },\n eth: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n cost_to_move_down_usd: {\n type: 'number',\n description: 'coin ticker cost to move down in usd'\n },\n cost_to_move_up_usd: {\n type: 'number',\n description: 'coin ticker cost to move up in usd'\n },\n is_anomaly: {\n type: 'boolean',\n description: 'coin ticker anomaly'\n },\n is_stale: {\n type: 'boolean',\n description: 'coin ticker stale'\n },\n last: {\n type: 'number',\n description: 'coin ticker last price'\n },\n last_fetch_at: {\n type: 'string',\n description: 'coin ticker last fetch timestamp'\n },\n last_traded_at: {\n type: 'string',\n description: 'coin ticker last traded timestamp'\n },\n market: {\n type: 'object',\n description: 'coin ticker exchange',\n properties: {\n has_trading_incentive: {\n type: 'boolean',\n description: 'exchange trading incentive'\n },\n identifier: {\n type: 'string',\n description: 'exchange identifier'\n },\n name: {\n type: 'string',\n description: 'exchange name'\n },\n logo: {\n type: 'string',\n description: 'exchange image url'\n }\n },\n required: [ 'has_trading_incentive',\n 'identifier',\n 'name'\n ]\n },\n target: {\n type: 'string',\n description: 'coin ticker target currency'\n },\n target_coin_id: {\n type: 'string',\n description: 'coin ticker target currency coin ID'\n },\n timestamp: {\n type: 'string',\n description: 'coin ticker timestamp'\n },\n token_info_url: {\n type: 'string',\n description: 'coin ticker token info url'\n },\n trade_url: {\n type: 'string',\n description: 'coin ticker trade url'\n },\n trust_score: {\n type: 'string',\n description: 'coin ticker trust score'\n },\n volume: {\n type: 'number',\n description: 'coin ticker volume'\n }\n }\n }\n }\n }\n }\n },\n trade_volume_24h_btc: {\n type: 'number'\n },\n trust_score: {\n type: 'number',\n description: 'exchange trust score'\n },\n trust_score_rank: {\n type: 'number',\n description: 'exchange trust score rank'\n },\n twitter_handle: {\n type: 'string',\n description: 'exchange twitter handle'\n },\n url: {\n type: 'string',\n description: 'exchange website url'\n },\n year_established: {\n type: 'number',\n description: 'exchange established year'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - dex_pair_format: { - type: 'string', - description: - 'set to `symbol` to display DEX pair base and target as symbols, default: `contract_address`', - enum: ['contract_address', 'symbol'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.exchanges.getID(id, body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/exchanges/get-list-exchanges.ts b/packages/mcp-server/src/tools/exchanges/get-list-exchanges.ts deleted file mode 100644 index da26cb3..0000000 --- a/packages/mcp-server/src/tools/exchanges/get-list-exchanges.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'exchanges', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/exchanges/list', - operationId: 'exchanges-list', -}; - -export const tool: Tool = { - name: 'get_list_exchanges', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the exchanges with ID and name**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/exchange_get_list_response',\n $defs: {\n exchange_get_list_response: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'exchange ID'\n },\n name: {\n type: 'string',\n description: 'exchange name'\n }\n },\n required: [ 'id',\n 'name'\n ]\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - status: { - type: 'string', - description: 'filter by status of exchanges, default: active', - enum: ['active', 'inactive'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.exchanges.getList(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/exchanges/tickers/get-exchanges-tickers.ts b/packages/mcp-server/src/tools/exchanges/tickers/get-exchanges-tickers.ts deleted file mode 100644 index 5187e33..0000000 --- a/packages/mcp-server/src/tools/exchanges/tickers/get-exchanges-tickers.ts +++ /dev/null @@ -1,91 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'exchanges.tickers', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/exchanges/{id}/tickers', - operationId: 'exchanges-id-tickers', -}; - -export const tool: Tool = { - name: 'get_exchanges_tickers', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query exchange's tickers based on exchange's ID**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/ticker_get_response',\n $defs: {\n ticker_get_response: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description: 'coin name'\n },\n tickers: {\n type: 'array',\n description: 'list of tickers',\n items: {\n type: 'object',\n properties: {\n base: {\n type: 'string',\n description: 'coin ticker base currency'\n },\n bid_ask_spread_percentage: {\n type: 'number',\n description: 'coin ticker bid ask spread percentage'\n },\n coin_id: {\n type: 'string',\n description: 'coin ticker base currency coin ID'\n },\n converted_last: {\n type: 'object',\n description: 'coin ticker converted last price',\n properties: {\n btc: {\n type: 'number'\n },\n eth: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n converted_volume: {\n type: 'object',\n description: 'coin ticker converted volume',\n properties: {\n btc: {\n type: 'number'\n },\n eth: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n cost_to_move_down_usd: {\n type: 'number',\n description: 'coin ticker cost to move down in usd'\n },\n cost_to_move_up_usd: {\n type: 'number',\n description: 'coin ticker cost to move up in usd'\n },\n is_anomaly: {\n type: 'boolean',\n description: 'coin ticker anomaly'\n },\n is_stale: {\n type: 'boolean',\n description: 'coin ticker stale'\n },\n last: {\n type: 'number',\n description: 'coin ticker last price'\n },\n last_fetch_at: {\n type: 'string',\n description: 'coin ticker last fetch timestamp'\n },\n last_traded_at: {\n type: 'string',\n description: 'coin ticker last traded timestamp'\n },\n market: {\n type: 'object',\n description: 'coin ticker exchange',\n properties: {\n has_trading_incentive: {\n type: 'boolean',\n description: 'exchange trading incentive'\n },\n identifier: {\n type: 'string',\n description: 'exchange identifier'\n },\n name: {\n type: 'string',\n description: 'exchange name'\n },\n logo: {\n type: 'string',\n description: 'exchange image url'\n }\n },\n required: [ 'has_trading_incentive',\n 'identifier',\n 'name'\n ]\n },\n target: {\n type: 'string',\n description: 'coin ticker target currency'\n },\n target_coin_id: {\n type: 'string',\n description: 'coin ticker target currency coin ID'\n },\n timestamp: {\n type: 'string',\n description: 'coin ticker timestamp'\n },\n token_info_url: {\n type: 'string',\n description: 'coin ticker token info url'\n },\n trade_url: {\n type: 'string',\n description: 'coin ticker trade url'\n },\n trust_score: {\n type: 'string',\n description: 'coin ticker trust score'\n },\n volume: {\n type: 'number',\n description: 'coin ticker volume'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - coin_ids: { - type: 'string', - description: - 'filter tickers by coin IDs, comma-separated if querying more than 1 coin \n *refers to [`/coins/list`](/reference/coins-list).', - }, - depth: { - type: 'boolean', - description: - 'include 2% orderbook depth (Example: cost_to_move_up_usd & cost_to_move_down_usd),default: false', - }, - dex_pair_format: { - type: 'string', - description: - 'set to `symbol` to display DEX pair base and target as symbols, default: `contract_address`', - enum: ['contract_address', 'symbol'], - }, - include_exchange_logo: { - type: 'boolean', - description: 'include exchange logo, default: false', - }, - order: { - type: 'string', - description: 'use this to sort the order of responses, default: trust_score_desc', - enum: [ - 'market_cap_asc', - 'market_cap_desc', - 'trust_score_desc', - 'trust_score_asc', - 'volume_desc', - 'volume_asc', - 'base_target', - ], - }, - page: { - type: 'number', - description: 'page through results', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.exchanges.tickers.get(id, body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/exchanges/volume-chart/get-range-exchanges-volume-chart.ts b/packages/mcp-server/src/tools/exchanges/volume-chart/get-range-exchanges-volume-chart.ts deleted file mode 100644 index 10b284f..0000000 --- a/packages/mcp-server/src/tools/exchanges/volume-chart/get-range-exchanges-volume-chart.ts +++ /dev/null @@ -1,64 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'exchanges.volume_chart', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/exchanges/{id}/volume_chart/range', - operationId: 'exchanges-id-volume-chart-range', -}; - -export const tool: Tool = { - name: 'get_range_exchanges_volume_chart', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query the historical volume chart data in BTC by specifying date range in UNIX based on exchange's ID**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/volume_chart_get_range_response',\n $defs: {\n volume_chart_get_range_response: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - from: { - type: 'number', - description: 'starting date in UNIX timestamp ', - }, - to: { - type: 'number', - description: 'ending date in UNIX timestamp', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id', 'from', 'to'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.exchanges.volumeChart.getRange(id, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/global/get-global.ts b/packages/mcp-server/src/tools/global/get-global.ts deleted file mode 100644 index 94afae0..0000000 --- a/packages/mcp-server/src/tools/global/get-global.ts +++ /dev/null @@ -1,51 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'global', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/global', - operationId: 'crypto-global', -}; - -export const tool: Tool = { - name: 'get_global', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you **query cryptocurrency global data including active cryptocurrencies, markets, total crypto market cap and etc**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/global_get_response',\n $defs: {\n global_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n active_cryptocurrencies: {\n type: 'number',\n description: 'number of active cryptocurrencies'\n },\n ended_icos: {\n type: 'number',\n description: 'number of ended icos'\n },\n market_cap_change_percentage_24h_usd: {\n type: 'number',\n description: 'cryptocurrencies market cap change percentage in 24 hours in usd'\n },\n market_cap_percentage: {\n type: 'object',\n description: 'cryptocurrencies market cap percentage',\n properties: {\n btc: {\n type: 'number'\n },\n eth: {\n type: 'number'\n }\n }\n },\n markets: {\n type: 'number',\n description: 'number of exchanges'\n },\n ongoing_icos: {\n type: 'number',\n description: 'number of ongoing icos'\n },\n total_market_cap: {\n type: 'object',\n description: 'cryptocurrencies total market cap',\n properties: {\n btc: {\n type: 'number'\n },\n eth: {\n type: 'number'\n }\n }\n },\n total_volume: {\n type: 'object',\n description: 'cryptocurrencies total volume',\n properties: {\n btc: {\n type: 'number'\n },\n eth: {\n type: 'number'\n }\n }\n },\n upcoming_icos: {\n type: 'number',\n description: 'number of upcoming icos'\n },\n updated_at: {\n type: 'number'\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.global.get())); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts deleted file mode 100644 index 65feef9..0000000 --- a/packages/mcp-server/src/tools/index.ts +++ /dev/null @@ -1,167 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, Endpoint, HandlerFunction } from './types'; - -export { Metadata, Endpoint, HandlerFunction }; - -import get_asset_platforms from './asset-platforms/get-asset-platforms'; -import get_id_coins from './coins/get-id-coins'; -import get_list_coins_categories from './coins/categories/get-list-coins-categories'; -import get_new_coins_list from './coins/list/get-new-coins-list'; -import get_coins_markets from './coins/markets/get-coins-markets'; -import get_coins_top_gainers_losers from './coins/top-gainers-losers/get-coins-top-gainers-losers'; -import get_coins_contract from './coins/contract/get-coins-contract'; -import get_range_contract_coins_market_chart from './coins/contract/market-chart/get-range-contract-coins-market-chart'; -import get_coins_history from './coins/history/get-coins-history'; -import get_range_coins_market_chart from './coins/market-chart/get-range-coins-market-chart'; -import get_range_coins_ohlc from './coins/ohlc/get-range-coins-ohlc'; -import get_id_exchanges from './exchanges/get-id-exchanges'; -import get_list_exchanges from './exchanges/get-list-exchanges'; -import get_exchanges_tickers from './exchanges/tickers/get-exchanges-tickers'; -import get_range_exchanges_volume_chart from './exchanges/volume-chart/get-range-exchanges-volume-chart'; -import get_global from './global/get-global'; -import get_id_nfts from './nfts/get-id-nfts'; -import get_list_nfts from './nfts/get-list-nfts'; -import get_markets_nfts from './nfts/get-markets-nfts'; -import get_nfts_market_chart from './nfts/market-chart/get-nfts-market-chart'; -import get_onchain_categories from './onchain/categories/get-onchain-categories'; -import get_pools_onchain_categories from './onchain/categories/get-pools-onchain-categories'; -import get_onchain_networks from './onchain/networks/get-onchain-networks'; -import get_networks_onchain_new_pools from './onchain/networks/new-pools/get-networks-onchain-new-pools'; -import get_network_networks_onchain_new_pools from './onchain/networks/new-pools/get-network-networks-onchain-new-pools'; -import get_networks_onchain_dexes from './onchain/networks/dexes/get-networks-onchain-dexes'; -import get_addresses_pools_networks_onchain_multi from './onchain/networks/pools/multi/get-addresses-pools-networks-onchain-multi'; -import get_pools_networks_onchain_info from './onchain/networks/pools/info/get-pools-networks-onchain-info'; -import get_timeframe_pools_networks_onchain_ohlcv from './onchain/networks/pools/ohlcv/get-timeframe-pools-networks-onchain-ohlcv'; -import get_pools_networks_onchain_trades from './onchain/networks/pools/trades/get-pools-networks-onchain-trades'; -import get_addresses_tokens_networks_onchain_multi from './onchain/networks/tokens/multi/get-addresses-tokens-networks-onchain-multi'; -import get_tokens_networks_onchain_info from './onchain/networks/tokens/info/get-tokens-networks-onchain-info'; -import get_tokens_networks_onchain_top_holders from './onchain/networks/tokens/top-holders/get-tokens-networks-onchain-top-holders'; -import get_tokens_networks_onchain_holders_chart from './onchain/networks/tokens/holders-chart/get-tokens-networks-onchain-holders-chart'; -import get_timeframe_tokens_networks_onchain_ohlcv from './onchain/networks/tokens/ohlcv/get-timeframe-tokens-networks-onchain-ohlcv'; -import get_tokens_networks_onchain_pools from './onchain/networks/tokens/pools/get-tokens-networks-onchain-pools'; -import get_tokens_networks_onchain_trades from './onchain/networks/tokens/trades/get-tokens-networks-onchain-trades'; -import get_tokens_networks_onchain_top_traders from './onchain/networks/tokens/top-traders/get-tokens-networks-onchain-top-traders'; -import get_pools_onchain_megafilter from './onchain/pools/megafilter/get-pools-onchain-megafilter'; -import get_pools_onchain_trending_search from './onchain/pools/trending-search/get-pools-onchain-trending-search'; -import get_search_onchain_pools from './onchain/search/pools/get-search-onchain-pools'; -import get_addresses_networks_simple_onchain_token_price from './onchain/simple/networks/token-price/get-addresses-networks-simple-onchain-token-price'; -import get_holding_chart_public_treasury from './public-treasury/get-holding-chart-public-treasury'; -import get_transaction_history_public_treasury from './public-treasury/get-transaction-history-public-treasury'; -import get_search from './search/get-search'; -import get_search_trending from './search/trending/get-search-trending'; -import get_simple_price from './simple/price/get-simple-price'; -import get_simple_supported_vs_currencies from './simple/supported-vs-currencies/get-simple-supported-vs-currencies'; -import get_id_simple_token_price from './simple/token-price/get-id-simple-token-price'; - -export const endpoints: Endpoint[] = []; - -function addEndpoint(endpoint: Endpoint) { - endpoints.push(endpoint); -} - -addEndpoint(get_asset_platforms); -addEndpoint(get_id_coins); -addEndpoint(get_list_coins_categories); -addEndpoint(get_new_coins_list); -addEndpoint(get_coins_markets); -addEndpoint(get_coins_top_gainers_losers); -addEndpoint(get_coins_contract); -addEndpoint(get_range_contract_coins_market_chart); -addEndpoint(get_coins_history); -addEndpoint(get_range_coins_market_chart); -addEndpoint(get_range_coins_ohlc); -addEndpoint(get_id_exchanges); -addEndpoint(get_list_exchanges); -addEndpoint(get_exchanges_tickers); -addEndpoint(get_range_exchanges_volume_chart); -addEndpoint(get_global); -addEndpoint(get_id_nfts); -addEndpoint(get_list_nfts); -addEndpoint(get_markets_nfts); -addEndpoint(get_nfts_market_chart); -addEndpoint(get_onchain_categories); -addEndpoint(get_pools_onchain_categories); -addEndpoint(get_onchain_networks); -addEndpoint(get_networks_onchain_new_pools); -addEndpoint(get_network_networks_onchain_new_pools); -addEndpoint(get_networks_onchain_dexes); -addEndpoint(get_addresses_pools_networks_onchain_multi); -addEndpoint(get_pools_networks_onchain_info); -addEndpoint(get_timeframe_pools_networks_onchain_ohlcv); -addEndpoint(get_pools_networks_onchain_trades); -addEndpoint(get_addresses_tokens_networks_onchain_multi); -addEndpoint(get_tokens_networks_onchain_info); -addEndpoint(get_tokens_networks_onchain_top_holders); -addEndpoint(get_tokens_networks_onchain_holders_chart); -addEndpoint(get_timeframe_tokens_networks_onchain_ohlcv); -addEndpoint(get_tokens_networks_onchain_pools); -addEndpoint(get_tokens_networks_onchain_trades); -addEndpoint(get_tokens_networks_onchain_top_traders); -addEndpoint(get_pools_onchain_megafilter); -addEndpoint(get_pools_onchain_trending_search); -addEndpoint(get_search_onchain_pools); -addEndpoint(get_addresses_networks_simple_onchain_token_price); -addEndpoint(get_holding_chart_public_treasury); -addEndpoint(get_transaction_history_public_treasury); -addEndpoint(get_search); -addEndpoint(get_search_trending); -addEndpoint(get_simple_price); -addEndpoint(get_simple_supported_vs_currencies); -addEndpoint(get_id_simple_token_price); - -export type Filter = { - type: 'resource' | 'operation' | 'tag' | 'tool'; - op: 'include' | 'exclude'; - value: string; -}; - -export function query(filters: Filter[], endpoints: Endpoint[]): Endpoint[] { - const allExcludes = filters.length > 0 && filters.every((filter) => filter.op === 'exclude'); - const unmatchedFilters = new Set(filters); - - const filtered = endpoints.filter((endpoint: Endpoint) => { - let included = false || allExcludes; - - for (const filter of filters) { - if (match(filter, endpoint)) { - unmatchedFilters.delete(filter); - included = filter.op === 'include'; - } - } - - return included; - }); - - // Check if any filters didn't match - const unmatched = Array.from(unmatchedFilters).filter((f) => f.type === 'tool' || f.type === 'resource'); - if (unmatched.length > 0) { - throw new Error( - `The following filters did not match any endpoints: ${unmatched - .map((f) => `${f.type}=${f.value}`) - .join(', ')}`, - ); - } - - return filtered; -} - -function match({ type, value }: Filter, endpoint: Endpoint): boolean { - switch (type) { - case 'resource': { - const regexStr = '^' + normalizeResource(value).replace(/\*/g, '.*') + '$'; - const regex = new RegExp(regexStr); - return regex.test(normalizeResource(endpoint.metadata.resource)); - } - case 'operation': - return endpoint.metadata.operation === value; - case 'tag': - return endpoint.metadata.tags.includes(value); - case 'tool': - return endpoint.tool.name === value; - } -} - -function normalizeResource(resource: string): string { - return resource.toLowerCase().replace(/[^a-z.*\-_]*/g, ''); -} diff --git a/packages/mcp-server/src/tools/nfts/get-id-nfts.ts b/packages/mcp-server/src/tools/nfts/get-id-nfts.ts deleted file mode 100644 index b54710a..0000000 --- a/packages/mcp-server/src/tools/nfts/get-id-nfts.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'nfts', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/nfts/{id}', - operationId: 'nfts-id', -}; - -export const tool: Tool = { - name: 'get_id_nfts', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the NFT data (name, floor price, 24hr volume ...) based on the NFT collection ID**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/nft_get_id_response',\n $defs: {\n nft_get_id_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'NFT collection ID'\n },\n asset_platform_id: {\n type: 'string',\n description: 'NFT collection asset platform ID'\n },\n ath: {\n type: 'object',\n description: 'NFT collection all time highs',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n ath_change_percentage: {\n type: 'object',\n description: 'NFT collection all time highs change percentage',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n ath_date: {\n type: 'object',\n description: 'NFT collection all time highs date',\n properties: {\n native_currency: {\n type: 'string',\n format: 'date-time'\n },\n usd: {\n type: 'string',\n format: 'date-time'\n }\n }\n },\n banner_image: {\n type: 'object',\n description: 'NFT collection banner image url',\n properties: {\n small: {\n type: 'string'\n }\n }\n },\n contract_address: {\n type: 'string',\n description: 'NFT collection contract address'\n },\n description: {\n type: 'string',\n description: 'NFT collection description'\n },\n explorers: {\n type: 'array',\n description: 'NFT collection block explorers links',\n items: {\n type: 'object',\n properties: {\n link: {\n type: 'string'\n },\n name: {\n type: 'string'\n }\n }\n }\n },\n floor_price: {\n type: 'object',\n description: 'NFT collection floor price',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n floor_price_14d_percentage_change: {\n type: 'object',\n description: 'NFT collection floor price 14 days percentage change',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n floor_price_1y_percentage_change: {\n type: 'object',\n description: 'NFT collection floor price 1 year percentage change',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n floor_price_24h_percentage_change: {\n type: 'object',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n floor_price_30d_percentage_change: {\n type: 'object',\n description: 'NFT collection floor price 30 days percentage change',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n floor_price_60d_percentage_change: {\n type: 'object',\n description: 'NFT collection floor price 60 days percentage change',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n floor_price_7d_percentage_change: {\n type: 'object',\n description: 'NFT collection floor price 7 days percentage change',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n floor_price_in_usd_24h_percentage_change: {\n type: 'number',\n description: 'NFT collection floor price in usd 24 hours percentage change'\n },\n image: {\n type: 'object',\n description: 'NFT collection image url',\n properties: {\n small: {\n type: 'string'\n },\n small_2x: {\n type: 'string'\n }\n }\n },\n links: {\n type: 'object',\n description: 'NFT collection links',\n properties: {\n discord: {\n type: 'string'\n },\n homepage: {\n type: 'string'\n },\n twitter: {\n type: 'string'\n }\n }\n },\n market_cap: {\n type: 'object',\n description: 'NFT collection market cap',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n market_cap_24h_percentage_change: {\n type: 'object',\n description: 'NFT collection market cap 24 hours percentage change',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n market_cap_rank: {\n type: 'number',\n description: 'coin market cap rank'\n },\n name: {\n type: 'string',\n description: 'NFT collection name'\n },\n native_currency: {\n type: 'string',\n description: 'NFT collection native currency'\n },\n native_currency_symbol: {\n type: 'string',\n description: 'NFT collection native currency symbol'\n },\n number_of_unique_addresses: {\n type: 'number',\n description: 'number of unique address owning the NFTs'\n },\n number_of_unique_addresses_24h_percentage_change: {\n type: 'number',\n description: 'number of unique address owning the NFTs 24 hours percentage change'\n },\n one_day_average_sale_price: {\n type: 'number',\n description: 'NFT collection one day average sale price'\n },\n one_day_average_sale_price_24h_percentage_change: {\n type: 'number',\n description: 'NFT collection one day average sale price 24 hours percentage change'\n },\n one_day_sales: {\n type: 'number',\n description: 'NFT collection one day sales'\n },\n one_day_sales_24h_percentage_change: {\n type: 'number',\n description: 'NFT collection one day sales 24 hours percentage change'\n },\n symbol: {\n type: 'string',\n description: 'NFT collection symbol'\n },\n total_supply: {\n type: 'number',\n description: 'NFT collection total supply'\n },\n user_favorites_count: {\n type: 'number',\n description: 'NFT collection user favorites count'\n },\n volume_24h: {\n type: 'object',\n description: 'NFT collection volume in 24 hours',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n volume_24h_percentage_change: {\n type: 'object',\n description: 'NFT collection volume in 24 hours percentage change',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n volume_in_usd_24h_percentage_change: {\n type: 'number',\n description: 'NFT collection volume in usd 24 hours percentage change'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.nfts.getID(id))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/nfts/get-list-nfts.ts b/packages/mcp-server/src/tools/nfts/get-list-nfts.ts deleted file mode 100644 index a3a1596..0000000 --- a/packages/mcp-server/src/tools/nfts/get-list-nfts.ts +++ /dev/null @@ -1,75 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'nfts', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/nfts/list', - operationId: 'nfts-list', -}; - -export const tool: Tool = { - name: 'get_list_nfts', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all supported NFTs with ID, contract address, name, asset platform ID and symbol on CoinGecko**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/nft_get_list_response',\n $defs: {\n nft_get_list_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'NFT collection ID'\n },\n asset_platform_id: {\n type: 'string',\n description: 'NFT collection asset platform ID'\n },\n contract_address: {\n type: 'string',\n description: 'NFT collection contract address'\n },\n name: {\n type: 'string',\n description: 'NFT collection name'\n },\n symbol: {\n type: 'string',\n description: 'NFT collection symbol'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - order: { - type: 'string', - description: 'use this to sort the order of responses', - enum: [ - 'h24_volume_usd_asc', - 'h24_volume_usd_desc', - 'h24_volume_native_asc', - 'h24_volume_native_desc', - 'floor_price_native_asc', - 'floor_price_native_desc', - 'market_cap_native_asc', - 'market_cap_native_desc', - 'market_cap_usd_asc', - 'market_cap_usd_desc', - ], - }, - page: { - type: 'number', - description: 'page through results', - }, - per_page: { - type: 'number', - description: 'total results per page \n Valid values: 1...250', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.nfts.getList(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/nfts/get-markets-nfts.ts b/packages/mcp-server/src/tools/nfts/get-markets-nfts.ts deleted file mode 100644 index 0a28d19..0000000 --- a/packages/mcp-server/src/tools/nfts/get-markets-nfts.ts +++ /dev/null @@ -1,77 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'nfts', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/nfts/markets', - operationId: 'nfts-markets', -}; - -export const tool: Tool = { - name: 'get_markets_nfts', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the supported NFT collections with floor price, market cap, volume and market related data on CoinGecko**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/nft_get_markets_response',\n $defs: {\n nft_get_markets_response: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'NFT collection ID'\n },\n asset_platform_id: {\n type: 'string',\n description: 'NFT collection asset platform ID'\n },\n contract_address: {\n type: 'string',\n description: 'NFT collection contract address'\n },\n description: {\n type: 'string',\n description: 'NFT collection description'\n },\n floor_price: {\n type: 'object',\n description: 'NFT collection floor price',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n floor_price_24h_percentage_change: {\n type: 'object',\n description: 'NFT collection floor price 24 hours percentage change',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n floor_price_in_usd_24h_percentage_change: {\n type: 'number',\n description: 'NFT collection floor price in usd 24 hours percentage change'\n },\n image: {\n type: 'object',\n description: 'NFT collection image url',\n properties: {\n small: {\n type: 'string'\n },\n small_2x: {\n type: 'string'\n }\n }\n },\n market_cap: {\n type: 'object',\n description: 'NFT collection market cap',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n market_cap_24h_percentage_change: {\n type: 'object',\n description: 'NFT collection market cap 24 hours percentage change',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n market_cap_rank: {\n type: 'number',\n description: 'coin market cap rank'\n },\n name: {\n type: 'string',\n description: 'NFT collection name'\n },\n native_currency: {\n type: 'string',\n description: 'NFT collection native currency'\n },\n native_currency_symbol: {\n type: 'string',\n description: 'NFT collection native currency symbol'\n },\n number_of_unique_addresses: {\n type: 'number',\n description: 'number of unique address owning the NFTs'\n },\n number_of_unique_addresses_24h_percentage_change: {\n type: 'number',\n description: 'number of unique address owning the NFTs 24 hours percentage change'\n },\n one_day_average_sale_price: {\n type: 'number',\n description: 'NFT collection one day average sale price'\n },\n one_day_average_sale_price_24h_percentage_change: {\n type: 'number',\n description: 'NFT collection one day average sale price 24 hours percentage change'\n },\n one_day_sales: {\n type: 'number',\n description: 'NFT collection one day sales'\n },\n one_day_sales_24h_percentage_change: {\n type: 'number',\n description: 'NFT collection one day sales 24 hours percentage change'\n },\n symbol: {\n type: 'string',\n description: 'NFT collection symbol'\n },\n total_supply: {\n type: 'number',\n description: 'NFT collection total supply'\n },\n volume_24h: {\n type: 'object',\n description: 'NFT collection volume in 24 hours',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n volume_24h_percentage_change: {\n type: 'object',\n description: 'NFT collection volume in 24 hours percentage change',\n properties: {\n native_currency: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n volume_in_usd_24h_percentage_change: {\n type: 'number',\n description: 'NFT collection volume in usd 24 hours percentage change'\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - asset_platform_id: { - type: 'string', - description: - 'filter result by asset platform (blockchain network) \n *refers to [`/asset_platforms`](/reference/asset-platforms-list) filter=`nft`', - }, - order: { - type: 'string', - description: 'sort results by field \n Default: `market_cap_usd_desc`', - enum: [ - 'h24_volume_native_asc', - 'h24_volume_native_desc', - 'h24_volume_usd_asc', - 'h24_volume_usd_desc', - 'market_cap_usd_asc', - 'market_cap_usd_desc', - ], - }, - page: { - type: 'number', - description: 'page through results \n Default: `1`', - }, - per_page: { - type: 'number', - description: - 'total results per page \n Valid values: any integer between 1 and 250 \n Default: `100`', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.nfts.getMarkets(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/nfts/market-chart/get-nfts-market-chart.ts b/packages/mcp-server/src/tools/nfts/market-chart/get-nfts-market-chart.ts deleted file mode 100644 index ea64867..0000000 --- a/packages/mcp-server/src/tools/nfts/market-chart/get-nfts-market-chart.ts +++ /dev/null @@ -1,58 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'nfts.market_chart', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/nfts/{id}/market_chart', - operationId: 'nfts-id-market-chart', -}; - -export const tool: Tool = { - name: 'get_nfts_market_chart', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you **query historical market data of a NFT collection, including floor price, market cap, and 24hr volume, by number of days away from now**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/market_chart_get_response',\n $defs: {\n market_chart_get_response: {\n type: 'object',\n properties: {\n floor_price_native: {\n type: 'array',\n description: 'NFT collection floor price in native currency',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n },\n floor_price_usd: {\n type: 'array',\n description: 'NFT collection floor price in usd',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n },\n h24_volume_native: {\n type: 'array',\n description: 'NFT collection volume in 24 hours in native currency',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n },\n h24_volume_usd: {\n type: 'array',\n description: 'NFT collection volume in 24 hours in usd',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n },\n market_cap_native: {\n type: 'array',\n description: 'NFT collection market cap in native currency',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n },\n market_cap_usd: {\n type: 'array',\n description: 'NFT collection market cap in usd',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - days: { - type: 'string', - description: 'data up to number of days \n Valid values: any integer or max', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id', 'days'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.nfts.marketChart.get(id, body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/categories/get-onchain-categories.ts b/packages/mcp-server/src/tools/onchain/categories/get-onchain-categories.ts deleted file mode 100644 index 5e64ced..0000000 --- a/packages/mcp-server/src/tools/onchain/categories/get-onchain-categories.ts +++ /dev/null @@ -1,68 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.categories', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/categories', - operationId: 'categories-list', -}; - -export const tool: Tool = { - name: 'get_onchain_categories', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the supported categories on GeckoTerminal**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/category_get_response',\n $defs: {\n category_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n description: {\n type: 'string'\n },\n fdv_usd: {\n type: 'string'\n },\n h24_tx_count: {\n type: 'integer'\n },\n h24_volume_usd: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n reserve_in_usd: {\n type: 'string'\n },\n volume_change_percentage: {\n type: 'object',\n properties: {\n h1: {\n type: 'string'\n },\n h12: {\n type: 'string'\n },\n h24: {\n type: 'string'\n },\n h6: {\n type: 'string'\n }\n }\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - page: { - type: 'integer', - description: 'page through results \n Default value: `1`', - }, - sort: { - type: 'string', - description: 'sort the categories by field \n Default value: `h6_volume_percentage_desc`', - enum: [ - 'h1_volume_percentage_desc', - 'h6_volume_percentage_desc', - 'h12_volume_percentage_desc', - 'h24_tx_count_desc', - 'h24_volume_usd_desc', - 'fdv_usd_desc', - 'reserve_in_usd_desc', - ], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.onchain.categories.get(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/categories/get-pools-onchain-categories.ts b/packages/mcp-server/src/tools/onchain/categories/get-pools-onchain-categories.ts deleted file mode 100644 index 070552b..0000000 --- a/packages/mcp-server/src/tools/onchain/categories/get-pools-onchain-categories.ts +++ /dev/null @@ -1,79 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.categories', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/categories/{category_id}/pools', - operationId: 'pools-category', -}; - -export const tool: Tool = { - name: 'get_pools_onchain_categories', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the pools based on the provided category ID**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/category_get_pools_response',\n $defs: {\n category_get_pools_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n base_token_price_native_currency: {\n type: 'string'\n },\n base_token_price_quote_token: {\n type: 'string'\n },\n base_token_price_usd: {\n type: 'string'\n },\n fdv_usd: {\n type: 'string'\n },\n h24_tx_count: {\n type: 'integer'\n },\n h24_volume_usd: {\n type: 'string'\n },\n market_cap_usd: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n pool_created_at: {\n type: 'string',\n format: 'date-time'\n },\n price_change_percentage: {\n type: 'object',\n properties: {\n h1: {\n type: 'string'\n },\n h24: {\n type: 'string'\n },\n h6: {\n type: 'string'\n },\n m15: {\n type: 'string'\n },\n m30: {\n type: 'string'\n },\n m5: {\n type: 'string'\n }\n }\n },\n quote_token_price_base_token: {\n type: 'string'\n },\n quote_token_price_native_currency: {\n type: 'string'\n },\n quote_token_price_usd: {\n type: 'string'\n },\n reserve_in_usd: {\n type: 'string'\n }\n }\n },\n relationships: {\n type: 'object',\n properties: {\n base_token: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n },\n dex: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n },\n network: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n },\n quote_token: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n },\n included: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n coingecko_coin_id: {\n type: 'string'\n },\n decimals: {\n type: 'integer'\n },\n image_url: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - category_id: { - type: 'string', - }, - include: { - type: 'string', - description: - 'attributes to include, comma-separated if more than one to include \n Available values: `base_token`, `quote_token`, `dex`, `network`. \n Example: `base_token` or `base_token,dex`', - }, - page: { - type: 'integer', - description: 'page through results \n Default value: `1`', - }, - sort: { - type: 'string', - description: 'sort the pools by field \n Default value: `pool_created_at_desc`', - enum: [ - 'm5_trending', - 'h1_trending', - 'h6_trending', - 'h24_trending', - 'h24_tx_count_desc', - 'h24_volume_usd_desc', - 'pool_created_at_desc', - 'h24_price_change_percentage_desc', - ], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['category_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { category_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.categories.getPools(category_id, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/dexes/get-networks-onchain-dexes.ts b/packages/mcp-server/src/tools/onchain/networks/dexes/get-networks-onchain-dexes.ts deleted file mode 100644 index 0420aef..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/dexes/get-networks-onchain-dexes.ts +++ /dev/null @@ -1,60 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.dexes', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/dexes', - operationId: 'dexes-list', -}; - -export const tool: Tool = { - name: 'get_networks_onchain_dexes', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the supported decentralized exchanges (DEXs) based on the provided network on GeckoTerminal**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/dex_get_response',\n $defs: {\n dex_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n name: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - page: { - type: 'integer', - description: 'page through results \n Default value: 1', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { network, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.networks.dexes.get(network, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/get-onchain-networks.ts b/packages/mcp-server/src/tools/onchain/networks/get-onchain-networks.ts deleted file mode 100644 index edba120..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/get-onchain-networks.ts +++ /dev/null @@ -1,55 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks', - operationId: 'networks-list', -}; - -export const tool: Tool = { - name: 'get_onchain_networks', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the supported networks on GeckoTerminal**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/network_get_response',\n $defs: {\n network_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n coingecko_asset_platform_id: {\n type: 'string'\n },\n name: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - page: { - type: 'integer', - description: 'page through results \n Default value: 1', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.onchain.networks.get(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/new-pools/get-network-networks-onchain-new-pools.ts b/packages/mcp-server/src/tools/onchain/networks/new-pools/get-network-networks-onchain-new-pools.ts deleted file mode 100644 index 00b20d1..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/new-pools/get-network-networks-onchain-new-pools.ts +++ /dev/null @@ -1,60 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.new_pools', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/new_pools', - operationId: 'latest-pools-network', -}; - -export const tool: Tool = { - name: 'get_network_networks_onchain_new_pools', - description: 'This endpoint allows you to **query all the latest pools based on provided network**', - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - include: { - type: 'string', - description: - 'attributes to include, comma-separated if more than one to include \n Available values: `base_token`, `quote_token`, `dex`', - }, - include_gt_community_data: { - type: 'boolean', - description: - 'include GeckoTerminal community data (Sentiment votes, Suspicious reports) \n Default value: false', - }, - page: { - type: 'integer', - description: 'page through results \n Default value: 1', - }, - }, - required: ['network'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { network, ...body } = args as any; - try { - return asTextContentResult(await client.onchain.networks.newPools.getNetwork(network, body)); - } catch (error) { - if (error instanceof Coingecko.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/new-pools/get-networks-onchain-new-pools.ts b/packages/mcp-server/src/tools/onchain/networks/new-pools/get-networks-onchain-new-pools.ts deleted file mode 100644 index 8e10ecf..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/new-pools/get-networks-onchain-new-pools.ts +++ /dev/null @@ -1,67 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.new_pools', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/new_pools', - operationId: 'latest-pools-list', -}; - -export const tool: Tool = { - name: 'get_networks_onchain_new_pools', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the latest pools across all networks on GeckoTerminal**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/new_pool_get_response',\n $defs: {\n new_pool_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n base_token_price_native_currency: {\n type: 'string'\n },\n base_token_price_quote_token: {\n type: 'string'\n },\n base_token_price_usd: {\n type: 'string'\n },\n community_sus_report: {\n type: 'number'\n },\n fdv_usd: {\n type: 'string'\n },\n market_cap_usd: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n pool_created_at: {\n type: 'string'\n },\n price_change_percentage: {\n type: 'object',\n properties: {\n h1: {\n type: 'string'\n },\n h24: {\n type: 'string'\n },\n h6: {\n type: 'string'\n },\n m15: {\n type: 'string'\n },\n m30: {\n type: 'string'\n },\n m5: {\n type: 'string'\n }\n }\n },\n quote_token_price_base_token: {\n type: 'string'\n },\n quote_token_price_native_currency: {\n type: 'string'\n },\n quote_token_price_usd: {\n type: 'string'\n },\n reserve_in_usd: {\n type: 'string'\n },\n sentiment_vote_negative_percentage: {\n type: 'number'\n },\n sentiment_vote_positive_percentage: {\n type: 'number'\n },\n transactions: {\n type: 'object',\n properties: {\n h1: {\n type: 'object',\n properties: {\n buyers: {\n type: 'integer'\n },\n buys: {\n type: 'integer'\n },\n sellers: {\n type: 'integer'\n },\n sells: {\n type: 'integer'\n }\n }\n },\n h24: {\n type: 'object',\n properties: {\n buyers: {\n type: 'integer'\n },\n buys: {\n type: 'integer'\n },\n sellers: {\n type: 'integer'\n },\n sells: {\n type: 'integer'\n }\n }\n },\n m15: {\n type: 'object',\n properties: {\n buyers: {\n type: 'integer'\n },\n buys: {\n type: 'integer'\n },\n sellers: {\n type: 'integer'\n },\n sells: {\n type: 'integer'\n }\n }\n },\n m30: {\n type: 'object',\n properties: {\n buyers: {\n type: 'integer'\n },\n buys: {\n type: 'integer'\n },\n sellers: {\n type: 'integer'\n },\n sells: {\n type: 'integer'\n }\n }\n },\n m5: {\n type: 'object',\n properties: {\n buyers: {\n type: 'integer'\n },\n buys: {\n type: 'integer'\n },\n sellers: {\n type: 'integer'\n },\n sells: {\n type: 'integer'\n }\n }\n }\n }\n },\n volume_usd: {\n type: 'object',\n properties: {\n h1: {\n type: 'string'\n },\n h24: {\n type: 'string'\n },\n h6: {\n type: 'string'\n },\n m15: {\n type: 'string'\n },\n m30: {\n type: 'string'\n },\n m5: {\n type: 'string'\n }\n }\n }\n }\n },\n relationships: {\n type: 'object',\n properties: {\n base_token: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n },\n dex: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n },\n quote_token: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n },\n included: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n coingecko_coin_id: {\n type: 'string'\n },\n decimals: {\n type: 'integer'\n },\n image_url: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - include: { - type: 'string', - description: - 'attributes to include, comma-separated if more than one to include \n Available values: `base_token`, `quote_token`, `dex`, `network`', - }, - include_gt_community_data: { - type: 'boolean', - description: - 'include GeckoTerminal community data (Sentiment votes, Suspicious reports) \n Default value: false', - }, - page: { - type: 'integer', - description: 'page through results \n Default value: 1', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.networks.newPools.get(body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/pools/info/get-pools-networks-onchain-info.ts b/packages/mcp-server/src/tools/onchain/networks/pools/info/get-pools-networks-onchain-info.ts deleted file mode 100644 index 364756e..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/pools/info/get-pools-networks-onchain-info.ts +++ /dev/null @@ -1,64 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.pools.info', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/pools/{pool_address}/info', - operationId: 'pool-token-info-contract-address', -}; - -export const tool: Tool = { - name: 'get_pools_networks_onchain_info', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query pool metadata (base and quote token details, image, socials, websites, description, contract address, etc.) based on a provided pool contract address on a network**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/info_get_response',\n $defs: {\n info_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n categories: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n coingecko_coin_id: {\n type: 'string'\n },\n description: {\n type: 'string'\n },\n discord_url: {\n type: 'string'\n },\n farcaster_url: {\n type: 'string'\n },\n freeze_authority: {\n type: 'string'\n },\n gt_categories_id: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n gt_score: {\n type: 'number'\n },\n gt_score_details: {\n type: 'object',\n properties: {\n creation: {\n type: 'number'\n },\n holders: {\n type: 'number'\n },\n info: {\n type: 'number'\n },\n pool: {\n type: 'number'\n },\n transaction: {\n type: 'number'\n }\n }\n },\n holders: {\n type: 'object',\n properties: {\n count: {\n type: 'integer'\n },\n distribution_percentage: {\n type: 'object',\n properties: {\n '11_30': {\n type: 'number'\n },\n '31_50': {\n type: 'number'\n },\n rest: {\n type: 'number'\n },\n top_10: {\n type: 'number'\n }\n }\n },\n last_updated: {\n type: 'string'\n }\n }\n },\n image: {\n type: 'object',\n properties: {\n large: {\n type: 'string'\n },\n small: {\n type: 'string'\n },\n thumb: {\n type: 'string'\n }\n }\n },\n image_url: {\n type: 'string'\n },\n is_honeypot: {\n anyOf: [ {\n type: 'boolean'\n },\n {\n type: 'string'\n }\n ]\n },\n mint_authority: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n },\n telegram_handle: {\n type: 'string'\n },\n twitter_handle: {\n type: 'string'\n },\n websites: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n zora_url: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n },\n included: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n base_token_address: {\n type: 'string'\n },\n community_sus_report: {\n type: 'number'\n },\n quote_token_address: {\n type: 'string'\n },\n sentiment_vote_negative_percentage: {\n type: 'number'\n },\n sentiment_vote_positive_percentage: {\n type: 'number'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - pool_address: { - type: 'string', - }, - include: { - type: 'string', - description: 'attributes to include', - enum: ['pool'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network', 'pool_address'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { pool_address, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.networks.pools.info.get(pool_address, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/pools/multi/get-addresses-pools-networks-onchain-multi.ts b/packages/mcp-server/src/tools/onchain/networks/pools/multi/get-addresses-pools-networks-onchain-multi.ts deleted file mode 100644 index b69639c..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/pools/multi/get-addresses-pools-networks-onchain-multi.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.pools.multi', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/pools/multi/{addresses}', - operationId: 'pools-addresses', -}; - -export const tool: Tool = { - name: 'get_addresses_pools_networks_onchain_multi', - description: - 'This endpoint allows you to **query multiple pools based on the provided network and pool address**', - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - addresses: { - type: 'string', - }, - include: { - type: 'string', - description: - 'attributes to include, comma-separated if more than one to include \n Available values: `base_token`, `quote_token`, `dex`', - }, - include_composition: { - type: 'boolean', - description: 'include pool composition, default: false', - }, - include_volume_breakdown: { - type: 'boolean', - description: 'include volume breakdown, default: false', - }, - }, - required: ['network', 'addresses'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { addresses, ...body } = args as any; - try { - return asTextContentResult(await client.onchain.networks.pools.multi.getAddresses(addresses, body)); - } catch (error) { - if (error instanceof Coingecko.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/pools/ohlcv/get-timeframe-pools-networks-onchain-ohlcv.ts b/packages/mcp-server/src/tools/onchain/networks/pools/ohlcv/get-timeframe-pools-networks-onchain-ohlcv.ts deleted file mode 100644 index 0dc7604..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/pools/ohlcv/get-timeframe-pools-networks-onchain-ohlcv.ts +++ /dev/null @@ -1,90 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.pools.ohlcv', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/pools/{pool_address}/ohlcv/{timeframe}', - operationId: 'pool-ohlcv-contract-address', -}; - -export const tool: Tool = { - name: 'get_timeframe_pools_networks_onchain_ohlcv', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **get the OHLCV chart (Open, High, Low, Close, Volume) of a pool based on the provided pool address on a network**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/ohlcv_get_timeframe_response',\n $defs: {\n ohlcv_get_timeframe_response: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n ohlcv_list: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n },\n meta: {\n type: 'object',\n properties: {\n base: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n coingecko_coin_id: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n }\n }\n },\n quote: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n coingecko_coin_id: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - pool_address: { - type: 'string', - }, - timeframe: { - type: 'string', - enum: ['day', 'hour', 'minute', 'second'], - }, - token: { - type: 'string', - description: - "return OHLCV for token \n use this to invert the chart \n Available values: 'base', 'quote' or token address \n Default value: 'base'", - }, - aggregate: { - type: 'string', - description: - 'time period to aggregate each OHLCV \n Available values (day): `1` \n Available values (hour): `1` , `4` , `12` \n Available values (minute): `1` , `5` , `15` \n Available values (second): `1`, `15`, `30` \n Default value: 1', - }, - before_timestamp: { - type: 'integer', - description: 'return OHLCV data before this timestamp (integer seconds since epoch)', - }, - currency: { - type: 'string', - description: 'return OHLCV in USD or quote token \n Default value: usd', - enum: ['usd', 'token'], - }, - include_empty_intervals: { - type: 'boolean', - description: 'include empty intervals with no trade data, default: false', - }, - limit: { - type: 'integer', - description: 'number of OHLCV results to return, maximum 1000 \n Default value: 100', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network', 'pool_address', 'timeframe'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { timeframe, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.networks.pools.ohlcv.getTimeframe(timeframe, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/pools/trades/get-pools-networks-onchain-trades.ts b/packages/mcp-server/src/tools/onchain/networks/pools/trades/get-pools-networks-onchain-trades.ts deleted file mode 100644 index 90ff0d8..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/pools/trades/get-pools-networks-onchain-trades.ts +++ /dev/null @@ -1,68 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.pools.trades', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/pools/{pool_address}/trades', - operationId: 'pool-trades-contract-address', -}; - -export const tool: Tool = { - name: 'get_pools_networks_onchain_trades', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query the last 300 trades in the past 24 hours based on the provided pool address**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/trade_get_response',\n $defs: {\n trade_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n block_number: {\n type: 'integer'\n },\n block_timestamp: {\n type: 'string'\n },\n from_token_address: {\n type: 'string'\n },\n from_token_amount: {\n type: 'string'\n },\n kind: {\n type: 'string'\n },\n price_from_in_currency_token: {\n type: 'string'\n },\n price_from_in_usd: {\n type: 'string'\n },\n price_to_in_currency_token: {\n type: 'string'\n },\n price_to_in_usd: {\n type: 'string'\n },\n to_token_address: {\n type: 'string'\n },\n to_token_amount: {\n type: 'string'\n },\n tx_from_address: {\n type: 'string'\n },\n tx_hash: {\n type: 'string'\n },\n volume_in_usd: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - pool_address: { - type: 'string', - }, - token: { - type: 'string', - description: - "return trades for token \n use this to invert the chart \n Available values: 'base', 'quote' or token address \n Default value: 'base'", - }, - trade_volume_in_usd_greater_than: { - type: 'number', - description: 'filter trades by trade volume in USD greater than this value \n Default value: 0', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network', 'pool_address'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { pool_address, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.networks.pools.trades.get(pool_address, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/tokens/holders-chart/get-tokens-networks-onchain-holders-chart.ts b/packages/mcp-server/src/tools/onchain/networks/tokens/holders-chart/get-tokens-networks-onchain-holders-chart.ts deleted file mode 100644 index 7c935d7..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/tokens/holders-chart/get-tokens-networks-onchain-holders-chart.ts +++ /dev/null @@ -1,67 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.tokens.holders_chart', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/tokens/{token_address}/holders_chart', - operationId: 'token-holders-chart-token-address', -}; - -export const tool: Tool = { - name: 'get_tokens_networks_onchain_holders_chart', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **get the historical token holders chart based on the provided token contract address on a network**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/holders_chart_get_response',\n $defs: {\n holders_chart_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n token_holders_list: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'string'\n }\n }\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n },\n meta: {\n type: 'object',\n properties: {\n token: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n coingecko_coin_id: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - token_address: { - type: 'string', - }, - days: { - type: 'string', - description: 'number of days to return the historical token holders chart \n Default value: 7', - enum: ['7', '30', 'max'], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network', 'token_address'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { token_address, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter( - jq_filter, - await client.onchain.networks.tokens.holdersChart.get(token_address, body), - ), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/tokens/info/get-tokens-networks-onchain-info.ts b/packages/mcp-server/src/tools/onchain/networks/tokens/info/get-tokens-networks-onchain-info.ts deleted file mode 100644 index fbc078b..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/tokens/info/get-tokens-networks-onchain-info.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.tokens.info', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/tokens/{address}/info', - operationId: 'token-info-contract-address', -}; - -export const tool: Tool = { - name: 'get_tokens_networks_onchain_info', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query token metadata (name, symbol, CoinGecko ID, image, socials, websites, description, etc.) based on a provided token contract address on a network**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/info_get_response',\n $defs: {\n info_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n categories: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n coingecko_coin_id: {\n type: 'string'\n },\n description: {\n type: 'string'\n },\n discord_url: {\n type: 'string'\n },\n farcaster_url: {\n type: 'string'\n },\n freeze_authority: {\n type: 'string'\n },\n gt_categories_id: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n gt_score: {\n type: 'number'\n },\n gt_score_details: {\n type: 'object',\n properties: {\n creation: {\n type: 'number'\n },\n holders: {\n type: 'number'\n },\n info: {\n type: 'number'\n },\n pool: {\n type: 'number'\n },\n transaction: {\n type: 'number'\n }\n }\n },\n holders: {\n type: 'object',\n properties: {\n count: {\n type: 'integer'\n },\n distribution_percentage: {\n type: 'object',\n properties: {\n '11_30': {\n type: 'number'\n },\n '31_50': {\n type: 'number'\n },\n rest: {\n type: 'number'\n },\n top_10: {\n type: 'number'\n }\n }\n },\n last_updated: {\n type: 'string'\n }\n }\n },\n image: {\n type: 'object',\n properties: {\n large: {\n type: 'string'\n },\n small: {\n type: 'string'\n },\n thumb: {\n type: 'string'\n }\n }\n },\n image_url: {\n type: 'string'\n },\n is_honeypot: {\n anyOf: [ {\n type: 'boolean'\n },\n {\n type: 'string'\n }\n ]\n },\n mint_authority: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n },\n telegram_handle: {\n type: 'string'\n },\n twitter_handle: {\n type: 'string'\n },\n websites: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n zora_url: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - address: { - type: 'string', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network', 'address'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { address, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.networks.tokens.info.get(address, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/tokens/multi/get-addresses-tokens-networks-onchain-multi.ts b/packages/mcp-server/src/tools/onchain/networks/tokens/multi/get-addresses-tokens-networks-onchain-multi.ts deleted file mode 100644 index f3d14a2..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/tokens/multi/get-addresses-tokens-networks-onchain-multi.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.tokens.multi', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/tokens/multi/{addresses}', - operationId: 'tokens-data-contract-addresses', -}; - -export const tool: Tool = { - name: 'get_addresses_tokens_networks_onchain_multi', - description: - 'This endpoint allows you to **query multiple tokens data based on the provided token contract addresses on a network**', - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - addresses: { - type: 'string', - }, - include: { - type: 'string', - description: 'attributes to include', - enum: ['top_pools'], - }, - include_composition: { - type: 'boolean', - description: 'include pool composition, default: false', - }, - include_inactive_source: { - type: 'boolean', - description: 'include tokens from inactive pools using the most recent swap, default: false', - }, - }, - required: ['network', 'addresses'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { addresses, ...body } = args as any; - try { - return asTextContentResult(await client.onchain.networks.tokens.multi.getAddresses(addresses, body)); - } catch (error) { - if (error instanceof Coingecko.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/tokens/ohlcv/get-timeframe-tokens-networks-onchain-ohlcv.ts b/packages/mcp-server/src/tools/onchain/networks/tokens/ohlcv/get-timeframe-tokens-networks-onchain-ohlcv.ts deleted file mode 100644 index 2161c05..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/tokens/ohlcv/get-timeframe-tokens-networks-onchain-ohlcv.ts +++ /dev/null @@ -1,89 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.tokens.ohlcv', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/tokens/{token_address}/ohlcv/{timeframe}', - operationId: 'token-ohlcv-token-address', -}; - -export const tool: Tool = { - name: 'get_timeframe_tokens_networks_onchain_ohlcv', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **get the OHLCV chart (Open, High, Low, Close, Volume) of a token based on the provided token address on a network**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/ohlcv_get_timeframe_response',\n $defs: {\n ohlcv_get_timeframe_response: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n ohlcv_list: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n },\n meta: {\n type: 'object',\n properties: {\n base: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n coingecko_coin_id: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n }\n }\n },\n quote: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n coingecko_coin_id: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - token_address: { - type: 'string', - }, - timeframe: { - type: 'string', - enum: ['day', 'hour', 'minute', 'second'], - }, - aggregate: { - type: 'string', - description: - 'time period to aggregate each OHLCV \n Available values (day): `1` \n Available values (hour): `1` , `4` , `12` \n Available values (minute): `1` , `5` , `15` \n Available values (second): `1`, `15`, `30` \n Default value: 1', - }, - before_timestamp: { - type: 'integer', - description: 'return OHLCV data before this timestamp (integer seconds since epoch)', - }, - currency: { - type: 'string', - description: 'return OHLCV in USD or quote token \n Default value: usd', - enum: ['usd', 'token'], - }, - include_empty_intervals: { - type: 'boolean', - description: 'include empty intervals with no trade data, default: false', - }, - include_inactive_source: { - type: 'boolean', - description: 'include token data from inactive pools using the most recent swap, default: false', - }, - limit: { - type: 'integer', - description: 'number of OHLCV results to return, maximum 1000 \n Default value: 100', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network', 'token_address', 'timeframe'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { timeframe, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.networks.tokens.ohlcv.getTimeframe(timeframe, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/tokens/pools/get-tokens-networks-onchain-pools.ts b/packages/mcp-server/src/tools/onchain/networks/tokens/pools/get-tokens-networks-onchain-pools.ts deleted file mode 100644 index e0c5c74..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/tokens/pools/get-tokens-networks-onchain-pools.ts +++ /dev/null @@ -1,68 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.tokens.pools', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/tokens/{token_address}/pools', - operationId: 'top-pools-contract-address', -}; - -export const tool: Tool = { - name: 'get_tokens_networks_onchain_pools', - description: - 'This endpoint allows you to **query top pools based on the provided token contract address on a network**', - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - token_address: { - type: 'string', - }, - include: { - type: 'string', - description: - 'attributes to include, comma-separated if more than one to include \n Available values: `base_token`, `quote_token`, `dex`', - }, - include_inactive_source: { - type: 'boolean', - description: 'include tokens from inactive pools using the most recent swap, default: false', - }, - page: { - type: 'integer', - description: 'page through results \n Default value: 1', - }, - sort: { - type: 'string', - description: 'sort the pools by field \n Default value: h24_volume_usd_liquidity_desc', - enum: ['h24_volume_usd_liquidity_desc', 'h24_tx_count_desc', 'h24_volume_usd_desc'], - }, - }, - required: ['network', 'token_address'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { token_address, ...body } = args as any; - try { - return asTextContentResult(await client.onchain.networks.tokens.pools.get(token_address, body)); - } catch (error) { - if (error instanceof Coingecko.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/tokens/top-holders/get-tokens-networks-onchain-top-holders.ts b/packages/mcp-server/src/tools/onchain/networks/tokens/top-holders/get-tokens-networks-onchain-top-holders.ts deleted file mode 100644 index 4c0b616..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/tokens/top-holders/get-tokens-networks-onchain-top-holders.ts +++ /dev/null @@ -1,64 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.tokens.top_holders', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/tokens/{address}/top_holders', - operationId: 'top-token-holders-token-address', -}; - -export const tool: Tool = { - name: 'get_tokens_networks_onchain_top_holders', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query top token holders based on the provided token contract address on a network**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/top_holder_get_response',\n $defs: {\n top_holder_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n holders: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n amount: {\n type: 'string'\n },\n label: {\n type: 'string'\n },\n percentage: {\n type: 'string'\n },\n rank: {\n type: 'number'\n },\n value: {\n type: 'string'\n }\n }\n }\n },\n last_updated_at: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - address: { - type: 'string', - }, - holders: { - type: 'string', - description: - 'number of top token holders to return, you may use any integer or `max` \n Default value: 10', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network', 'address'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { address, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.networks.tokens.topHolders.get(address, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/tokens/top-traders/get-tokens-networks-onchain-top-traders.ts b/packages/mcp-server/src/tools/onchain/networks/tokens/top-traders/get-tokens-networks-onchain-top-traders.ts deleted file mode 100644 index 2b6fb55..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/tokens/top-traders/get-tokens-networks-onchain-top-traders.ts +++ /dev/null @@ -1,78 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.tokens.top_traders', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network_id}/tokens/{token_address}/top_traders', - operationId: 'top-token-traders-token-address', -}; - -export const tool: Tool = { - name: 'get_tokens_networks_onchain_top_traders', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query top token traders based on the provided token contract address on a network**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/top_trader_get_response',\n $defs: {\n top_trader_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n traders: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n average_buy_price_usd: {\n type: 'string'\n },\n average_sell_price_usd: {\n type: 'string'\n },\n explorer_url: {\n type: 'string'\n },\n label: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n realized_pnl_usd: {\n type: 'string'\n },\n token_balance: {\n type: 'string'\n },\n total_buy_count: {\n type: 'integer'\n },\n total_buy_token_amount: {\n type: 'string'\n },\n total_buy_usd: {\n type: 'string'\n },\n total_sell_count: {\n type: 'integer'\n },\n total_sell_token_amount: {\n type: 'string'\n },\n total_sell_usd: {\n type: 'string'\n },\n type: {\n type: 'string'\n },\n unrealized_pnl_usd: {\n type: 'string'\n }\n }\n }\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network_id: { - type: 'string', - }, - token_address: { - type: 'string', - }, - include_address_label: { - type: 'boolean', - description: 'include address label data, default: false', - }, - sort: { - type: 'string', - description: 'sort the traders by field \n Default value: realized_pnl_usd_desc', - enum: [ - 'realized_pnl_usd_desc', - 'unrealized_pnl_usd_desc', - 'total_buy_usd_desc', - 'total_sell_usd_desc', - ], - }, - traders: { - type: 'string', - description: - 'number of top token traders to return, you may use any integer or `max` \n Default value: 10', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network_id', 'token_address'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { token_address, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.networks.tokens.topTraders.get(token_address, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/networks/tokens/trades/get-tokens-networks-onchain-trades.ts b/packages/mcp-server/src/tools/onchain/networks/tokens/trades/get-tokens-networks-onchain-trades.ts deleted file mode 100644 index 723e34d..0000000 --- a/packages/mcp-server/src/tools/onchain/networks/tokens/trades/get-tokens-networks-onchain-trades.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.networks.tokens.trades', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/networks/{network}/tokens/{token_address}/trades', - operationId: 'token-trades-contract-address', -}; - -export const tool: Tool = { - name: 'get_tokens_networks_onchain_trades', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query the last 300 trades in the past 24 hours, across all pools, based on the provided token contract address on a network**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/trade_get_response',\n $defs: {\n trade_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n block_number: {\n type: 'integer'\n },\n block_timestamp: {\n type: 'string'\n },\n from_token_address: {\n type: 'string'\n },\n from_token_amount: {\n type: 'string'\n },\n kind: {\n type: 'string'\n },\n pool_address: {\n type: 'string'\n },\n pool_dex: {\n type: 'string'\n },\n price_from_in_currency_token: {\n type: 'string'\n },\n price_from_in_usd: {\n type: 'string'\n },\n price_to_in_currency_token: {\n type: 'string'\n },\n price_to_in_usd: {\n type: 'string'\n },\n to_token_address: {\n type: 'string'\n },\n to_token_amount: {\n type: 'string'\n },\n tx_from_address: {\n type: 'string'\n },\n tx_hash: {\n type: 'string'\n },\n volume_in_usd: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - token_address: { - type: 'string', - }, - trade_volume_in_usd_greater_than: { - type: 'number', - description: 'filter trades by trade volume in USD greater than this value \n Default value: 0', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network', 'token_address'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { token_address, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.networks.tokens.trades.get(token_address, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/pools/megafilter/get-pools-onchain-megafilter.ts b/packages/mcp-server/src/tools/onchain/pools/megafilter/get-pools-onchain-megafilter.ts deleted file mode 100644 index 584658b..0000000 --- a/packages/mcp-server/src/tools/onchain/pools/megafilter/get-pools-onchain-megafilter.ts +++ /dev/null @@ -1,185 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.pools.megafilter', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/pools/megafilter', - operationId: 'pools-megafilter', -}; - -export const tool: Tool = { - name: 'get_pools_onchain_megafilter', - description: - 'This endpoint allows you to **query pools based on various filters across all networks on GeckoTerminal**', - inputSchema: { - type: 'object', - properties: { - buy_tax_percentage_max: { - type: 'number', - description: 'maximum buy tax percentage', - }, - buy_tax_percentage_min: { - type: 'number', - description: 'minimum buy tax percentage', - }, - buys_duration: { - type: 'string', - description: 'duration for buy transactions metric \n Default value: 24h', - enum: ['5m', '1h', '6h', '24h'], - }, - buys_max: { - type: 'integer', - description: 'maximum number of buy transactions', - }, - buys_min: { - type: 'integer', - description: 'minimum number of buy transactions', - }, - checks: { - type: 'string', - description: - 'filter options for various checks, comma-separated if more than one \n Available values: `no_honeypot`, `good_gt_score`, `on_coingecko`, `has_social`', - }, - dexes: { - type: 'string', - description: - 'filter pools by DEXes, comma-separated if more than one \n DEX ID refers to [/networks/{network}/dexes](/reference/dexes-list)', - }, - fdv_usd_max: { - type: 'number', - description: 'maximum fully diluted value in USD', - }, - fdv_usd_min: { - type: 'number', - description: 'minimum fully diluted value in USD', - }, - h24_volume_usd_max: { - type: 'number', - description: 'maximum 24hr volume in USD', - }, - h24_volume_usd_min: { - type: 'number', - description: 'minimum 24hr volume in USD', - }, - include: { - type: 'string', - description: - 'attributes to include, comma-separated if more than one to include \n Available values: `base_token`, `quote_token`, `dex`, `network`', - }, - include_unknown_honeypot_tokens: { - type: 'boolean', - description: - "when `checks` includes `no_honeypot`, set to **`true`** to also include 'unknown honeypot' tokens. Default value: `false`", - }, - networks: { - type: 'string', - description: - 'filter pools by networks, comma-separated if more than one \n Network ID refers to [/networks](/reference/networks-list)', - }, - page: { - type: 'integer', - description: 'page through results \n Default value: 1', - }, - pool_created_hour_max: { - type: 'number', - description: 'maximum pool age in hours', - }, - pool_created_hour_min: { - type: 'number', - description: 'minimum pool age in hours', - }, - reserve_in_usd_max: { - type: 'number', - description: 'maximum reserve in USD', - }, - reserve_in_usd_min: { - type: 'number', - description: 'minimum reserve in USD', - }, - sell_tax_percentage_max: { - type: 'number', - description: 'maximum sell tax percentage', - }, - sell_tax_percentage_min: { - type: 'number', - description: 'minimum sell tax percentage', - }, - sells_duration: { - type: 'string', - description: 'duration for sell transactions metric \n Default value: 24h', - enum: ['5m', '1h', '6h', '24h'], - }, - sells_max: { - type: 'integer', - description: 'maximum number of sell transactions', - }, - sells_min: { - type: 'integer', - description: 'minimum number of sell transactions', - }, - sort: { - type: 'string', - description: 'sort the pools by field \n Default value: h6_trending', - enum: [ - 'm5_trending', - 'h1_trending', - 'h6_trending', - 'h24_trending', - 'h24_tx_count_desc', - 'h24_volume_usd_desc', - 'm5_price_change_percentage_asc', - 'h1_price_change_percentage_asc', - 'h6_price_change_percentage_asc', - 'h24_price_change_percentage_asc', - 'm5_price_change_percentage_desc', - 'h1_price_change_percentage_desc', - 'h6_price_change_percentage_desc', - 'h24_price_change_percentage_desc', - 'fdv_usd_asc', - 'fdv_usd_desc', - 'reserve_in_usd_asc', - 'reserve_in_usd_desc', - 'pool_created_at_desc', - ], - }, - tx_count_duration: { - type: 'string', - description: 'duration for transaction count metric \n Default value: 24h', - enum: ['5m', '1h', '6h', '24h'], - }, - tx_count_max: { - type: 'integer', - description: 'maximum transaction count', - }, - tx_count_min: { - type: 'integer', - description: 'minimum transaction count', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const body = args as any; - try { - return asTextContentResult(await client.onchain.pools.megafilter.get(body)); - } catch (error) { - if (error instanceof Coingecko.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/pools/trending-search/get-pools-onchain-trending-search.ts b/packages/mcp-server/src/tools/onchain/pools/trending-search/get-pools-onchain-trending-search.ts deleted file mode 100644 index 7391438..0000000 --- a/packages/mcp-server/src/tools/onchain/pools/trending-search/get-pools-onchain-trending-search.ts +++ /dev/null @@ -1,62 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.pools.trending_search', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/pools/trending_search', - operationId: 'trending-search-pools', -}; - -export const tool: Tool = { - name: 'get_pools_onchain_trending_search', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the trending search pools across all networks on GeckoTerminal**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/trending_search_get_response',\n $defs: {\n trending_search_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n fdv_usd: {\n type: 'string'\n },\n market_cap_usd: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n pool_created_at: {\n type: 'string'\n },\n reserve_in_usd: {\n type: 'string'\n },\n trending_rank: {\n type: 'number'\n },\n volume_usd: {\n type: 'object',\n properties: {\n h24: {\n type: 'string'\n }\n }\n }\n }\n },\n relationships: {\n type: 'object',\n properties: {\n base_token: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n },\n dex: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n },\n network: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n },\n quote_token: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n },\n included: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n coingecko_coin_id: {\n type: 'string'\n },\n decimals: {\n type: 'integer'\n },\n image_url: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - include: { - type: 'string', - description: - 'attributes to include, comma-separated if more than one to include \n Available values: `base_token`, `quote_token`, `dex`, `network`', - }, - pools: { - type: 'integer', - description: 'number of pools to return, maximum 10 \n Default value: 4', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.onchain.pools.trendingSearch.get(body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/search/pools/get-search-onchain-pools.ts b/packages/mcp-server/src/tools/onchain/search/pools/get-search-onchain-pools.ts deleted file mode 100644 index 18fa7ef..0000000 --- a/packages/mcp-server/src/tools/onchain/search/pools/get-search-onchain-pools.ts +++ /dev/null @@ -1,68 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.search.pools', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/search/pools', - operationId: 'search-pools', -}; - -export const tool: Tool = { - name: 'get_search_onchain_pools', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **search for pools on a network**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/pool_get_response',\n $defs: {\n pool_get_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n base_token_price_native_currency: {\n type: 'string'\n },\n base_token_price_quote_token: {\n type: 'string'\n },\n base_token_price_usd: {\n type: 'string'\n },\n fdv_usd: {\n type: 'string'\n },\n market_cap_usd: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n pool_created_at: {\n type: 'string'\n },\n price_change_percentage: {\n type: 'object',\n properties: {\n h1: {\n type: 'string'\n },\n h24: {\n type: 'string'\n },\n h6: {\n type: 'string'\n },\n m15: {\n type: 'string'\n },\n m30: {\n type: 'string'\n },\n m5: {\n type: 'string'\n }\n }\n },\n quote_token_price_base_token: {\n type: 'string'\n },\n quote_token_price_native_currency: {\n type: 'string'\n },\n quote_token_price_usd: {\n type: 'string'\n },\n reserve_in_usd: {\n type: 'string'\n },\n transactions: {\n type: 'object',\n properties: {\n h1: {\n type: 'object',\n properties: {\n buyers: {\n type: 'integer'\n },\n buys: {\n type: 'integer'\n },\n sellers: {\n type: 'integer'\n },\n sells: {\n type: 'integer'\n }\n }\n },\n h24: {\n type: 'object',\n properties: {\n buyers: {\n type: 'integer'\n },\n buys: {\n type: 'integer'\n },\n sellers: {\n type: 'integer'\n },\n sells: {\n type: 'integer'\n }\n }\n },\n m15: {\n type: 'object',\n properties: {\n buyers: {\n type: 'integer'\n },\n buys: {\n type: 'integer'\n },\n sellers: {\n type: 'integer'\n },\n sells: {\n type: 'integer'\n }\n }\n },\n m30: {\n type: 'object',\n properties: {\n buyers: {\n type: 'integer'\n },\n buys: {\n type: 'integer'\n },\n sellers: {\n type: 'integer'\n },\n sells: {\n type: 'integer'\n }\n }\n },\n m5: {\n type: 'object',\n properties: {\n buyers: {\n type: 'integer'\n },\n buys: {\n type: 'integer'\n },\n sellers: {\n type: 'integer'\n },\n sells: {\n type: 'integer'\n }\n }\n }\n }\n },\n volume_usd: {\n type: 'object',\n properties: {\n h1: {\n type: 'string'\n },\n h24: {\n type: 'string'\n },\n h6: {\n type: 'string'\n },\n m15: {\n type: 'string'\n },\n m30: {\n type: 'string'\n },\n m5: {\n type: 'string'\n }\n }\n }\n }\n },\n relationships: {\n type: 'object',\n properties: {\n base_token: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n },\n dex: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n },\n quote_token: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n },\n included: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n address: {\n type: 'string'\n },\n coingecko_coin_id: {\n type: 'string'\n },\n decimals: {\n type: 'integer'\n },\n image_url: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n symbol: {\n type: 'string'\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - include: { - type: 'string', - description: - 'attributes to include, comma-separated if more than one to include \n Available values: `base_token`, `quote_token`, `dex`', - }, - network: { - type: 'string', - description: 'network ID \n *refers to [/networks](/reference/networks-list)', - }, - page: { - type: 'integer', - description: 'page through results \n Default value: 1', - }, - query: { - type: 'string', - description: 'search query', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.onchain.search.pools.get(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/onchain/simple/networks/token-price/get-addresses-networks-simple-onchain-token-price.ts b/packages/mcp-server/src/tools/onchain/simple/networks/token-price/get-addresses-networks-simple-onchain-token-price.ts deleted file mode 100644 index 4e090ba..0000000 --- a/packages/mcp-server/src/tools/onchain/simple/networks/token-price/get-addresses-networks-simple-onchain-token-price.ts +++ /dev/null @@ -1,87 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'onchain.simple.networks.token_price', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/onchain/simple/networks/{network}/token_price/{addresses}', - operationId: 'onchain-simple-price', -}; - -export const tool: Tool = { - name: 'get_addresses_networks_simple_onchain_token_price', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **get token price based on the provided token contract address on a network**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/token_price_get_addresses_response',\n $defs: {\n token_price_get_addresses_response: {\n type: 'object',\n properties: {\n data: {\n type: 'object',\n properties: {\n id: {\n type: 'string'\n },\n attributes: {\n type: 'object',\n properties: {\n h24_price_change_percentage: {\n type: 'object',\n additionalProperties: true\n },\n h24_volume_usd: {\n type: 'object',\n additionalProperties: true\n },\n last_trade_timestamp: {\n type: 'object',\n additionalProperties: true\n },\n market_cap_usd: {\n type: 'object',\n additionalProperties: true\n },\n token_prices: {\n type: 'object',\n additionalProperties: true\n },\n total_reserve_in_usd: {\n type: 'object',\n additionalProperties: true\n }\n }\n },\n type: {\n type: 'string'\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - network: { - type: 'string', - }, - addresses: { - type: 'string', - }, - include_24hr_price_change: { - type: 'boolean', - description: 'include 24hr price change, default: false', - }, - include_24hr_vol: { - type: 'boolean', - description: 'include 24hr volume, default: false', - }, - include_inactive_source: { - type: 'boolean', - description: - 'include token price data from inactive pools using the most recent swap, default: false', - }, - include_market_cap: { - type: 'boolean', - description: 'include market capitalization, default: false', - }, - include_total_reserve_in_usd: { - type: 'boolean', - description: 'include total reserve in USD, default: false', - }, - mcap_fdv_fallback: { - type: 'boolean', - description: 'return FDV if market cap is not available, default: false', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['network', 'addresses'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { addresses, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter( - jq_filter, - await client.onchain.simple.networks.tokenPrice.getAddresses(addresses, body), - ), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/public-treasury/get-holding-chart-public-treasury.ts b/packages/mcp-server/src/tools/public-treasury/get-holding-chart-public-treasury.ts deleted file mode 100644 index c872029..0000000 --- a/packages/mcp-server/src/tools/public-treasury/get-holding-chart-public-treasury.ts +++ /dev/null @@ -1,67 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'public_treasury', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/public_treasury/{entity_id}/{coin_id}/holding_chart', - operationId: 'public-treasury-entity-chart', -}; - -export const tool: Tool = { - name: 'get_holding_chart_public_treasury', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query historical cryptocurrency holdings chart of public companies & governments** by Entity ID and Coin ID\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/public_treasury_get_holding_chart_response',\n $defs: {\n public_treasury_get_holding_chart_response: {\n type: 'object',\n properties: {\n holding_value_in_usd: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n },\n holdings: {\n type: 'array',\n items: {\n type: 'array',\n items: {\n type: 'number'\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_id: { - type: 'string', - }, - coin_id: { - type: 'string', - }, - days: { - type: 'string', - description: 'data up to number of days ago \n Valid values: `7, 14, 30, 90, 180, 365, 730, max`', - }, - include_empty_intervals: { - type: 'boolean', - description: 'include empty intervals with no transaction data, default: false', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['entity_id', 'coin_id', 'days'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { coin_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.publicTreasury.getHoldingChart(coin_id, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/public-treasury/get-transaction-history-public-treasury.ts b/packages/mcp-server/src/tools/public-treasury/get-transaction-history-public-treasury.ts deleted file mode 100644 index 60050d7..0000000 --- a/packages/mcp-server/src/tools/public-treasury/get-transaction-history-public-treasury.ts +++ /dev/null @@ -1,83 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'public_treasury', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/public_treasury/{entity_id}/transaction_history', - operationId: 'public-treasury-transaction-history', -}; - -export const tool: Tool = { - name: 'get_transaction_history_public_treasury', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you **query public companies & governments' cryptocurrency transaction history** by Entity ID\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/public_treasury_get_transaction_history_response',\n $defs: {\n public_treasury_get_transaction_history_response: {\n type: 'object',\n properties: {\n transactions: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n average_entry_value_usd: {\n type: 'number',\n description: 'average entry value in usd after the transaction'\n },\n coin_id: {\n type: 'string',\n description: 'coin ID'\n },\n date: {\n type: 'number',\n description: 'transaction date in UNIX timestamp'\n },\n holding_balance: {\n type: 'number',\n description: 'total holding balance after the transaction'\n },\n holding_net_change: {\n type: 'number',\n description: 'net change in holdings after the transaction'\n },\n source_url: {\n type: 'string',\n description: 'source document URL'\n },\n transaction_value_usd: {\n type: 'number',\n description: 'transaction value in usd'\n },\n type: {\n type: 'string',\n description: 'transaction type: buy or sell',\n enum: [ 'buy',\n 'sell'\n ]\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_id: { - type: 'string', - }, - coin_ids: { - type: 'string', - description: - 'filter transactions by coin IDs, comma-separated if querying more than 1 coin \n *refers to [`/coins/list`](/reference/coins-list).', - }, - order: { - type: 'string', - description: 'use this to sort the order of transactions, default: `date_desc`', - enum: [ - 'date_desc', - 'date_asc', - 'holding_net_change_desc', - 'holding_net_change_asc', - 'transaction_value_usd_desc', - 'transaction_value_usd_asc', - 'average_cost_desc', - 'average_cost_asc', - ], - }, - page: { - type: 'number', - description: 'page through results, default: `1`', - }, - per_page: { - type: 'number', - description: 'total results per page, default: `100` \n Valid values: 1...250', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['entity_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { entity_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.publicTreasury.getTransactionHistory(entity_id, body)), - ); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/search/get-search.ts b/packages/mcp-server/src/tools/search/get-search.ts deleted file mode 100644 index 830442b..0000000 --- a/packages/mcp-server/src/tools/search/get-search.ts +++ /dev/null @@ -1,55 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'search', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/search', - operationId: 'search-data', -}; - -export const tool: Tool = { - name: 'get_search', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **search for coins, categories and markets listed on CoinGecko**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/search_get_response',\n $defs: {\n search_get_response: {\n type: 'object',\n properties: {\n categories: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'category ID'\n },\n name: {\n type: 'string',\n description: 'category name'\n }\n }\n }\n },\n coins: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'coin ID'\n },\n api_symbol: {\n type: 'string',\n description: 'coin api symbol'\n },\n large: {\n type: 'string',\n description: 'coin large image url'\n },\n market_cap_rank: {\n type: 'number',\n description: 'coin market cap rank'\n },\n name: {\n type: 'string',\n description: 'coin name'\n },\n symbol: {\n type: 'string',\n description: 'coin symbol'\n },\n thumb: {\n type: 'string',\n description: 'coin thumb image url'\n }\n }\n }\n },\n exchanges: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'exchange ID'\n },\n large: {\n type: 'string',\n description: 'exchange large image url'\n },\n market_type: {\n type: 'string',\n description: 'exchange market type'\n },\n name: {\n type: 'string',\n description: 'exchange name'\n },\n thumb: {\n type: 'string',\n description: 'exchange thumb image url'\n }\n }\n }\n },\n icos: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n nfts: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'NFT collection ID'\n },\n name: {\n type: 'string',\n description: 'NFT name'\n },\n symbol: {\n type: 'string',\n description: 'NFT collection symbol'\n },\n thumb: {\n type: 'string',\n description: 'NFT collection thumb image url'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - query: { - type: 'string', - description: 'search query', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['query'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.search.get(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/search/trending/get-search-trending.ts b/packages/mcp-server/src/tools/search/trending/get-search-trending.ts deleted file mode 100644 index 0063800..0000000 --- a/packages/mcp-server/src/tools/search/trending/get-search-trending.ts +++ /dev/null @@ -1,56 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'search.trending', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/search/trending', - operationId: 'trending-search', -}; - -export const tool: Tool = { - name: 'get_search_trending', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you **query trending search coins, NFTs and categories on CoinGecko in the last 24 hours**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/trending_get_response',\n $defs: {\n trending_get_response: {\n type: 'object',\n properties: {\n categories: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'number'\n },\n coins_count: {\n type: 'number',\n description: 'category number of coins'\n },\n data: {\n type: 'object',\n properties: {\n market_cap: {\n type: 'number',\n description: 'category market cap'\n },\n market_cap_btc: {\n type: 'number',\n description: 'category market cap in btc'\n },\n market_cap_change_percentage_24h: {\n type: 'object',\n description: 'category market cap change percentage in 24 hours',\n properties: {\n btc: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n sparkline: {\n type: 'string',\n description: 'category sparkline image url'\n },\n total_volume: {\n type: 'number',\n description: 'category total volume'\n },\n total_volume_btc: {\n type: 'number',\n description: 'category total volume in btc'\n }\n }\n },\n market_cap_1h_change: {\n type: 'number',\n description: 'category market cap 1 hour change'\n },\n name: {\n type: 'string',\n description: 'category name'\n },\n slug: {\n type: 'string',\n description: 'category web slug'\n }\n }\n }\n },\n coins: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'coin ID'\n },\n coin_id: {\n type: 'number'\n },\n data: {\n type: 'object',\n properties: {\n content: {\n type: 'string'\n },\n market_cap: {\n type: 'string',\n description: 'coin market cap in usd'\n },\n market_cap_btc: {\n type: 'string',\n description: 'coin market cap in btc'\n },\n price: {\n type: 'number',\n description: 'coin price in usd'\n },\n price_btc: {\n type: 'string',\n description: 'coin price in btc'\n },\n price_change_percentage_24h: {\n type: 'object',\n description: 'coin price change percentage in 24 hours',\n properties: {\n btc: {\n type: 'number'\n },\n usd: {\n type: 'number'\n }\n }\n },\n sparkline: {\n type: 'string',\n description: 'coin sparkline image url'\n },\n total_volume: {\n type: 'string',\n description: 'coin total volume in usd'\n },\n total_volume_btc: {\n type: 'string',\n description: 'coin total volume in btc'\n }\n }\n },\n large: {\n type: 'string',\n description: 'coin large image url'\n },\n market_cap_rank: {\n type: 'number',\n description: 'coin market cap rank'\n },\n name: {\n type: 'string',\n description: 'coin name'\n },\n price_btc: {\n type: 'number',\n description: 'coin price in btc'\n },\n score: {\n type: 'number',\n description: 'coin sequence in the list'\n },\n slug: {\n type: 'string',\n description: 'coin web slug'\n },\n small: {\n type: 'string',\n description: 'coin small image url'\n },\n symbol: {\n type: 'string',\n description: 'coin symbol'\n },\n thumb: {\n type: 'string',\n description: 'coin thumb image url'\n }\n }\n }\n },\n nfts: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'NFT collection ID'\n },\n data: {\n type: 'object',\n properties: {\n content: {\n type: 'string'\n },\n floor_price: {\n type: 'string',\n description: 'NFT collection floor price'\n },\n floor_price_in_usd_24h_percentage_change: {\n type: 'string',\n description: 'NFT collection floor price in usd 24 hours percentage change'\n },\n h24_average_sale_price: {\n type: 'string',\n description: 'NFT collection 24 hours average sale price'\n },\n h24_volume: {\n type: 'string',\n description: 'NFT collection volume in 24 hours'\n },\n sparkline: {\n type: 'string',\n description: 'NFT collection sparkline image url'\n }\n }\n },\n floor_price_24h_percentage_change: {\n type: 'number',\n description: 'NFT collection floor price 24 hours percentage change'\n },\n floor_price_in_native_currency: {\n type: 'number',\n description: 'NFT collection floor price in native currency'\n },\n name: {\n type: 'string',\n description: 'NFT collection name'\n },\n native_currency_symbol: {\n type: 'string',\n description: 'NFT collection native currency symbol'\n },\n nft_contract_id: {\n type: 'number'\n },\n symbol: {\n type: 'string',\n description: 'NFT collection symbol'\n },\n thumb: {\n type: 'string',\n description: 'NFT collection thumb image url'\n }\n }\n }\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - show_max: { - type: 'string', - description: - 'show max number of results available for the given type \n Available values: `coins`, `nfts`, `categories` \n Example: `coins` or `coins,nfts,categories`', - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.search.trending.get(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/simple/price/get-simple-price.ts b/packages/mcp-server/src/tools/simple/price/get-simple-price.ts deleted file mode 100644 index b67b290..0000000 --- a/packages/mcp-server/src/tools/simple/price/get-simple-price.ts +++ /dev/null @@ -1,117 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'simple.price', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/simple/price', - operationId: 'simple-price', -}; - -export const tool: Tool = { - name: 'get_simple_price', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query the prices of one or more coins by using their unique Coin API IDs**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/price_get_response',\n $defs: {\n price_get_response: {\n type: 'object',\n additionalProperties: true\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - vs_currencies: { - type: 'string', - description: - 'target currency of coins, comma-separated if querying more than 1 currency. \n *refers to [`/simple/supported_vs_currencies`](/reference/simple-supported-currencies).', - }, - ids: { - type: 'string', - description: - "coins' IDs, comma-separated if querying more than 1 coin. \n *refers to [`/coins/list`](/reference/coins-list).", - }, - include_24hr_change: { - type: 'boolean', - description: 'include 24hr change percentage, default: false', - }, - include_24hr_vol: { - type: 'boolean', - description: 'include 24hr volume, default: false', - }, - include_last_updated_at: { - type: 'boolean', - description: 'include last updated price time in UNIX, default: false', - }, - include_market_cap: { - type: 'boolean', - description: 'include market capitalization, default: false', - }, - include_tokens: { - type: 'string', - description: - 'for `symbols` lookups, specify `all` to include all matching tokens \n Default `top` returns top-ranked tokens (by market cap or volume)', - enum: ['top', 'all'], - }, - names: { - type: 'string', - description: "coins' names, comma-separated if querying more than 1 coin.", - }, - precision: { - type: 'string', - description: 'decimal place for currency price value', - enum: [ - 'full', - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '10', - '11', - '12', - '13', - '14', - '15', - '16', - '17', - '18', - ], - }, - symbols: { - type: 'string', - description: "coins' symbols, comma-separated if querying more than 1 coin.", - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['vs_currencies'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.simple.price.get(body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/simple/supported-vs-currencies/get-simple-supported-vs-currencies.ts b/packages/mcp-server/src/tools/simple/supported-vs-currencies/get-simple-supported-vs-currencies.ts deleted file mode 100644 index bbab340..0000000 --- a/packages/mcp-server/src/tools/simple/supported-vs-currencies/get-simple-supported-vs-currencies.ts +++ /dev/null @@ -1,51 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'simple.supported_vs_currencies', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/simple/supported_vs_currencies', - operationId: 'simple-supported-currencies', -}; - -export const tool: Tool = { - name: 'get_simple_supported_vs_currencies', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query all the supported currencies on CoinGecko**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/supported_vs_currency_get_response',\n $defs: {\n supported_vs_currency_get_response: {\n type: 'array',\n items: {\n type: 'string'\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { jq_filter } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.simple.supportedVsCurrencies.get())); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/simple/token-price/get-id-simple-token-price.ts b/packages/mcp-server/src/tools/simple/token-price/get-id-simple-token-price.ts deleted file mode 100644 index 5aeabab..0000000 --- a/packages/mcp-server/src/tools/simple/token-price/get-id-simple-token-price.ts +++ /dev/null @@ -1,106 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@coingecko/coingecko-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@coingecko/coingecko-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Coingecko from '@coingecko/coingecko-typescript'; - -export const metadata: Metadata = { - resource: 'simple.token_price', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/simple/token_price/{id}', - operationId: 'simple-token-price', -}; - -export const tool: Tool = { - name: 'get_id_simple_token_price', - description: - "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis endpoint allows you to **query one or more token prices using their token contract addresses**\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/token_price_get_id_response',\n $defs: {\n token_price_get_id_response: {\n type: 'object',\n properties: {\n last_updated_at: {\n type: 'number',\n description: 'last updated timestamp'\n },\n usd: {\n type: 'number',\n description: 'price in USD'\n },\n usd_24h_change: {\n type: 'number',\n description: '24hr change in USD'\n },\n usd_24h_vol: {\n type: 'number',\n description: '24hr volume in USD'\n },\n usd_market_cap: {\n type: 'number',\n description: 'market cap in USD'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - id: { - type: 'string', - }, - contract_addresses: { - type: 'string', - description: - "the contract addresses of tokens, comma-separated if querying more than 1 token's contract address", - }, - vs_currencies: { - type: 'string', - description: - 'target currency of coins, comma-separated if querying more than 1 currency. \n *refers to [`/simple/supported_vs_currencies`](/reference/simple-supported-currencies).', - }, - include_24hr_change: { - type: 'boolean', - description: 'include 24hr change \n default: false', - }, - include_24hr_vol: { - type: 'boolean', - description: 'include 24hr volume, default: false', - }, - include_last_updated_at: { - type: 'boolean', - description: 'include last updated price time in UNIX , default: false', - }, - include_market_cap: { - type: 'boolean', - description: 'include market capitalization, default: false', - }, - precision: { - type: 'string', - description: 'decimal place for currency price value', - enum: [ - 'full', - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '10', - '11', - '12', - '13', - '14', - '15', - '16', - '17', - '18', - ], - }, - jq_filter: { - type: 'string', - title: 'jq Filter', - description: - 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).', - }, - }, - required: ['id', 'contract_addresses', 'vs_currencies'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Coingecko, args: Record | undefined) => { - const { id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.simple.tokenPrice.getID(id, body))); - } catch (error) { - if (error instanceof Coingecko.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/types.ts b/packages/mcp-server/src/types.ts similarity index 98% rename from packages/mcp-server/src/tools/types.ts rename to packages/mcp-server/src/types.ts index 962b2ca..29e12ec 100644 --- a/packages/mcp-server/src/tools/types.ts +++ b/packages/mcp-server/src/types.ts @@ -108,7 +108,7 @@ export type Metadata = { operationId?: string; }; -export type Endpoint = { +export type McpTool = { metadata: Metadata; tool: Tool; handler: HandlerFunction; diff --git a/packages/mcp-server/tests/compat.test.ts b/packages/mcp-server/tests/compat.test.ts deleted file mode 100644 index d6272f6..0000000 --- a/packages/mcp-server/tests/compat.test.ts +++ /dev/null @@ -1,1166 +0,0 @@ -import { - truncateToolNames, - removeTopLevelUnions, - removeAnyOf, - inlineRefs, - applyCompatibilityTransformations, - removeFormats, - findUsedDefs, -} from '../src/compat'; -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import { JSONSchema } from '../src/compat'; -import { Endpoint } from '../src/tools'; - -describe('truncateToolNames', () => { - it('should return original names when maxLength is 0 or negative', () => { - const names = ['tool1', 'tool2', 'tool3']; - expect(truncateToolNames(names, 0)).toEqual(new Map()); - expect(truncateToolNames(names, -1)).toEqual(new Map()); - }); - - it('should return original names when all names are shorter than maxLength', () => { - const names = ['tool1', 'tool2', 'tool3']; - expect(truncateToolNames(names, 10)).toEqual(new Map()); - }); - - it('should truncate names longer than maxLength', () => { - const names = ['very-long-tool-name', 'another-long-tool-name', 'short']; - expect(truncateToolNames(names, 10)).toEqual( - new Map([ - ['very-long-tool-name', 'very-long-'], - ['another-long-tool-name', 'another-lo'], - ]), - ); - }); - - it('should handle duplicate truncated names by appending numbers', () => { - const names = ['tool-name-a', 'tool-name-b', 'tool-name-c']; - expect(truncateToolNames(names, 8)).toEqual( - new Map([ - ['tool-name-a', 'tool-na1'], - ['tool-name-b', 'tool-na2'], - ['tool-name-c', 'tool-na3'], - ]), - ); - }); -}); - -describe('removeTopLevelUnions', () => { - const createTestTool = (overrides = {}): Tool => ({ - name: 'test-tool', - description: 'Test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - ...overrides, - }); - - it('should return the original tool if it has no anyOf at the top level', () => { - const tool = createTestTool({ - inputSchema: { - type: 'object', - properties: { - foo: { type: 'string' }, - }, - }, - }); - - expect(removeTopLevelUnions(tool)).toEqual([tool]); - }); - - it('should split a tool with top-level anyOf into multiple tools', () => { - const tool = createTestTool({ - name: 'union-tool', - description: 'A tool with unions', - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - description: 'Its the first variant', - properties: { - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - ], - }, - }); - - const result = removeTopLevelUnions(tool); - - expect(result).toEqual([ - { - name: 'union-tool_first_variant', - description: 'Its the first variant', - inputSchema: { - type: 'object', - title: 'first variant', - description: 'Its the first variant', - properties: { - common: { type: 'string' }, - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - }, - { - name: 'union-tool_second_variant', - description: 'A tool with unions', - inputSchema: { - type: 'object', - title: 'second variant', - description: 'A tool with unions', - properties: { - common: { type: 'string' }, - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - }, - ]); - }); - - it('should handle $defs and only include those used by the variant', () => { - const tool = createTestTool({ - name: 'defs-tool', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - $defs: { - def1: { type: 'string', format: 'email' }, - def2: { type: 'number', minimum: 0 }, - unused: { type: 'boolean' }, - }, - anyOf: [ - { - properties: { - email: { $ref: '#/$defs/def1' }, - }, - }, - { - properties: { - count: { $ref: '#/$defs/def2' }, - }, - }, - ], - }, - }); - - const result = removeTopLevelUnions(tool); - - expect(result).toEqual([ - { - name: 'defs-tool_variant1', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - description: 'A tool with $defs', - properties: { - common: { type: 'string' }, - email: { $ref: '#/$defs/def1' }, - }, - $defs: { - def1: { type: 'string', format: 'email' }, - }, - }, - }, - { - name: 'defs-tool_variant2', - description: 'A tool with $defs', - inputSchema: { - type: 'object', - description: 'A tool with $defs', - properties: { - common: { type: 'string' }, - count: { $ref: '#/$defs/def2' }, - }, - $defs: { - def2: { type: 'number', minimum: 0 }, - }, - }, - }, - ]); - }); -}); - -describe('removeAnyOf', () => { - it('should return original schema if it has no anyOf', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - bar: { type: 'number' }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(schema); - }); - - it('should remove anyOf field and use the first variant', () => { - const schema = { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - properties: { - variant1: { type: 'string' }, - }, - required: ['variant1'], - }, - { - properties: { - variant2: { type: 'number' }, - }, - required: ['variant2'], - }, - ], - }; - - const expected = { - type: 'object', - properties: { - common: { type: 'string' }, - variant1: { type: 'string' }, - }, - required: ['variant1'], - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); - - it('should recursively remove anyOf fields from nested properties', () => { - const schema = { - type: 'object', - properties: { - foo: { type: 'string' }, - nested: { - type: 'object', - properties: { - bar: { type: 'number' }, - }, - anyOf: [ - { - properties: { - option1: { type: 'boolean' }, - }, - }, - { - properties: { - option2: { type: 'array' }, - }, - }, - ], - }, - }, - }; - - const expected = { - type: 'object', - properties: { - foo: { type: 'string' }, - nested: { - type: 'object', - properties: { - bar: { type: 'number' }, - option1: { type: 'boolean' }, - }, - }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); - - it('should handle arrays', () => { - const schema = { - type: 'object', - properties: { - items: { - type: 'array', - items: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - items: { - type: 'array', - items: { - type: 'string', - }, - }, - }, - }; - - expect(removeAnyOf(schema)).toEqual(expected); - }); -}); - -describe('findUsedDefs', () => { - it('should handle circular references without stack overflow', () => { - const defs = { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - friend: { $ref: '#/$defs/person' }, // Circular reference - }, - }, - }; - - const schema = { - type: 'object', - properties: { - user: { $ref: '#/$defs/person' }, - }, - }; - - // This should not throw a stack overflow error - expect(() => { - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('person'); - }).not.toThrow(); - }); - - it('should handle indirect circular references without stack overflow', () => { - const defs = { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { $ref: '#/$defs/childNode' }, - }, - }, - childNode: { - type: 'object', - properties: { - value: { type: 'string' }, - parent: { $ref: '#/$defs/node' }, // Indirect circular reference - }, - }, - }; - - const schema = { - type: 'object', - properties: { - root: { $ref: '#/$defs/node' }, - }, - }; - - // This should not throw a stack overflow error - expect(() => { - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('node'); - expect(result).toHaveProperty('childNode'); - }).not.toThrow(); - }); - - it('should find all used definitions in non-circular schemas', () => { - const defs = { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - address: { $ref: '#/$defs/address' }, - }, - }, - address: { - type: 'object', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - }, - unused: { - type: 'object', - properties: { - data: { type: 'string' }, - }, - }, - }; - - const schema = { - type: 'object', - properties: { - person: { $ref: '#/$defs/user' }, - }, - }; - - const result = findUsedDefs(schema, defs); - expect(result).toHaveProperty('user'); - expect(result).toHaveProperty('address'); - expect(result).not.toHaveProperty('unused'); - }); -}); - -describe('inlineRefs', () => { - it('should return the original schema if it does not contain $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - name: { type: 'string' }, - age: { type: 'number' }, - }, - }; - - expect(inlineRefs(schema)).toEqual(schema); - }); - - it('should inline simple $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should inline nested $refs', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - order: { $ref: '#/$defs/order' }, - }, - $defs: { - order: { - type: 'object', - properties: { - id: { type: 'string' }, - items: { type: 'array', items: { $ref: '#/$defs/item' } }, - }, - }, - item: { - type: 'object', - properties: { - product: { type: 'string' }, - quantity: { type: 'integer' }, - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - order: { - type: 'object', - properties: { - id: { type: 'string' }, - items: { - type: 'array', - items: { - type: 'object', - properties: { - product: { type: 'string' }, - quantity: { type: 'integer' }, - }, - }, - }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should handle circular references by removing the circular part', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - person: { $ref: '#/$defs/person' }, - }, - $defs: { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - friend: { $ref: '#/$defs/person' }, // Circular reference - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - person: { - type: 'object', - properties: { - name: { type: 'string' }, - // friend property is removed to break the circular reference - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should handle indirect circular references', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - node: { $ref: '#/$defs/node' }, - }, - $defs: { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { $ref: '#/$defs/childNode' }, - }, - }, - childNode: { - type: 'object', - properties: { - value: { type: 'string' }, - parent: { $ref: '#/$defs/node' }, // Circular reference through childNode - }, - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - node: { - type: 'object', - properties: { - value: { type: 'string' }, - child: { - type: 'object', - properties: { - value: { type: 'string' }, - // parent property is removed to break the circular reference - }, - }, - }, - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); - - it('should preserve other properties when inlining references', () => { - const schema: JSONSchema = { - type: 'object', - properties: { - address: { $ref: '#/$defs/address', description: 'User address' }, - }, - $defs: { - address: { - type: 'object', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - required: ['street'], - }, - }, - }; - - const expected: JSONSchema = { - type: 'object', - properties: { - address: { - type: 'object', - description: 'User address', - properties: { - street: { type: 'string' }, - city: { type: 'string' }, - }, - required: ['street'], - }, - }, - }; - - expect(inlineRefs(schema)).toEqual(expected); - }); -}); - -describe('removeFormats', () => { - it('should return original schema if formats capability is true', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - email: { type: 'string', description: 'An email field', format: 'email' }, - }, - }; - - expect(removeFormats(schema, true)).toEqual(schema); - }); - - it('should move format to description when formats capability is false', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - email: { type: 'string', description: 'An email field', format: 'email' }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field (format: "date")' }, - email: { type: 'string', description: 'An email field (format: "email")' }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle properties without description', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', format: 'date' }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: '(format: "date")' }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle nested properties', () => { - const schema = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - created_at: { type: 'string', description: 'Creation date', format: 'date-time' }, - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - user: { - type: 'object', - properties: { - created_at: { type: 'string', description: 'Creation date (format: "date-time")' }, - }, - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle arrays of objects', () => { - const schema = { - type: 'object', - properties: { - dates: { - type: 'array', - items: { - type: 'object', - properties: { - start: { type: 'string', description: 'Start date', format: 'date' }, - end: { type: 'string', description: 'End date', format: 'date' }, - }, - }, - }, - }, - }; - - const expected = { - type: 'object', - properties: { - dates: { - type: 'array', - items: { - type: 'object', - properties: { - start: { type: 'string', description: 'Start date (format: "date")' }, - end: { type: 'string', description: 'End date (format: "date")' }, - }, - }, - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); - - it('should handle schemas with $defs', () => { - const schema = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field', format: 'date' }, - }, - $defs: { - timestamp: { - type: 'string', - description: 'A timestamp field', - format: 'date-time', - }, - }, - }; - - const expected = { - type: 'object', - properties: { - date: { type: 'string', description: 'A date field (format: "date")' }, - }, - $defs: { - timestamp: { - type: 'string', - description: 'A timestamp field (format: "date-time")', - }, - }, - }; - - expect(removeFormats(schema, false)).toEqual(expected); - }); -}); - -describe('applyCompatibilityTransformations', () => { - const createTestTool = (name: string, overrides = {}): Tool => ({ - name, - description: 'Test tool', - inputSchema: { - type: 'object', - properties: {}, - }, - ...overrides, - }); - - const createTestEndpoint = (tool: Tool): Endpoint => ({ - tool, - handler: jest.fn(), - metadata: { - resource: 'test', - operation: 'read' as const, - tags: [], - }, - }); - - it('should not modify endpoints when all capabilities are enabled', () => { - const tool = createTestTool('test-tool'); - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed).toEqual(endpoints); - }); - - it('should split tools with top-level unions when topLevelUnions is disabled', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - properties: { - variant1: { type: 'string' }, - }, - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - expect(transformed[0]!.tool.name).toBe('union-tool_first_variant'); - expect(transformed[1]!.tool.name).toBe('union-tool_second_variant'); - }); - - it('should handle variants without titles in removeTopLevelUnions', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - properties: { - variant1: { type: 'string' }, - }, - }, - { - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - expect(transformed[0]!.tool.name).toBe('union-tool_variant1'); - expect(transformed[1]!.tool.name).toBe('union-tool_variant2'); - }); - - it('should truncate tool names when toolNameLength is set', () => { - const tools = [ - createTestTool('very-long-tool-name-that-exceeds-limit'), - createTestTool('another-long-tool-name-to-truncate'), - createTestTool('short-name'), - ]; - - const endpoints = tools.map(createTestEndpoint); - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 20, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed[0]!.tool.name).toBe('very-long-tool-name-'); - expect(transformed[1]!.tool.name).toBe('another-long-tool-na'); - expect(transformed[2]!.tool.name).toBe('short-name'); - }); - - it('should inline refs when refs capability is disabled', () => { - const tool = createTestTool('ref-tool', { - inputSchema: { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - expect(schema.$defs).toBeUndefined(); - - if (schema.properties) { - expect(schema.properties['user']).toEqual({ - type: 'object', - properties: { - name: { type: 'string' }, - email: { type: 'string' }, - }, - }); - } - }); - - it('should preserve external refs when inlining', () => { - const tool = createTestTool('ref-tool', { - inputSchema: { - type: 'object', - properties: { - internal: { $ref: '#/$defs/internal' }, - external: { $ref: 'https://example.com/schemas/external.json' }, - }, - $defs: { - internal: { - type: 'object', - properties: { - name: { type: 'string' }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: true, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties) { - expect(schema.properties['internal']).toEqual({ - type: 'object', - properties: { - name: { type: 'string' }, - }, - }); - expect(schema.properties['external']).toEqual({ - $ref: 'https://example.com/schemas/external.json', - }); - } - }); - - it('should remove anyOf fields when unions capability is disabled', () => { - const tool = createTestTool('union-tool', { - inputSchema: { - type: 'object', - properties: { - field: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: false, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties && schema.properties['field']) { - const field = schema.properties['field']; - expect(field.anyOf).toBeUndefined(); - expect(field.type).toBe('string'); - } - }); - - it('should correctly combine topLevelUnions and toolNameLength transformations', () => { - const tool = createTestTool('very-long-union-tool-name', { - inputSchema: { - type: 'object', - properties: { - common: { type: 'string' }, - }, - anyOf: [ - { - title: 'first variant', - properties: { - variant1: { type: 'string' }, - }, - }, - { - title: 'second variant', - properties: { - variant2: { type: 'number' }, - }, - }, - ], - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: false, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 20, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - expect(transformed.length).toBe(2); - - // Both names should be truncated because they exceed 20 characters - expect(transformed[0]!.tool.name).toBe('very-long-union-too1'); - expect(transformed[1]!.tool.name).toBe('very-long-union-too2'); - }); - - it('should correctly combine refs and unions transformations', () => { - const tool = createTestTool('complex-tool', { - inputSchema: { - type: 'object', - properties: { - user: { $ref: '#/$defs/user' }, - }, - $defs: { - user: { - type: 'object', - properties: { - preference: { - anyOf: [{ type: 'string' }, { type: 'number' }], - }, - }, - }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: false, - unions: false, - formats: true, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - // Refs should be inlined - expect(schema.$defs).toBeUndefined(); - - // Safely access nested properties - if (schema.properties && schema.properties['user']) { - const user = schema.properties['user']; - // User should be inlined - expect(user.type).toBe('object'); - - // AnyOf in the inlined user.preference should be removed - if (user.properties && user.properties['preference']) { - const preference = user.properties['preference']; - expect(preference.anyOf).toBeUndefined(); - expect(preference.type).toBe('string'); - } - } - }); - - it('should handle formats capability being false', () => { - const tool = createTestTool('format-tool', { - inputSchema: { - type: 'object', - properties: { - date: { type: 'string', description: 'A date', format: 'date' }, - }, - }, - }); - - const endpoints = [createTestEndpoint(tool)]; - - const capabilities = { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: false, - toolNameLength: undefined, - }; - - const transformed = applyCompatibilityTransformations(endpoints, capabilities); - const schema = transformed[0]!.tool.inputSchema as JSONSchema; - - if (schema.properties && schema.properties['date']) { - const dateField = schema.properties['date']; - expect(dateField['format']).toBeUndefined(); - expect(dateField['description']).toBe('A date (format: "date")'); - } - }); -}); diff --git a/packages/mcp-server/tests/dynamic-tools.test.ts b/packages/mcp-server/tests/dynamic-tools.test.ts deleted file mode 100644 index 08963af..0000000 --- a/packages/mcp-server/tests/dynamic-tools.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { dynamicTools } from '../src/dynamic-tools'; -import { Endpoint } from '../src/tools'; - -describe('dynamicTools', () => { - const fakeClient = {} as any; - - const endpoints: Endpoint[] = [ - makeEndpoint('test_read_endpoint', 'test_resource', 'read', ['test']), - makeEndpoint('test_write_endpoint', 'test_resource', 'write', ['test']), - makeEndpoint('user_endpoint', 'user', 'read', ['user', 'admin']), - makeEndpoint('admin_endpoint', 'admin', 'write', ['admin']), - ]; - - const tools = dynamicTools(endpoints); - - const toolsMap = { - list_api_endpoints: toolOrError('list_api_endpoints'), - get_api_endpoint_schema: toolOrError('get_api_endpoint_schema'), - invoke_api_endpoint: toolOrError('invoke_api_endpoint'), - }; - - describe('list_api_endpoints', () => { - it('should return all endpoints when no search query is provided', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, {}); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(endpoints.length); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_read_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('test_write_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('user_endpoint'); - expect(result.tools.map((t: { name: string }) => t.name)).toContain('admin_endpoint'); - }); - - it('should filter endpoints by name', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'user' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - }); - - it('should filter endpoints by resource', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.some((t: { resource: string }) => t.resource === 'admin')).toBeTruthy(); - }); - - it('should filter endpoints by tag', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'admin' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.some((t: { tags: string[] }) => t.tags.includes('admin'))).toBeTruthy(); - }); - - it('should be case insensitive in search', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { search_query: 'ADMIN' }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools.length).toBe(2); - result.tools.forEach((tool: { name: string; resource: string; tags: string[] }) => { - expect( - tool.name.toLowerCase().includes('admin') || - tool.resource.toLowerCase().includes('admin') || - tool.tags.some((tag: string) => tag.toLowerCase().includes('admin')), - ).toBeTruthy(); - }); - }); - - it('should filter endpoints by description', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { - search_query: 'Test endpoint for user_endpoint', - }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - expect(result.tools[0].description).toBe('Test endpoint for user_endpoint'); - }); - - it('should filter endpoints by partial description match', async () => { - const content = await toolsMap.list_api_endpoints.handler(fakeClient, { - search_query: 'endpoint for user', - }); - const result = JSON.parse(content.content[0].text); - - expect(result.tools).toHaveLength(1); - expect(result.tools[0].name).toBe('user_endpoint'); - }); - }); - - describe('get_api_endpoint_schema', () => { - it('should return schema for existing endpoint', async () => { - const content = await toolsMap.get_api_endpoint_schema.handler(fakeClient, { - endpoint: 'test_read_endpoint', - }); - const result = JSON.parse(content.content[0].text); - - expect(result).toEqual(endpoints[0]?.tool); - }); - - it('should throw error for non-existent endpoint', async () => { - await expect( - toolsMap.get_api_endpoint_schema.handler(fakeClient, { endpoint: 'non_existent_endpoint' }), - ).rejects.toThrow('Endpoint non_existent_endpoint not found'); - }); - - it('should throw error when no endpoint provided', async () => { - await expect(toolsMap.get_api_endpoint_schema.handler(fakeClient, undefined)).rejects.toThrow( - 'No endpoint provided', - ); - }); - }); - - describe('invoke_api_endpoint', () => { - it('should successfully invoke endpoint with valid arguments', async () => { - const mockHandler = endpoints[0]?.handler as jest.Mock; - mockHandler.mockClear(); - - await toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'test_read_endpoint', - args: { testParam: 'test value' }, - }); - - expect(mockHandler).toHaveBeenCalledWith(fakeClient, { testParam: 'test value' }); - }); - - it('should throw error for non-existent endpoint', async () => { - await expect( - toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'non_existent_endpoint', - args: { testParam: 'test value' }, - }), - ).rejects.toThrow(/Endpoint non_existent_endpoint not found/); - }); - - it('should throw error when no arguments provided', async () => { - await expect(toolsMap.invoke_api_endpoint.handler(fakeClient, undefined)).rejects.toThrow( - 'No endpoint provided', - ); - }); - - it('should throw error for invalid argument schema', async () => { - await expect( - toolsMap.invoke_api_endpoint.handler(fakeClient, { - endpoint_name: 'test_read_endpoint', - args: { wrongParam: 'test value' }, // Missing required testParam - }), - ).rejects.toThrow(/Invalid arguments for endpoint/); - }); - }); - - function toolOrError(name: string) { - const tool = tools.find((tool) => tool.tool.name === name); - if (!tool) throw new Error(`Tool ${name} not found`); - return tool; - } -}); - -function makeEndpoint( - name: string, - resource: string, - operation: 'read' | 'write', - tags: string[] = [], -): Endpoint { - return { - metadata: { - resource, - operation, - tags, - }, - tool: { - name, - description: `Test endpoint for ${name}`, - inputSchema: { - type: 'object', - properties: { - testParam: { type: 'string' }, - }, - required: ['testParam'], - }, - }, - handler: jest.fn().mockResolvedValue({ success: true }), - }; -} diff --git a/packages/mcp-server/tests/options.test.ts b/packages/mcp-server/tests/options.test.ts index 4d9b60c..532666a 100644 --- a/packages/mcp-server/tests/options.test.ts +++ b/packages/mcp-server/tests/options.test.ts @@ -1,6 +1,4 @@ import { parseCLIOptions, parseQueryOptions } from '../src/options'; -import { Filter } from '../src/tools'; -import { parseEmbeddedJSON } from '../src/compat'; // Mock process.argv const mockArgv = (args: string[]) => { @@ -12,338 +10,35 @@ const mockArgv = (args: string[]) => { }; describe('parseCLIOptions', () => { - it('should parse basic filter options', () => { - const cleanup = mockArgv([ - '--tool=test-tool', - '--resource=test-resource', - '--operation=read', - '--tag=test-tag', - ]); + it('default parsing should be stdio', () => { + const cleanup = mockArgv([]); const result = parseCLIOptions(); - expect(result.filters).toEqual([ - { type: 'tag', op: 'include', value: 'test-tag' }, - { type: 'resource', op: 'include', value: 'test-resource' }, - { type: 'tool', op: 'include', value: 'test-tool' }, - { type: 'operation', op: 'include', value: 'read' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({}); - - expect(result.list).toBe(false); + expect(result.transport).toBe('stdio'); cleanup(); }); - it('should parse exclusion filters', () => { - const cleanup = mockArgv([ - '--no-tool=exclude-tool', - '--no-resource=exclude-resource', - '--no-operation=write', - '--no-tag=exclude-tag', - ]); + it('using http transport with a port', () => { + const cleanup = mockArgv(['--transport=http', '--port=2222']); const result = parseCLIOptions(); - expect(result.filters).toEqual([ - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'resource', op: 'exclude', value: 'exclude-resource' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - { type: 'operation', op: 'exclude', value: 'write' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({}); - - cleanup(); - }); - - it('should parse client presets', () => { - const cleanup = mockArgv(['--client=openai-agents']); - - const result = parseCLIOptions(); - - expect(result.client).toEqual('openai-agents'); - - cleanup(); - }); - - it('should parse individual capabilities', () => { - const cleanup = mockArgv([ - '--capability=top-level-unions', - '--capability=valid-json', - '--capability=refs', - '--capability=unions', - '--capability=tool-name-length=40', - ]); - - const result = parseCLIOptions(); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - toolNameLength: 40, - }); - - cleanup(); - }); - - it('should handle list option', () => { - const cleanup = mockArgv(['--list']); - - const result = parseCLIOptions(); - - expect(result.list).toBe(true); - - cleanup(); - }); - - it('should handle multiple filters of the same type', () => { - const cleanup = mockArgv(['--tool=tool1', '--tool=tool2', '--resource=res1', '--resource=res2']); - - const result = parseCLIOptions(); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ] as Filter[]); - - cleanup(); - }); - - it('should handle comma-separated values in array options', () => { - const cleanup = mockArgv([ - '--tool=tool1,tool2', - '--resource=res1,res2', - '--capability=top-level-unions,valid-json,unions', - ]); - - const result = parseCLIOptions(); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ] as Filter[]); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - unions: true, - }); - - cleanup(); - }); - - it('should handle invalid tool-name-length format', () => { - const cleanup = mockArgv(['--capability=tool-name-length=invalid']); - - // Mock console.error to prevent output during test - const originalError = console.error; - console.error = jest.fn(); - - expect(() => parseCLIOptions()).toThrow(); - - console.error = originalError; - cleanup(); - }); - - it('should handle unknown capability', () => { - const cleanup = mockArgv(['--capability=unknown-capability']); - - // Mock console.error to prevent output during test - const originalError = console.error; - console.error = jest.fn(); - - expect(() => parseCLIOptions()).toThrow(); - - console.error = originalError; + expect(result.transport).toBe('http'); + expect(result.port).toBe('2222'); cleanup(); }); }); describe('parseQueryOptions', () => { - const defaultOptions = { - client: undefined, - includeDynamicTools: undefined, - includeCodeTools: undefined, - includeAllTools: undefined, - filters: [], - capabilities: { - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }, - }; - - it('should parse basic filter options from query string', () => { - const query = 'tool=test-tool&resource=test-resource&operation=read&tag=test-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'test-resource' }, - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'test-tag' }, - { type: 'tool', op: 'include', value: 'test-tool' }, - ]); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: undefined, - }); - }); - - it('should parse exclusion filters from query string', () => { - const query = 'no_tool=exclude-tool&no_resource=exclude-resource&no_operation=write&no_tag=exclude-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'exclude', value: 'exclude-resource' }, - { type: 'operation', op: 'exclude', value: 'write' }, - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - ]); - }); - - it('should parse client option from query string', () => { - const query = 'client=openai-agents'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.client).toBe('openai-agents'); - }); - - it('should parse client capabilities from query string', () => { - const query = 'capability=top-level-unions&capability=valid-json&capability=tool-name-length%3D40'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: true, - refs: true, - unions: true, - formats: true, - toolNameLength: 40, - }); - }); - - it('should parse no-capability options from query string', () => { - const query = 'no_capability=top-level-unions&no_capability=refs&no_capability=formats'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: false, - validJson: true, - refs: false, - unions: true, - formats: false, - toolNameLength: undefined, - }); - }); - - it('should parse tools options from query string', () => { - const query = 'tools=dynamic&tools=all'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.includeDynamicTools).toBe(true); - expect(result.includeAllTools).toBe(true); - }); - - it('should parse no-tools options from query string', () => { - const query = 'tools=dynamic&tools=all&no_tools=dynamic'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.includeDynamicTools).toBe(false); - expect(result.includeAllTools).toBe(true); - }); - - it('should handle array values in query string', () => { - const query = 'tool[]=tool1&tool[]=tool2&resource[]=res1&resource[]=res2'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'res1' }, - { type: 'resource', op: 'include', value: 'res2' }, - { type: 'tool', op: 'include', value: 'tool1' }, - { type: 'tool', op: 'include', value: 'tool2' }, - ]); - }); - - it('should merge with default options', () => { - const defaultWithFilters = { - ...defaultOptions, - filters: [{ type: 'tag' as const, op: 'include' as const, value: 'existing-tag' }], - client: 'cursor' as const, - includeDynamicTools: true, - }; - - const query = 'tool=new-tool&resource=new-resource'; - const result = parseQueryOptions(defaultWithFilters, query); - - expect(result.filters).toEqual([ - { type: 'tag', op: 'include', value: 'existing-tag' }, - { type: 'resource', op: 'include', value: 'new-resource' }, - { type: 'tool', op: 'include', value: 'new-tool' }, - ]); - - expect(result.client).toBe('cursor'); - expect(result.includeDynamicTools).toBe(true); - }); - - it('should override client from default options', () => { - const defaultWithClient = { - ...defaultOptions, - client: 'cursor' as const, - }; + const defaultOptions = {}; - const query = 'client=openai-agents'; - const result = parseQueryOptions(defaultWithClient, query); - - expect(result.client).toBe('openai-agents'); - }); - - it('should merge capabilities with default options', () => { - const defaultWithCapabilities = { - ...defaultOptions, - capabilities: { - topLevelUnions: false, - validJson: false, - refs: true, - unions: true, - formats: true, - toolNameLength: 30, - }, - }; - - const query = 'capability=top-level-unions&no_capability=refs'; - const result = parseQueryOptions(defaultWithCapabilities, query); - - expect(result.capabilities).toEqual({ - topLevelUnions: true, - validJson: false, - refs: false, - unions: true, - formats: true, - toolNameLength: 30, - }); - }); - - it('should handle empty query string', () => { + it('default parsing should be empty', () => { const query = ''; const result = parseQueryOptions(defaultOptions, query); - expect(result).toEqual(defaultOptions); + expect(result).toBe({}); }); it('should handle invalid query string gracefully', () => { @@ -352,189 +47,4 @@ describe('parseQueryOptions', () => { // Should throw due to Zod validation for invalid operation expect(() => parseQueryOptions(defaultOptions, query)).toThrow(); }); - - it('should preserve default undefined values when not specified', () => { - const defaultWithUndefined = { - ...defaultOptions, - client: undefined, - includeDynamicTools: undefined, - includeAllTools: undefined, - }; - - const query = 'tool=test-tool'; - const result = parseQueryOptions(defaultWithUndefined, query); - - expect(result.client).toBeUndefined(); - expect(result.includeDynamicTools).toBeFalsy(); - expect(result.includeAllTools).toBeFalsy(); - }); - - it('should handle complex query with mixed include and exclude filters', () => { - const query = - 'tool=include-tool&no_tool=exclude-tool&resource=include-res&no_resource=exclude-res&operation=read&tag=include-tag&no_tag=exclude-tag'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.filters).toEqual([ - { type: 'resource', op: 'include', value: 'include-res' }, - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'include-tag' }, - { type: 'tool', op: 'include', value: 'include-tool' }, - { type: 'resource', op: 'exclude', value: 'exclude-res' }, - { type: 'tag', op: 'exclude', value: 'exclude-tag' }, - { type: 'tool', op: 'exclude', value: 'exclude-tool' }, - ]); - }); - - it('code tools are enabled on http servers with default option set', () => { - const query = 'tools=code'; - const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: true }, query); - - expect(result.includeCodeTools).toBe(true); - }); - - it('code tools are prevented on http servers when no default option set', () => { - const query = 'tools=code'; - const result = parseQueryOptions(defaultOptions, query); - - expect(result.includeCodeTools).toBe(undefined); - }); - - it('code tools are prevented on http servers when default option is explicitly false', () => { - const query = 'tools=code'; - const result = parseQueryOptions({ ...defaultOptions, includeCodeTools: false }, query); - - expect(result.includeCodeTools).toBe(false); - }); -}); - -describe('parseEmbeddedJSON', () => { - it('should not change non-string values', () => { - const args = { - numberProp: 42, - booleanProp: true, - objectProp: { nested: 'value' }, - arrayProp: [1, 2, 3], - nullProp: null, - undefinedProp: undefined, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['numberProp']).toBe(42); - expect(result['booleanProp']).toBe(true); - expect(result['objectProp']).toEqual({ nested: 'value' }); - expect(result['arrayProp']).toEqual([1, 2, 3]); - expect(result['nullProp']).toBe(null); - expect(result['undefinedProp']).toBe(undefined); - }); - - it('should parse valid JSON objects in string properties', () => { - const args = { - jsonObjectString: '{"key": "value", "number": 123}', - regularString: 'not json', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).not.toBe(args); // Should return new object since changes were made - expect(result['jsonObjectString']).toEqual({ key: 'value', number: 123 }); - expect(result['regularString']).toBe('not json'); - }); - - it('should leave invalid JSON in string properties unchanged', () => { - const args = { - invalidJson1: '{"key": value}', // Missing quotes around value - invalidJson2: '{key: "value"}', // Missing quotes around key - invalidJson3: '{"key": "value",}', // Trailing comma - invalidJson4: 'just a regular string', - emptyString: '', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['invalidJson1']).toBe('{"key": value}'); - expect(result['invalidJson2']).toBe('{key: "value"}'); - expect(result['invalidJson3']).toBe('{"key": "value",}'); - expect(result['invalidJson4']).toBe('just a regular string'); - expect(result['emptyString']).toBe(''); - }); - - it('should not parse JSON primitives in string properties', () => { - const args = { - numberString: '123', - floatString: '45.67', - negativeNumberString: '-89', - booleanTrueString: 'true', - booleanFalseString: 'false', - nullString: 'null', - jsonArrayString: '[1, 2, 3, "test"]', - regularString: 'not json', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - expect(result['numberString']).toBe('123'); - expect(result['floatString']).toBe('45.67'); - expect(result['negativeNumberString']).toBe('-89'); - expect(result['booleanTrueString']).toBe('true'); - expect(result['booleanFalseString']).toBe('false'); - expect(result['nullString']).toBe('null'); - expect(result['jsonArrayString']).toBe('[1, 2, 3, "test"]'); - expect(result['regularString']).toBe('not json'); - }); - - it('should handle mixed valid objects and other JSON types', () => { - const args = { - validObject: '{"success": true}', - invalidObject: '{"missing": quote}', - validNumber: '42', - validArray: '[1, 2, 3]', - keepAsString: 'hello world', - nonString: 123, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).not.toBe(args); // Should return new object since some changes were made - expect(result['validObject']).toEqual({ success: true }); - expect(result['invalidObject']).toBe('{"missing": quote}'); - expect(result['validNumber']).toBe('42'); // Not parsed, remains string - expect(result['validArray']).toBe('[1, 2, 3]'); // Not parsed, remains string - expect(result['keepAsString']).toBe('hello world'); - expect(result['nonString']).toBe(123); - }); - - it('should return original object when no strings are present', () => { - const args = { - number: 42, - boolean: true, - object: { key: 'value' }, - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - }); - - it('should return original object when all strings are invalid JSON', () => { - const args = { - string1: 'hello', - string2: 'world', - string3: 'not json at all', - }; - const schema = {}; - - const result = parseEmbeddedJSON(args, schema); - - expect(result).toBe(args); // Should return original object since no changes made - }); }); diff --git a/packages/mcp-server/tests/tools.test.ts b/packages/mcp-server/tests/tools.test.ts deleted file mode 100644 index cfff24a..0000000 --- a/packages/mcp-server/tests/tools.test.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { Endpoint, Filter, Metadata, query } from '../src/tools'; - -describe('Endpoint filtering', () => { - const endpoints: Endpoint[] = [ - endpoint({ - resource: 'user', - operation: 'read', - tags: ['admin'], - toolName: 'retrieve_user', - }), - endpoint({ - resource: 'user.profile', - operation: 'write', - tags: [], - toolName: 'create_user_profile', - }), - endpoint({ - resource: 'user.profile', - operation: 'read', - tags: [], - toolName: 'get_user_profile', - }), - endpoint({ - resource: 'user.roles.permissions', - operation: 'write', - tags: ['admin', 'security'], - toolName: 'update_user_role_permissions', - }), - endpoint({ - resource: 'documents.metadata.tags', - operation: 'write', - tags: ['taxonomy', 'metadata'], - toolName: 'create_document_metadata_tags', - }), - endpoint({ - resource: 'organization.settings', - operation: 'read', - tags: ['admin', 'configuration'], - toolName: 'get_organization_settings', - }), - ]; - - const tests: { name: string; filters: Filter[]; expected: string[] }[] = [ - { - name: 'match none', - filters: [], - expected: [], - }, - - // Resource tests - { - name: 'simple resource', - filters: [{ type: 'resource', op: 'include', value: 'user' }], - expected: ['retrieve_user'], - }, - { - name: 'exclude resource', - filters: [{ type: 'resource', op: 'exclude', value: 'user' }], - expected: [ - 'create_user_profile', - 'get_user_profile', - 'update_user_role_permissions', - 'create_document_metadata_tags', - 'get_organization_settings', - ], - }, - { - name: 'resource and subresources', - filters: [{ type: 'resource', op: 'include', value: 'user*' }], - expected: ['retrieve_user', 'create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - { - name: 'just subresources', - filters: [{ type: 'resource', op: 'include', value: 'user.*' }], - expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - { - name: 'specific subresource', - filters: [{ type: 'resource', op: 'include', value: 'user.roles.permissions' }], - expected: ['update_user_role_permissions'], - }, - { - name: 'deep wildcard match', - filters: [{ type: 'resource', op: 'include', value: '*.*.tags' }], - expected: ['create_document_metadata_tags'], - }, - - // Operation tests - { - name: 'read operation', - filters: [{ type: 'operation', op: 'include', value: 'read' }], - expected: ['retrieve_user', 'get_user_profile', 'get_organization_settings'], - }, - { - name: 'write operation', - filters: [{ type: 'operation', op: 'include', value: 'write' }], - expected: ['create_user_profile', 'update_user_role_permissions', 'create_document_metadata_tags'], - }, - { - name: 'resource and operation combined', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'operation', op: 'exclude', value: 'write' }, - ], - expected: ['get_user_profile'], - }, - - // Tag tests - { - name: 'admin tag', - filters: [{ type: 'tag', op: 'include', value: 'admin' }], - expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], - }, - { - name: 'taxonomy tag', - filters: [{ type: 'tag', op: 'include', value: 'taxonomy' }], - expected: ['create_document_metadata_tags'], - }, - { - name: 'multiple tags (OR logic)', - filters: [ - { type: 'tag', op: 'include', value: 'admin' }, - { type: 'tag', op: 'include', value: 'security' }, - ], - expected: ['retrieve_user', 'update_user_role_permissions', 'get_organization_settings'], - }, - { - name: 'excluding a tag', - filters: [ - { type: 'tag', op: 'include', value: 'admin' }, - { type: 'tag', op: 'exclude', value: 'security' }, - ], - expected: ['retrieve_user', 'get_organization_settings'], - }, - - // Tool name tests - { - name: 'tool name match', - filters: [{ type: 'tool', op: 'include', value: 'get_organization_settings' }], - expected: ['get_organization_settings'], - }, - { - name: 'two tools match', - filters: [ - { type: 'tool', op: 'include', value: 'get_organization_settings' }, - { type: 'tool', op: 'include', value: 'create_user_profile' }, - ], - expected: ['create_user_profile', 'get_organization_settings'], - }, - { - name: 'excluding tool by name', - filters: [ - { type: 'resource', op: 'include', value: 'user*' }, - { type: 'tool', op: 'exclude', value: 'retrieve_user' }, - ], - expected: ['create_user_profile', 'get_user_profile', 'update_user_role_permissions'], - }, - - // Complex combinations - { - name: 'complex filter: read operations with admin tag', - filters: [ - { type: 'operation', op: 'include', value: 'read' }, - { type: 'tag', op: 'include', value: 'admin' }, - ], - expected: [ - 'retrieve_user', - 'get_user_profile', - 'update_user_role_permissions', - 'get_organization_settings', - ], - }, - { - name: 'complex filter: user resources with no tags', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'tag', op: 'exclude', value: 'admin' }, - ], - expected: ['create_user_profile', 'get_user_profile'], - }, - { - name: 'complex filter: user resources and tags', - filters: [ - { type: 'resource', op: 'include', value: 'user.profile' }, - { type: 'tag', op: 'include', value: 'admin' }, - ], - expected: [ - 'retrieve_user', - 'create_user_profile', - 'get_user_profile', - 'update_user_role_permissions', - 'get_organization_settings', - ], - }, - ]; - - tests.forEach((test) => { - it(`filters by ${test.name}`, () => { - const filtered = query(test.filters, endpoints); - expect(filtered.map((e) => e.tool.name)).toEqual(test.expected); - }); - }); -}); - -function endpoint({ - resource, - operation, - tags, - toolName, -}: { - resource: string; - operation: Metadata['operation']; - tags: string[]; - toolName: string; -}): Endpoint { - return { - metadata: { - resource, - operation, - tags, - }, - tool: { name: toolName, inputSchema: { type: 'object', properties: {} } }, - handler: jest.fn(), - }; -} diff --git a/src/resources/exchanges/exchanges.ts b/src/resources/exchanges/exchanges.ts index ce5a6dc..c206f73 100644 --- a/src/resources/exchanges/exchanges.ts +++ b/src/resources/exchanges/exchanges.ts @@ -91,7 +91,7 @@ export interface ExchangeGetResponse { has_trading_incentive?: boolean; /** - * exchange image url + * exchange image URL */ image?: string; @@ -116,7 +116,7 @@ export interface ExchangeGetResponse { trust_score_rank?: number; /** - * exchange website url + * exchange website URL */ url?: string; diff --git a/src/resources/public-treasury.ts b/src/resources/public-treasury.ts index 6a5d7dc..cba3bd4 100644 --- a/src/resources/public-treasury.ts +++ b/src/resources/public-treasury.ts @@ -88,61 +88,125 @@ export class PublicTreasury extends APIResource { } } -export interface PublicTreasuryGetCoinIDResponse { - companies?: Array; - - /** - * market cap dominance - */ - market_cap_dominance?: number; - - /** - * total crypto holdings of companies or government - */ - total_holdings?: number; - - /** - * total crypto holdings value in usd - */ - total_value_usd?: number; -} +export type PublicTreasuryGetCoinIDResponse = + | PublicTreasuryGetCoinIDResponse.CompaniesTreasury + | PublicTreasuryGetCoinIDResponse.GovernmentsTreasury; export namespace PublicTreasuryGetCoinIDResponse { - export interface Company { - /** - * company incorporated or government country - */ - country?: string; + export interface CompaniesTreasury { + companies: Array; /** - * company or government name + * market cap dominance */ - name?: string; + market_cap_dominance: number; /** - * percentage of total crypto supply + * total crypto holdings of companies */ - percentage_of_total_supply?: number; + total_holdings: number; /** - * company symbol + * total crypto holdings value in usd */ - symbol?: string; + total_value_usd: number; + } + + export namespace CompaniesTreasury { + export interface Company { + /** + * company incorporated or government country + */ + country?: string; + + /** + * company or government name + */ + name?: string; + + /** + * percentage of total crypto supply + */ + percentage_of_total_supply?: number; + + /** + * company symbol + */ + symbol?: string; + + /** + * total current value of crypto holdings in usd + */ + total_current_value_usd?: number; + + /** + * total entry value in usd + */ + total_entry_value_usd?: number; + + /** + * total crypto holdings + */ + total_holdings?: number; + } + } + + export interface GovernmentsTreasury { + governments: Array; /** - * total current value of crypto holdings in usd + * market cap dominance */ - total_current_value_usd?: number; + market_cap_dominance: number; /** - * total entry value in usd + * total crypto holdings of governments */ - total_entry_value_usd?: number; + total_holdings: number; /** - * total crypto holdings of company + * total crypto holdings value in usd */ - total_holdings?: number; + total_value_usd: number; + } + + export namespace GovernmentsTreasury { + export interface Government { + /** + * company incorporated or government country + */ + country?: string; + + /** + * company or government name + */ + name?: string; + + /** + * percentage of total crypto supply + */ + percentage_of_total_supply?: number; + + /** + * company symbol + */ + symbol?: string; + + /** + * total current value of crypto holdings in usd + */ + total_current_value_usd?: number; + + /** + * total entry value in usd + */ + total_entry_value_usd?: number; + + /** + * total crypto holdings + */ + total_holdings?: number; + } } } diff --git a/src/version.ts b/src/version.ts index 38ceab2..bccd5c2 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '2.5.0'; // x-release-please-version +export const VERSION = '3.0.0'; // x-release-please-version