diff --git a/.release-please-manifest.json b/.release-please-manifest.json index aeda91d84..32ac6588b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.0.0" + ".": "8.0.0" } diff --git a/.stats.yml b/.stats.yml index 459fe7372..ceb8bf2d5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 46 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-39e0191e43a9db93c8f35e91d10013f05352a2bedcf7ead6bac437957f6e922e.yml -openapi_spec_hash: 58c2cf578f0736b8c5df957f6a61190b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/finch%2Ffinch-f81c5824a9002c980fc0d66c4d52e6cbd8baf7678f5e0f2215909357cff6f82c.yml +openapi_spec_hash: 7714062cac3bb5597b8571172775bc92 config_hash: 0892e2e0eeb0343a022afa62e9080dd1 diff --git a/CHANGELOG.md b/CHANGELOG.md index d4bcf4300..4738df344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 8.0.0 (2025-12-18) + +Full Changelog: [v7.0.0...v8.0.0](https://github.com/Finch-API/finch-api-node/compare/v7.0.0...v8.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. + +### Bug Fixes + +* **mcp:** pass base url to code tool ([631bb5c](https://github.com/Finch-API/finch-api-node/commit/631bb5c63a966a3227a94488d92b135457075dad)) + + +### Chores + +* **mcp:** remove deprecated tool schemes ([9b1ff85](https://github.com/Finch-API/finch-api-node/commit/9b1ff85ec8bfb1195a63f8d16174800735c6997a)) + ## 7.0.0 (2025-12-17) Full Changelog: [v6.38.0...v7.0.0](https://github.com/Finch-API/finch-api-node/compare/v6.38.0...v7.0.0) diff --git a/package.json b/package.json index 2cd579786..f3112cbea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tryfinch/finch-api", - "version": "7.0.0", + "version": "8.0.0", "description": "The official TypeScript library for the Finch API", "author": "Finch ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md index 5d51973fc..dbc3141b4 100644 --- a/packages/mcp-server/README.md +++ b/packages/mcp-server/README.md @@ -28,7 +28,7 @@ For clients with a configuration JSON, it might look something like this: "mcpServers": { "tryfinch_finch_api_api": { "command": "npx", - "args": ["-y", "@tryfinch/finch-api-mcp", "--client=claude", "--tools=dynamic"], + "args": ["-y", "@tryfinch/finch-api-mcp"], "env": { "FINCH_ACCESS_TOKEN": "My Access Token", "FINCH_CLIENT_ID": "4ab15e51-11ad-49f4-acae-f343b7794375", @@ -63,110 +63,22 @@ environment variables in Claude Code's `.claude.json`, which can be found in you claude mcp add --transport stdio tryfinch_finch_api_api --env FINCH_ACCESS_TOKEN="Your FINCH_ACCESS_TOKEN here." FINCH_CLIENT_ID="Your FINCH_CLIENT_ID here." FINCH_CLIENT_SECRET="Your FINCH_CLIENT_SECRET here." FINCH_WEBHOOK_SECRET="Your FINCH_WEBHOOK_SECRET here." -- npx -y @tryfinch/finch-api-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 @@ -194,203 +106,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 "@tryfinch/finch-api-mcp/server"; - -// import a specific tool -import createAccessTokens from "@tryfinch/finch-api-mcp/tools/access-tokens/create-access-tokens"; - -// 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: [createAccessTokens, myCustomEndpoint] }); -``` - -## Available Tools - -The following tools are available in this MCP server. - -### Resource `access_tokens`: - -- `create_access_tokens` (`write`): Exchange the authorization code for an access token - -### Resource `hris.company`: - -- `retrieve_hris_company` (`read`): Read basic company data - -### Resource `hris.company.pay_statement_item`: - -- `list_company_hris_pay_statement_item` (`read`): **Beta:** this endpoint currently serves employers onboarded after March 4th and historical support will be added soon - Retrieve a list of detailed pay statement items for the access token's connection account. - -### Resource `hris.company.pay_statement_item.rules`: - -- `create_pay_statement_item_company_hris_rules` (`write`): **Beta:** this endpoint currently serves employers onboarded after March 4th and historical support will be added soon - Custom rules can be created to associate specific attributes to pay statement items depending on the use case. For example, pay statement items that meet certain conditions can be labeled as a pre-tax 401k. This metadata can be retrieved where pay statement item information is available. -- `update_pay_statement_item_company_hris_rules` (`write`): **Beta:** this endpoint currently serves employers onboarded after March 4th and historical support will be added soon - Update a rule for a pay statement item. -- `list_pay_statement_item_company_hris_rules` (`read`): **Beta:** this endpoint currently serves employers onboarded after March 4th and historical support will be added soon - List all rules of a connection account. -- `delete_pay_statement_item_company_hris_rules` (`write`): **Beta:** this endpoint currently serves employers onboarded after March 4th and historical support will be added soon - Delete a rule for a pay statement item. - -### Resource `hris.directory`: - -- `list_hris_directory` (`read`): Read company directory and organization structure - -### Resource `hris.individuals`: - -- `retrieve_many_hris_individuals` (`write`): Read individual data, excluding income and employment data - -### Resource `hris.employments`: - -- `retrieve_many_hris_employments` (`write`): Read individual employment and income data - -### Resource `hris.payments`: - -- `list_hris_payments` (`read`): Read payroll and contractor related payments by the company. - -### Resource `hris.pay_statements`: - -- `retrieve_many_hris_pay_statements` (`write`): Read detailed pay statements for each individual. - - Deduction and contribution types are supported by the payroll systems that supports Benefits. - -### Resource `hris.documents`: - -- `list_hris_documents` (`read`): **Beta:** This endpoint is in beta and may change. - Retrieve a list of company-wide documents. -- `retreive_hris_documents` (`read`): **Beta:** This endpoint is in beta and may change. - Retrieve details of a specific document by its ID. - -### Resource `hris.benefits`: - -- `create_hris_benefits` (`write`): Creates a new company-wide deduction or contribution. Please use the `/providers` endpoint to view available types for each provider. -- `retrieve_hris_benefits` (`read`): Lists deductions and contributions information for a given item -- `update_hris_benefits` (`write`): Updates an existing company-wide deduction or contribution -- `list_hris_benefits` (`read`): List all company-wide deductions and contributions. -- `list_supported_benefits_hris_benefits` (`read`): Get deductions metadata - -### Resource `hris.benefits.individuals`: - -- `enroll_many_benefits_hris_individuals` (`write`): Enroll an individual into a deduction or contribution. This is an overwrite operation. If the employee is already enrolled, the enrollment amounts will be adjusted. Making the same request multiple times will not create new enrollments, but will continue to set the state of the existing enrollment. -- `enrolled_ids_benefits_hris_individuals` (`read`): Lists individuals currently enrolled in a given deduction. -- `retrieve_many_benefits_benefits_hris_individuals` (`read`): Get enrollment information for the given individuals. -- `unenroll_many_benefits_hris_individuals` (`write`): Unenroll individuals from a deduction or contribution - -### Resource `providers`: - -- `list_providers` (`read`): Return details on all available payroll and HR systems. - -### Resource `account`: - -- `disconnect_account` (`write`): Disconnect one or more `access_token`s from your application. -- `introspect_account` (`read`): Read account information associated with an `access_token` - -### Resource `request_forwarding`: - -- `forward_request_forwarding` (`write`): The Forward API allows you to make direct requests to an employment system. If Finch's unified API - doesn't have a data model that cleanly fits your needs, then Forward allows you to push or pull - data models directly against an integration's API. - -### Resource `jobs.automated`: - -- `create_jobs_automated` (`write`): Enqueue an automated job. - - `data_sync_all`: Enqueue a job to re-sync all data for a connection. `data_sync_all` has a concurrency limit of 1 job at a time per connection. This means that if this endpoint is called while a job is already in progress for this connection, Finch will return the `job_id` of the job that is currently in progress. Finch allows a fixed window rate limit of 1 forced refresh per hour per connection. - - `w4_form_employee_sync`: Enqueues a job for sync W-4 data for a particular individual, identified by `individual_id`. This feature is currently in beta. - - This endpoint is available for _Scale_ tier customers as an add-on. To request access to this endpoint, please contact your Finch account manager. - -- `retrieve_jobs_automated` (`read`): Get an automated job by `job_id`. -- `list_jobs_automated` (`read`): Get all automated jobs. Automated jobs are completed by a machine. By default, jobs are sorted in descending order by submission time. For scheduled jobs such as data syncs, only the next scheduled job is shown. - -### Resource `jobs.manual`: - -- `retrieve_jobs_manual` (`read`): Get a manual job by `job_id`. Manual jobs are completed by a human and include Assisted Benefits jobs. - -### Resource `sandbox.connections`: - -- `create_sandbox_connections` (`write`): Create a new connection (new company/provider pair) with a new account - -### Resource `sandbox.connections.accounts`: - -- `create_connections_sandbox_accounts` (`write`): Create a new account for an existing connection (company/provider pair) -- `update_connections_sandbox_accounts` (`write`): Update an existing sandbox account. Change the connection status to understand how the Finch API responds. - -### Resource `sandbox.company`: - -- `update_sandbox_company` (`write`): Update a sandbox company's data - -### Resource `sandbox.directory`: - -- `create_sandbox_directory` (`write`): Add new individuals to a sandbox company - -### Resource `sandbox.individual`: - -- `update_sandbox_individual` (`write`): Update sandbox individual - -### Resource `sandbox.employment`: - -- `update_sandbox_employment` (`write`): Update sandbox employment - -### Resource `sandbox.payment`: - -- `create_sandbox_payment` (`write`): Add a new sandbox payment - -### Resource `sandbox.jobs`: - -- `create_sandbox_jobs` (`write`): Enqueue a new sandbox job - -### Resource `sandbox.jobs.configuration`: - -- `retrieve_jobs_sandbox_configuration` (`read`): Get configurations for sandbox jobs -- `update_jobs_sandbox_configuration` (`write`): Update configurations for sandbox jobs - -### Resource `payroll.pay_groups`: - -- `retrieve_payroll_pay_groups` (`read`): Read information from a single pay group -- `list_payroll_pay_groups` (`read`): Read company pay groups and frequencies - -### Resource `connect.sessions`: - -- `new_connect_sessions` (`write`): Create a new connect session for an employer -- `reauthenticate_connect_sessions` (`write`): Create a new Connect session for reauthenticating an existing connection diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index b326ec838..dac8ed8f6 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@tryfinch/finch-api-mcp", - "version": "7.0.0", + "version": "8.0.0", "description": "The official MCP Server for the Finch API", "author": "Finch ", "types": "dist/index.d.ts", diff --git a/packages/mcp-server/src/code-tool.ts b/packages/mcp-server/src/code-tool.ts index 929723420..3b93c29f5 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', @@ -40,6 +40,7 @@ export async function codeTool() { FINCH_CLIENT_ID: readEnv('FINCH_CLIENT_ID'), FINCH_CLIENT_SECRET: readEnv('FINCH_CLIENT_SECRET'), FINCH_WEBHOOK_SECRET: readEnv('FINCH_WEBHOOK_SECRET'), + FINCH_BASE_URL: readEnv('FINCH_BASE_URL'), }), }, body: JSON.stringify({ diff --git a/packages/mcp-server/src/compat.ts b/packages/mcp-server/src/compat.ts deleted file mode 100644 index f84053c77..000000000 --- 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 d2f4174d3..4874ac4bb 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 abd09c0b2..000000000 --- a/packages/mcp-server/src/dynamic-tools.ts +++ /dev/null @@ -1,153 +0,0 @@ -import Finch from '@tryfinch/finch-api'; -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 Finch TypeScript API', - inputSchema: zodToInputSchema(listEndpointsSchema), - }, - handler: async (client: Finch, 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 Finch 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: Finch, 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 Finch 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: Finch, 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 eaae0fcfb..000000000 --- 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 84517003c..2366d8f94 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 4850a0e26..0f6dd426a 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 b6ff59763..6c8bb8d1f 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 f41433991..df3595356 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 '@tryfinch/finch-api'; import Finch from '@tryfinch/finch-api'; -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 '@tryfinch/finch-api'; -export { endpoints } from './tools'; export const newMcpServer = () => new McpServer( { name: 'tryfinch_finch_api_api', - version: '7.0.0', + version: '8.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: Finch, 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 d902a5bb4..f07696f34 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 7e516de7c..000000000 --- a/packages/mcp-server/src/tools.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tools/index'; diff --git a/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts b/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts deleted file mode 100644 index c890c2255..000000000 --- a/packages/mcp-server/src/tools/access-tokens/create-access-tokens.ts +++ /dev/null @@ -1,65 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'access_tokens', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/auth/token', - operationId: 'create-access-token', -}; - -export const tool: Tool = { - name: 'create_access_tokens', - 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\nExchange the authorization code for an access token\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/create_access_token_response',\n $defs: {\n create_access_token_response: {\n type: 'object',\n properties: {\n access_token: {\n type: 'string',\n description: 'The access token for the connection'\n },\n client_type: {\n type: 'string',\n title: 'ClientType',\n description: 'The type of application associated with a token.',\n enum: [ 'development',\n 'production',\n 'sandbox'\n ]\n },\n connection_id: {\n type: 'string',\n description: 'The Finch UUID of the connection associated with the `access_token`'\n },\n connection_type: {\n type: 'string',\n title: 'ConnectionType',\n description: 'The type of the connection associated with the token.\\n- `provider` - connection to an external provider\\n- `finch` - finch-generated data.',\n enum: [ 'finch',\n 'provider'\n ]\n },\n entity_ids: {\n type: 'array',\n description: 'An array of entity IDs that can be accessed with this access token',\n items: {\n type: 'string'\n }\n },\n products: {\n type: 'array',\n description: 'An array of the authorized products associated with the `access_token`',\n items: {\n type: 'string'\n }\n },\n provider_id: {\n type: 'string',\n description: 'The ID of the provider associated with the `access_token`'\n },\n token_type: {\n type: 'string',\n description: 'The RFC 8693 token type (Finch uses `bearer` tokens)'\n },\n account_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to identify the connection instead of this account ID'\n },\n company_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to identify the connection instead of this company ID'\n },\n customer_id: {\n type: 'string',\n description: 'The ID of your customer you provided to Finch when a connect session was created for this connection'\n }\n },\n required: [ 'access_token',\n 'client_type',\n 'connection_id',\n 'connection_type',\n 'entity_ids',\n 'products',\n 'provider_id',\n 'token_type'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - code: { - type: 'string', - description: 'The authorization code received from the authorization server', - }, - client_id: { - type: 'string', - description: 'The client ID for your application', - }, - client_secret: { - type: 'string', - description: 'The client secret for your application', - }, - redirect_uri: { - type: 'string', - description: 'The redirect URI used in the authorization request (optional)', - }, - 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: ['code'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.accessTokens.create(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/account/disconnect-account.ts b/packages/mcp-server/src/tools/account/disconnect-account.ts deleted file mode 100644 index 29bd39a61..000000000 --- a/packages/mcp-server/src/tools/account/disconnect-account.ts +++ /dev/null @@ -1,49 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'account', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/disconnect', - operationId: 'post-disconnect', -}; - -export const tool: Tool = { - name: 'disconnect_account', - 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\nDisconnect one or more `access_token`s from your application.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/disconnect_response',\n $defs: {\n disconnect_response: {\n type: 'object',\n properties: {\n status: {\n type: 'string',\n description: 'If the request is successful, Finch will return \"success\" (HTTP 200 status).'\n }\n },\n required: [ 'status'\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: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.account.disconnect())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/account/introspect-account.ts b/packages/mcp-server/src/tools/account/introspect-account.ts deleted file mode 100644 index 2ef68e175..000000000 --- a/packages/mcp-server/src/tools/account/introspect-account.ts +++ /dev/null @@ -1,51 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'account', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/introspect', - operationId: 'get-introspect', -}; - -export const tool: Tool = { - name: 'introspect_account', - 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\nRead account information associated with an `access_token`\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/introspection',\n $defs: {\n introspection: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The Finch UUID of the token being introspected'\n },\n client_id: {\n type: 'string',\n description: 'The client ID of the application associated with the `access_token`'\n },\n client_type: {\n type: 'string',\n title: 'ClientType',\n description: 'The type of application associated with a token.',\n enum: [ 'development',\n 'production',\n 'sandbox'\n ]\n },\n connection_id: {\n type: 'string',\n description: 'The Finch UUID of the connection associated with the `access_token`'\n },\n connection_status: {\n type: 'object',\n properties: {\n status: {\n $ref: '#/$defs/connection_status_type'\n },\n last_successful_sync: {\n anyOf: [ {\n type: 'string',\n format: 'date-time'\n },\n {\n type: 'string'\n }\n ],\n description: 'The datetime when the connection was last successfully synced'\n },\n message: {\n type: 'string'\n }\n },\n required: [ 'status'\n ]\n },\n connection_type: {\n type: 'string',\n title: 'ConnectionType',\n description: 'The type of the connection associated with the token.\\n- `provider` - connection to an external provider\\n- `finch` - finch-generated data.',\n enum: [ 'finch',\n 'provider'\n ]\n },\n products: {\n type: 'array',\n description: 'An array of the authorized products associated with the `access_token`.',\n items: {\n type: 'string'\n }\n },\n provider_id: {\n type: 'string',\n description: 'The ID of the provider associated with the `access_token`.'\n },\n account_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate tokens with a Finch connection instead of this account ID'\n },\n authentication_methods: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n description: 'The type of authentication method',\n enum: [ 'assisted',\n 'credential',\n 'api_token',\n 'api_credential',\n 'oauth'\n ]\n },\n connection_status: {\n type: 'object',\n properties: {\n status: {\n $ref: '#/$defs/connection_status_type'\n },\n last_successful_sync: {\n anyOf: [ {\n type: 'string',\n format: 'date-time'\n },\n {\n type: 'string'\n }\n ],\n description: 'The datetime when the connection was last successfully synced'\n },\n message: {\n type: 'string'\n }\n },\n required: [ 'status'\n ]\n },\n products: {\n type: 'array',\n description: 'An array of the authorized products associated with the `access_token`',\n items: {\n type: 'string'\n }\n }\n },\n required: [ 'type'\n ]\n }\n },\n company_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate tokens with a Finch connection instead of this company ID'\n },\n customer_email: {\n type: 'string',\n description: 'The email of your customer you provided to Finch when a connect session was created for this connection'\n },\n customer_id: {\n type: 'string',\n description: 'The ID of your customer you provided to Finch when a connect session was created for this connection'\n },\n customer_name: {\n type: 'string',\n description: 'The name of your customer you provided to Finch when a connect session was created for this connection'\n },\n entities: {\n type: 'array',\n description: 'Array of detailed entity information for each connected account in multi-account mode',\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The connection account ID for this entity'\n },\n name: {\n type: 'string',\n description: 'The name of the entity (payroll provider company name)'\n },\n source_id: {\n type: 'string',\n description: 'The source ID of the entity'\n }\n },\n required: [ 'id',\n 'name',\n 'source_id'\n ]\n }\n },\n manual: {\n type: 'boolean',\n description: 'Whether the connection associated with the `access_token` uses the Assisted Connect Flow. (`true` if using Assisted Connect, `false` if connection is automated)'\n },\n payroll_provider_id: {\n type: 'string',\n description: '[DEPRECATED] Use `provider_id` to identify the provider instead of this payroll provider ID.'\n },\n username: {\n type: 'string',\n description: 'The account username used for login associated with the `access_token`.'\n }\n },\n required: [ 'id',\n 'client_id',\n 'client_type',\n 'connection_id',\n 'connection_status',\n 'connection_type',\n 'products',\n 'provider_id'\n ]\n },\n connection_status_type: {\n type: 'string',\n enum: [ 'pending',\n 'processing',\n 'connected',\n 'error_no_account_setup',\n 'error_permissions',\n 'reauth'\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: Finch, args: Record | undefined) => { - const { jq_filter } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.account.introspect())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts b/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts deleted file mode 100644 index 2b2aceb06..000000000 --- a/packages/mcp-server/src/tools/connect/sessions/new-connect-sessions.ts +++ /dev/null @@ -1,115 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'connect.sessions', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/connect/sessions', - operationId: 'post-connect-sessions', -}; - -export const tool: Tool = { - name: 'new_connect_sessions', - 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\nCreate a new connect session for an employer\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/session_new_response',\n $defs: {\n session_new_response: {\n type: 'object',\n properties: {\n connect_url: {\n type: 'string',\n description: 'The Connect URL to redirect the user to for authentication'\n },\n session_id: {\n type: 'string',\n description: 'The unique identifier for the created connect session'\n }\n },\n required: [ 'connect_url',\n 'session_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - customer_id: { - type: 'string', - description: 'Unique identifier for the customer', - }, - customer_name: { - type: 'string', - description: 'Name of the customer', - }, - products: { - type: 'array', - description: 'The Finch products to request access to', - items: { - type: 'string', - description: 'The Finch products that can be requested during the Connect flow.', - enum: [ - 'benefits', - 'company', - 'deduction', - 'directory', - 'documents', - 'employment', - 'individual', - 'payment', - 'pay_statement', - 'ssn', - ], - }, - }, - customer_email: { - type: 'string', - description: 'Email address of the customer', - }, - integration: { - type: 'object', - description: 'Integration configuration for the connect session', - properties: { - provider: { - type: 'string', - description: 'The provider to integrate with', - }, - auth_method: { - type: 'string', - description: 'The authentication method to use', - enum: ['assisted', 'credential', 'oauth', 'api_token'], - }, - }, - required: ['provider'], - }, - manual: { - type: 'boolean', - description: 'Enable manual authentication mode', - }, - minutes_to_expire: { - type: 'number', - description: - 'The number of minutes until the session expires (defaults to 129,600, which is 90 days)', - }, - redirect_uri: { - type: 'string', - description: 'The URI to redirect to after the Connect flow is completed', - }, - sandbox: { - type: 'string', - description: 'Sandbox mode for testing', - enum: ['finch', 'provider'], - }, - 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: ['customer_id', 'customer_name', 'products'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.connect.sessions.new(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts b/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts deleted file mode 100644 index 1c9b8a92f..000000000 --- a/packages/mcp-server/src/tools/connect/sessions/reauthenticate-connect-sessions.ts +++ /dev/null @@ -1,83 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'connect.sessions', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/connect/sessions/reauthenticate', - operationId: 'post-connect-sessions-reauthenticate', -}; - -export const tool: Tool = { - name: 'reauthenticate_connect_sessions', - 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\nCreate a new Connect session for reauthenticating an existing connection\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/session_reauthenticate_response',\n $defs: {\n session_reauthenticate_response: {\n type: 'object',\n properties: {\n connect_url: {\n type: 'string',\n description: 'The Connect URL to redirect the user to for reauthentication'\n },\n session_id: {\n type: 'string',\n description: 'The unique identifier for the created connect session'\n }\n },\n required: [ 'connect_url',\n 'session_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - connection_id: { - type: 'string', - description: 'The ID of the existing connection to reauthenticate', - }, - minutes_to_expire: { - type: 'integer', - description: 'The number of minutes until the session expires (defaults to 43,200, which is 30 days)', - }, - products: { - type: 'array', - description: 'The products to request access to (optional for reauthentication)', - items: { - type: 'string', - description: 'The Finch products that can be requested during the Connect flow.', - enum: [ - 'benefits', - 'company', - 'deduction', - 'directory', - 'documents', - 'employment', - 'individual', - 'payment', - 'pay_statement', - 'ssn', - ], - }, - }, - redirect_uri: { - type: 'string', - description: 'The URI to redirect to after the Connect flow is completed', - }, - 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: ['connection_id'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.connect.sessions.reauthenticate(body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts deleted file mode 100644 index 633564654..000000000 --- a/packages/mcp-server/src/tools/hris/benefits/create-hris-benefits.ts +++ /dev/null @@ -1,129 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.benefits', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/employer/benefits', - operationId: 'create-company-benefits', -}; - -export const tool: Tool = { - name: 'create_hris_benefits', - 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\nCreates a new company-wide deduction or contribution. Please use the `/providers` endpoint to view available types for each provider.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/create_company_benefits_response',\n $defs: {\n create_company_benefits_response: {\n type: 'object',\n properties: {\n benefit_id: {\n type: 'string',\n description: 'The id of the benefit.'\n },\n job_id: {\n type: 'string'\n }\n },\n required: [ 'benefit_id',\n 'job_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - company_contribution: { - type: 'object', - title: 'BenefitCompanyMatchContribution', - description: 'The company match for this benefit.', - properties: { - tiers: { - type: 'array', - items: { - type: 'object', - properties: { - match: { - type: 'integer', - }, - threshold: { - type: 'integer', - }, - }, - required: ['match', 'threshold'], - }, - }, - type: { - type: 'string', - enum: ['match'], - }, - }, - required: ['tiers', 'type'], - }, - description: { - type: 'string', - title: 'BenefitDescription', - description: - 'Name of the benefit as it appears in the provider and pay statements. Recommend limiting this to <30 characters due to limitations in specific providers (e.g. Justworks).', - }, - frequency: { - $ref: '#/$defs/benefit_frequency', - }, - type: { - $ref: '#/$defs/benefit_type', - }, - 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: [], - $defs: { - benefit_frequency: { - type: 'string', - title: 'BenefitFrequency', - description: 'The frequency of the benefit deduction/contribution.', - enum: ['every_paycheck', 'monthly', 'one_time'], - }, - benefit_type: { - type: 'string', - title: 'BenefitType', - description: 'Type of benefit.', - enum: [ - '457', - '401k', - '401k_roth', - '401k_loan', - '403b', - '403b_roth', - '457_roth', - 'commuter', - 'custom_post_tax', - 'custom_pre_tax', - 'fsa_dependent_care', - 'fsa_medical', - 'hsa_post', - 'hsa_pre', - 's125_dental', - 's125_medical', - 's125_vision', - 'simple', - 'simple_ira', - ], - }, - }, - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.hris.benefits.create(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts deleted file mode 100644 index 42a9bf106..000000000 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/enroll-many-benefits-hris-individuals.ts +++ /dev/null @@ -1,146 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.benefits.individuals', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/employer/benefits/{benefit_id}/individuals', - operationId: 'post-employer-individual-benefits-benefit_id', -}; - -export const tool: Tool = { - name: 'enroll_many_benefits_hris_individuals', - 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\nEnroll an individual into a deduction or contribution. This is an overwrite operation. If the employee is already enrolled, the enrollment amounts will be adjusted. Making the same request multiple times will not create new enrollments, but will continue to set the state of the existing enrollment.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/enrolled_individual_benefit_response',\n $defs: {\n enrolled_individual_benefit_response: {\n type: 'object',\n properties: {\n job_id: {\n type: 'string'\n }\n },\n required: [ 'job_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - benefit_id: { - type: 'string', - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - individuals: { - type: 'array', - description: 'Array of the individual_id to enroll and a configuration object.', - items: { - type: 'object', - properties: { - configuration: { - type: 'object', - properties: { - annual_contribution_limit: { - type: 'string', - description: - 'For HSA benefits only - whether the contribution limit is for an individual or family', - enum: ['individual', 'family'], - }, - annual_maximum: { - type: 'integer', - description: 'Maximum annual amount in cents', - }, - catch_up: { - type: 'boolean', - description: 'For retirement benefits only - whether catch up contributions are enabled', - }, - company_contribution: { - type: 'object', - properties: { - amount: { - type: 'integer', - description: - 'Amount in cents for fixed type or basis points (1/100th of a percent) for percent type', - }, - tiers: { - type: 'array', - description: - 'Array of tier objects for tiered contribution matching (required when type is tiered)', - items: { - type: 'object', - properties: { - match: { - type: 'integer', - description: 'The employer match percentage in basis points (0-10000 = 0-100%)', - }, - threshold: { - type: 'integer', - description: - 'The employee contribution threshold in basis points (0-10000 = 0-100%)', - }, - }, - required: ['match', 'threshold'], - }, - }, - type: { - type: 'string', - enum: ['fixed', 'percent', 'tiered'], - }, - }, - }, - effective_date: { - type: 'string', - description: 'The date the enrollment will take effect', - format: 'date', - }, - employee_deduction: { - type: 'object', - properties: { - amount: { - type: 'integer', - description: - 'Amount in cents for fixed type or basis points (1/100th of a percent) for percent type', - }, - type: { - type: 'string', - enum: ['fixed', 'percent'], - }, - }, - }, - }, - }, - individual_id: { - type: 'string', - description: 'Finch id (uuidv4) for the individual to enroll', - }, - }, - }, - }, - 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: ['benefit_id'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { benefit_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.individuals.enrollMany(benefit_id, body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts deleted file mode 100644 index 1c5372751..000000000 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/enrolled-ids-benefits-hris-individuals.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.benefits.individuals', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/benefits/{benefit_id}/enrolled', - operationId: 'get-company-benefits-enrolled', -}; - -export const tool: Tool = { - name: 'enrolled_ids_benefits_hris_individuals', - 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\nLists individuals currently enrolled in a given deduction.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/individual_enrolled_ids_response',\n $defs: {\n individual_enrolled_ids_response: {\n type: 'object',\n properties: {\n benefit_id: {\n type: 'string',\n description: 'The id of the benefit.'\n },\n individual_ids: {\n type: 'array',\n items: {\n type: 'string',\n description: 'A stable Finch `id` (UUID v4) for an individual in the company.'\n }\n }\n },\n required: [ 'benefit_id',\n 'individual_ids'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - benefit_id: { - type: 'string', - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - 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: ['benefit_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { benefit_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.individuals.enrolledIDs(benefit_id, body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts deleted file mode 100644 index 19f106162..000000000 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals.ts +++ /dev/null @@ -1,67 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.benefits.individuals', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/benefits/{benefit_id}/individuals', - operationId: 'get-individual-benefits', -}; - -export const tool: Tool = { - name: 'retrieve_many_benefits_benefits_hris_individuals', - 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\nGet enrollment information for the given individuals.\n\n# Response Schema\n```json\n{\n type: 'array',\n title: 'IndividualBenefits',\n items: {\n $ref: '#/$defs/individual_benefit'\n },\n $defs: {\n individual_benefit: {\n type: 'object',\n properties: {\n body: {\n anyOf: [ {\n type: 'object',\n properties: {\n annual_maximum: {\n type: 'integer',\n description: 'If the benefit supports annual maximum, the amount in cents for this individual.'\n },\n catch_up: {\n type: 'boolean',\n description: 'If the benefit supports catch up (401k, 403b, etc.), whether catch up is enabled for this individual.'\n },\n company_contribution: {\n anyOf: [ {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in cents (for type=fixed) or basis points (for type=percent, where 100 = 1%). Not used for type=tiered.'\n },\n type: {\n type: 'string',\n description: 'Contribution type. Supported values: \"fixed\" (amount in cents), \"percent\" (amount in basis points), or \"tiered\" (multi-tier matching).',\n enum: [ 'fixed'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n },\n {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in cents (for type=fixed) or basis points (for type=percent, where 100 = 1%). Not used for type=tiered.'\n },\n type: {\n type: 'string',\n description: 'Contribution type. Supported values: \"fixed\" (amount in cents), \"percent\" (amount in basis points), or \"tiered\" (multi-tier matching).',\n enum: [ 'percent'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n },\n {\n type: 'object',\n properties: {\n tiers: {\n type: 'array',\n description: 'Array of tier objects defining employer match tiers based on employee contribution thresholds. Required when type=tiered.',\n items: {\n type: 'object',\n properties: {\n match: {\n type: 'integer'\n },\n threshold: {\n type: 'integer'\n }\n },\n required: [ 'match',\n 'threshold'\n ]\n }\n },\n type: {\n type: 'string',\n description: 'Contribution type. Supported values: \"fixed\" (amount in cents), \"percent\" (amount in basis points), or \"tiered\" (multi-tier matching).',\n enum: [ 'tiered'\n ]\n }\n },\n required: [ 'tiers',\n 'type'\n ]\n }\n ],\n title: 'CompanyContribution',\n description: 'Company contribution configuration. Supports fixed amounts (in cents), percentage-based contributions (in basis points where 100 = 1%), or tiered matching structures.'\n },\n employee_deduction: {\n anyOf: [ {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in cents (for type=fixed) or basis points (for type=percent, where 100 = 1%).'\n },\n type: {\n type: 'string',\n description: 'Contribution type. Supported values: \"fixed\" (amount in cents) or \"percent\" (amount in basis points).',\n enum: [ 'fixed'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n },\n {\n type: 'object',\n properties: {\n amount: {\n type: 'integer',\n description: 'Contribution amount in cents (for type=fixed) or basis points (for type=percent, where 100 = 1%).'\n },\n type: {\n type: 'string',\n description: 'Contribution type. Supported values: \"fixed\" (amount in cents) or \"percent\" (amount in basis points).',\n enum: [ 'percent'\n ]\n }\n },\n required: [ 'amount',\n 'type'\n ]\n }\n ],\n title: 'EmployeeDeductionContribution',\n description: 'Employee deduction configuration. Supports both fixed amounts (in cents) and percentage-based contributions (in basis points where 100 = 1%).'\n },\n hsa_contribution_limit: {\n type: 'string',\n description: 'Type for HSA contribution limit if the benefit is a HSA.',\n enum: [ 'individual',\n 'family'\n ]\n }\n },\n required: [ 'annual_maximum',\n 'catch_up',\n 'company_contribution',\n 'employee_deduction'\n ]\n },\n {\n type: 'object',\n properties: {\n code: {\n type: 'number'\n },\n message: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n finch_code: {\n type: 'string'\n }\n },\n required: [ 'code',\n 'message',\n 'name'\n ]\n }\n ]\n },\n code: {\n type: 'integer'\n },\n individual_id: {\n type: 'string'\n }\n },\n required: [ 'body',\n 'code',\n 'individual_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - benefit_id: { - type: 'string', - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - individual_ids: { - type: 'string', - description: - 'comma-delimited list of stable Finch uuids for each individual. If empty, defaults to all individuals', - }, - 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: ['benefit_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { benefit_id, jq_filter, ...body } = args as any; - const response = await client.hris.benefits.individuals.retrieveManyBenefits(benefit_id, body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts b/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts deleted file mode 100644 index 26d739bb5..000000000 --- a/packages/mcp-server/src/tools/hris/benefits/individuals/unenroll-many-benefits-hris-individuals.ts +++ /dev/null @@ -1,70 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.benefits.individuals', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/employer/benefits/{benefit_id}/individuals', - operationId: 'delete-individual-benefits', -}; - -export const tool: Tool = { - name: 'unenroll_many_benefits_hris_individuals', - 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\nUnenroll individuals from a deduction or contribution\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/unenrolled_individual_benefit_response',\n $defs: {\n unenrolled_individual_benefit_response: {\n type: 'object',\n properties: {\n job_id: {\n type: 'string'\n }\n },\n required: [ 'job_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - benefit_id: { - type: 'string', - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - individual_ids: { - type: 'array', - description: 'Array of individual_ids to unenroll.', - items: { - 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: ['benefit_id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { benefit_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.individuals.unenrollMany(benefit_id, body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts deleted file mode 100644 index dc696b8fb..000000000 --- a/packages/mcp-server/src/tools/hris/benefits/list-hris-benefits.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.benefits', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/benefits', - operationId: 'get-company-benefits', -}; - -export const tool: Tool = { - name: 'list_hris_benefits', - 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\nList all company-wide deductions and contributions.\n\n# Response Schema\n```json\n{\n type: 'array',\n description: 'Array of company benefits.',\n items: {\n $ref: '#/$defs/company_benefit'\n },\n $defs: {\n company_benefit: {\n type: 'object',\n title: 'CompanyBenefit',\n properties: {\n benefit_id: {\n type: 'string',\n description: 'The id of the benefit.'\n },\n description: {\n type: 'string'\n },\n frequency: {\n $ref: '#/$defs/benefit_frequency'\n },\n type: {\n $ref: '#/$defs/benefit_type'\n },\n company_contribution: {\n type: 'object',\n title: 'BenefitCompanyMatchContribution',\n description: 'The company match for this benefit.',\n properties: {\n tiers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n match: {\n type: 'integer'\n },\n threshold: {\n type: 'integer'\n }\n },\n required: [ 'match',\n 'threshold'\n ]\n }\n },\n type: {\n type: 'string',\n enum: [ 'match'\n ]\n }\n },\n required: [ 'tiers',\n 'type'\n ]\n }\n },\n required: [ 'benefit_id',\n 'description',\n 'frequency',\n 'type'\n ]\n },\n benefit_frequency: {\n type: 'string',\n title: 'BenefitFrequency',\n description: 'The frequency of the benefit deduction/contribution.',\n enum: [ 'every_paycheck',\n 'monthly',\n 'one_time'\n ]\n },\n benefit_type: {\n type: 'string',\n title: 'BenefitType',\n description: 'Type of benefit.',\n enum: [ '457',\n '401k',\n '401k_roth',\n '401k_loan',\n '403b',\n '403b_roth',\n '457_roth',\n 'commuter',\n 'custom_post_tax',\n 'custom_pre_tax',\n 'fsa_dependent_care',\n 'fsa_medical',\n 'hsa_post',\n 'hsa_pre',\n 's125_dental',\n 's125_medical',\n 's125_vision',\n 'simple',\n 'simple_ira'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - 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: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.hris.benefits.list(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts deleted file mode 100644 index 4e8404dda..000000000 --- a/packages/mcp-server/src/tools/hris/benefits/list-supported-benefits-hris-benefits.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.benefits', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/benefits/meta', - operationId: 'get-company-benefits-meta', -}; - -export const tool: Tool = { - name: 'list_supported_benefits_hris_benefits', - 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\nGet deductions metadata\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/supported_benefit'\n },\n $defs: {\n supported_benefit: {\n type: 'object',\n title: 'BenefitFeature',\n properties: {\n annual_maximum: {\n type: 'boolean',\n description: 'Whether the provider supports an annual maximum for this benefit.'\n },\n company_contribution: {\n type: 'array',\n description: 'Supported contribution types. An empty array indicates contributions are not supported.',\n items: {\n type: 'string',\n enum: [ 'fixed',\n 'percent',\n 'tiered'\n ]\n }\n },\n description: {\n type: 'string'\n },\n employee_deduction: {\n type: 'array',\n description: 'Supported deduction types. An empty array indicates deductions are not supported.',\n items: {\n type: 'string',\n enum: [ 'fixed',\n 'percent'\n ]\n }\n },\n frequencies: {\n type: 'array',\n description: 'The list of frequencies supported by the provider for this benefit',\n items: {\n $ref: '#/$defs/benefit_frequency'\n }\n },\n catch_up: {\n type: 'boolean',\n description: 'Whether the provider supports catch up for this benefit. This field will only be true for retirement benefits.'\n },\n hsa_contribution_limit: {\n type: 'array',\n description: 'Whether the provider supports HSA contribution limits. Empty if this feature is not supported for the benefit. This array only has values for HSA benefits.',\n items: {\n type: 'string',\n enum: [ 'family',\n 'individual'\n ]\n }\n }\n },\n required: [ 'annual_maximum',\n 'company_contribution',\n 'description',\n 'employee_deduction',\n 'frequencies'\n ]\n },\n benefit_frequency: {\n type: 'string',\n title: 'BenefitFrequency',\n description: 'The frequency of the benefit deduction/contribution.',\n enum: [ 'every_paycheck',\n 'monthly',\n 'one_time'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - 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: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.hris.benefits.listSupportedBenefits(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts deleted file mode 100644 index bd8c9a25e..000000000 --- a/packages/mcp-server/src/tools/hris/benefits/retrieve-hris-benefits.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.benefits', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/benefits/{benefit_id}', - operationId: 'get-company-benefit', -}; - -export const tool: Tool = { - name: 'retrieve_hris_benefits', - 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\nLists deductions and contributions information for a given item\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/company_benefit',\n $defs: {\n company_benefit: {\n type: 'object',\n title: 'CompanyBenefit',\n properties: {\n benefit_id: {\n type: 'string',\n description: 'The id of the benefit.'\n },\n description: {\n type: 'string'\n },\n frequency: {\n $ref: '#/$defs/benefit_frequency'\n },\n type: {\n $ref: '#/$defs/benefit_type'\n },\n company_contribution: {\n type: 'object',\n title: 'BenefitCompanyMatchContribution',\n description: 'The company match for this benefit.',\n properties: {\n tiers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n match: {\n type: 'integer'\n },\n threshold: {\n type: 'integer'\n }\n },\n required: [ 'match',\n 'threshold'\n ]\n }\n },\n type: {\n type: 'string',\n enum: [ 'match'\n ]\n }\n },\n required: [ 'tiers',\n 'type'\n ]\n }\n },\n required: [ 'benefit_id',\n 'description',\n 'frequency',\n 'type'\n ]\n },\n benefit_frequency: {\n type: 'string',\n title: 'BenefitFrequency',\n description: 'The frequency of the benefit deduction/contribution.',\n enum: [ 'every_paycheck',\n 'monthly',\n 'one_time'\n ]\n },\n benefit_type: {\n type: 'string',\n title: 'BenefitType',\n description: 'Type of benefit.',\n enum: [ '457',\n '401k',\n '401k_roth',\n '401k_loan',\n '403b',\n '403b_roth',\n '457_roth',\n 'commuter',\n 'custom_post_tax',\n 'custom_pre_tax',\n 'fsa_dependent_care',\n 'fsa_medical',\n 'hsa_post',\n 'hsa_pre',\n 's125_dental',\n 's125_medical',\n 's125_vision',\n 'simple',\n 'simple_ira'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - benefit_id: { - type: 'string', - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - 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: ['benefit_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { benefit_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.retrieve(benefit_id, body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts b/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts deleted file mode 100644 index db8e04d1d..000000000 --- a/packages/mcp-server/src/tools/hris/benefits/update-hris-benefits.ts +++ /dev/null @@ -1,65 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.benefits', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/employer/benefits/{benefit_id}', - operationId: 'update-company-benefits', -}; - -export const tool: Tool = { - name: 'update_hris_benefits', - 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\nUpdates an existing company-wide deduction or contribution\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/update_company_benefit_response',\n $defs: {\n update_company_benefit_response: {\n type: 'object',\n properties: {\n benefit_id: {\n type: 'string',\n description: 'The id of the benefit.'\n },\n job_id: {\n type: 'string'\n }\n },\n required: [ 'benefit_id',\n 'job_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - benefit_id: { - type: 'string', - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - description: { - type: 'string', - description: 'Updated name or description.', - }, - 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: ['benefit_id'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { benefit_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.benefits.update(benefit_id, body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts deleted file mode 100644 index 1d6b40212..000000000 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/list-company-hris-pay-statement-item.ts +++ /dev/null @@ -1,88 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.company.pay_statement_item', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/pay-statement-item', - operationId: 'get-pay-statement-item', -}; - -export const tool: Tool = { - name: 'list_company_hris_pay_statement_item', - 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\n**Beta:** this endpoint currently serves employers onboarded after March 4th and historical support will be added soon\n Retrieve a list of detailed pay statement items for the access token's connection account.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n responses: {\n type: 'array',\n items: {\n $ref: '#/$defs/pay_statement_item_list_response'\n }\n }\n },\n required: [ 'responses'\n ],\n $defs: {\n pay_statement_item_list_response: {\n type: 'object',\n properties: {\n attributes: {\n type: 'object',\n description: 'The attributes of the pay statement item.',\n properties: {\n metadata: {\n type: 'object',\n description: 'The metadata of the pay statement item derived by the rules engine if available. Each attribute will be a key-value pair defined by a rule.',\n additionalProperties: true\n },\n employer: {\n type: 'boolean',\n description: '`true` if the amount is paid by the employers. This field is only available for taxes.'\n },\n pre_tax: {\n type: 'boolean',\n description: '`true` if the pay statement item is pre-tax. This field is only available for employee deductions.'\n },\n type: {\n type: 'string',\n description: 'The type of the pay statement item.'\n }\n },\n required: [ 'metadata'\n ]\n },\n category: {\n type: 'string',\n description: 'The category of the pay statement item.',\n enum: [ 'earnings',\n 'taxes',\n 'employee_deductions',\n 'employer_contributions'\n ]\n },\n name: {\n type: 'string',\n description: 'The name of the pay statement item.'\n }\n },\n required: [ 'attributes',\n 'category',\n 'name'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - categories: { - type: 'array', - description: - 'Comma-delimited list of pay statement item categories to filter on. If empty, defaults to all categories.', - items: { - type: 'string', - enum: ['earnings', 'taxes', 'employee_deductions', 'employer_contributions'], - }, - }, - end_date: { - type: 'string', - description: - 'The end date to retrieve pay statement items by via their last seen pay date in `YYYY-MM-DD` format.', - format: 'date', - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - name: { - type: 'string', - description: 'Case-insensitive partial match search by pay statement item name.', - }, - start_date: { - type: 'string', - description: - 'The start date to retrieve pay statement items by via their last seen pay date (inclusive) in `YYYY-MM-DD` format.', - format: 'date', - }, - type: { - type: 'string', - description: 'String search by pay statement item type.', - }, - 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: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.hris.company.payStatementItem.list(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts deleted file mode 100644 index 2c6558b65..000000000 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules.ts +++ /dev/null @@ -1,106 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.company.pay_statement_item.rules', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/employer/pay-statement-item/rule', - operationId: 'create-rule', -}; - -export const tool: Tool = { - name: 'create_pay_statement_item_company_hris_rules', - 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\n**Beta:** this endpoint currently serves employers onboarded after March 4th and historical support will be added soon\nCustom rules can be created to associate specific attributes to pay statement items depending on the use case. For example, pay statement items that meet certain conditions can be labeled as a pre-tax 401k. This metadata can be retrieved where pay statement item information is available.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/rule_create_response',\n $defs: {\n rule_create_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Finch id (uuidv4) for the rule.'\n },\n attributes: {\n type: 'object',\n description: 'Specifies the fields to be applied when the condition is met.',\n properties: {\n metadata: {\n type: 'object',\n description: 'The metadata to be attached in the entity. It is a key-value pairs where the values can be of any type (string, number, boolean, object, array, etc.).',\n additionalProperties: true\n }\n }\n },\n conditions: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n field: {\n type: 'string',\n description: 'The field to be checked in the rule.'\n },\n operator: {\n type: 'string',\n description: 'The operator to be used in the rule.',\n enum: [ 'equals'\n ]\n },\n value: {\n type: 'string',\n description: 'The value of the field to be checked in the rule.'\n }\n }\n }\n },\n created_at: {\n type: 'string',\n description: 'The datetime when the rule was created.',\n format: 'date-time'\n },\n effective_end_date: {\n type: 'string',\n title: 'Date',\n description: 'Specifies when the rules should stop applying rules based on the date.'\n },\n effective_start_date: {\n type: 'string',\n title: 'Date',\n description: 'Specifies when the rule should begin applying based on the date.'\n },\n entity_type: {\n type: 'string',\n description: 'The entity type to which the rule is applied.',\n enum: [ 'pay_statement_item'\n ]\n },\n priority: {\n type: 'integer',\n description: 'The priority of the rule.'\n },\n updated_at: {\n type: 'string',\n description: 'The datetime when the rule was last updated.',\n format: 'date-time'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_ids: { - type: 'array', - description: 'The entity IDs to create the rule for.', - items: { - type: 'string', - }, - }, - attributes: { - type: 'object', - description: 'Specifies the fields to be applied when the condition is met.', - properties: { - metadata: { - type: 'object', - description: - 'The metadata to be attached in the entity. It is a key-value pairs where the values can be of any type (string, number, boolean, object, array, etc.).', - additionalProperties: true, - }, - }, - }, - conditions: { - type: 'array', - items: { - type: 'object', - properties: { - field: { - type: 'string', - description: 'The field to be checked in the rule.', - }, - operator: { - type: 'string', - description: 'The operator to be used in the rule.', - enum: ['equals'], - }, - value: { - type: 'string', - description: 'The value of the field to be checked in the rule.', - }, - }, - }, - }, - effective_end_date: { - type: 'string', - title: 'Date', - description: 'Specifies when the rules should stop applying rules based on the date.', - }, - effective_start_date: { - type: 'string', - title: 'Date', - description: 'Specifies when the rule should begin applying based on the date.', - }, - entity_type: { - type: 'string', - description: 'The entity type to which the rule is applied.', - enum: ['pay_statement_item'], - }, - 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: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.create(body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts deleted file mode 100644 index f20a273f3..000000000 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.company.pay_statement_item.rules', - operation: 'write', - tags: [], - httpMethod: 'delete', - httpPath: '/employer/pay-statement-item/rule/{rule_id}', - operationId: 'delete-rule', -}; - -export const tool: Tool = { - name: 'delete_pay_statement_item_company_hris_rules', - 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\n**Beta:** this endpoint currently serves employers onboarded after March 4th and historical support will be added soon\nDelete a rule for a pay statement item.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/rule_delete_response',\n $defs: {\n rule_delete_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Finch id (uuidv4) for the rule.'\n },\n attributes: {\n type: 'object',\n description: 'Specifies the fields to be applied when the condition is met.',\n properties: {\n metadata: {\n type: 'object',\n description: 'The metadata to be attached in the entity. It is a key-value pairs where the values can be of any type (string, number, boolean, object, array, etc.).',\n additionalProperties: true\n }\n }\n },\n conditions: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n field: {\n type: 'string',\n description: 'The field to be checked in the rule.'\n },\n operator: {\n type: 'string',\n description: 'The operator to be used in the rule.',\n enum: [ 'equals'\n ]\n },\n value: {\n type: 'string',\n description: 'The value of the field to be checked in the rule.'\n }\n }\n }\n },\n created_at: {\n type: 'string',\n description: 'The datetime when the rule was created.',\n format: 'date-time'\n },\n deleted_at: {\n type: 'string',\n description: 'The datetime when the rule was deleted.',\n format: 'date-time'\n },\n effective_end_date: {\n type: 'string',\n title: 'Date',\n description: 'Specifies when the rules should stop applying rules based on the date.'\n },\n effective_start_date: {\n type: 'string',\n title: 'Date',\n description: 'Specifies when the rule should begin applying based on the date.'\n },\n entity_type: {\n type: 'string',\n description: 'The entity type to which the rule is applied.',\n enum: [ 'pay_statement_item'\n ]\n },\n priority: {\n type: 'integer',\n description: 'The priority of the rule.'\n },\n updated_at: {\n type: 'string',\n description: 'The datetime when the rule was last updated.',\n format: 'date-time'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - rule_id: { - type: 'string', - }, - entity_ids: { - type: 'array', - description: 'The entity IDs to delete the rule for.', - items: { - 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: ['rule_id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { rule_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.delete(rule_id, body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts deleted file mode 100644 index e4b8742e3..000000000 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.company.pay_statement_item.rules', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/pay-statement-item/rule', - operationId: 'get-rules', -}; - -export const tool: Tool = { - name: 'list_pay_statement_item_company_hris_rules', - 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\n**Beta:** this endpoint currently serves employers onboarded after March 4th and historical support will be added soon\nList all rules of a connection account.\n\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n responses: {\n type: 'array',\n items: {\n $ref: '#/$defs/rule_list_response'\n }\n }\n },\n required: [ 'responses'\n ],\n $defs: {\n rule_list_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Finch id (uuidv4) for the rule.'\n },\n attributes: {\n type: 'object',\n description: 'Specifies the fields to be applied when the condition is met.',\n properties: {\n metadata: {\n type: 'object',\n description: 'The metadata to be attached in the entity. It is a key-value pairs where the values can be of any type (string, number, boolean, object, array, etc.).',\n additionalProperties: true\n }\n }\n },\n conditions: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n field: {\n type: 'string',\n description: 'The field to be checked in the rule.'\n },\n operator: {\n type: 'string',\n description: 'The operator to be used in the rule.',\n enum: [ 'equals'\n ]\n },\n value: {\n type: 'string',\n description: 'The value of the field to be checked in the rule.'\n }\n }\n }\n },\n created_at: {\n type: 'string',\n description: 'The datetime when the rule was created.',\n format: 'date-time'\n },\n effective_end_date: {\n type: 'string',\n title: 'Date',\n description: 'Specifies when the rules should stop applying rules based on the date.'\n },\n effective_start_date: {\n type: 'string',\n title: 'Date',\n description: 'Specifies when the rule should begin applying based on the date.'\n },\n entity_type: {\n type: 'string',\n description: 'The entity type to which the rule is applied.',\n enum: [ 'pay_statement_item'\n ]\n },\n priority: {\n type: 'integer',\n description: 'The priority of the rule.'\n },\n updated_at: {\n type: 'string',\n description: 'The datetime when the rule was last updated.',\n format: 'date-time'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_ids: { - type: 'array', - description: 'The entity IDs to retrieve rules for.', - items: { - 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: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.hris.company.payStatementItem.rules.list(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts b/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts deleted file mode 100644 index 5dd2ecf90..000000000 --- a/packages/mcp-server/src/tools/hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules.ts +++ /dev/null @@ -1,67 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.company.pay_statement_item.rules', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/employer/pay-statement-item/rule/{rule_id}', - operationId: 'update-rule', -}; - -export const tool: Tool = { - name: 'update_pay_statement_item_company_hris_rules', - 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\n**Beta:** this endpoint currently serves employers onboarded after March 4th and historical support will be added soon\nUpdate a rule for a pay statement item.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/rule_update_response',\n $defs: {\n rule_update_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Finch id (uuidv4) for the rule.'\n },\n attributes: {\n type: 'object',\n description: 'Specifies the fields to be applied when the condition is met.',\n properties: {\n metadata: {\n type: 'object',\n description: 'The metadata to be attached in the entity. It is a key-value pairs where the values can be of any type (string, number, boolean, object, array, etc.).',\n additionalProperties: true\n }\n }\n },\n conditions: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n field: {\n type: 'string',\n description: 'The field to be checked in the rule.'\n },\n operator: {\n type: 'string',\n description: 'The operator to be used in the rule.',\n enum: [ 'equals'\n ]\n },\n value: {\n type: 'string',\n description: 'The value of the field to be checked in the rule.'\n }\n }\n }\n },\n created_at: {\n type: 'string',\n description: 'The datetime when the rule was created.',\n format: 'date-time'\n },\n effective_end_date: {\n type: 'string',\n title: 'Date',\n description: 'Specifies when the rules should stop applying rules based on the date.'\n },\n effective_start_date: {\n type: 'string',\n title: 'Date',\n description: 'Specifies when the rule should begin applying based on the date.'\n },\n entity_type: {\n type: 'string',\n description: 'The entity type to which the rule is applied.',\n enum: [ 'pay_statement_item'\n ]\n },\n priority: {\n type: 'integer',\n description: 'The priority of the rule.'\n },\n updated_at: {\n type: 'string',\n description: 'The datetime when the rule was last updated.',\n format: 'date-time'\n }\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - rule_id: { - type: 'string', - }, - entity_ids: { - type: 'array', - description: 'The entity IDs to update the rule for.', - items: { - type: 'string', - }, - }, - optionalProperty: { - type: 'object', - additionalProperties: 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: ['rule_id'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { rule_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.company.payStatementItem.rules.update(rule_id, body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts b/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts deleted file mode 100644 index 6f56dc9e1..000000000 --- a/packages/mcp-server/src/tools/hris/company/retrieve-hris-company.ts +++ /dev/null @@ -1,58 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.company', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/company', - operationId: 'get-company', -}; - -export const tool: Tool = { - name: 'retrieve_hris_company', - 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\nRead basic company data\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/company',\n $defs: {\n company: {\n type: 'object',\n title: 'GetCompany',\n properties: {\n id: {\n type: 'string',\n description: 'A stable Finch `id` (UUID v4) for the company.'\n },\n accounts: {\n type: 'array',\n description: 'An array of bank account objects associated with the payroll/HRIS system.',\n items: {\n type: 'object',\n properties: {\n account_name: {\n type: 'string',\n description: 'The name of the bank associated in the payroll/HRIS system.'\n },\n account_number: {\n type: 'string',\n description: '10-12 digit number to specify the bank account'\n },\n account_type: {\n type: 'string',\n description: 'The type of bank account.',\n enum: [ 'checking',\n 'savings'\n ]\n },\n institution_name: {\n type: 'string',\n description: 'Name of the banking institution.'\n },\n routing_number: {\n type: 'string',\n description: 'A nine-digit code that\\'s based on the U.S. Bank location where your account was opened.'\n }\n },\n required: [ 'account_name',\n 'account_number',\n 'account_type',\n 'institution_name',\n 'routing_number'\n ]\n }\n },\n departments: {\n type: 'array',\n description: 'The array of company departments.',\n items: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description: 'The department name.'\n },\n parent: {\n type: 'object',\n description: 'The parent department, if present.',\n properties: {\n name: {\n type: 'string',\n description: 'The parent department\\'s name.'\n }\n },\n required: [ 'name'\n ]\n }\n },\n required: [ 'name',\n 'parent'\n ]\n }\n },\n ein: {\n type: 'string',\n description: 'The employer identification number.'\n },\n entity: {\n type: 'object',\n description: 'The entity type object.',\n properties: {\n subtype: {\n type: 'string',\n description: 'The tax payer subtype of the company.',\n enum: [ 's_corporation',\n 'c_corporation',\n 'b_corporation'\n ]\n },\n type: {\n type: 'string',\n description: 'The tax payer type of the company.',\n enum: [ 'llc',\n 'lp',\n 'corporation',\n 'sole_proprietor',\n 'non_profit',\n 'partnership',\n 'cooperative'\n ]\n }\n },\n required: [ 'subtype',\n 'type'\n ]\n },\n legal_name: {\n type: 'string',\n description: 'The legal name of the company.'\n },\n locations: {\n type: 'array',\n items: {\n $ref: '#/$defs/location'\n }\n },\n primary_email: {\n type: 'string',\n description: 'The email of the main administrator on the account.'\n },\n primary_phone_number: {\n type: 'string',\n description: 'The phone number of the main administrator on the account. Format: E.164, with extension where applicable, e.g. `+NNNNNNNNNNN xExtension`'\n }\n },\n required: [ 'id',\n 'accounts',\n 'departments',\n 'ein',\n 'entity',\n 'legal_name',\n 'locations',\n 'primary_email',\n 'primary_phone_number'\n ]\n },\n location: {\n type: 'object',\n title: 'Location',\n properties: {\n city: {\n type: 'string',\n description: 'City, district, suburb, town, or village.'\n },\n country: {\n type: 'string',\n description: 'The 2-letter ISO 3166 country code.'\n },\n line1: {\n type: 'string',\n description: 'Street address or PO box.'\n },\n line2: {\n type: 'string',\n description: 'Apartment, suite, unit, or building.'\n },\n postal_code: {\n type: 'string',\n description: 'The postal code or zip code.'\n },\n state: {\n type: 'string',\n description: 'The state code.'\n },\n name: {\n type: 'string'\n },\n source_id: {\n type: 'string'\n }\n },\n required: [ 'city',\n 'country',\n 'line1',\n 'line2',\n 'postal_code',\n 'state'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - 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: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.hris.company.retrieve(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts b/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts deleted file mode 100644 index 23c677438..000000000 --- a/packages/mcp-server/src/tools/hris/directory/list-hris-directory.ts +++ /dev/null @@ -1,67 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.directory', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/directory', - operationId: 'get-directory', -}; - -export const tool: Tool = { - name: 'list_hris_directory', - 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\nRead company directory and organization structure\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n individuals: {\n type: 'array',\n description: 'The array of employees.',\n items: {\n $ref: '#/$defs/individual_in_directory'\n }\n },\n paging: {\n $ref: '#/$defs/paging'\n }\n },\n required: [ 'individuals',\n 'paging'\n ],\n $defs: {\n individual_in_directory: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'A stable Finch `id` (UUID v4) for an individual in the company.'\n },\n department: {\n type: 'object',\n description: 'The department object.',\n properties: {\n name: {\n type: 'string',\n description: 'The name of the department.'\n }\n }\n },\n first_name: {\n type: 'string',\n description: 'The legal first name of the individual.'\n },\n is_active: {\n type: 'boolean',\n description: '`true` if the individual is an active employee or contractor at the company.'\n },\n last_name: {\n type: 'string',\n description: 'The legal last name of the individual.'\n },\n manager: {\n type: 'object',\n description: 'The manager object.',\n properties: {\n id: {\n type: 'string',\n description: 'A stable Finch `id` (UUID v4) for an individual in the company.'\n }\n },\n required: [ 'id'\n ]\n },\n middle_name: {\n type: 'string',\n description: 'The legal middle name of the individual.'\n }\n },\n required: [ 'id',\n 'department',\n 'first_name',\n 'is_active',\n 'last_name',\n 'manager',\n 'middle_name'\n ]\n },\n paging: {\n type: 'object',\n title: 'Paging',\n properties: {\n offset: {\n type: 'integer',\n description: 'The current start index of the returned list of elements'\n },\n count: {\n type: 'integer',\n description: 'The total number of elements for the entire query (not just the given page)'\n }\n },\n required: [ 'offset'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - limit: { - type: 'integer', - description: 'Number of employees to return (defaults to all)', - }, - offset: { - type: 'integer', - description: 'Index to start from (defaults to 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: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.hris.directory.list(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts b/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts deleted file mode 100644 index 182c20844..000000000 --- a/packages/mcp-server/src/tools/hris/documents/list-hris-documents.ts +++ /dev/null @@ -1,82 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.documents', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/documents', - operationId: 'list-documents', -}; - -export const tool: Tool = { - name: 'list_hris_documents', - 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\n**Beta:** This endpoint is in beta and may change.\nRetrieve a list of company-wide documents.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/document_list_response',\n $defs: {\n document_list_response: {\n type: 'object',\n properties: {\n documents: {\n type: 'array',\n items: {\n $ref: '#/$defs/document_response'\n }\n },\n paging: {\n $ref: '#/$defs/paging'\n }\n },\n required: [ 'documents',\n 'paging'\n ]\n },\n document_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'A stable Finch id for the document.'\n },\n individual_id: {\n type: 'string',\n description: 'The ID of the individual associated with the document. This will be null for employer-level documents.'\n },\n type: {\n type: 'string',\n description: 'The type of document.',\n enum: [ 'w4_2020',\n 'w4_2005'\n ]\n },\n url: {\n type: 'string',\n description: 'A URL to access the document. Format: `https://api.tryfinch.com/employer/documents/:document_id`.'\n },\n year: {\n type: 'number',\n description: 'The year the document applies to, if available.'\n }\n },\n required: [ 'id',\n 'individual_id',\n 'type',\n 'url',\n 'year'\n ]\n },\n paging: {\n type: 'object',\n title: 'Paging',\n properties: {\n offset: {\n type: 'integer',\n description: 'The current start index of the returned list of elements'\n },\n count: {\n type: 'integer',\n description: 'The total number of elements for the entire query (not just the given page)'\n }\n },\n required: [ 'offset'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - individual_ids: { - type: 'array', - description: - 'Comma-delimited list of stable Finch uuids for each individual. If empty, defaults to all individuals', - items: { - type: 'string', - }, - }, - limit: { - type: 'integer', - description: 'Number of documents to return (defaults to all)', - }, - offset: { - type: 'integer', - description: 'Index to start from (defaults to 0)', - }, - types: { - type: 'array', - description: 'Comma-delimited list of document types to filter on. If empty, defaults to all types', - items: { - type: 'string', - enum: ['w4_2020', 'w4_2005'], - }, - }, - 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: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.hris.documents.list(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts b/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts deleted file mode 100644 index ff8e1613e..000000000 --- a/packages/mcp-server/src/tools/hris/documents/retreive-hris-documents.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.documents', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/documents/{document_id}', - operationId: 'get-document', -}; - -export const tool: Tool = { - name: 'retreive_hris_documents', - 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\n**Beta:** This endpoint is in beta and may change.\nRetrieve details of a specific document by its ID.\n\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/document_retreive_response',\n $defs: {\n document_retreive_response: {\n anyOf: [ {\n $ref: '#/$defs/w42020'\n },\n {\n $ref: '#/$defs/w42005'\n }\n ],\n description: 'A 2020 version of the W-4 tax form containing information on an individual\\'s filing status, dependents, and withholding details.'\n },\n w42020: {\n type: 'object',\n description: 'A 2020 version of the W-4 tax form containing information on an individual\\'s filing status, dependents, and withholding details.',\n properties: {\n data: {\n type: 'object',\n description: 'Detailed information specific to the 2020 W4 form.',\n properties: {\n amount_for_other_dependents: {\n type: 'integer',\n description: 'Amount claimed for dependents other than qualifying children under 17 (in cents).'\n },\n amount_for_qualifying_children_under_17: {\n type: 'integer',\n description: 'Amount claimed for dependents under 17 years old (in cents).'\n },\n deductions: {\n type: 'integer',\n description: 'Deductible expenses (in cents).'\n },\n extra_withholding: {\n type: 'integer',\n description: 'Additional withholding amount (in cents).'\n },\n filing_status: {\n type: 'string',\n description: 'The individual\\'s filing status for tax purposes.',\n enum: [ 'head_of_household',\n 'married_filing_jointly_or_qualifying_surviving_spouse',\n 'single_or_married_filing_separately'\n ]\n },\n individual_id: {\n type: 'string',\n description: 'The unique identifier for the individual associated with this document.'\n },\n other_income: {\n type: 'integer',\n description: 'Additional income from sources outside of primary employment (in cents).'\n },\n total_claim_dependent_and_other_credits: {\n type: 'integer',\n description: 'Total amount claimed for dependents and other credits (in cents).'\n }\n },\n required: [ 'amount_for_other_dependents',\n 'amount_for_qualifying_children_under_17',\n 'deductions',\n 'extra_withholding',\n 'filing_status',\n 'individual_id',\n 'other_income',\n 'total_claim_dependent_and_other_credits'\n ]\n },\n type: {\n type: 'string',\n description: 'Specifies the form type, indicating that this document is a 2020 W4 form.',\n enum: [ 'w4_2020'\n ]\n },\n year: {\n type: 'number',\n description: 'The tax year this W4 document applies to.'\n }\n },\n required: [ 'data',\n 'type',\n 'year'\n ]\n },\n w42005: {\n type: 'object',\n description: 'A 2005 version of the W-4 tax form containing information on an individual\\'s filing status, dependents, and withholding details.',\n properties: {\n data: {\n type: 'object',\n description: 'Detailed information specific to the 2005 W4 form.',\n properties: {\n additional_withholding: {\n type: 'integer',\n description: 'Additional withholding amount (in cents).'\n },\n exemption: {\n type: 'string',\n description: 'Indicates exemption status from federal tax withholding.',\n enum: [ 'exempt',\n 'non_exempt'\n ]\n },\n filing_status: {\n type: 'string',\n description: 'The individual\\'s filing status for tax purposes.',\n enum: [ 'married',\n 'married_but_withhold_at_higher_single_rate',\n 'single'\n ]\n },\n individual_id: {\n type: 'string',\n description: 'The unique identifier for the individual associated with this 2005 W4 form.'\n },\n total_number_of_allowances: {\n type: 'integer',\n description: 'Total number of allowances claimed (in cents).'\n }\n },\n required: [ 'additional_withholding',\n 'exemption',\n 'filing_status',\n 'individual_id',\n 'total_number_of_allowances'\n ]\n },\n type: {\n type: 'string',\n description: 'Specifies the form type, indicating that this document is a 2005 W4 form.',\n enum: [ 'w4_2005'\n ]\n },\n year: {\n type: 'number',\n description: 'The tax year this W4 document applies to.'\n }\n },\n required: [ 'data',\n 'type',\n 'year'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - document_id: { - type: 'string', - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - 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: ['document_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { document_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.hris.documents.retreive(document_id, body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/employments/retrieve-many-hris-employments.ts b/packages/mcp-server/src/tools/hris/employments/retrieve-many-hris-employments.ts deleted file mode 100644 index a01047251..000000000 --- a/packages/mcp-server/src/tools/hris/employments/retrieve-many-hris-employments.ts +++ /dev/null @@ -1,64 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.employments', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/employer/employment', - operationId: 'get-employment', -}; - -export const tool: Tool = { - name: 'retrieve_many_hris_employments', - description: 'Read individual employment and income data', - inputSchema: { - type: 'object', - properties: { - requests: { - type: 'array', - description: 'The array of batch requests.', - items: { - type: 'object', - properties: { - individual_id: { - type: 'string', - description: - 'A stable Finch `id` (UUID v4) for an individual in the company. There is no limit to the number of `individual_id` to send per request. It is preferantial to send all ids in a single request for Finch to optimize provider rate-limits.', - }, - }, - required: ['individual_id'], - }, - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - }, - required: ['requests'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const body = args as any; - const response = await client.hris.employments.retrieveMany(body).asResponse(); - try { - return asTextContentResult(await response.json()); - } catch (error) { - if (error instanceof Finch.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts b/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts deleted file mode 100644 index ec636ac35..000000000 --- a/packages/mcp-server/src/tools/hris/individuals/retrieve-many-hris-individuals.ts +++ /dev/null @@ -1,79 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.individuals', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/employer/individual', - operationId: 'get-individual', -}; - -export const tool: Tool = { - name: 'retrieve_many_hris_individuals', - 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\nRead individual data, excluding income and employment data\n\n# Response Schema\n```json\n{\n type: 'object',\n properties: {\n responses: {\n type: 'array',\n items: {\n $ref: '#/$defs/individual_response'\n }\n }\n },\n required: [ 'responses'\n ],\n $defs: {\n individual_response: {\n type: 'object',\n properties: {\n body: {\n $ref: '#/$defs/individual'\n },\n code: {\n type: 'integer'\n },\n individual_id: {\n type: 'string'\n }\n },\n required: [ 'body',\n 'code',\n 'individual_id'\n ]\n },\n individual: {\n anyOf: [ {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'A stable Finch `id` (UUID v4) for an individual in the company.'\n },\n dob: {\n type: 'string',\n title: 'Date'\n },\n ethnicity: {\n type: 'string',\n description: 'The EEOC-defined ethnicity of the individual.',\n enum: [ 'asian',\n 'white',\n 'black_or_african_american',\n 'native_hawaiian_or_pacific_islander',\n 'american_indian_or_alaska_native',\n 'hispanic_or_latino',\n 'two_or_more_races',\n 'decline_to_specify'\n ]\n },\n first_name: {\n type: 'string',\n description: 'The legal first name of the individual.'\n },\n gender: {\n type: 'string',\n description: 'The gender of the individual.',\n enum: [ 'female',\n 'male',\n 'other',\n 'decline_to_specify'\n ]\n },\n last_name: {\n type: 'string',\n description: 'The legal last name of the individual.'\n },\n middle_name: {\n type: 'string',\n description: 'The legal middle name of the individual.'\n },\n phone_numbers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n data: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'work',\n 'personal'\n ]\n }\n },\n required: [ 'data',\n 'type'\n ]\n }\n },\n preferred_name: {\n type: 'string',\n description: 'The preferred name of the individual.'\n },\n residence: {\n $ref: '#/$defs/location'\n },\n emails: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n data: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'work',\n 'personal'\n ]\n }\n },\n required: [ 'data',\n 'type'\n ]\n }\n },\n encrypted_ssn: {\n type: 'string',\n description: 'Social Security Number of the individual in **encrypted** format. This field is only available with the `ssn` scope enabled and the `options: { include: [\\'ssn\\'] }` param set in the body.'\n },\n ssn: {\n type: 'string',\n description: 'Social Security Number of the individual. This field is only available with the `ssn` scope enabled and the `options: { include: [\\'ssn\\'] }` param set in the body. [Click here to learn more about enabling the SSN field](/developer-resources/Enable-SSN-Field).'\n }\n },\n required: [ 'id',\n 'dob',\n 'ethnicity',\n 'first_name',\n 'gender',\n 'last_name',\n 'middle_name',\n 'phone_numbers',\n 'preferred_name',\n 'residence'\n ]\n },\n {\n type: 'object',\n properties: {\n code: {\n type: 'number'\n },\n message: {\n type: 'string'\n },\n name: {\n type: 'string'\n },\n finch_code: {\n type: 'string'\n }\n },\n required: [ 'code',\n 'message',\n 'name'\n ]\n }\n ]\n },\n location: {\n type: 'object',\n title: 'Location',\n properties: {\n city: {\n type: 'string',\n description: 'City, district, suburb, town, or village.'\n },\n country: {\n type: 'string',\n description: 'The 2-letter ISO 3166 country code.'\n },\n line1: {\n type: 'string',\n description: 'Street address or PO box.'\n },\n line2: {\n type: 'string',\n description: 'Apartment, suite, unit, or building.'\n },\n postal_code: {\n type: 'string',\n description: 'The postal code or zip code.'\n },\n state: {\n type: 'string',\n description: 'The state code.'\n },\n name: {\n type: 'string'\n },\n source_id: {\n type: 'string'\n }\n },\n required: [ 'city',\n 'country',\n 'line1',\n 'line2',\n 'postal_code',\n 'state'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - options: { - type: 'object', - properties: { - include: { - type: 'array', - items: { - type: 'string', - }, - }, - }, - }, - requests: { - type: 'array', - items: { - type: 'object', - properties: { - individual_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: [], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.hris.individuals.retrieveMany(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/pay-statements/retrieve-many-hris-pay-statements.ts b/packages/mcp-server/src/tools/hris/pay-statements/retrieve-many-hris-pay-statements.ts deleted file mode 100644 index 70f7bb9ca..000000000 --- a/packages/mcp-server/src/tools/hris/pay-statements/retrieve-many-hris-pay-statements.ts +++ /dev/null @@ -1,72 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.pay_statements', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/employer/pay-statement', - operationId: 'get-pay-statement', -}; - -export const tool: Tool = { - name: 'retrieve_many_hris_pay_statements', - description: - 'Read detailed pay statements for each individual.\n\nDeduction and contribution types are supported by the payroll systems that supports Benefits.', - inputSchema: { - type: 'object', - properties: { - requests: { - type: 'array', - description: 'The array of batch requests.', - items: { - type: 'object', - properties: { - payment_id: { - type: 'string', - description: 'A stable Finch `id` (UUID v4) for a payment.', - }, - limit: { - type: 'integer', - description: 'Number of pay statements to return (defaults to all).', - }, - offset: { - type: 'integer', - description: 'Index to start from.', - }, - }, - required: ['payment_id'], - }, - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - }, - required: ['requests'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const body = args as any; - const response = await client.hris.payStatements.retrieveMany(body).asResponse(); - try { - return asTextContentResult(await response.json()); - } catch (error) { - if (error instanceof Finch.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts b/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts deleted file mode 100644 index 89f768608..000000000 --- a/packages/mcp-server/src/tools/hris/payments/list-hris-payments.ts +++ /dev/null @@ -1,69 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'hris.payments', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/payment', - operationId: 'get-payment', -}; - -export const tool: Tool = { - name: 'list_hris_payments', - 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\nRead payroll and contractor related payments by the company.\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/payment'\n },\n $defs: {\n payment: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The unique id for the payment.'\n },\n company_debit: {\n $ref: '#/$defs/money'\n },\n debit_date: {\n type: 'string',\n title: 'Date'\n },\n employee_taxes: {\n $ref: '#/$defs/money'\n },\n employer_taxes: {\n $ref: '#/$defs/money'\n },\n gross_pay: {\n $ref: '#/$defs/money'\n },\n individual_ids: {\n type: 'array',\n description: 'Array of every individual on this payment.',\n items: {\n type: 'string'\n }\n },\n net_pay: {\n $ref: '#/$defs/money'\n },\n pay_date: {\n type: 'string',\n title: 'Date'\n },\n pay_frequencies: {\n type: 'array',\n description: 'List of pay frequencies associated with this payment.',\n items: {\n type: 'string',\n enum: [ 'annually',\n 'bi_weekly',\n 'daily',\n 'monthly',\n 'other',\n 'quarterly',\n 'semi_annually',\n 'semi_monthly',\n 'weekly'\n ]\n }\n },\n pay_group_ids: {\n type: 'array',\n description: 'Array of the Finch id (uuidv4) of every pay group associated with this payment.',\n items: {\n type: 'string'\n }\n },\n pay_period: {\n type: 'object',\n description: 'The pay period object.',\n properties: {\n end_date: {\n type: 'string',\n title: 'Date'\n },\n start_date: {\n type: 'string',\n title: 'Date'\n }\n },\n required: [ 'end_date',\n 'start_date'\n ]\n }\n },\n required: [ 'id',\n 'company_debit',\n 'debit_date',\n 'employee_taxes',\n 'employer_taxes',\n 'gross_pay',\n 'individual_ids',\n 'net_pay',\n 'pay_date',\n 'pay_frequencies',\n 'pay_group_ids',\n 'pay_period'\n ]\n },\n money: {\n type: 'object',\n title: 'Money',\n properties: {\n amount: {\n type: 'integer',\n description: 'Amount for money object (in cents)'\n },\n currency: {\n type: 'string'\n }\n },\n required: [ 'amount',\n 'currency'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - end_date: { - type: 'string', - description: 'The end date to retrieve payments by a company (inclusive) in `YYYY-MM-DD` format.', - format: 'date', - }, - start_date: { - type: 'string', - description: 'The start date to retrieve payments by a company (inclusive) in `YYYY-MM-DD` format.', - format: 'date', - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - 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: ['end_date', 'start_date'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.hris.payments.list(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (error instanceof Finch.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 438763953..000000000 --- a/packages/mcp-server/src/tools/index.ts +++ /dev/null @@ -1,161 +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 create_access_tokens from './access-tokens/create-access-tokens'; -import retrieve_hris_company from './hris/company/retrieve-hris-company'; -import list_company_hris_pay_statement_item from './hris/company/pay-statement-item/list-company-hris-pay-statement-item'; -import create_pay_statement_item_company_hris_rules from './hris/company/pay-statement-item/rules/create-pay-statement-item-company-hris-rules'; -import update_pay_statement_item_company_hris_rules from './hris/company/pay-statement-item/rules/update-pay-statement-item-company-hris-rules'; -import list_pay_statement_item_company_hris_rules from './hris/company/pay-statement-item/rules/list-pay-statement-item-company-hris-rules'; -import delete_pay_statement_item_company_hris_rules from './hris/company/pay-statement-item/rules/delete-pay-statement-item-company-hris-rules'; -import list_hris_directory from './hris/directory/list-hris-directory'; -import retrieve_many_hris_individuals from './hris/individuals/retrieve-many-hris-individuals'; -import retrieve_many_hris_employments from './hris/employments/retrieve-many-hris-employments'; -import list_hris_payments from './hris/payments/list-hris-payments'; -import retrieve_many_hris_pay_statements from './hris/pay-statements/retrieve-many-hris-pay-statements'; -import list_hris_documents from './hris/documents/list-hris-documents'; -import retreive_hris_documents from './hris/documents/retreive-hris-documents'; -import create_hris_benefits from './hris/benefits/create-hris-benefits'; -import retrieve_hris_benefits from './hris/benefits/retrieve-hris-benefits'; -import update_hris_benefits from './hris/benefits/update-hris-benefits'; -import list_hris_benefits from './hris/benefits/list-hris-benefits'; -import list_supported_benefits_hris_benefits from './hris/benefits/list-supported-benefits-hris-benefits'; -import enroll_many_benefits_hris_individuals from './hris/benefits/individuals/enroll-many-benefits-hris-individuals'; -import enrolled_ids_benefits_hris_individuals from './hris/benefits/individuals/enrolled-ids-benefits-hris-individuals'; -import retrieve_many_benefits_benefits_hris_individuals from './hris/benefits/individuals/retrieve-many-benefits-benefits-hris-individuals'; -import unenroll_many_benefits_hris_individuals from './hris/benefits/individuals/unenroll-many-benefits-hris-individuals'; -import list_providers from './providers/list-providers'; -import disconnect_account from './account/disconnect-account'; -import introspect_account from './account/introspect-account'; -import forward_request_forwarding from './request-forwarding/forward-request-forwarding'; -import create_jobs_automated from './jobs/automated/create-jobs-automated'; -import retrieve_jobs_automated from './jobs/automated/retrieve-jobs-automated'; -import list_jobs_automated from './jobs/automated/list-jobs-automated'; -import retrieve_jobs_manual from './jobs/manual/retrieve-jobs-manual'; -import create_sandbox_connections from './sandbox/connections/create-sandbox-connections'; -import create_connections_sandbox_accounts from './sandbox/connections/accounts/create-connections-sandbox-accounts'; -import update_connections_sandbox_accounts from './sandbox/connections/accounts/update-connections-sandbox-accounts'; -import update_sandbox_company from './sandbox/company/update-sandbox-company'; -import create_sandbox_directory from './sandbox/directory/create-sandbox-directory'; -import update_sandbox_individual from './sandbox/individual/update-sandbox-individual'; -import update_sandbox_employment from './sandbox/employment/update-sandbox-employment'; -import create_sandbox_payment from './sandbox/payment/create-sandbox-payment'; -import create_sandbox_jobs from './sandbox/jobs/create-sandbox-jobs'; -import retrieve_jobs_sandbox_configuration from './sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration'; -import update_jobs_sandbox_configuration from './sandbox/jobs/configuration/update-jobs-sandbox-configuration'; -import retrieve_payroll_pay_groups from './payroll/pay-groups/retrieve-payroll-pay-groups'; -import list_payroll_pay_groups from './payroll/pay-groups/list-payroll-pay-groups'; -import new_connect_sessions from './connect/sessions/new-connect-sessions'; -import reauthenticate_connect_sessions from './connect/sessions/reauthenticate-connect-sessions'; - -export const endpoints: Endpoint[] = []; - -function addEndpoint(endpoint: Endpoint) { - endpoints.push(endpoint); -} - -addEndpoint(create_access_tokens); -addEndpoint(retrieve_hris_company); -addEndpoint(list_company_hris_pay_statement_item); -addEndpoint(create_pay_statement_item_company_hris_rules); -addEndpoint(update_pay_statement_item_company_hris_rules); -addEndpoint(list_pay_statement_item_company_hris_rules); -addEndpoint(delete_pay_statement_item_company_hris_rules); -addEndpoint(list_hris_directory); -addEndpoint(retrieve_many_hris_individuals); -addEndpoint(retrieve_many_hris_employments); -addEndpoint(list_hris_payments); -addEndpoint(retrieve_many_hris_pay_statements); -addEndpoint(list_hris_documents); -addEndpoint(retreive_hris_documents); -addEndpoint(create_hris_benefits); -addEndpoint(retrieve_hris_benefits); -addEndpoint(update_hris_benefits); -addEndpoint(list_hris_benefits); -addEndpoint(list_supported_benefits_hris_benefits); -addEndpoint(enroll_many_benefits_hris_individuals); -addEndpoint(enrolled_ids_benefits_hris_individuals); -addEndpoint(retrieve_many_benefits_benefits_hris_individuals); -addEndpoint(unenroll_many_benefits_hris_individuals); -addEndpoint(list_providers); -addEndpoint(disconnect_account); -addEndpoint(introspect_account); -addEndpoint(forward_request_forwarding); -addEndpoint(create_jobs_automated); -addEndpoint(retrieve_jobs_automated); -addEndpoint(list_jobs_automated); -addEndpoint(retrieve_jobs_manual); -addEndpoint(create_sandbox_connections); -addEndpoint(create_connections_sandbox_accounts); -addEndpoint(update_connections_sandbox_accounts); -addEndpoint(update_sandbox_company); -addEndpoint(create_sandbox_directory); -addEndpoint(update_sandbox_individual); -addEndpoint(update_sandbox_employment); -addEndpoint(create_sandbox_payment); -addEndpoint(create_sandbox_jobs); -addEndpoint(retrieve_jobs_sandbox_configuration); -addEndpoint(update_jobs_sandbox_configuration); -addEndpoint(retrieve_payroll_pay_groups); -addEndpoint(list_payroll_pay_groups); -addEndpoint(new_connect_sessions); -addEndpoint(reauthenticate_connect_sessions); - -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/jobs/automated/create-jobs-automated.ts b/packages/mcp-server/src/tools/jobs/automated/create-jobs-automated.ts deleted file mode 100644 index 472edaff7..000000000 --- a/packages/mcp-server/src/tools/jobs/automated/create-jobs-automated.ts +++ /dev/null @@ -1,82 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'jobs.automated', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/jobs/automated', - operationId: 'post-jobs-automated', -}; - -export const tool: Tool = { - name: 'create_jobs_automated', - 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\nEnqueue an automated job.\n\n`data_sync_all`: Enqueue a job to re-sync all data for a connection. `data_sync_all` has a concurrency limit of 1 job at a time per connection. This means that if this endpoint is called while a job is already in progress for this connection, Finch will return the `job_id` of the job that is currently in progress. Finch allows a fixed window rate limit of 1 forced refresh per hour per connection.\n\n`w4_form_employee_sync`: Enqueues a job for sync W-4 data for a particular individual, identified by `individual_id`. This feature is currently in beta.\n\nThis endpoint is available for *Scale* tier customers as an add-on. To request access to this endpoint, please contact your Finch account manager.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/automated_create_response',\n $defs: {\n automated_create_response: {\n type: 'object',\n properties: {\n allowed_refreshes: {\n type: 'integer',\n description: 'The number of allowed refreshes per hour (per hour, fixed window)'\n },\n remaining_refreshes: {\n type: 'integer',\n description: 'The number of remaining refreshes available (per hour, fixed window)'\n },\n job_id: {\n type: 'string',\n description: 'The id of the job that has been created.'\n },\n job_url: {\n type: 'string',\n description: 'The url that can be used to retrieve the job status'\n },\n retry_at: {\n type: 'string',\n description: 'ISO 8601 timestamp indicating when to retry the request'\n }\n },\n required: [ 'allowed_refreshes',\n 'remaining_refreshes'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - anyOf: [ - { - type: 'object', - properties: { - type: { - type: 'string', - description: 'The type of job to start.', - enum: ['data_sync_all'], - }, - }, - required: ['type'], - }, - { - type: 'object', - properties: { - params: { - type: 'object', - properties: { - individual_id: { - type: 'string', - description: 'The unique ID of the individual for W-4 data sync.', - }, - }, - required: ['individual_id'], - }, - type: { - type: 'string', - description: 'The type of job to start.', - enum: ['w4_form_employee_sync'], - }, - }, - required: ['params', 'type'], - }, - ], - 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/).', - }, - }, - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.create(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts b/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts deleted file mode 100644 index 7bcb3d797..000000000 --- a/packages/mcp-server/src/tools/jobs/automated/list-jobs-automated.ts +++ /dev/null @@ -1,59 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'jobs.automated', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/jobs/automated', - operationId: 'get-jobs-automated', -}; - -export const tool: Tool = { - name: 'list_jobs_automated', - 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\nGet all automated jobs. Automated jobs are completed by a machine. By default, jobs are sorted in descending order by submission time. For scheduled jobs such as data syncs, only the next scheduled job is shown.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/automated_list_response',\n $defs: {\n automated_list_response: {\n type: 'object',\n properties: {\n data: {\n type: 'array',\n items: {\n $ref: '#/$defs/automated_async_job'\n }\n },\n meta: {\n type: 'object',\n properties: {\n quotas: {\n type: 'object',\n description: 'Information about remaining quotas for this connection. Only applicable for customers opted in to use Finch\\'s Data Sync Refresh endpoint (`POST /jobs/automated`). Please contact a Finch representative for more details.',\n properties: {\n data_sync_all: {\n type: 'object',\n properties: {\n allowed_refreshes: {\n type: 'integer'\n },\n remaining_refreshes: {\n type: 'integer'\n }\n }\n }\n }\n }\n }\n }\n },\n required: [ 'data',\n 'meta'\n ]\n },\n automated_async_job: {\n type: 'object',\n title: 'AutomatedAsyncJob',\n properties: {\n completed_at: {\n type: 'string',\n description: 'The datetime the job completed.',\n format: 'date-time'\n },\n created_at: {\n type: 'string',\n description: 'The datetime when the job was created. for scheduled jobs, this will be the initial connection time. For ad-hoc jobs, this will be the time the creation request was received.',\n format: 'date-time'\n },\n job_id: {\n type: 'string',\n description: 'The id of the job that has been created.'\n },\n job_url: {\n type: 'string',\n description: 'The url that can be used to retrieve the job status'\n },\n params: {\n type: 'object',\n description: 'The input parameters for the job.',\n properties: {\n individual_id: {\n type: 'string',\n description: 'The ID of the individual that the job was completed for.'\n }\n }\n },\n scheduled_at: {\n type: 'string',\n description: 'The datetime a job is scheduled to be run. For scheduled jobs, this datetime can be in the future if the job has not yet been enqueued. For ad-hoc jobs, this field will be null.',\n format: 'date-time'\n },\n started_at: {\n type: 'string',\n description: 'The datetime a job entered into the job queue.',\n format: 'date-time'\n },\n status: {\n type: 'string',\n enum: [ 'pending',\n 'in_progress',\n 'complete',\n 'error',\n 'reauth_error',\n 'permissions_error'\n ]\n },\n type: {\n type: 'string',\n description: 'The type of automated job',\n enum: [ 'data_sync_all',\n 'w4_form_employee_sync'\n ]\n }\n },\n required: [ 'completed_at',\n 'created_at',\n 'job_id',\n 'job_url',\n 'params',\n 'scheduled_at',\n 'started_at',\n 'status',\n 'type'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - limit: { - type: 'integer', - description: 'Number of items to return', - }, - offset: { - type: 'integer', - description: 'Index to start from (defaults to 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: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.list(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts b/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts deleted file mode 100644 index 96a9d97ff..000000000 --- a/packages/mcp-server/src/tools/jobs/automated/retrieve-jobs-automated.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'jobs.automated', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/jobs/automated/{job_id}', - operationId: 'get-jobs-job_id', -}; - -export const tool: Tool = { - name: 'retrieve_jobs_automated', - 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\nGet an automated job by `job_id`.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/automated_async_job',\n $defs: {\n automated_async_job: {\n type: 'object',\n title: 'AutomatedAsyncJob',\n properties: {\n completed_at: {\n type: 'string',\n description: 'The datetime the job completed.',\n format: 'date-time'\n },\n created_at: {\n type: 'string',\n description: 'The datetime when the job was created. for scheduled jobs, this will be the initial connection time. For ad-hoc jobs, this will be the time the creation request was received.',\n format: 'date-time'\n },\n job_id: {\n type: 'string',\n description: 'The id of the job that has been created.'\n },\n job_url: {\n type: 'string',\n description: 'The url that can be used to retrieve the job status'\n },\n params: {\n type: 'object',\n description: 'The input parameters for the job.',\n properties: {\n individual_id: {\n type: 'string',\n description: 'The ID of the individual that the job was completed for.'\n }\n }\n },\n scheduled_at: {\n type: 'string',\n description: 'The datetime a job is scheduled to be run. For scheduled jobs, this datetime can be in the future if the job has not yet been enqueued. For ad-hoc jobs, this field will be null.',\n format: 'date-time'\n },\n started_at: {\n type: 'string',\n description: 'The datetime a job entered into the job queue.',\n format: 'date-time'\n },\n status: {\n type: 'string',\n enum: [ 'pending',\n 'in_progress',\n 'complete',\n 'error',\n 'reauth_error',\n 'permissions_error'\n ]\n },\n type: {\n type: 'string',\n description: 'The type of automated job',\n enum: [ 'data_sync_all',\n 'w4_form_employee_sync'\n ]\n }\n },\n required: [ 'completed_at',\n 'created_at',\n 'job_id',\n 'job_url',\n 'params',\n 'scheduled_at',\n 'started_at',\n 'status',\n 'type'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - job_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: ['job_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { job_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.automated.retrieve(job_id))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts b/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts deleted file mode 100644 index 6f5b671d9..000000000 --- a/packages/mcp-server/src/tools/jobs/manual/retrieve-jobs-manual.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'jobs.manual', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/jobs/manual/{job_id}', - operationId: 'get-jobs-manual-job_id', -}; - -export const tool: Tool = { - name: 'retrieve_jobs_manual', - 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\nGet a manual job by `job_id`. Manual jobs are completed by a human and include Assisted Benefits jobs.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/manual_async_job',\n $defs: {\n manual_async_job: {\n type: 'object',\n title: 'ManualAsyncJob',\n properties: {\n body: {\n type: 'array',\n description: 'Specific information about the job, such as individual statuses for batch jobs.',\n items: {\n type: 'object',\n additionalProperties: true\n }\n },\n job_id: {\n type: 'string'\n },\n status: {\n type: 'string',\n enum: [ 'pending',\n 'in_progress',\n 'error',\n 'complete'\n ]\n }\n },\n required: [ 'body',\n 'job_id',\n 'status'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - job_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: ['job_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { job_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.jobs.manual.retrieve(job_id))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts b/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts deleted file mode 100644 index 3f5c70e75..000000000 --- a/packages/mcp-server/src/tools/payroll/pay-groups/list-payroll-pay-groups.ts +++ /dev/null @@ -1,68 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'payroll.pay_groups', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/pay-groups', - operationId: 'get-all-pay-groups', -}; - -export const tool: Tool = { - name: 'list_payroll_pay_groups', - 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\nRead company pay groups and frequencies\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/pay_group_list_response'\n },\n $defs: {\n pay_group_list_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Finch id (uuidv4) for the pay group'\n },\n name: {\n type: 'string',\n description: 'Name of the pay group'\n },\n pay_frequencies: {\n type: 'array',\n description: 'List of pay frequencies associated with this pay group',\n items: {\n type: 'string',\n enum: [ 'annually',\n 'bi_weekly',\n 'daily',\n 'monthly',\n 'other',\n 'quarterly',\n 'semi_annually',\n 'semi_monthly',\n 'weekly'\n ]\n }\n }\n },\n required: [ 'id',\n 'name',\n 'pay_frequencies'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - type: 'string', - }, - }, - individual_id: { - type: 'string', - }, - pay_frequencies: { - type: 'array', - items: { - 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: [], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - const response = await client.payroll.payGroups.list(body).asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts b/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts deleted file mode 100644 index 4cb29e1b3..000000000 --- a/packages/mcp-server/src/tools/payroll/pay-groups/retrieve-payroll-pay-groups.ts +++ /dev/null @@ -1,63 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'payroll.pay_groups', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/employer/pay-groups/{pay_group_id}', - operationId: 'get-pay-group', -}; - -export const tool: Tool = { - name: 'retrieve_payroll_pay_groups', - 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\nRead information from a single pay group\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/pay_group_retrieve_response',\n $defs: {\n pay_group_retrieve_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Finch id (uuidv4) for the pay group'\n },\n individual_ids: {\n type: 'array',\n items: {\n type: 'string',\n description: 'Finch id (uuidv4) for an individual assigned to this pay group'\n }\n },\n name: {\n type: 'string',\n description: 'Name of the pay group'\n },\n pay_frequencies: {\n type: 'array',\n description: 'List of pay frequencies associated with this pay group',\n items: {\n type: 'string',\n enum: [ 'annually',\n 'bi_weekly',\n 'daily',\n 'monthly',\n 'other',\n 'quarterly',\n 'semi_annually',\n 'semi_monthly',\n 'weekly'\n ]\n }\n }\n },\n required: [ 'id',\n 'individual_ids',\n 'name',\n 'pay_frequencies'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - pay_group_id: { - type: 'string', - }, - entity_ids: { - type: 'array', - description: "The entity IDs to specify which entities' data to access.", - items: { - 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: ['pay_group_id'], - }, - annotations: { - readOnlyHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { pay_group_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.payroll.payGroups.retrieve(pay_group_id, body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/providers/list-providers.ts b/packages/mcp-server/src/tools/providers/list-providers.ts deleted file mode 100644 index f317254c2..000000000 --- a/packages/mcp-server/src/tools/providers/list-providers.ts +++ /dev/null @@ -1,52 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'providers', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/providers', - operationId: 'get-providers', -}; - -export const tool: Tool = { - name: 'list_providers', - 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\nReturn details on all available payroll and HR systems.\n\n# Response Schema\n```json\n{\n type: 'array',\n items: {\n $ref: '#/$defs/provider_list_response'\n },\n $defs: {\n provider_list_response: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'The id of the payroll provider used in Connect.'\n },\n display_name: {\n type: 'string',\n description: 'The display name of the payroll provider.'\n },\n products: {\n type: 'array',\n description: 'The list of Finch products supported on this payroll provider.',\n items: {\n type: 'string'\n }\n },\n authentication_methods: {\n type: 'array',\n description: 'The authentication methods supported by the provider.',\n items: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n description: 'The type of authentication method',\n enum: [ 'assisted',\n 'credential',\n 'api_token',\n 'api_credential',\n 'oauth',\n 'api'\n ]\n },\n benefits_support: {\n type: 'object',\n description: 'The supported benefit types and their configurations',\n additionalProperties: true\n },\n supported_fields: {\n type: 'object',\n description: 'The supported fields for each Finch product',\n additionalProperties: true\n }\n },\n required: [ 'type'\n ]\n }\n },\n beta: {\n type: 'boolean',\n description: '`true` if the integration is in a beta state, `false` otherwise'\n },\n icon: {\n type: 'string',\n description: 'The url to the official icon of the payroll provider.'\n },\n logo: {\n type: 'string',\n description: 'The url to the official logo of the payroll provider.'\n },\n manual: {\n type: 'boolean',\n description: '[DEPRECATED] Whether the Finch integration with this provider uses the Assisted Connect Flow by default. This field is now deprecated. Please check for a `type` of `assisted` in the `authentication_methods` field instead.'\n },\n mfa_required: {\n type: 'boolean',\n description: 'whether MFA is required for the provider.'\n },\n primary_color: {\n type: 'string',\n description: 'The hex code for the primary color of the payroll provider.'\n }\n },\n required: [ 'id',\n 'display_name',\n 'products'\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: Finch, args: Record | undefined) => { - const { jq_filter } = args as any; - const response = await client.providers.list().asResponse(); - try { - return asTextContentResult(await maybeFilter(jq_filter, await response.json())); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts b/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts deleted file mode 100644 index 9055907ad..000000000 --- a/packages/mcp-server/src/tools/request-forwarding/forward-request-forwarding.ts +++ /dev/null @@ -1,76 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'request_forwarding', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/forward', - operationId: 'post-forward', -}; - -export const tool: Tool = { - name: 'forward_request_forwarding', - 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\nThe Forward API allows you to make direct requests to an employment system. If Finch's unified API\ndoesn't have a data model that cleanly fits your needs, then Forward allows you to push or pull\ndata models directly against an integration's API.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/request_forwarding_forward_response',\n $defs: {\n request_forwarding_forward_response: {\n type: 'object',\n properties: {\n request: {\n type: 'object',\n description: 'An object containing details of your original forwarded request, for your ease of reference.',\n properties: {\n method: {\n type: 'string',\n description: 'The HTTP method that was specified for the forwarded request. Valid values include: `GET` , `POST` , `PUT` , `DELETE` , and `PATCH`.'\n },\n route: {\n type: 'string',\n description: 'The URL route path that was specified for the forwarded request.'\n },\n data: {\n anyOf: [ {\n type: 'string'\n },\n {\n type: 'object',\n additionalProperties: true\n }\n ],\n description: 'The body that was specified for the forwarded request.'\n },\n headers: {\n type: 'object',\n description: 'The HTTP headers that were specified for the forwarded request.',\n additionalProperties: true\n },\n params: {\n type: 'object',\n description: 'The query parameters that were specified for the forwarded request.',\n additionalProperties: true\n }\n },\n required: [ 'method',\n 'route'\n ]\n },\n statusCode: {\n type: 'integer',\n description: 'The HTTP status code of the forwarded request\\'s response, exactly received from the underlying integration\\'s API. This value will be returned as an integer.'\n },\n data: {\n type: 'string',\n description: 'A string representation of the HTTP response body of the forwarded request\\'s response received from the underlying integration\\'s API. This field may be null in the case where the upstream system\\'s response is empty.'\n },\n headers: {\n type: 'object',\n description: 'The HTTP headers of the forwarded request\\'s response, exactly as received from the underlying integration\\'s API.',\n additionalProperties: true\n }\n },\n required: [ 'request',\n 'statusCode'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - method: { - type: 'string', - description: - 'The HTTP method for the forwarded request. Valid values include: `GET` , `POST` , `PUT` , `DELETE` , and `PATCH`.', - }, - route: { - type: 'string', - description: - 'The URL route path for the forwarded request. This value must begin with a forward-slash ( / ) and may only contain alphanumeric characters, hyphens, and underscores.', - }, - data: { - type: 'string', - description: - 'The body for the forwarded request. This value must be specified as either a string or a valid JSON object.', - }, - params: { - type: 'object', - description: - 'The query parameters for the forwarded request. This value must be specified as a valid JSON object rather than a query string.', - additionalProperties: true, - }, - request_headers: { - type: 'object', - description: - 'The HTTP headers to include on the forwarded request. This value must be specified as an object of key-value pairs. Example: `{"Content-Type": "application/xml", "X-API-Version": "v1" }`', - additionalProperties: 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: ['method', 'route'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.requestForwarding.forward(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts b/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts deleted file mode 100644 index 4f6036205..000000000 --- a/packages/mcp-server/src/tools/sandbox/company/update-sandbox-company.ts +++ /dev/null @@ -1,192 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.company', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/sandbox/company', - operationId: 'put-sandbox-company', -}; - -export const tool: Tool = { - name: 'update_sandbox_company', - 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\nUpdate a sandbox company's data\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/company_update_response',\n $defs: {\n company_update_response: {\n type: 'object',\n properties: {\n accounts: {\n type: 'array',\n description: 'An array of bank account objects associated with the payroll/HRIS system.',\n items: {\n type: 'object',\n properties: {\n account_name: {\n type: 'string',\n description: 'The name of the bank associated in the payroll/HRIS system.'\n },\n account_number: {\n type: 'string',\n description: '10-12 digit number to specify the bank account'\n },\n account_type: {\n type: 'string',\n description: 'The type of bank account.',\n enum: [ 'checking',\n 'savings'\n ]\n },\n institution_name: {\n type: 'string',\n description: 'Name of the banking institution.'\n },\n routing_number: {\n type: 'string',\n description: 'A nine-digit code that\\'s based on the U.S. Bank location where your account was opened.'\n }\n }\n }\n },\n departments: {\n type: 'array',\n description: 'The array of company departments.',\n items: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description: 'The department name.'\n },\n parent: {\n type: 'object',\n description: 'The parent department, if present.',\n properties: {\n name: {\n type: 'string',\n description: 'The parent department\\'s name.'\n }\n }\n }\n }\n }\n },\n ein: {\n type: 'string',\n description: 'The employer identification number.'\n },\n entity: {\n type: 'object',\n description: 'The entity type object.',\n properties: {\n subtype: {\n type: 'string',\n description: 'The tax payer subtype of the company.',\n enum: [ 's_corporation',\n 'c_corporation',\n 'b_corporation'\n ]\n },\n type: {\n type: 'string',\n description: 'The tax payer type of the company.',\n enum: [ 'llc',\n 'lp',\n 'corporation',\n 'sole_proprietor',\n 'non_profit',\n 'partnership',\n 'cooperative'\n ]\n }\n }\n },\n legal_name: {\n type: 'string',\n description: 'The legal name of the company.'\n },\n locations: {\n type: 'array',\n items: {\n $ref: '#/$defs/location'\n }\n },\n primary_email: {\n type: 'string',\n description: 'The email of the main administrator on the account.'\n },\n primary_phone_number: {\n type: 'string',\n description: 'The phone number of the main administrator on the account. Format: E.164, with extension where applicable, e.g. `+NNNNNNNNNNN xExtension`'\n }\n },\n required: [ 'accounts',\n 'departments',\n 'ein',\n 'entity',\n 'legal_name',\n 'locations',\n 'primary_email',\n 'primary_phone_number'\n ]\n },\n location: {\n type: 'object',\n title: 'Location',\n properties: {\n city: {\n type: 'string',\n description: 'City, district, suburb, town, or village.'\n },\n country: {\n type: 'string',\n description: 'The 2-letter ISO 3166 country code.'\n },\n line1: {\n type: 'string',\n description: 'Street address or PO box.'\n },\n line2: {\n type: 'string',\n description: 'Apartment, suite, unit, or building.'\n },\n postal_code: {\n type: 'string',\n description: 'The postal code or zip code.'\n },\n state: {\n type: 'string',\n description: 'The state code.'\n },\n name: {\n type: 'string'\n },\n source_id: {\n type: 'string'\n }\n },\n required: [ 'city',\n 'country',\n 'line1',\n 'line2',\n 'postal_code',\n 'state'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - accounts: { - type: 'array', - description: 'An array of bank account objects associated with the payroll/HRIS system.', - items: { - type: 'object', - properties: { - account_name: { - type: 'string', - description: 'The name of the bank associated in the payroll/HRIS system.', - }, - account_number: { - type: 'string', - description: '10-12 digit number to specify the bank account', - }, - account_type: { - type: 'string', - description: 'The type of bank account.', - enum: ['checking', 'savings'], - }, - institution_name: { - type: 'string', - description: 'Name of the banking institution.', - }, - routing_number: { - type: 'string', - description: - "A nine-digit code that's based on the U.S. Bank location where your account was opened.", - }, - }, - }, - }, - departments: { - type: 'array', - description: 'The array of company departments.', - items: { - type: 'object', - properties: { - name: { - type: 'string', - description: 'The department name.', - }, - parent: { - type: 'object', - description: 'The parent department, if present.', - properties: { - name: { - type: 'string', - description: "The parent department's name.", - }, - }, - }, - }, - }, - }, - ein: { - type: 'string', - description: 'The employer identification number.', - }, - entity: { - type: 'object', - description: 'The entity type object.', - properties: { - subtype: { - type: 'string', - description: 'The tax payer subtype of the company.', - enum: ['s_corporation', 'c_corporation', 'b_corporation'], - }, - type: { - type: 'string', - description: 'The tax payer type of the company.', - enum: ['llc', 'lp', 'corporation', 'sole_proprietor', 'non_profit', 'partnership', 'cooperative'], - }, - }, - }, - legal_name: { - type: 'string', - description: 'The legal name of the company.', - }, - locations: { - type: 'array', - items: { - $ref: '#/$defs/location', - }, - }, - primary_email: { - type: 'string', - description: 'The email of the main administrator on the account.', - }, - primary_phone_number: { - type: 'string', - description: - 'The phone number of the main administrator on the account. Format: E.164, with extension where applicable, e.g. `+NNNNNNNNNNN xExtension`', - }, - 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: [ - 'accounts', - 'departments', - 'ein', - 'entity', - 'legal_name', - 'locations', - 'primary_email', - 'primary_phone_number', - ], - $defs: { - location: { - type: 'object', - title: 'Location', - properties: { - city: { - type: 'string', - description: 'City, district, suburb, town, or village.', - }, - country: { - type: 'string', - description: 'The 2-letter ISO 3166 country code.', - }, - line1: { - type: 'string', - description: 'Street address or PO box.', - }, - line2: { - type: 'string', - description: 'Apartment, suite, unit, or building.', - }, - postal_code: { - type: 'string', - description: 'The postal code or zip code.', - }, - state: { - type: 'string', - description: 'The state code.', - }, - name: { - type: 'string', - }, - source_id: { - type: 'string', - }, - }, - required: ['city', 'country', 'line1', 'line2', 'postal_code', 'state'], - }, - }, - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.company.update(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts b/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts deleted file mode 100644 index 310eb2f4f..000000000 --- a/packages/mcp-server/src/tools/sandbox/connections/accounts/create-connections-sandbox-accounts.ts +++ /dev/null @@ -1,71 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.connections.accounts', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/sandbox/connections/accounts', - operationId: 'post-sandbox-connections-accounts', -}; - -export const tool: Tool = { - name: 'create_connections_sandbox_accounts', - 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\nCreate a new account for an existing connection (company/provider pair)\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/account_create_response',\n $defs: {\n account_create_response: {\n type: 'object',\n properties: {\n access_token: {\n type: 'string'\n },\n account_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate a connection with an access token'\n },\n authentication_type: {\n type: 'string',\n title: 'AuthenticationType',\n enum: [ 'credential',\n 'api_token',\n 'oauth',\n 'assisted'\n ]\n },\n company_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate a connection with an access token'\n },\n connection_id: {\n type: 'string',\n description: 'The ID of the new connection'\n },\n products: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n provider_id: {\n type: 'string',\n description: 'The ID of the provider associated with the `access_token`'\n }\n },\n required: [ 'access_token',\n 'account_id',\n 'authentication_type',\n 'company_id',\n 'connection_id',\n 'products',\n 'provider_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - company_id: { - type: 'string', - }, - provider_id: { - type: 'string', - description: 'The provider associated with the `access_token`', - }, - authentication_type: { - type: 'string', - title: 'AuthenticationType', - enum: ['credential', 'api_token', 'oauth', 'assisted'], - }, - products: { - type: 'array', - description: - 'Optional, defaults to Organization products (`company`, `directory`, `employment`, `individual`)', - items: { - 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: ['company_id', 'provider_id'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.connections.accounts.create(body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts b/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts deleted file mode 100644 index c1f34d38e..000000000 --- a/packages/mcp-server/src/tools/sandbox/connections/accounts/update-connections-sandbox-accounts.ts +++ /dev/null @@ -1,62 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.connections.accounts', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/sandbox/connections/accounts', - operationId: 'put-sandbox-connections-accounts', -}; - -export const tool: Tool = { - name: 'update_connections_sandbox_accounts', - 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\nUpdate an existing sandbox account. Change the connection status to understand how the Finch API responds.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/account_update_response',\n $defs: {\n account_update_response: {\n type: 'object',\n properties: {\n account_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate a connection with an access token'\n },\n authentication_type: {\n type: 'string',\n title: 'AuthenticationType',\n enum: [ 'credential',\n 'api_token',\n 'oauth',\n 'assisted'\n ]\n },\n company_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate a connection with an access token'\n },\n products: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n provider_id: {\n type: 'string',\n description: 'The ID of the provider associated with the `access_token`'\n },\n connection_id: {\n type: 'string',\n description: 'The ID of the new connection'\n }\n },\n required: [ 'account_id',\n 'authentication_type',\n 'company_id',\n 'products',\n 'provider_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - connection_status: { - $ref: '#/$defs/connection_status_type', - }, - 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: [], - $defs: { - connection_status_type: { - type: 'string', - enum: ['pending', 'processing', 'connected', 'error_no_account_setup', 'error_permissions', 'reauth'], - }, - }, - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.connections.accounts.update(body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts b/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts deleted file mode 100644 index aaaceb5c9..000000000 --- a/packages/mcp-server/src/tools/sandbox/connections/create-sandbox-connections.ts +++ /dev/null @@ -1,69 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.connections', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/sandbox/connections', - operationId: 'post-sandbox-connections', -}; - -export const tool: Tool = { - name: 'create_sandbox_connections', - 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\nCreate a new connection (new company/provider pair) with a new account\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/connection_create_response',\n $defs: {\n connection_create_response: {\n type: 'object',\n properties: {\n access_token: {\n type: 'string'\n },\n account_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate a connection with an access token'\n },\n authentication_type: {\n type: 'string',\n title: 'AuthenticationType',\n enum: [ 'credential',\n 'api_token',\n 'oauth',\n 'assisted'\n ]\n },\n company_id: {\n type: 'string',\n description: '[DEPRECATED] Use `connection_id` to associate a connection with an access token'\n },\n connection_id: {\n type: 'string',\n description: 'The ID of the new connection'\n },\n products: {\n type: 'array',\n items: {\n type: 'string'\n }\n },\n provider_id: {\n type: 'string',\n description: 'The ID of the provider associated with the `access_token`.'\n },\n token_type: {\n type: 'string'\n }\n },\n required: [ 'access_token',\n 'account_id',\n 'authentication_type',\n 'company_id',\n 'connection_id',\n 'products',\n 'provider_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - provider_id: { - type: 'string', - description: 'The provider associated with the connection', - }, - authentication_type: { - type: 'string', - title: 'AuthenticationType', - enum: ['credential', 'api_token', 'oauth', 'assisted'], - }, - employee_size: { - type: 'integer', - description: - 'Optional: the size of the employer to be created with this connection. Defaults to 20. Note that if this is higher than 100, historical payroll data will not be generated, and instead only one pay period will be created.', - }, - products: { - type: 'array', - items: { - 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: ['provider_id'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.connections.create(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts b/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts deleted file mode 100644 index 69aa60342..000000000 --- a/packages/mcp-server/src/tools/sandbox/directory/create-sandbox-directory.ts +++ /dev/null @@ -1,319 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.directory', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/sandbox/directory', - operationId: 'post-sandbox-directory', -}; - -export const tool: Tool = { - name: 'create_sandbox_directory', - 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\nAdd new individuals to a sandbox company\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/directory_create_response',\n $defs: {\n directory_create_response: {\n type: 'array',\n description: 'The individuals which were created',\n items: {\n type: 'object',\n additionalProperties: true\n }\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - body: { - type: 'array', - description: - 'Array of individuals to create. Takes all combined fields from `/individual` and `/employment` endpoints. All fields are optional.', - items: { - type: 'object', - properties: { - class_code: { - type: 'string', - description: "Worker's compensation classification code for this employee", - }, - custom_fields: { - type: 'array', - description: - 'Custom fields for the individual. These are fields which are defined by the employer in the system. Custom fields are not currently supported for assisted connections.', - items: { - type: 'object', - properties: { - name: { - type: 'string', - }, - value: { - type: 'object', - additionalProperties: true, - }, - }, - }, - }, - department: { - type: 'object', - description: 'The department object.', - properties: { - name: { - type: 'string', - description: 'The name of the department associated with the individual.', - }, - }, - }, - dob: { - type: 'string', - title: 'Date', - }, - emails: { - type: 'array', - items: { - type: 'object', - properties: { - data: { - type: 'string', - }, - type: { - type: 'string', - enum: ['work', 'personal'], - }, - }, - }, - }, - employment: { - type: 'object', - description: 'The employment object.', - properties: { - subtype: { - type: 'string', - description: - 'The secondary employment type of the individual. Options: `full_time`, `part_time`, `intern`, `temp`, `seasonal` and `individual_contractor`.', - enum: ['full_time', 'intern', 'part_time', 'temp', 'seasonal', 'individual_contractor'], - }, - type: { - type: 'string', - description: 'The main employment type of the individual.', - enum: ['employee', 'contractor'], - }, - }, - }, - employment_status: { - type: 'string', - description: - 'The detailed employment status of the individual. Available options: `active`, `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`.', - enum: ['active', 'deceased', 'leave', 'onboarding', 'prehire', 'retired', 'terminated'], - }, - encrypted_ssn: { - type: 'string', - description: - "Social Security Number of the individual in **encrypted** format. This field is only available with the `ssn` scope enabled and the `options: { include: ['ssn'] }` param set in the body.", - }, - end_date: { - type: 'string', - title: 'Date', - }, - ethnicity: { - type: 'string', - description: 'The EEOC-defined ethnicity of the individual.', - enum: [ - 'asian', - 'white', - 'black_or_african_american', - 'native_hawaiian_or_pacific_islander', - 'american_indian_or_alaska_native', - 'hispanic_or_latino', - 'two_or_more_races', - 'decline_to_specify', - ], - }, - first_name: { - type: 'string', - description: 'The legal first name of the individual.', - }, - gender: { - type: 'string', - description: 'The gender of the individual.', - enum: ['female', 'male', 'other', 'decline_to_specify'], - }, - income: { - $ref: '#/$defs/income', - }, - income_history: { - type: 'array', - description: 'The array of income history.', - items: { - $ref: '#/$defs/income', - }, - }, - is_active: { - type: 'boolean', - description: '`true` if the individual an an active employee or contractor at the company.', - }, - last_name: { - type: 'string', - description: 'The legal last name of the individual.', - }, - latest_rehire_date: { - type: 'string', - title: 'Date', - }, - location: { - $ref: '#/$defs/location', - }, - manager: { - type: 'object', - description: 'The manager object representing the manager of the individual within the org.', - properties: { - id: { - type: 'string', - description: 'A stable Finch `id` (UUID v4) for an individual in the company.', - }, - }, - }, - middle_name: { - type: 'string', - description: 'The legal middle name of the individual.', - }, - phone_numbers: { - type: 'array', - items: { - type: 'object', - properties: { - data: { - type: 'string', - }, - type: { - type: 'string', - enum: ['work', 'personal'], - }, - }, - }, - }, - preferred_name: { - type: 'string', - description: 'The preferred name of the individual.', - }, - residence: { - $ref: '#/$defs/location', - }, - source_id: { - type: 'string', - description: "The source system's unique employment identifier for this individual", - }, - ssn: { - type: 'string', - description: - "Social Security Number of the individual. This field is only available with the `ssn` scope enabled and the `options: { include: ['ssn'] }` param set in the body. [Click here to learn more about enabling the SSN field](/developer-resources/Enable-SSN-Field).", - }, - start_date: { - type: 'string', - title: 'Date', - }, - title: { - type: 'string', - description: 'The current title of the individual.', - }, - }, - }, - }, - 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: [], - $defs: { - income: { - type: 'object', - title: 'Income', - description: - "The employee's income as reported by the provider. This may not always be annualized income, but may be in units of bi-weekly, semi-monthly, daily, etc, depending on what information the provider returns.", - properties: { - amount: { - type: 'integer', - description: 'The income amount in cents.', - }, - currency: { - type: 'string', - description: 'The currency code.', - }, - effective_date: { - type: 'string', - description: 'The date the income amount went into effect.', - format: 'date', - }, - unit: { - type: 'string', - description: - 'The income unit of payment. Options: `yearly`, `quarterly`, `monthly`, `semi_monthly`, `bi_weekly`, `weekly`, `daily`, `hourly`, and `fixed`.', - enum: [ - 'yearly', - 'quarterly', - 'monthly', - 'semi_monthly', - 'bi_weekly', - 'weekly', - 'daily', - 'hourly', - 'fixed', - ], - }, - }, - required: ['amount', 'currency', 'effective_date', 'unit'], - }, - location: { - type: 'object', - title: 'Location', - properties: { - city: { - type: 'string', - description: 'City, district, suburb, town, or village.', - }, - country: { - type: 'string', - description: 'The 2-letter ISO 3166 country code.', - }, - line1: { - type: 'string', - description: 'Street address or PO box.', - }, - line2: { - type: 'string', - description: 'Apartment, suite, unit, or building.', - }, - postal_code: { - type: 'string', - description: 'The postal code or zip code.', - }, - state: { - type: 'string', - description: 'The state code.', - }, - name: { - type: 'string', - }, - source_id: { - type: 'string', - }, - }, - required: ['city', 'country', 'line1', 'line2', 'postal_code', 'state'], - }, - }, - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.directory.create(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/employment/update-sandbox-employment.ts b/packages/mcp-server/src/tools/sandbox/employment/update-sandbox-employment.ts deleted file mode 100644 index 17cf07156..000000000 --- a/packages/mcp-server/src/tools/sandbox/employment/update-sandbox-employment.ts +++ /dev/null @@ -1,236 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.employment', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/sandbox/employment/{individual_id}', - operationId: 'put-sandbox-employment-individual_id', -}; - -export const tool: Tool = { - name: 'update_sandbox_employment', - description: 'Update sandbox employment', - inputSchema: { - type: 'object', - properties: { - individual_id: { - type: 'string', - }, - class_code: { - type: 'string', - description: "Worker's compensation classification code for this employee", - }, - custom_fields: { - type: 'array', - description: - 'Custom fields for the individual. These are fields which are defined by the employer in the system. Custom fields are not currently supported for assisted connections.', - items: { - type: 'object', - properties: { - name: { - type: 'string', - }, - value: { - type: 'object', - additionalProperties: true, - }, - }, - }, - }, - department: { - type: 'object', - description: 'The department object.', - properties: { - name: { - type: 'string', - description: 'The name of the department associated with the individual.', - }, - }, - }, - employment: { - type: 'object', - description: 'The employment object.', - properties: { - subtype: { - type: 'string', - description: - 'The secondary employment type of the individual. Options: `full_time`, `part_time`, `intern`, `temp`, `seasonal` and `individual_contractor`.', - enum: ['full_time', 'intern', 'part_time', 'temp', 'seasonal', 'individual_contractor'], - }, - type: { - type: 'string', - description: 'The main employment type of the individual.', - enum: ['employee', 'contractor'], - }, - }, - }, - employment_status: { - type: 'string', - description: - 'The detailed employment status of the individual. Available options: `active`, `deceased`, `leave`, `onboarding`, `prehire`, `retired`, `terminated`.', - enum: ['active', 'deceased', 'leave', 'onboarding', 'prehire', 'retired', 'terminated'], - }, - end_date: { - type: 'string', - title: 'Date', - }, - first_name: { - type: 'string', - description: 'The legal first name of the individual.', - }, - income: { - $ref: '#/$defs/income', - }, - income_history: { - type: 'array', - description: 'The array of income history.', - items: { - $ref: '#/$defs/income', - }, - }, - is_active: { - type: 'boolean', - description: '`true` if the individual an an active employee or contractor at the company.', - }, - last_name: { - type: 'string', - description: 'The legal last name of the individual.', - }, - latest_rehire_date: { - type: 'string', - title: 'Date', - }, - location: { - $ref: '#/$defs/location', - }, - manager: { - type: 'object', - description: 'The manager object representing the manager of the individual within the org.', - properties: { - id: { - type: 'string', - description: 'A stable Finch `id` (UUID v4) for an individual in the company.', - }, - }, - }, - middle_name: { - type: 'string', - description: 'The legal middle name of the individual.', - }, - source_id: { - type: 'string', - description: "The source system's unique employment identifier for this individual", - }, - start_date: { - type: 'string', - title: 'Date', - }, - title: { - type: 'string', - description: 'The current title of the individual.', - }, - }, - required: ['individual_id'], - $defs: { - income: { - type: 'object', - title: 'Income', - description: - "The employee's income as reported by the provider. This may not always be annualized income, but may be in units of bi-weekly, semi-monthly, daily, etc, depending on what information the provider returns.", - properties: { - amount: { - type: 'integer', - description: 'The income amount in cents.', - }, - currency: { - type: 'string', - description: 'The currency code.', - }, - effective_date: { - type: 'string', - description: 'The date the income amount went into effect.', - format: 'date', - }, - unit: { - type: 'string', - description: - 'The income unit of payment. Options: `yearly`, `quarterly`, `monthly`, `semi_monthly`, `bi_weekly`, `weekly`, `daily`, `hourly`, and `fixed`.', - enum: [ - 'yearly', - 'quarterly', - 'monthly', - 'semi_monthly', - 'bi_weekly', - 'weekly', - 'daily', - 'hourly', - 'fixed', - ], - }, - }, - required: ['amount', 'currency', 'effective_date', 'unit'], - }, - location: { - type: 'object', - title: 'Location', - properties: { - city: { - type: 'string', - description: 'City, district, suburb, town, or village.', - }, - country: { - type: 'string', - description: 'The 2-letter ISO 3166 country code.', - }, - line1: { - type: 'string', - description: 'Street address or PO box.', - }, - line2: { - type: 'string', - description: 'Apartment, suite, unit, or building.', - }, - postal_code: { - type: 'string', - description: 'The postal code or zip code.', - }, - state: { - type: 'string', - description: 'The state code.', - }, - name: { - type: 'string', - }, - source_id: { - type: 'string', - }, - }, - required: ['city', 'country', 'line1', 'line2', 'postal_code', 'state'], - }, - }, - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { individual_id, ...body } = args as any; - try { - return asTextContentResult(await client.sandbox.employment.update(individual_id, body)); - } catch (error) { - if (error instanceof Finch.APIError) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts b/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts deleted file mode 100644 index 7da561133..000000000 --- a/packages/mcp-server/src/tools/sandbox/individual/update-sandbox-individual.ts +++ /dev/null @@ -1,177 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.individual', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/sandbox/individual/{individual_id}', - operationId: 'put-sandbox-individual-individual_id', -}; - -export const tool: Tool = { - name: 'update_sandbox_individual', - 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\nUpdate sandbox individual\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/individual_update_response',\n $defs: {\n individual_update_response: {\n type: 'object',\n title: 'Individual',\n properties: {\n id: {\n type: 'string',\n description: 'A stable Finch `id` (UUID v4) for an individual in the company.'\n },\n dob: {\n type: 'string',\n title: 'Date'\n },\n emails: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n data: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'work',\n 'personal'\n ]\n }\n }\n }\n },\n encrypted_ssn: {\n type: 'string',\n description: 'Social Security Number of the individual in **encrypted** format. This field is only available with the `ssn` scope enabled and the `options: { include: [\\'ssn\\'] }` param set in the body.'\n },\n ethnicity: {\n type: 'string',\n description: 'The EEOC-defined ethnicity of the individual.',\n enum: [ 'asian',\n 'white',\n 'black_or_african_american',\n 'native_hawaiian_or_pacific_islander',\n 'american_indian_or_alaska_native',\n 'hispanic_or_latino',\n 'two_or_more_races',\n 'decline_to_specify'\n ]\n },\n first_name: {\n type: 'string',\n description: 'The legal first name of the individual.'\n },\n gender: {\n type: 'string',\n description: 'The gender of the individual.',\n enum: [ 'female',\n 'male',\n 'other',\n 'decline_to_specify'\n ]\n },\n last_name: {\n type: 'string',\n description: 'The legal last name of the individual.'\n },\n middle_name: {\n type: 'string',\n description: 'The legal middle name of the individual.'\n },\n phone_numbers: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n data: {\n type: 'string'\n },\n type: {\n type: 'string',\n enum: [ 'work',\n 'personal'\n ]\n }\n }\n }\n },\n preferred_name: {\n type: 'string',\n description: 'The preferred name of the individual.'\n },\n residence: {\n $ref: '#/$defs/location'\n },\n ssn: {\n type: 'string',\n description: 'Social Security Number of the individual. This field is only available with the `ssn` scope enabled and the `options: { include: [\\'ssn\\'] }` param set in the body. [Click here to learn more about enabling the SSN field](/developer-resources/Enable-SSN-Field).'\n }\n }\n },\n location: {\n type: 'object',\n title: 'Location',\n properties: {\n city: {\n type: 'string',\n description: 'City, district, suburb, town, or village.'\n },\n country: {\n type: 'string',\n description: 'The 2-letter ISO 3166 country code.'\n },\n line1: {\n type: 'string',\n description: 'Street address or PO box.'\n },\n line2: {\n type: 'string',\n description: 'Apartment, suite, unit, or building.'\n },\n postal_code: {\n type: 'string',\n description: 'The postal code or zip code.'\n },\n state: {\n type: 'string',\n description: 'The state code.'\n },\n name: {\n type: 'string'\n },\n source_id: {\n type: 'string'\n }\n },\n required: [ 'city',\n 'country',\n 'line1',\n 'line2',\n 'postal_code',\n 'state'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - individual_id: { - type: 'string', - }, - dob: { - type: 'string', - title: 'Date', - }, - emails: { - type: 'array', - items: { - type: 'object', - properties: { - data: { - type: 'string', - }, - type: { - type: 'string', - enum: ['work', 'personal'], - }, - }, - }, - }, - encrypted_ssn: { - type: 'string', - description: - "Social Security Number of the individual in **encrypted** format. This field is only available with the `ssn` scope enabled and the `options: { include: ['ssn'] }` param set in the body.", - }, - ethnicity: { - type: 'string', - description: 'The EEOC-defined ethnicity of the individual.', - enum: [ - 'asian', - 'white', - 'black_or_african_american', - 'native_hawaiian_or_pacific_islander', - 'american_indian_or_alaska_native', - 'hispanic_or_latino', - 'two_or_more_races', - 'decline_to_specify', - ], - }, - first_name: { - type: 'string', - description: 'The legal first name of the individual.', - }, - gender: { - type: 'string', - description: 'The gender of the individual.', - enum: ['female', 'male', 'other', 'decline_to_specify'], - }, - last_name: { - type: 'string', - description: 'The legal last name of the individual.', - }, - middle_name: { - type: 'string', - description: 'The legal middle name of the individual.', - }, - phone_numbers: { - type: 'array', - items: { - type: 'object', - properties: { - data: { - type: 'string', - }, - type: { - type: 'string', - enum: ['work', 'personal'], - }, - }, - }, - }, - preferred_name: { - type: 'string', - description: 'The preferred name of the individual.', - }, - residence: { - $ref: '#/$defs/location', - }, - ssn: { - type: 'string', - description: - "Social Security Number of the individual. This field is only available with the `ssn` scope enabled and the `options: { include: ['ssn'] }` param set in the body. [Click here to learn more about enabling the SSN field](/developer-resources/Enable-SSN-Field).", - }, - 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: ['individual_id'], - $defs: { - location: { - type: 'object', - title: 'Location', - properties: { - city: { - type: 'string', - description: 'City, district, suburb, town, or village.', - }, - country: { - type: 'string', - description: 'The 2-letter ISO 3166 country code.', - }, - line1: { - type: 'string', - description: 'Street address or PO box.', - }, - line2: { - type: 'string', - description: 'Apartment, suite, unit, or building.', - }, - postal_code: { - type: 'string', - description: 'The postal code or zip code.', - }, - state: { - type: 'string', - description: 'The state code.', - }, - name: { - type: 'string', - }, - source_id: { - type: 'string', - }, - }, - required: ['city', 'country', 'line1', 'line2', 'postal_code', 'state'], - }, - }, - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { individual_id, jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.individual.update(individual_id, body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts b/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts deleted file mode 100644 index 61aa1a249..000000000 --- a/packages/mcp-server/src/tools/sandbox/jobs/configuration/retrieve-jobs-sandbox-configuration.ts +++ /dev/null @@ -1,53 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.jobs.configuration', - operation: 'read', - tags: [], - httpMethod: 'get', - httpPath: '/sandbox/jobs/configuration', - operationId: 'get-sandbox-jobs-configuration', -}; - -export const tool: Tool = { - name: 'retrieve_jobs_sandbox_configuration', - 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\nGet configurations for sandbox jobs\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/configuration_retrieve_response',\n $defs: {\n configuration_retrieve_response: {\n type: 'array',\n items: {\n $ref: '#/$defs/sandbox_job_configuration'\n }\n },\n sandbox_job_configuration: {\n type: 'object',\n title: 'SandboxJobConfiguration',\n properties: {\n completion_status: {\n type: 'string',\n enum: [ 'complete',\n 'reauth_error',\n 'permissions_error',\n 'error'\n ]\n },\n type: {\n type: 'string',\n enum: [ 'data_sync_all'\n ]\n }\n },\n required: [ 'completion_status',\n 'type'\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: Finch, args: Record | undefined) => { - const { jq_filter } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.jobs.configuration.retrieve()), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts b/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts deleted file mode 100644 index 336b97af5..000000000 --- a/packages/mcp-server/src/tools/sandbox/jobs/configuration/update-jobs-sandbox-configuration.ts +++ /dev/null @@ -1,61 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.jobs.configuration', - operation: 'write', - tags: [], - httpMethod: 'put', - httpPath: '/sandbox/jobs/configuration', - operationId: 'put-sandbox-jobs-configuration', -}; - -export const tool: Tool = { - name: 'update_jobs_sandbox_configuration', - 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\nUpdate configurations for sandbox jobs\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/sandbox_job_configuration',\n $defs: {\n sandbox_job_configuration: {\n type: 'object',\n title: 'SandboxJobConfiguration',\n properties: {\n completion_status: {\n type: 'string',\n enum: [ 'complete',\n 'reauth_error',\n 'permissions_error',\n 'error'\n ]\n },\n type: {\n type: 'string',\n enum: [ 'data_sync_all'\n ]\n }\n },\n required: [ 'completion_status',\n 'type'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - completion_status: { - type: 'string', - enum: ['complete', 'reauth_error', 'permissions_error', 'error'], - }, - type: { - type: 'string', - enum: ['data_sync_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: ['completion_status', 'type'], - }, - annotations: { - idempotentHint: true, - }, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult( - await maybeFilter(jq_filter, await client.sandbox.jobs.configuration.update(body)), - ); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts b/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts deleted file mode 100644 index 562841dcc..000000000 --- a/packages/mcp-server/src/tools/sandbox/jobs/create-sandbox-jobs.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.jobs', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/sandbox/jobs', - operationId: 'post-sandbox-job', -}; - -export const tool: Tool = { - name: 'create_sandbox_jobs', - 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\nEnqueue a new sandbox job\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/job_create_response',\n $defs: {\n job_create_response: {\n type: 'object',\n properties: {\n allowed_refreshes: {\n type: 'integer',\n description: 'The number of allowed refreshes per hour (per hour, fixed window)'\n },\n job_id: {\n type: 'string',\n description: 'The id of the job that has been created.'\n },\n job_url: {\n type: 'string',\n description: 'The url that can be used to retrieve the job status'\n },\n remaining_refreshes: {\n type: 'integer',\n description: 'The number of remaining refreshes available (per hour, fixed window)'\n }\n },\n required: [ 'allowed_refreshes',\n 'job_id',\n 'job_url',\n 'remaining_refreshes'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - type: { - type: 'string', - description: 'The type of job to start. Currently the only supported type is `data_sync_all`', - enum: ['data_sync_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: ['type'], - }, - annotations: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.jobs.create(body))); - } catch (error) { - if (error instanceof Finch.APIError || isJqError(error)) { - return asErrorResult(error.message); - } - throw error; - } -}; - -export default { metadata, tool, handler }; diff --git a/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts b/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts deleted file mode 100644 index 6b8d3ed12..000000000 --- a/packages/mcp-server/src/tools/sandbox/payment/create-sandbox-payment.ts +++ /dev/null @@ -1,224 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { isJqError, maybeFilter } from '@tryfinch/finch-api-mcp/filtering'; -import { Metadata, asErrorResult, asTextContentResult } from '@tryfinch/finch-api-mcp/tools/types'; - -import { Tool } from '@modelcontextprotocol/sdk/types.js'; -import Finch from '@tryfinch/finch-api'; - -export const metadata: Metadata = { - resource: 'sandbox.payment', - operation: 'write', - tags: [], - httpMethod: 'post', - httpPath: '/sandbox/payment', - operationId: 'post-sandbox-payment', -}; - -export const tool: Tool = { - name: 'create_sandbox_payment', - 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\nAdd a new sandbox payment\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/payment_create_response',\n $defs: {\n payment_create_response: {\n type: 'object',\n properties: {\n pay_date: {\n type: 'string',\n description: 'The date of the payment.'\n },\n payment_id: {\n type: 'string',\n description: 'The ID of the payment.'\n }\n },\n required: [ 'pay_date',\n 'payment_id'\n ]\n }\n }\n}\n```", - inputSchema: { - type: 'object', - properties: { - end_date: { - type: 'string', - format: 'date', - }, - pay_statements: { - type: 'array', - description: 'Array of pay statements to include in the payment.', - items: { - type: 'object', - properties: { - individual_id: { - type: 'string', - }, - earnings: { - type: 'array', - items: { - type: 'object', - properties: { - amount: { - type: 'integer', - }, - hours: { - type: 'number', - }, - name: { - type: 'string', - }, - type: { - type: 'string', - enum: [ - 'bonus', - 'commission', - 'double_overtime', - 'other', - 'overtime', - 'pto', - 'reimbursement', - 'salary', - 'severance', - 'sick', - 'tips', - 'wage', - '1099', - ], - }, - }, - }, - }, - employee_deductions: { - type: 'array', - items: { - type: 'object', - properties: { - amount: { - type: 'integer', - }, - name: { - type: 'string', - description: 'The deduction name. Required when type is specified.', - }, - pre_tax: { - type: 'boolean', - }, - type: { - type: 'string', - enum: [ - '457', - '401k', - '401k_roth', - '401k_loan', - '403b', - '403b_roth', - '457_roth', - 'commuter', - 'custom_post_tax', - 'custom_pre_tax', - 'fsa_dependent_care', - 'fsa_medical', - 'hsa_post', - 'hsa_pre', - 's125_dental', - 's125_medical', - 's125_vision', - 'simple', - 'simple_ira', - ], - }, - }, - }, - }, - employer_contributions: { - type: 'array', - items: { - type: 'object', - properties: { - amount: { - type: 'integer', - }, - name: { - type: 'string', - description: 'The contribution name. Required when type is specified.', - }, - type: { - type: 'string', - enum: [ - '457', - '401k', - '401k_roth', - '401k_loan', - '403b', - '403b_roth', - '457_roth', - 'commuter', - 'custom_post_tax', - 'custom_pre_tax', - 'fsa_dependent_care', - 'fsa_medical', - 'hsa_post', - 'hsa_pre', - 's125_dental', - 's125_medical', - 's125_vision', - 'simple', - 'simple_ira', - ], - }, - }, - }, - }, - gross_pay: { - type: 'integer', - }, - net_pay: { - type: 'integer', - }, - payment_method: { - type: 'string', - enum: ['check', 'direct_deposit', 'other'], - }, - taxes: { - type: 'array', - items: { - type: 'object', - properties: { - amount: { - type: 'integer', - }, - employer: { - type: 'boolean', - }, - name: { - type: 'string', - }, - type: { - type: 'string', - enum: ['federal', 'fica', 'local', 'state'], - }, - }, - }, - }, - total_hours: { - type: 'number', - }, - type: { - type: 'string', - enum: ['off_cycle_payroll', 'one_time_payment', 'regular_payroll'], - }, - }, - required: ['individual_id'], - }, - }, - start_date: { - type: 'string', - format: 'date', - }, - 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: {}, -}; - -export const handler = async (client: Finch, args: Record | undefined) => { - const { jq_filter, ...body } = args as any; - try { - return asTextContentResult(await maybeFilter(jq_filter, await client.sandbox.payment.create(body))); - } catch (error) { - if (error instanceof Finch.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 317283d93..cdd3afa46 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 d6272f6c9..000000000 --- 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 08963af8c..000000000 --- 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 4d9b60ca3..532666a80 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 cfff24a2d..000000000 --- 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/version.ts b/src/version.ts index e1f023d32..ee13ebb30 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '7.0.0'; // x-release-please-version +export const VERSION = '8.0.0'; // x-release-please-version