Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/__tests__/__snapshots__/options.defaults.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

exports[`options defaults should return specific properties: defaults 1`] = `
{
"contextManagement": false,
"contextPath": "/",
"contextUrl": "file:///",
"docsPathSlug": "documentation:",
Expand Down Expand Up @@ -30,6 +31,10 @@ exports[`options defaults should return specific properties: defaults 1`] = `
"max": 256,
"min": 1,
},
"resourceSearches": {
"max": 15,
"min": 0,
},
"toolSearches": {
"max": 10,
"min": 0,
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ describe('runServer', () => {
it.each([
{
description: 'use default tools, stdio',
options: { name: 'test-server-1', version: '1.0.0' },
options: { name: 'test-server-1', version: '1.0.0', contextManagement: undefined },
tools: undefined,
transportMethod: MockStdioServerTransport
},
{
description: 'use default tools, http',
options: { name: 'test-server-2', version: '1.0.0', isHttp: true },
options: { name: 'test-server-2', version: '1.0.0', isHttp: true, contextManagement: false },
tools: undefined,
transportMethod: MockStartHttpTransport
},
Expand Down
45 changes: 35 additions & 10 deletions src/mcpSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
type ResourceMetadata,
type CompleteResourceTemplateCallback
} from '@modelcontextprotocol/sdk/server/mcp.js';
import { type Tool } from '@modelcontextprotocol/sdk/types.js';
import { type GlobalOptions } from './options';
import { listAllCombinations, listIncrementalCombinations, splitUri } from './server.helpers';

/**
* A tool registered with the MCP server.
*
* @note Use of `any` here is intentional as part of a pass-through policy around
* `inputSchema`. Input schemas are actually reconstructed as part of the
* @note Use of `any` here is intentional as part of this typing. This is part of a general
* pass-through policy around our SDK types.
* - `inputSchema`: Input schemas are actually reconstructed as part of the
* tools-as-plugins architecture to help guarantee that a minimal tool schema is
* always available and minimally valid.
*
Expand All @@ -20,15 +22,22 @@
* - `schema.description` `{string}`: Concise description of functionality for the tool.
* - `schema.inputSchema` `{*}`: Internally, a raw Zod schema. Externally, a JSON or raw Zod schema. External tools are
* converted to Zod for user convenience.
* 2. `handler` `{Function}`: Tool handler function for returning content.
* - `schema.annotations` `{Object}`: Optional annotations for the tool.
* 2. `handler` `{Function}`: Resource handler function for returning content.
* 3. `_config` `{Object}`: Internal Tool configuration.
* - `config.shouldRegister`: Optional callback to determine if the tool should be registered.
*/
type McpTool = [
name: string,
schema: {
description: string;
inputSchema: any;

Check warning on line 34 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (24.x)

Unexpected any. Specify a different type

Check warning on line 34 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (22.x)

Unexpected any. Specify a different type
annotations?: Tool['annotations'] | any;

Check warning on line 35 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (24.x)

Unexpected any. Specify a different type

Check warning on line 35 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (22.x)

Unexpected any. Specify a different type
},
handler: (arg?: unknown) => any | Promise<any>
handler: (arg?: unknown) => any | Promise<any>,

Check warning on line 37 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (24.x)

Unexpected any. Specify a different type

Check warning on line 37 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (24.x)

Unexpected any. Specify a different type

Check warning on line 37 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (22.x)

Unexpected any. Specify a different type

Check warning on line 37 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (22.x)

Unexpected any. Specify a different type
_config?: {
shouldRegister?: (options: GlobalOptions) => boolean | Promise<boolean>;
}
];

/**
Expand Down Expand Up @@ -85,19 +94,35 @@
/**
* A resource registered with the MCP server.
*
* 0. `name`: Registered name of the resource.
* 1. `uriOrTemplate`: URI string or template. {@link ResourceTemplate}
* 2. `config`: Resource configuration metadata. {@link ResourceMetadata}
* 3. `handler`: Resource handler function.
* 4. `metadata`: Optional **internal metadata** object, not used by the standard MCP SDK
* 0. `name` `{string}`: Registered name of the resource.
* 1. `uriOrTemplate` `{string}`: URI string or template. {@link ResourceTemplate}
* 2. `config` `{Object}`: Resource configuration metadata. {@link ResourceMetadata}
* 3. `handler` `{Function}`: Resource handler function.
* 4. `metadata` `{Object}`: Optional **internal metadata** object, not used by the standard MCP SDK
* resource registry. {@link McpResourceMetadata}
* 5. `_config` `{Object}`: Internal Resource configuration.
* - `_config.shouldRegister` `{Function|Promise}`: Optional callback to determine if the resource should be registered.
*
* @note Annotations help with prioritizing resources and help manage context. They contain 3 primary properties:
* - `priority`: A ranking from `0.0` to `1.0`. `1.0` being the highest priority, and `0.0` being the lowest.
* - `audience`: This can be `user` or `assistant`, possibly both.
* - `lastModified': an ISO 8601 formatted string, representing the last time the resource was modified, helps invalidate caches.
*
* How to assign a priority:
* - `Indexes`: A resource index for directory nav is generally higher `0.8` to `1.0`, it's an anchor
* point if the model needs a map.
* - `Dynamic resource templates`: A resource template that contains dynamic content is generally lower `0.3` to `0.5`,
* it's a placeholder for a resource, and can generally shift. It can also be reattained by calling again.
*/
type McpResource = [
name: string,
uriOrTemplate: string | ResourceTemplate,
config: ResourceMetadata,
handler: (...args: any[]) => any | Promise<any>,

Check warning on line 121 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (24.x)

Unexpected any. Specify a different type

Check warning on line 121 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (24.x)

Unexpected any. Specify a different type

Check warning on line 121 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (24.x)

Unexpected any. Specify a different type

Check warning on line 121 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (22.x)

Unexpected any. Specify a different type

Check warning on line 121 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (22.x)

Unexpected any. Specify a different type

Check warning on line 121 in src/mcpSdk.ts

View workflow job for this annotation

GitHub Actions / Integration-checks (22.x)

Unexpected any. Specify a different type
metadata?: McpResourceMetadata | undefined
metadata?: McpResourceMetadata | undefined,
_config?: {
shouldRegister?: (options: GlobalOptions) => boolean | Promise<boolean>;
}
];

/**
Expand Down
19 changes: 18 additions & 1 deletion src/options.defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { getNodeMajorVersion } from './options.helpers';
* @interface DefaultOptions
*
* @template TLogOptions The logging options type, defaulting to LoggingOptions.
* @property contextManagement - Strategy for managing agent context and response sizes, primarily within MCP tools.
* - 'false': Default standard text-heavy responses.
* - 'true': High-efficiency mode for MCP tools, using McpResource links.
* @property contextPath - Current working directory.
* @property contextUrl - Current working directory URL.
* @property docsPaths - List of allowed local documentation directories handled by `docsPathSlug`
Expand Down Expand Up @@ -49,6 +52,7 @@ import { getNodeMajorVersion } from './options.helpers';
* @property xhrFetch - XHR and Fetch options.
*/
interface DefaultOptions<TLogOptions = LoggingOptions> {
contextManagement: boolean;
contextPath: string;
contextUrl: string;
docsPaths: string[];
Expand Down Expand Up @@ -131,7 +135,8 @@ interface LoggingOptions {
* @interface MinMax
*
* @property urlString Minimum and maximum length for URL strings.
* @property toolSearches Minimum and maximum number of tool searches.
* @property resourceSearches Minimum and maximum number of resource results for searches.
* @property toolSearches Minimum and maximum number of tool results for searches.
* @property inputStrings Minimum and maximum length for input strings.
* @property docsToLoad Minimum and maximum number of docs to load.
*/
Expand All @@ -140,6 +145,10 @@ interface MinMax {
min: number;
max: number;
}
resourceSearches: {
min: number;
max: number;
}
toolSearches: {
min: number;
max: number;
Expand Down Expand Up @@ -319,12 +328,19 @@ const HTTP_OPTIONS: HttpOptions = {

/**
* Minimum and maximum ranges for various options.
*
* @note For resourceSearches you still have to take into account that for every result
* there could be multiple resources.
*/
const MIN_MAX: MinMax = {
urlString: {
min: 11,
max: 1500
},
resourceSearches: {
min: 0,
max: 15
},
toolSearches: {
min: 0,
max: 10
Expand Down Expand Up @@ -498,6 +514,7 @@ const PLUGIN_ISOLATION: DefaultOptions['pluginIsolation'][] = ['none', 'strict']
* @type {DefaultOptions} Default options object.
*/
const DEFAULT_OPTIONS: DefaultOptions = {
contextManagement: false,
contextPath: (process.env.NODE_ENV === 'local' && '/') || resolve(process.cwd()),
contextUrl: pathToFileURL((process.env.NODE_ENV === 'local' && '/') || resolve(process.cwd())).href,
docsPaths: [],
Expand Down
3 changes: 3 additions & 0 deletions src/options.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ const parseCliOptions = (
}
}
break;
case '--context-management':
result.contextManagement = true;
break;
}
};

Expand Down
4 changes: 3 additions & 1 deletion src/options.registry.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type McpToolCreator, type McpResourceCreator } from './mcpSdk';
import { searchPatternFlyTool } from './tool.searchPatternFly';
import { usePatternFlyDocsTool } from './tool.patternFlyDocs';
import { searchPatternFlyDocsTool } from './tool.searchPatternFlyDocs';
import { patternFlyComponentsIndexResource } from './resource.patternFlyComponentsIndex';
Expand All @@ -15,7 +16,8 @@ import { patternFlySchemasTemplateResource } from './resource.patternFlySchemasT
*/
const builtinTools: McpToolCreator[] = [
usePatternFlyDocsTool,
searchPatternFlyDocsTool
searchPatternFlyDocsTool,
searchPatternFlyTool
];

/**
Expand Down
3 changes: 2 additions & 1 deletion src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ const SET_OPTIONS = {
docsPaths: defineOption({ cli: false })<DefaultOptions['docsPaths']>(),
name: defineOption({ cli: false })<string>(),
toolModules: defineOption({ cli: true })<DefaultOptions['toolModules']>(),
version: defineOption({ cli: false })<string>()
version: defineOption({ cli: false })<string>(),
contextManagement: defineOption({ cli: true, experimental: true })<DefaultOptions['contextManagement']>()
} as const;

/**
Expand Down
6 changes: 5 additions & 1 deletion src/resource.patternFlyComponentsIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ const URI_DESCRIPTION = `Filter by PatternFly version and category. ${URI_TEMPLA
const CONFIG = {
title: 'PatternFly Components Index',
description: `A list of all PatternFly component names available for documentation retrieval. ${URI_DESCRIPTION}`,
mimeType: 'text/markdown'
mimeType: 'text/markdown',
annotations: {
priority: 0.9,
audience: ['assistant' as const]
}
};

/**
Expand Down
12 changes: 9 additions & 3 deletions src/resource.patternFlyContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ const URI_TEMPLATE = 'patternfly://context';
const CONFIG = {
title: 'PatternFly Design System Context',
description: 'Information about the PatternFly design system and how to use this MCP server, including environment and troubleshooting information.',
mimeType: 'text/markdown'
mimeType: 'text/markdown',
annotations: {
priority: 0.5,
audience: ['assistant' as const]
}
};

/**
Expand All @@ -44,6 +48,7 @@ const resourceCallback = async (passedUri: URL, options = getOptions()) => {
options.repoBugs && `- **Report bugs:** ${options.repoBugs}`
);

const availableToolFunctions = options.contextManagement ? 'search, list and access' : 'search, fetch and display';
const context = `PatternFly is an open-source design system for building consistent, accessible user interfaces.

**What is PatternFly?**
Expand All @@ -57,13 +62,14 @@ PatternFly provides React components, design guidelines, and development tools f

**PatternFly MCP Server:**
This MCP server provides tools and resources to access all PatternFly documentation resources ranging from design to development.
- **MCP tools:** Can be used to search, fetch and display available documentation resources.
- **MCP resources:** Can be used to list, filter and display available documentation resources.
- **MCP tools:** Can be used to ${availableToolFunctions} available documentation resources.
- **MCP resources:** Can be used to list, filter, and read available documentation resources.

**Environment:**
- **MCP Server Mode:** ${options.mode}
- **MCP Server Version:** ${options.version || 'Unknown'}
- **Node.js Major Version:** ${options.nodeVersion || 'Unknown'}
- **Context Management:** ${options.contextManagement}

${(troubleshooting && stringJoin.newline('**Troubleshooting:**', troubleshooting)) || ''}
`;
Expand Down
6 changes: 5 additions & 1 deletion src/resource.patternFlyDocsIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ const URI_DESCRIPTION = `Filter by PatternFly version, category, and section. ${
const CONFIG = {
title: 'PatternFly Documentation Index',
description: `A list of PatternFly documentation links including accessibility, components, charts, development, writing, and AI guidance files. ${URI_DESCRIPTION}`,
mimeType: 'text/markdown'
mimeType: 'text/markdown',
annotations: {
priority: 1.0,
audience: ['assistant' as const]
}
};

/**
Expand Down
6 changes: 5 additions & 1 deletion src/resource.patternFlyDocsTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ const URI_DESCRIPTION = `Filter by PatternFly version, category, and section. ${
const CONFIG = {
title: 'PatternFly Documentation Page',
description: `Retrieve specific PatternFly documentation by name or path. ${URI_DESCRIPTION}`,
mimeType: 'text/markdown'
mimeType: 'text/markdown',
annotations: {
priority: 0.4,
audience: ['assistant' as const]
}
};

/**
Expand Down
6 changes: 5 additions & 1 deletion src/resource.patternFlySchemasIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ const URI_DESCRIPTION = `Filter by PatternFly version and category. ${URI_TEMPLA
const CONFIG = {
title: 'PatternFly Component Schemas Index',
description: `A list of all PatternFly component names available for JSON Schema retrieval. ${URI_DESCRIPTION}`,
mimeType: 'text/markdown'
mimeType: 'text/markdown',
annotations: {
priority: 0.8,
audience: ['assistant' as const]
}
};

/**
Expand Down
6 changes: 5 additions & 1 deletion src/resource.patternFlySchemasTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ const URI_DESCRIPTION = `Filter by PatternFly version and category. ${URI_TEMPLA
const CONFIG = {
title: 'PatternFly Component Schema',
description: `Retrieve the JSON Schema for a specific PatternFly component by name. ${URI_DESCRIPTION}`,
mimeType: 'application/json'
mimeType: 'application/json',
annotations: {
priority: 0.3,
audience: ['assistant' as const]
}
};

/**
Expand Down
25 changes: 23 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,18 @@ interface ServerInstance {
*/
const registerServerResources = async (resources: McpResourceCreator[], server: McpServer, options = getOptions(), session = getSessionOptions()) => {
for (const resourceCreator of resources) {
const [name, uri, config, callback, metadata] = resourceCreator(options);
const [name, uri, config, callback, metadata, _config] = resourceCreator(options);

const shouldRegister = _config?.shouldRegister;

if (shouldRegister) {
const status = await shouldRegister(options);

if (!status) {
log.debug(`Skipping resource registration: ${name}`);
continue;
}
}

try {
registerResource(server, name, uri, config, (...args: unknown[]) =>
Expand Down Expand Up @@ -156,7 +167,17 @@ const registerServerResources = async (resources: McpResourceCreator[], server:
*/
const registerServerTools = async (tools: McpToolCreator[], server: McpServer, options = getOptions(), session = getSessionOptions()) => {
for (const toolCreator of tools) {
const [name, schema, callback] = toolCreator(options);
const [name, schema, callback, _config] = toolCreator(options);
const shouldRegister = _config?.shouldRegister;

if (shouldRegister) {
const status = await shouldRegister(options);

if (!status) {
log.debug(`Skipping tool registration: ${name}`);
continue;
}
}

// Do NOT normalize schemas here. This is by design and is a fallback check for malformed schemas.
const isZod = isZodSchema(schema?.inputSchema) || isZodRawShape(schema?.inputSchema);
Expand Down
11 changes: 10 additions & 1 deletion src/tool.patternFlyDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,18 @@ const usePatternFlyDocsTool = (options = getOptions()): McpTool => {
.optional().describe('The name of a PatternFly component or resource to fetch documentation for (e.g., "Button", "Table", "Writing")'),
version: z.enum(options.patternflyOptions.availableSearchVersions)
.optional().describe(`Filter results by a specific PatternFly version (e.g. ${options.patternflyOptions.availableSearchVersions.map(value => `"${value}"`).join(', ')})`)
},
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: true
}
},
callback
callback,
{
shouldRegister: opts => opts.contextManagement === false || opts.contextManagement === undefined
}
];
};

Expand Down
Loading
Loading