From a2b6127a8545ae7ca19df79507b3e3f20111c4c8 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Wed, 21 Jan 2026 17:56:51 -0500 Subject: [PATCH] fix: search, max string length --- .../__snapshots__/options.defaults.test.ts.snap | 1 + src/options.defaults.ts | 3 +++ src/resource.patternFlyDocsTemplate.ts | 7 +++++++ src/resource.patternFlySchemasTemplate.ts | 11 ++++++++++- src/tool.componentSchemas.ts | 9 ++++++++- src/tool.patternFlyDocs.ts | 9 ++++++++- src/tool.searchPatternFlyDocs.ts | 13 +++++++++++-- 7 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/__tests__/__snapshots__/options.defaults.test.ts.snap b/src/__tests__/__snapshots__/options.defaults.test.ts.snap index 1a7c00f..61d83bf 100644 --- a/src/__tests__/__snapshots__/options.defaults.test.ts.snap +++ b/src/__tests__/__snapshots__/options.defaults.test.ts.snap @@ -22,6 +22,7 @@ exports[`options defaults should return specific properties: defaults 1`] = ` "transport": "stdio", }, "maxDocsToLoad": 500, + "maxSearchLength": 256, "name": "@patternfly/patternfly-mcp", "nodeVersion": 22, "pfExternal": "https://raw.githubusercontent.com/patternfly/patternfly-org/fb05713aba75998b5ecf5299ee3c1a259119bd74/packages/documentation-site/patternfly-docs/content", diff --git a/src/options.defaults.ts b/src/options.defaults.ts index dcddfb1..8054cdf 100644 --- a/src/options.defaults.ts +++ b/src/options.defaults.ts @@ -18,6 +18,7 @@ import { type ToolModule } from './server.toolsUser'; * @property llmsFilesPath - Path to the LLMs files directory. * @property {LoggingOptions} logging - Logging options. * @property maxDocsToLoad - Maximum number of docs to load. + * @property maxSearchLength - Maximum length for search strings. * @property recommendedMaxDocsToLoad - Recommended maximum number of docs to load. * @property name - Name of the package. * @property nodeVersion - Node.js major version. @@ -52,6 +53,7 @@ interface DefaultOptions { llmsFilesPath: string; logging: TLogOptions; maxDocsToLoad: number; + maxSearchLength: number; recommendedMaxDocsToLoad: number; name: string; nodeVersion: number; @@ -368,6 +370,7 @@ const DEFAULT_OPTIONS: DefaultOptions = { llmsFilesPath: (process.env.NODE_ENV === 'local' && '/llms-files') || join(resolve(process.cwd()), 'llms-files'), logging: LOGGING_OPTIONS, maxDocsToLoad: 500, + maxSearchLength: 256, recommendedMaxDocsToLoad: 15, name: packageJson.name, nodeVersion: (process.env.NODE_ENV === 'local' && 22) || getNodeMajorVersion(), diff --git a/src/resource.patternFlyDocsTemplate.ts b/src/resource.patternFlyDocsTemplate.ts index 171a0e8..c15d05f 100644 --- a/src/resource.patternFlyDocsTemplate.ts +++ b/src/resource.patternFlyDocsTemplate.ts @@ -49,6 +49,13 @@ const patternFlyDocsTemplateResource = (options = getOptions()): McpResource => ); } + if (name.length > options.maxSearchLength) { + throw new McpError( + ErrorCode.InvalidParams, + `Resource name exceeds maximum length of ${options.maxSearchLength} characters.` + ); + } + const docResults = []; const docs = []; const { exactMatches, searchResults } = searchComponents.memo(name); diff --git a/src/resource.patternFlySchemasTemplate.ts b/src/resource.patternFlySchemasTemplate.ts index 350e7cf..ac051bf 100644 --- a/src/resource.patternFlySchemasTemplate.ts +++ b/src/resource.patternFlySchemasTemplate.ts @@ -2,6 +2,7 @@ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; import { componentNames as pfComponentNames } from '@patternfly/patternfly-component-schemas/json'; import { type McpResource } from './server'; +import { getOptions } from './options.context'; import { getComponentSchema } from './tool.patternFlyDocs'; import { searchComponents } from './tool.searchPatternFlyDocs'; @@ -32,9 +33,10 @@ const CONFIG = { /** * Resource creator for the component schemas template. * + * @param options - Global options * @returns {McpResource} The resource definition tuple */ -const patternFlySchemasTemplateResource = (): McpResource => [ +const patternFlySchemasTemplateResource = (options = getOptions()): McpResource => [ NAME, URI_TEMPLATE, CONFIG, @@ -48,6 +50,13 @@ const patternFlySchemasTemplateResource = (): McpResource => [ ); } + if (name.length > options.maxSearchLength) { + throw new McpError( + ErrorCode.InvalidParams, + `Resource name exceeds maximum length of ${options.maxSearchLength} characters.` + ); + } + const { exactMatches, searchResults } = searchComponents.memo(name, { names: pfComponentNames }); let result: ComponentSchema | undefined = undefined; diff --git a/src/tool.componentSchemas.ts b/src/tool.componentSchemas.ts index d4e21f2..7da0323 100644 --- a/src/tool.componentSchemas.ts +++ b/src/tool.componentSchemas.ts @@ -37,6 +37,13 @@ const componentSchemasTool = (options = getOptions()): McpTool => { ); } + if (componentName.length > options.maxSearchLength) { + throw new McpError( + ErrorCode.InvalidParams, + `Component name exceeds maximum length of ${options.maxSearchLength} characters.` + ); + } + // Use fuzzySearch with `isFuzzyMatch` to handle exact and intentional suggestions in one pass const results = fuzzySearch(componentName, componentNames, { maxDistance: 3, @@ -89,7 +96,7 @@ const componentSchemasTool = (options = getOptions()): McpTool => { Returns prop definitions, types, and validation rules. Use this for structured component metadata, not documentation.`, inputSchema: { - componentName: z.string().describe('Name of the PatternFly component (e.g., "Button", "Table")') + componentName: z.string().max(options.maxSearchLength).describe('Name of the PatternFly component (e.g., "Button", "Table")') } }, callback diff --git a/src/tool.patternFlyDocs.ts b/src/tool.patternFlyDocs.ts index ccf61b0..54c76ad 100644 --- a/src/tool.patternFlyDocs.ts +++ b/src/tool.patternFlyDocs.ts @@ -61,6 +61,13 @@ const usePatternFlyDocsTool = (options = getOptions()): McpTool => { ); } + if (isName && name.length > options.maxSearchLength) { + throw new McpError( + ErrorCode.InvalidParams, + `String "name" exceeds maximum length of ${options.maxSearchLength} characters.` + ); + } + const updatedUrlList = isUrlList ? urlList.slice(0, options.recommendedMaxDocsToLoad) : []; if (isUrlList && urlList.length > options.recommendedMaxDocsToLoad) { @@ -171,7 +178,7 @@ const usePatternFlyDocsTool = (options = getOptions()): McpTool => { `, inputSchema: { urlList: z.array(z.string()).max(options.recommendedMaxDocsToLoad).optional().describe(`The list of URLs to fetch the documentation from (max ${options.recommendedMaxDocsToLoad} at a time`), - name: z.string().optional().describe('The name of a PatternFly component to fetch documentation for (e.g., "Button", "Table")') + name: z.string().max(options.maxSearchLength).optional().describe('The name of a PatternFly component to fetch documentation for (e.g., "Button", "Table")') } }, callback diff --git a/src/tool.searchPatternFlyDocs.ts b/src/tool.searchPatternFlyDocs.ts index 1a89bf1..facbbeb 100644 --- a/src/tool.searchPatternFlyDocs.ts +++ b/src/tool.searchPatternFlyDocs.ts @@ -7,6 +7,7 @@ import { LAYOUT_DOCS } from './docs.layout'; import { CHART_DOCS } from './docs.chart'; import { getLocalDocs } from './docs.local'; import { fuzzySearch, type FuzzySearchResult } from './server.search'; +import { getOptions } from './options.context'; import { memo } from './server.caching'; import { stringJoin } from './server.helpers'; import { DEFAULT_OPTIONS } from './options.defaults'; @@ -194,9 +195,10 @@ searchComponents.memo = memo(searchComponents, DEFAULT_OPTIONS.toolMemoOptions.s * Searches for PatternFly component documentation URLs using fuzzy search. * Returns URLs only (does not fetch content). Use usePatternFlyDocs to fetch the actual content. * + * @param options - Optional configuration options (defaults to OPTIONS) * @returns MCP tool tuple [name, schema, callback] */ -const searchPatternFlyDocsTool = (): McpTool => { +const searchPatternFlyDocsTool = (options = getOptions()): McpTool => { const callback = async (args: any = {}) => { const { searchQuery } = args; @@ -207,6 +209,13 @@ const searchPatternFlyDocsTool = (): McpTool => { ); } + if (searchQuery.length > options.maxSearchLength) { + throw new McpError( + ErrorCode.InvalidParams, + `Search query exceeds ${options.maxSearchLength} character max length.` + ); + } + const { isSearchWildCardAll, searchResults } = searchComponents.memo(searchQuery, { allowWildCardAll: true }); if (!isSearchWildCardAll && searchResults.length === 0) { @@ -271,7 +280,7 @@ const searchPatternFlyDocsTool = (): McpTool => { - Documentation URLs that can be used with "usePatternFlyDocs" `, inputSchema: { - searchQuery: z.string().describe('Full or partial component name to search for (e.g., "button", "table", "*")') + searchQuery: z.string().max(options.maxSearchLength).describe('Full or partial component name to search for (e.g., "button", "table", "*")') } }, callback