diff --git a/docs/usage.md b/docs/usage.md index e757da52..27bd13e9 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -57,8 +57,8 @@ Fetch full documentation and component JSON schemas for specific PatternFly URLs #### ~~Tool: fetchDocs~~ (Removed) > "fetchDocs" has been integrated into "usePatternFlyDocs." -#### ~~Tool: componentSchemas~~ (Deprecated) -> "componentSchemas" has been integrated into "usePatternFlyDocs." +#### ~~Tool: componentSchemas~~ (Removed) +> "componentSchemas" has been integrated into "usePatternFlyDocs" and MCP resources. ## Built-in resources diff --git a/src/__tests__/__snapshots__/server.test.ts.snap b/src/__tests__/__snapshots__/server.test.ts.snap index 30b7d27b..b05a0869 100644 --- a/src/__tests__/__snapshots__/server.test.ts.snap +++ b/src/__tests__/__snapshots__/server.test.ts.snap @@ -48,9 +48,6 @@ exports[`runServer should allow server to be stopped, http stop server: diagnost [ "Registered tool: searchPatternFlyDocs", ], - [ - "Registered tool: componentSchemas", - ], [ "test-server server running on HTTP transport", ], @@ -113,9 +110,6 @@ exports[`runServer should allow server to be stopped, stdio stop server: diagnos [ "Registered tool: searchPatternFlyDocs", ], - [ - "Registered tool: componentSchemas", - ], [ "test-server server running on stdio transport", ], @@ -643,9 +637,6 @@ exports[`runServer should attempt to run server, use default tools, http: diagno [ "Registered tool: searchPatternFlyDocs", ], - [ - "Registered tool: componentSchemas", - ], [ "test-server-2 server running on HTTP transport", ], @@ -676,7 +667,6 @@ exports[`runServer should attempt to run server, use default tools, http: diagno "registerTool": [ "usePatternFlyDocs", "searchPatternFlyDocs", - "componentSchemas", ], } `; @@ -729,9 +719,6 @@ exports[`runServer should attempt to run server, use default tools, stdio: diagn [ "Registered tool: searchPatternFlyDocs", ], - [ - "Registered tool: componentSchemas", - ], [ "test-server-1 server running on stdio transport", ], @@ -762,7 +749,6 @@ exports[`runServer should attempt to run server, use default tools, stdio: diagn "registerTool": [ "usePatternFlyDocs", "searchPatternFlyDocs", - "componentSchemas", ], } `; diff --git a/src/__tests__/__snapshots__/tool.componentSchemas.test.ts.snap b/src/__tests__/__snapshots__/tool.componentSchemas.test.ts.snap deleted file mode 100644 index b4dd365f..00000000 --- a/src/__tests__/__snapshots__/tool.componentSchemas.test.ts.snap +++ /dev/null @@ -1,173 +0,0 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing - -exports[`componentSchemasTool should have a consistent return structure: structure 1`] = ` -{ - "callback": [Function], - "name": "componentSchemas", - "schema": true, -} -`; - -exports[`componentSchemasTool, callback should parse parameters, default 1`] = ` -{ - "content": [ - { - "text": "{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "title": "Button Props", - "description": "Props for the Button component", - "properties": { - "variant": { - "type": "string", - "enum": [ - "primary", - "secondary" - ] - }, - "size": { - "type": "string", - "enum": [ - "sm", - "md", - "lg" - ] - }, - "children": { - "type": "string", - "description": "Content rendered inside the button" - } - }, - "required": [ - "children" - ], - "additionalProperties": false -}", - "type": "text", - }, - ], -} -`; - -exports[`componentSchemasTool, callback should parse parameters, with lower case componentName 1`] = ` -{ - "content": [ - { - "text": "{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "title": "Button Props", - "description": "Props for the Button component", - "properties": { - "variant": { - "type": "string", - "enum": [ - "primary", - "secondary" - ] - }, - "size": { - "type": "string", - "enum": [ - "sm", - "md", - "lg" - ] - }, - "children": { - "type": "string", - "description": "Content rendered inside the button" - } - }, - "required": [ - "children" - ], - "additionalProperties": false -}", - "type": "text", - }, - ], -} -`; - -exports[`componentSchemasTool, callback should parse parameters, with trimmed componentName 1`] = ` -{ - "content": [ - { - "text": "{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "title": "Button Props", - "description": "Props for the Button component", - "properties": { - "variant": { - "type": "string", - "enum": [ - "primary", - "secondary" - ] - }, - "size": { - "type": "string", - "enum": [ - "sm", - "md", - "lg" - ] - }, - "children": { - "type": "string", - "description": "Content rendered inside the button" - } - }, - "required": [ - "children" - ], - "additionalProperties": false -}", - "type": "text", - }, - ], -} -`; - -exports[`componentSchemasTool, callback should parse parameters, with upper case componentName 1`] = ` -{ - "content": [ - { - "text": "{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "title": "Button Props", - "description": "Props for the Button component", - "properties": { - "variant": { - "type": "string", - "enum": [ - "primary", - "secondary" - ] - }, - "size": { - "type": "string", - "enum": [ - "sm", - "md", - "lg" - ] - }, - "children": { - "type": "string", - "description": "Content rendered inside the button" - } - }, - "required": [ - "children" - ], - "additionalProperties": false -}", - "type": "text", - }, - ], -} -`; diff --git a/src/__tests__/__snapshots__/tool.patternFlyDocs.test.ts.snap b/src/__tests__/__snapshots__/tool.patternFlyDocs.test.ts.snap index a4729c78..549f0931 100644 --- a/src/__tests__/__snapshots__/tool.patternFlyDocs.test.ts.snap +++ b/src/__tests__/__snapshots__/tool.patternFlyDocs.test.ts.snap @@ -19,17 +19,34 @@ exports[`usePatternFlyDocsTool, callback should attempt to parse parameters, wit exports[`usePatternFlyDocsTool, callback should have a specific markdown format: Button 1`] = ` [ { - "text": "# Content for components/ipsumButton.md + "text": "# Documentation for Button (v6) [Documentation] +Source: components/loremButton.md + +lorem documentation content + +--- + +# Content for components/ipsumButton.md Source: components/ipsumButton.md ipsum documentation content --- -# Content for components/loremButton.md -Source: components/loremButton.md - -lorem documentation content", +# Component Schema for button (v6) +This machine-readable JSON schema defines the component's props, types, and validation rules. +\`\`\`json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Button", + "type": "object", + "properties": { + "variant": { + "type": "string" + } + } +} +\`\`\`", "type": "text", }, ] diff --git a/src/__tests__/__snapshots__/tool.searchPatternFlyDocs.test.ts.snap b/src/__tests__/__snapshots__/tool.searchPatternFlyDocs.test.ts.snap index 046f671b..4cd4f980 100644 --- a/src/__tests__/__snapshots__/tool.searchPatternFlyDocs.test.ts.snap +++ b/src/__tests__/__snapshots__/tool.searchPatternFlyDocs.test.ts.snap @@ -8,36 +8,19 @@ exports[`searchPatternFlyDocsTool should have a consistent return structure: str } `; -exports[`searchPatternFlyDocsTool, callback should have a specific markdown format: tooltip 1`] = ` +exports[`searchPatternFlyDocsTool, callback should have a specific markdown format: Button 1`] = ` [ { - "text": "# Search results for PatternFly version "v6" and "button". Showing 2 exact matches. + "text": "# Search results for PatternFly version "v6" and "button". Showing 1 exact match. 1. **button**: "usePatternFlyDocs" resource parameter "name" and "URLs" - **Name**: button - **URLs**: - - [Button - (v6) - Design Guidelines for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/components/button/button.md) - - [Button - (v6) - Accessibility for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/components/accessibility/button/button.md) - - [Button - (v6) - Examples for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-react/476782a298df81cb78de7f3cd804f3ff8f33993c/packages/react-core/src/components/Button/examples/Button.md) + - [Button - (v6) - Design Guidelines](https://pf.org/button.md) - **Resources**: - **URI**: patternfly://docs/button?version=v6 - **JSON Schemas**: patternfly://schemas/button?version=v6 -2. **patterns**: - "usePatternFlyDocs" resource parameter "name" and "URLs" - - **Name**: patterns - - **URLs**: - - [Actions - (v6) - The best practices for designing processes that a user can trigger by clicking or selecting a UI element, such as a button or link.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/actions/actions.md) - - [Bulk selection - (v6) - A defined method for users to select or deselect multiple items within complex content views or data tables.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/bulk-selection/bulk-selection.md) - - [Card view - (v6) - A structured layout designed to display a grid of cards in a gallery, optimizing for browsing and interaction.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/card-view/card-view.md) - - [Component usage and behavior - (v6) - Guidance on how to choose between similar components and use them appropriately based on specific user contexts and use cases.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/usage-and-behavior.md) - - [Dashboard - (v6) - A highly customizable layout that serves as a high-level overview of key metrics or performance indicators.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/dashboard/dashboard.md) - - [Filters - (v6) - The design rules for implementing filtering mechanisms that allow users to narrow down content from large datasets or complex views.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/filters/filters.md) - - [Primary-detail - (v6) - A two-pane layout that shows a list of items and corresponding details for the currently selected item.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/primary-detail/primary-detail.md) - - [Status and severity - (v6) - Guidance on the consistent and accessible use of color and iconography to communicate status and severity across the UI.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/status-and-severity/status-and-severity.md) - - **Resources**: - - **URI**: patternfly://docs/patterns?version=v6 - --- @@ -50,3 +33,7 @@ exports[`searchPatternFlyDocsTool, callback should have a specific markdown form }, ] `; + +exports[`searchPatternFlyDocsTool, callback should parse parameters, exact match 1`] = `"# Search results for PatternFly version "v6" and "button". Showing 1 exact match."`; + +exports[`searchPatternFlyDocsTool, callback should parse parameters, wildcard search 1`] = `"# Search results for PatternFly version "v6" and "all" resources. Only showing the first 1 results. There are 50 potential match variations. Try searching with a more specific query."`; diff --git a/src/__tests__/tool.componentSchemas.test.ts b/src/__tests__/tool.componentSchemas.test.ts deleted file mode 100644 index 9d173cd4..00000000 --- a/src/__tests__/tool.componentSchemas.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { McpError } from '@modelcontextprotocol/sdk/types.js'; -import { componentSchemasTool } from '../tool.componentSchemas'; -import { isPlainObject } from '../server.helpers'; - -// Mock dependencies -jest.mock('../server.caching', () => ({ - memo: jest.fn(fn => fn) -})); - -describe('componentSchemasTool', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should have a consistent return structure', () => { - const tool = componentSchemasTool(); - - expect({ - name: tool[0], - schema: isPlainObject(tool[1]), - callback: tool[2] - }).toMatchSnapshot('structure'); - }); -}); - -describe('componentSchemasTool, callback', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it.each([ - { - description: 'default', - componentName: 'Button' - }, - { - description: 'with trimmed componentName', - componentName: ' Button ' - }, - { - description: 'with lower case componentName', - componentName: 'button' - }, - { - description: 'with upper case componentName', - componentName: 'BUTTON' - } - ])('should parse parameters, $description', async ({ componentName }) => { - const [_name, _schema, callback] = componentSchemasTool(); - const result = await callback({ componentName }); - - expect(result).toMatchSnapshot(); - }); - - it.each([ - { - description: 'with missing or undefined componentName', - error: 'Missing required parameter: componentName', - componentName: undefined - }, - { - description: 'with null componentName', - error: 'Missing required parameter: componentName', - componentName: null - }, - { - description: 'with empty componentName', - error: 'No similar components found', - componentName: '' - }, - { - description: 'with non-string componentName', - error: 'Missing required parameter: componentName', - componentName: 123 - }, - { - description: 'with non-existent component', - error: 'Component "NonExistentComponent" not found', - componentName: 'NonExistentComponent' - } - ])('should handle errors, $description', async ({ error, componentName }) => { - const [_name, _schema, callback] = componentSchemasTool(); - - await expect(callback({ componentName })).rejects.toThrow(McpError); - await expect(callback({ componentName })).rejects.toThrow(error); - }); -}); diff --git a/src/__tests__/tool.patternFlyDocs.test.ts b/src/__tests__/tool.patternFlyDocs.test.ts index 8903b6a6..9aa86ed9 100644 --- a/src/__tests__/tool.patternFlyDocs.test.ts +++ b/src/__tests__/tool.patternFlyDocs.test.ts @@ -1,15 +1,23 @@ import { McpError } from '@modelcontextprotocol/sdk/types.js'; -import { usePatternFlyDocsTool } from '../tool.patternFlyDocs'; import { processDocsFunction } from '../server.getResources'; +import { getPatternFlyComponentSchema, getPatternFlyMcpResources, setCategoryDisplayLabel } from '../patternFly.getResources'; +import { searchPatternFly } from '../patternFly.search'; import { isPlainObject } from '../server.helpers'; +import { usePatternFlyDocsTool } from '../tool.patternFlyDocs'; // Mock dependencies jest.mock('../server.getResources'); +jest.mock('../patternFly.getResources'); +jest.mock('../patternFly.search'); jest.mock('../server.caching', () => ({ memo: jest.fn(fn => fn) })); const mockProcessDocs = processDocsFunction as jest.MockedFunction; +const mockComponentSchema = getPatternFlyComponentSchema as jest.MockedFunction; +const mockGetResources = getPatternFlyMcpResources as jest.MockedFunction; +const mockSearch = searchPatternFly as jest.MockedFunction; +const mockSetCategoryLabel = setCategoryDisplayLabel as jest.MockedFunction; describe('usePatternFlyDocsTool', () => { beforeEach(() => { @@ -30,6 +38,21 @@ describe('usePatternFlyDocsTool', () => { describe('usePatternFlyDocsTool, callback', () => { beforeEach(() => { jest.clearAllMocks(); + + mockGetResources.mockResolvedValue({ + latestVersion: 'v6', + latestSchemasVersion: 'v6', + byPath: { + 'components/loremButton.md': { name: 'button', version: 'v6', displayName: 'Button' } + } + } as any); + + mockSearch.mockResolvedValue({ + exactMatches: [{ entries: [{ path: 'components/loremButton.md' }] }], + searchResults: [] + } as any); + + mockSetCategoryLabel.mockImplementation(entry => entry?.category || 'Documentation'); }); it.each([ @@ -139,6 +162,15 @@ describe('usePatternFlyDocsTool, callback', () => { } ] as any); + mockComponentSchema.mockResolvedValue({ + $schema: 'http://json-schema.org/draft-07/schema#', + title: 'Button', + type: 'object', + properties: { + variant: { type: 'string' } + } + } as any); + const [_name, _schema, callback] = usePatternFlyDocsTool(); const result = await callback({ name: 'button' }); diff --git a/src/__tests__/tool.searchPatternFlyDocs.test.ts b/src/__tests__/tool.searchPatternFlyDocs.test.ts index 0746c11e..caa3cb58 100644 --- a/src/__tests__/tool.searchPatternFlyDocs.test.ts +++ b/src/__tests__/tool.searchPatternFlyDocs.test.ts @@ -1,12 +1,19 @@ import { McpError } from '@modelcontextprotocol/sdk/types.js'; -import { searchPatternFlyDocsTool } from '../tool.searchPatternFlyDocs'; +import { searchPatternFly } from '../patternFly.search'; +import { getPatternFlyMcpResources } from '../patternFly.getResources'; import { isPlainObject } from '../server.helpers'; +import { searchPatternFlyDocsTool } from '../tool.searchPatternFlyDocs'; // Mock dependencies +jest.mock('../patternFly.search'); +jest.mock('../patternFly.getResources'); jest.mock('../server.caching', () => ({ memo: jest.fn(fn => fn) })); +const mockSearch = searchPatternFly as jest.MockedFunction; +const mockGetResources = getPatternFlyMcpResources as jest.MockedFunction; + describe('searchPatternFlyDocsTool', () => { beforeEach(() => { jest.clearAllMocks(); @@ -26,67 +33,56 @@ describe('searchPatternFlyDocsTool', () => { describe('searchPatternFlyDocsTool, callback', () => { beforeEach(() => { jest.clearAllMocks(); + + mockGetResources.mockResolvedValue({ + latestVersion: 'v6' + } as any); + + mockSearch.mockResolvedValue({ + isSearchWildCardAll: false, + exactMatches: [], + remainingMatches: [], + searchResults: [], + totalPotentialMatches: 0 + } as any); }); it.each([ { - description: 'default', - searchQuery: 'Button', - expected: '# Search results for PatternFly version "v6" and "Button". Showing' - }, - { - description: 'with trimmed componentName', - searchQuery: ' Button ', - expected: '# Search results for PatternFly version "v6" and " Button ". Showing' - }, - { - description: 'with lower case componentName', + description: 'exact match', searchQuery: 'button', - expected: '# Search results for PatternFly version "v6" and "button". Showing' - }, - { - description: 'with upper case componentName', - searchQuery: 'BUTTON', - expected: '# Search results for PatternFly version "v6" and "BUTTON". Showing' - }, - { - description: 'with explicit valid version', - searchQuery: 'Button', - version: 'v6', - expected: '# Search results for PatternFly version "v6" and "Button". Showing' - }, - { - description: 'with partial componentName', - searchQuery: 'ton', - expected: '# Search results for PatternFly version "v6" and "ton". Showing' - }, - { - description: 'with multiple words', - searchQuery: 'Button Card Table', - expected: '# Search results for PatternFly version "v6" and "Button Card Table". Showing' - }, - { - description: 'with made up componentName', - searchQuery: 'lorem ipsum dolor sit amet', - expected: 'No PatternFly resources found matching' + searchValue: { + totalPotentialMatches: 1 + } }, { - description: 'with "*" searchQuery all', + description: 'wildcard search', searchQuery: '*', - expected: '# Search results for PatternFly version "v6" and "all" resources. Only showing the first' - }, - { - description: 'with "all" searchQuery all', - searchQuery: 'ALL', - expected: '# Search results for PatternFly version "v6" and "all" resources. Only showing the first' + searchValue: { + isSearchWildCardAll: true, + totalPotentialMatches: 50 + } } - ])('should parse parameters, $description', async ({ searchQuery, version, expected }) => { + ])('should parse parameters, $description', async ({ searchValue, searchQuery }) => { + mockSearch.mockResolvedValue({ + isSearchWildCardAll: false, + exactMatches: [{ + name: 'button', + entries: [{ displayName: 'Button', version: 'v6', description: 'Docs', path: 'pf/button.md' }], + uri: 'patternfly://docs/button?version=v6', + uriSchemas: 'patternfly://schemas/button?version=v6' + }], + remainingMatches: [], + searchResults: [{ displayName: 'Button', version: 'v6', description: 'Docs', path: 'pf/button.md' }], + ...searchValue + } as any); + const [_name, _schema, callback] = searchPatternFlyDocsTool(); - const updatedParams = version ? { searchQuery, version } : { searchQuery }; - const result = await callback(updatedParams); - const firstLine = result.content[0].text.split('\n')[0]; + const result = await callback({ searchQuery }); - expect(firstLine).toContain(expected); + expect(mockSearch).toHaveBeenCalledTimes(1); + expect(result.content[0].text).toBeDefined(); + expect(result.content[0].text.split('\n')[0]).toMatchSnapshot(); }); it.each([ @@ -125,9 +121,27 @@ describe('searchPatternFlyDocsTool, callback', () => { }); it('should have a specific markdown format', async () => { + mockSearch.mockResolvedValue({ + isSearchWildCardAll: false, + exactMatches: [{ + name: 'button', + entries: [{ + displayName: 'Button', + version: 'v6', + description: 'Design Guidelines', + path: 'https://pf.org/button.md' + }], + uri: 'patternfly://docs/button?version=v6', + uriSchemas: 'patternfly://schemas/button?version=v6' + }], + remainingMatches: [], + searchResults: [{ displayName: 'Button', version: 'v6', description: 'Docs', path: 'pf/button.md' }], + totalPotentialMatches: 1 + } as any); + const [_name, _schema, callback] = searchPatternFlyDocsTool(); const result = await callback({ searchQuery: 'button' }); - expect(result.content).toMatchSnapshot('tooltip'); + expect(result.content).toMatchSnapshot('Button'); }); }); diff --git a/src/server.ts b/src/server.ts index 2fd1c2d9..ea058fee 100644 --- a/src/server.ts +++ b/src/server.ts @@ -9,7 +9,6 @@ import { registerResource } from './mcpSdk'; import { setMetaResources } from './server.resourceMeta'; import { usePatternFlyDocsTool } from './tool.patternFlyDocs'; import { searchPatternFlyDocsTool } from './tool.searchPatternFlyDocs'; -import { componentSchemasTool } from './tool.componentSchemas'; import { patternFlyComponentsIndexResource } from './resource.patternFlyComponentsIndex'; import { patternFlyContextResource } from './resource.patternFlyContext'; import { patternFlyDocsIndexResource } from './resource.patternFlyDocsIndex'; @@ -209,8 +208,7 @@ interface ServerInstance { */ const builtinTools: McpToolCreator[] = [ usePatternFlyDocsTool, - searchPatternFlyDocsTool, - componentSchemasTool + searchPatternFlyDocsTool ]; /** diff --git a/src/tool.componentSchemas.ts b/src/tool.componentSchemas.ts deleted file mode 100644 index b5d18f04..00000000 --- a/src/tool.componentSchemas.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { z } from 'zod'; -import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; -import { type McpTool } from './server'; -import { getOptions } from './options.context'; -import { - getPatternFlyComponentSchema, - getPatternFlyMcpResources, - type PatternFlyComponentSchema -} from './patternFly.getResources'; -import { searchPatternFly } from './patternFly.search'; - -/** - * componentSchemas tool function - * - * @deprecated - * - * Creates an MCP tool that retrieves JSON Schema for PatternFly React components. - * Uses fuzzy search to handle typos and case variations, with related fallback suggestions. - * - * @param options - Optional configuration options (defaults to OPTIONS) - * @returns MCP tool tuple [name, schema, callback] - */ -const componentSchemasTool = (options = getOptions()): McpTool => { - const callback = async (args: any = {}) => { - const { componentName } = args; - - if (typeof componentName !== 'string') { - throw new McpError( - ErrorCode.InvalidParams, - `Missing required parameter: componentName must be a string: ${componentName}` - ); - } - - if (componentName.length > options.minMax.inputStrings.max) { - throw new McpError( - ErrorCode.InvalidParams, - `Component name exceeds maximum length of ${options.minMax.inputStrings.max} characters.` - ); - } - - const { latestVersion } = await getPatternFlyMcpResources.memo(); - const { exactMatches, remainingMatches } = await searchPatternFly.memo( - componentName, - { version: latestVersion, section: 'components' }, - { maxDistance: 3, maxResults: 5 } - ); - - const exact = exactMatches.find(match => match.isSchemasAvailable === true); - - if (exact) { - let componentSchema: PatternFlyComponentSchema | undefined; - - try { - componentSchema = await getPatternFlyComponentSchema.memo(exact.item); - - if (componentSchema === undefined) { - throw new Error(`Component schema for "${exact.item}" doesn't exist.`); - } - } catch (error) { - throw new McpError( - ErrorCode.InternalError, - `Failed to fetch component schema: ${error}` - ); - } - - return { - content: [ - { - type: 'text', - text: JSON.stringify(componentSchema, null, 2) - } - ] - }; - } - - const suggestions = remainingMatches.map(result => result.item).slice(0, 3); - const suggestionMessage = suggestions.length - ? `Did you mean ${suggestions.map(suggestion => `"${suggestion}"`).join(', ')}?` - : 'No similar components found.'; - - throw new McpError( - ErrorCode.InvalidParams, - `Component "${componentName.trim()}" not found. ${suggestionMessage}` - ); - }; - - return [ - 'componentSchemas', - { - description: `[Deprecated: Use "usePatternFlyDocs" to retrieve component schemas from PatternFly documentation URLs.] - - Get JSON Schema for a PatternFly React component. - - Returns prop definitions, types, and validation rules. Use this for structured component metadata, not documentation.`, - inputSchema: { - componentName: z.string().max(options.minMax.inputStrings.max).describe('Name of the PatternFly component (e.g., "Button", "Table")') - } - }, - callback - ]; -}; - -/** - * A tool name, typically the first entry in the tuple. Used in logging and deduplication. - */ -componentSchemasTool.toolName = 'componentSchemas'; - -export { componentSchemasTool }; diff --git a/tests/e2e/__snapshots__/httpTransport.test.ts.snap b/tests/e2e/__snapshots__/httpTransport.test.ts.snap index 5f6a576b..46e46f49 100644 --- a/tests/e2e/__snapshots__/httpTransport.test.ts.snap +++ b/tests/e2e/__snapshots__/httpTransport.test.ts.snap @@ -90,7 +90,6 @@ This is a test document for mocking remote HTTP requests." exports[`Builtin tools, HTTP transport should expose expected tools and stable shape: tools 1`] = ` { "toolNames": [ - "componentSchemas", "searchPatternFlyDocs", "usePatternFlyDocs", ], @@ -104,3 +103,41 @@ exports[`Builtin tools, HTTP transport should initialize MCP session over HTTP 1 "version": "2024-11-05", } `; + +exports[`Builtin tools, HTTP transport should return expected markdown structure for search results: markdown 1`] = ` +"# Search results for PatternFly version "v6" and "button". Showing 2 exact matches. +1. **button**: + "usePatternFlyDocs" resource parameter "name" and "URLs" + - **Name**: button + - **URLs**: + - [Button - (v6) - Design Guidelines for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/components/button/button.md) + - [Button - (v6) - Accessibility for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/components/accessibility/button/button.md) + - [Button - (v6) - Examples for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-react/476782a298df81cb78de7f3cd804f3ff8f33993c/packages/react-core/src/components/Button/examples/Button.md) + - **Resources**: + - **URI**: patternfly://docs/button?version=v6 + - **JSON Schemas**: patternfly://schemas/button?version=v6 + +2. **patterns**: + "usePatternFlyDocs" resource parameter "name" and "URLs" + - **Name**: patterns + - **URLs**: + - [Actions - (v6) - The best practices for designing processes that a user can trigger by clicking or selecting a UI element, such as a button or link.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/actions/actions.md) + - [Bulk selection - (v6) - A defined method for users to select or deselect multiple items within complex content views or data tables.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/bulk-selection/bulk-selection.md) + - [Card view - (v6) - A structured layout designed to display a grid of cards in a gallery, optimizing for browsing and interaction.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/card-view/card-view.md) + - [Component usage and behavior - (v6) - Guidance on how to choose between similar components and use them appropriately based on specific user contexts and use cases.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/usage-and-behavior.md) + - [Dashboard - (v6) - A highly customizable layout that serves as a high-level overview of key metrics or performance indicators.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/dashboard/dashboard.md) + - [Filters - (v6) - The design rules for implementing filtering mechanisms that allow users to narrow down content from large datasets or complex views.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/filters/filters.md) + - [Primary-detail - (v6) - A two-pane layout that shows a list of items and corresponding details for the currently selected item.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/primary-detail/primary-detail.md) + - [Status and severity - (v6) - Guidance on the consistent and accessible use of color and iconography to communicate status and severity across the UI.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/status-and-severity/status-and-severity.md) + - **Resources**: + - **URI**: patternfly://docs/patterns?version=v6 + + + +--- + + +**Important**: + - Use the "usePatternFlyDocs" tool with the above names and URLs to fetch resource content. + - Use a search all ("*") to find all available resources." +`; diff --git a/tests/e2e/__snapshots__/stdioTransport.test.ts.snap b/tests/e2e/__snapshots__/stdioTransport.test.ts.snap index f5bc32ac..098cc273 100644 --- a/tests/e2e/__snapshots__/stdioTransport.test.ts.snap +++ b/tests/e2e/__snapshots__/stdioTransport.test.ts.snap @@ -98,13 +98,50 @@ Source: http://127.0.0.1:5010/notARealPath/README.md exports[`Builtin tools, STDIO should expose expected tools and stable shape 1`] = ` { "toolNames": [ - "componentSchemas", "searchPatternFlyDocs", "usePatternFlyDocs", ], } `; +exports[`Builtin tools, STDIO should return expected markdown structure for search results: markdown 1`] = ` +"# Search results for PatternFly version "v6" and "button". Showing 2 exact matches. +1. **button**: + "usePatternFlyDocs" resource parameter "name" and "URLs" + - **Name**: button + - **URLs**: + - [Button - (v6) - Design Guidelines for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/components/button/button.md) + - [Button - (v6) - Accessibility for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/components/accessibility/button/button.md) + - [Button - (v6) - Examples for the button component.](https://raw.githubusercontent.com/patternfly/patternfly-react/476782a298df81cb78de7f3cd804f3ff8f33993c/packages/react-core/src/components/Button/examples/Button.md) + - **Resources**: + - **URI**: patternfly://docs/button?version=v6 + - **JSON Schemas**: patternfly://schemas/button?version=v6 + +2. **patterns**: + "usePatternFlyDocs" resource parameter "name" and "URLs" + - **Name**: patterns + - **URLs**: + - [Actions - (v6) - The best practices for designing processes that a user can trigger by clicking or selecting a UI element, such as a button or link.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/actions/actions.md) + - [Bulk selection - (v6) - A defined method for users to select or deselect multiple items within complex content views or data tables.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/bulk-selection/bulk-selection.md) + - [Card view - (v6) - A structured layout designed to display a grid of cards in a gallery, optimizing for browsing and interaction.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/card-view/card-view.md) + - [Component usage and behavior - (v6) - Guidance on how to choose between similar components and use them appropriately based on specific user contexts and use cases.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/usage-and-behavior.md) + - [Dashboard - (v6) - A highly customizable layout that serves as a high-level overview of key metrics or performance indicators.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/dashboard/dashboard.md) + - [Filters - (v6) - The design rules for implementing filtering mechanisms that allow users to narrow down content from large datasets or complex views.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/filters/filters.md) + - [Primary-detail - (v6) - A two-pane layout that shows a list of items and corresponding details for the currently selected item.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/primary-detail/primary-detail.md) + - [Status and severity - (v6) - Guidance on the consistent and accessible use of color and iconography to communicate status and severity across the UI.](https://raw.githubusercontent.com/patternfly/patternfly-org/2d5fec39ddb8aa32ce78c9a63cdfc1653692b193/packages/documentation-site/patternfly-docs/content/patterns/status-and-severity/status-and-severity.md) + - **Resources**: + - **URI**: patternfly://docs/patterns?version=v6 + + + +--- + + +**Important**: + - Use the "usePatternFlyDocs" tool with the above names and URLs to fetch resource content. + - Use a search all ("*") to find all available resources." +`; + exports[`Logging should allow setting logging options, default 1`] = `[]`; exports[`Logging should allow setting logging options, stderr 1`] = ` @@ -138,8 +175,6 @@ exports[`Logging should allow setting logging options, stderr 1`] = ` "[INFO]: Registered tool: usePatternFlyDocs ", "[INFO]: Registered tool: searchPatternFlyDocs -", - "[INFO]: Registered tool: componentSchemas ", "[INFO]: @patternfly/patternfly-mcp server running on stdio transport ", diff --git a/tests/e2e/httpTransport.test.ts b/tests/e2e/httpTransport.test.ts index 67e1de2d..7873a123 100644 --- a/tests/e2e/httpTransport.test.ts +++ b/tests/e2e/httpTransport.test.ts @@ -129,6 +129,119 @@ describe('Builtin tools, HTTP transport', () => { expect(text).toMatchSnapshot(); await CLIENT.close(); }); + + it.each([ + { + description: 'exact match for Button', + searchQuery: 'Button', + contains: [ + '# Search results for PatternFly version "v6" and "Button". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'with trimmed query', + searchQuery: ' Button ', + contains: [ + '# Search results for PatternFly version "v6" and " Button ". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'with lower case match', + searchQuery: 'button', + contains: [ + '# Search results for PatternFly version "v6" and "button". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'with upper case match', + searchQuery: 'BUTTON', + contains: [ + '# Search results for PatternFly version "v6" and "BUTTON". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'wildcard search', + searchQuery: '*', + contains: [ + '# Search results for PatternFly version "v6" and "all" resources. Only showing the first', + '**a', + 'Use a search all' + ] + }, + { + description: 'fuzzy search for partial name', + searchQuery: 'ton', + contains: [ + '# Search results for PatternFly version "v6" and "ton". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'explicit version search', + searchQuery: 'Button', + version: 'v6', + contains: [ + '# Search results for PatternFly version "v6" and "Button". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'with multiple words', + searchQuery: 'Button Card Table', + contains: [ + '# Search results for PatternFly version "v6" and "Button Card Table". Showing', + '**button**', + '**card**', + '**table**', + 'Use a search all' + ] + }, + { + description: 'made up search query', + searchQuery: 'lorem ipsum dolor sit amet', + contains: [ + 'No PatternFly resources found matching "lorem ipsum dolor sit amet"', + 'Use a search all' + ] + } + ])('should perform searchPatternFlyDocs: $description', async ({ searchQuery, version, contains }) => { + const req = { + method: 'tools/call', + params: { + name: 'searchPatternFlyDocs', + arguments: version ? { searchQuery, version } : { searchQuery } + } + }; + + const response = await CLIENT?.send(req); + const text = response?.result?.content?.[0]?.text || ''; + + contains.forEach(item => expect(text).toContain(item)); + }); + + it('should return expected markdown structure for search results', async () => { + const response = await CLIENT?.send({ + method: 'tools/call', + params: { + name: 'searchPatternFlyDocs', + arguments: { searchQuery: 'button' } + } + }); + + const text = response?.result?.content?.[0]?.text || ''; + + expect(text).toMatchSnapshot('markdown'); + }); }); describe('Builtin resources, HTTP transport', () => { diff --git a/tests/e2e/stdioTransport.test.ts b/tests/e2e/stdioTransport.test.ts index 9ad4c574..8907206c 100644 --- a/tests/e2e/stdioTransport.test.ts +++ b/tests/e2e/stdioTransport.test.ts @@ -125,6 +125,119 @@ describe('Builtin tools, STDIO', () => { expect(text.includes('This is a generated offline fixture')).toBe(true); expect(text).toMatchSnapshot(); }); + + it.each([ + { + description: 'exact match for Button', + searchQuery: 'Button', + contains: [ + '# Search results for PatternFly version "v6" and "Button". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'with trimmed query', + searchQuery: ' Button ', + contains: [ + '# Search results for PatternFly version "v6" and " Button ". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'with lower case match', + searchQuery: 'button', + contains: [ + '# Search results for PatternFly version "v6" and "button". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'with upper case match', + searchQuery: 'BUTTON', + contains: [ + '# Search results for PatternFly version "v6" and "BUTTON". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'wildcard search', + searchQuery: '*', + contains: [ + '# Search results for PatternFly version "v6" and "all" resources. Only showing the first', + '**a', + 'Use a search all' + ] + }, + { + description: 'fuzzy search for partial name', + searchQuery: 'ton', + contains: [ + '# Search results for PatternFly version "v6" and "ton". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'explicit version search', + searchQuery: 'Button', + version: 'v6', + contains: [ + '# Search results for PatternFly version "v6" and "Button". Showing', + '**button**', + 'Use a search all' + ] + }, + { + description: 'with multiple words', + searchQuery: 'Button Card Table', + contains: [ + '# Search results for PatternFly version "v6" and "Button Card Table". Showing', + '**button**', + '**card**', + '**table**', + 'Use a search all' + ] + }, + { + description: 'made up search query', + searchQuery: 'lorem ipsum dolor sit amet', + contains: [ + 'No PatternFly resources found matching "lorem ipsum dolor sit amet"', + 'Use a search all' + ] + } + ])('should perform searchPatternFlyDocs: $description', async ({ searchQuery, version, contains }) => { + const req = { + method: 'tools/call', + params: { + name: 'searchPatternFlyDocs', + arguments: version ? { searchQuery, version } : { searchQuery } + } + }; + + const response = await CLIENT.send(req); + const text = response?.result?.content?.[0]?.text || ''; + + contains.forEach(item => expect(text).toContain(item)); + }); + + it('should return expected markdown structure for search results', async () => { + const response = await CLIENT.send({ + method: 'tools/call', + params: { + name: 'searchPatternFlyDocs', + arguments: { searchQuery: 'button' } + } + }); + + const text = response?.result?.content?.[0]?.text || ''; + + expect(text).toMatchSnapshot('markdown'); + }); }); describe('Builtin resources, STDIO', () => {