Skip to content
Merged
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
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Welcome to the PatternFly MCP Server documentation. This guide is organized by u
### 🚀 Usage
- **[MCP Tools and Resources](./usage.md)**: Use built-in tools and resources like `searchPatternFlyDocs` and `usePatternFlyDocs`.
- **[Client Configuration](./usage.md)**: Configure the server for your environment.
- **[Troubleshooting](./usage.md#troubleshooting)**: Steps for common setup problems.

### 🛠️ Developer reference
- **[CLI Reference](./development.md#cli-usage)**: Reference of server options.
Expand Down
2 changes: 2 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ These are **first-step checks** for common setup problems, not full diagnostics.

> **Note on Operating Systems**: Our primary development and testing environments are **macOS and Linux**. While we provide instructions for **Windows**, these commands are run at your own discretion. If you are unsure, please verify them with your IT or system administrator before proceeding.

> **Agents**: PatternFly MCP server information is available internally through the `patternfly://context` MCP resource.

### 1. Verify Node.js Version
The PatternFly MCP server requires **Node.js 20 or higher**.

Expand Down
1 change: 1 addition & 0 deletions guidelines/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Agents should use these phrases as signals to consult specific documentation and
| **"review development guide"** | Review `docs/development.md` for CLI, API, and plugin authoring. |
| **"create an example tool plugin"** | Review `guidelines/agent_coding.md`, `docs/development.md`, `docs/examples/*`, and `src/*` for context, coding standards, and existing example formats. |
| **"add documentation links"** / **"add doc entries"** / **"register docs"** / **"update docs.json"** / **"contribute to docs.json"** | Follow `guidelines/skills/add-docs-links/SKILL.md`: docs.json format, duplicate check, raw URL confirmation, then run unit tests and update meta. |
| **"troubleshoot server"** / **"debug server"** | Review `docs/usage.md#troubleshooting` and the PatternFly MCP server resource `patternfly://context` |

## Guidelines Processing Order

Expand Down
10 changes: 6 additions & 4 deletions guidelines/agent_behaviors.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,17 @@ For a detailed overview of the system design and roadmap, see [docs/architecture
- **Confirmation Required**: Confirm success; summarize changes; explain impact; verify understanding.
- **Guidance Review Scope**: Unless the user explicitly asks, do not make recommendations on improving guidance if all you're asked to do is review guidance.
- **Environment Awareness**:
- Server execution requires **Node.js >= 20**.
- External tool plugins (`--tool`) require **Node.js >= 22** primarily for its robust **Permission Model** (`--experimental-permission`), which enables strict filesystem and network isolation.
- Always verify environment compatibility when proposing tools using modern Node.js features.
- Server and plugin execution requirements are defined in `package.json`.
- Always verify environment compatibility by checking `patternfly://context` or `package.json`.
- Proactively check for environment mismatches (e.g., Node.js version) if tools fail to load.
- **Security Context**:
- Default to `--plugin-isolation strict`.
- If a tool requires filesystem or network access beyond the sandbox, document the need for `--plugin-isolation none` explicitly.
- If a tool requires filesystem or network access beyond the sandbox, document the need for `--plugin-isolation none`.
- **Implicit Diagnostics**: If a tool call fails, the agent MUST proactively check `patternfly://context` to see if the user's environment meets requirements before requesting more technical details.
- Warn users when a proposed solution requires disabling isolation.
- **State Management**: Use `.agent/` directory for local guidance and state; maintain context; preserve session information.
- **Security Awareness**: Be mindful of path traversal and isolation levels when working with external tools and resource loading.
- **Troubleshooting Reference**: When encountering environment or runtime issues, consult the [Troubleshooting section in docs/usage.md](../docs/usage.md#troubleshooting) for common fixes such as Node.js upgrades, cache resets, and Windows-specific symlink issues.

## 3. Trigger-Based Workflows

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,8 @@
"bugs": {
"url": "https://github.com/patternfly/patternfly-mcp/issues"
},
"support": {
"url": "https://github.com/patternfly/patternfly-mcp/blob/main/docs/usage.md#troubleshooting"
},
"homepage": "https://github.com/patternfly/patternfly-mcp#readme"
}
6 changes: 5 additions & 1 deletion src/__tests__/__snapshots__/options.defaults.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ exports[`options defaults should return specific properties: defaults 1`] = `
"test": {},
},
"name": "@patternfly/patternfly-mcp",
"nodeEngine": ">=20.0.0",
"nodeVersion": 22,
"nodeVersionPreferred": 22,
"patternflyOptions": {
"availableResourceVersions": [
"6.0.0",
Expand Down Expand Up @@ -84,12 +86,14 @@ exports[`options defaults should return specific properties: defaults 1`] = `
"loadTimeoutMs": 5000,
},
"pluginIsolation": "strict",
"repoBugs": "https://github.com/patternfly/patternfly-mcp/issues",
"repoName": "patternfly-mcp",
"repoResources": {
"bugs": "https://github.com/patternfly/patternfly-mcp/issues",
"git": "git+https://github.com/patternfly/patternfly-mcp.git",
"homepage": "https://github.com/patternfly/patternfly-mcp#readme",
},
"repoSupport": "https://github.com/patternfly/patternfly-mcp/blob/main/docs/usage.md#troubleshooting",
"resourceMemoOptions": {
"default": {
"cacheLimit": 3,
Expand All @@ -112,7 +116,7 @@ exports[`options defaults should return specific properties: defaults 1`] = `

",
"serverInstanceOptions": {
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development.",
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development. Use patternfly://context for server environment and troubleshooting links if runtime issues occur.",
},
"stats": {
"reportIntervalMs": {
Expand Down
2 changes: 2 additions & 0 deletions src/__tests__/__snapshots__/server.helpers.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,5 @@ ipsum
3
true"
`;

exports[`stringJoin should join values, newline filtered empty 1`] = `""`;
16 changes: 8 additions & 8 deletions src/__tests__/__snapshots__/server.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ exports[`runServer should attempt to run server, create transport, connect, and
"resources": {},
"tools": {},
},
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development.",
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development. Use patternfly://context for server environment and troubleshooting links if runtime issues occur.",
},
],
],
Expand Down Expand Up @@ -256,7 +256,7 @@ exports[`runServer should attempt to run server, disable SIGINT handler: diagnos
"resources": {},
"tools": {},
},
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development.",
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development. Use patternfly://context for server environment and troubleshooting links if runtime issues occur.",
},
],
],
Expand Down Expand Up @@ -324,7 +324,7 @@ exports[`runServer should attempt to run server, enable SIGINT handler explicitl
"resources": {},
"tools": {},
},
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development.",
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development. Use patternfly://context for server environment and troubleshooting links if runtime issues occur.",
},
],
],
Expand Down Expand Up @@ -403,7 +403,7 @@ exports[`runServer should attempt to run server, register a tool: diagnostics 1`
"resources": {},
"tools": {},
},
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development.",
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development. Use patternfly://context for server environment and troubleshooting links if runtime issues occur.",
},
],
],
Expand Down Expand Up @@ -490,7 +490,7 @@ exports[`runServer should attempt to run server, register multiple tools: diagno
"resources": {},
"tools": {},
},
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development.",
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development. Use patternfly://context for server environment and troubleshooting links if runtime issues occur.",
},
],
],
Expand Down Expand Up @@ -566,7 +566,7 @@ exports[`runServer should attempt to run server, use custom options: diagnostics
"resources": {},
"tools": {},
},
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development.",
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development. Use patternfly://context for server environment and troubleshooting links if runtime issues occur.",
},
],
],
Expand Down Expand Up @@ -645,7 +645,7 @@ exports[`runServer should attempt to run server, use default tools, http: diagno
"resources": {},
"tools": {},
},
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development.",
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development. Use patternfly://context for server environment and troubleshooting links if runtime issues occur.",
},
],
],
Expand Down Expand Up @@ -727,7 +727,7 @@ exports[`runServer should attempt to run server, use default tools, stdio: diagn
"resources": {},
"tools": {},
},
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development.",
"instructions": "Use the PatternFly MCP when a user asks about: PatternFly, pf, pf docs, design tokens, design guidelines, accessibility, PatternFly components, and frontend development. Use patternfly://context for server environment and troubleshooting links if runtime issues occur.",
},
],
],
Expand Down
26 changes: 1 addition & 25 deletions src/__tests__/options.defaults.test.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,7 @@
import { DEFAULT_OPTIONS, getNodeMajorVersion } from '../options.defaults';
import { DEFAULT_OPTIONS } from '../options.defaults';

describe('options defaults', () => {
it('should return specific properties', () => {
expect(DEFAULT_OPTIONS).toMatchSnapshot('defaults');
});
});

describe('getNodeMajorVersion', () => {
it('should get the current Node.js version', () => {
// Purposeful failure in the event the process.versions.node value is not available
expect(getNodeMajorVersion()).not.toBe(0);
});

it.each([
{
description: 'number',
value: 1_000_000
},
{
description: 'string',
value: 'lorem ipsum'
},
{
description: 'null',
value: null
}
])('should handle basic failure, $description', ({ value }) => {
expect(getNodeMajorVersion(value as any)).toBe(0);
});
});
48 changes: 48 additions & 0 deletions src/__tests__/options.helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { getNodeMajorVersion } from '../options.helpers';

describe('getNodeMajorVersion', () => {
it('should get the current Node.js version', () => {
// Purposeful failure in the event the process.versions.node value is not available
expect(getNodeMajorVersion(process.versions.node)).not.toBe(0);
});

it.each([
{
description: 'number failure',
value: 1_000_000,
expected: 0
},
{
description: 'string',
value: 'lorem ipsum',
expected: 0
},
{
description: 'null failure',
value: null,
expected: 0
},
{
description: 'undefined failure',
value: undefined,
expected: 0
},
{
description: 'NaN failure',
value: NaN,
expected: 0
},
{
description: 'operators',
value: '<=20',
expected: 20
},
{
description: 'operators and semver',
value: '<=20.0.1',
expected: 20
}
])('should handle, $description', ({ value, expected }) => {
expect(getNodeMajorVersion(value as any)).toBe(expected);
});
});
17 changes: 10 additions & 7 deletions src/__tests__/resource.patternFlyContext.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { patternFlyContextResource } from '../resource.patternFlyContext';
import {
patternFlyContextResource,
resourceCallback
} from '../resource.patternFlyContext';
import { isPlainObject } from '../server.helpers';

describe('patternFlyContextResource', () => {
Expand All @@ -18,21 +21,21 @@ describe('patternFlyContextResource', () => {
});
});

describe('patternFlyContextResource, callback', () => {
describe('resourceCallback', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it.each([
{
description: 'default',
args: []
expected: 'Troubleshooting'
}
])('should return context content, $description', async ({ args }) => {
const [_name, _uri, _config, callback] = patternFlyContextResource();
const result = await callback(...args);
])('should return context content, $description', async ({ expected }) => {
const result = await resourceCallback(undefined as any);

expect(result.contents).toBeDefined();
expect(Object.keys(result.contents[0])).toEqual(['uri', 'mimeType', 'text']);
expect(Object.keys(result.contents[0] as any)).toEqual(['uri', 'mimeType', 'text']);
expect(result.contents[0]?.text).toContain(expected);
});
});
5 changes: 5 additions & 0 deletions src/__tests__/server.helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,11 @@ describe('stringJoin', () => {
description: 'newline filtered',
args: ['lorem', 'ipsum', 0, 1, 2, 3, true, false, null, undefined],
settings: { sep: '\n', filterFalsyValues: true }
},
{
description: 'newline filtered empty',
args: [false, null, undefined],
settings: { sep: '\n', filterFalsyValues: true }
}
])('should join values, $description', ({ args, settings }) => {
expect(stringJoin(args, settings)).toMatchSnapshot();
Expand Down
88 changes: 82 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,85 @@
#!/usr/bin/env node

import { main } from './index';
import packageJson from '../package.json';
import { getNodeMajorVersion } from './options.helpers';

main({ mode: 'cli' }).catch(error => {
// Use console.error, log.error requires initialization
console.error('Failed to start server:', error);
process.exit(1);
});
/**
* CLI entry point with early error catching for environment and load-time issues.
*/
const run = async (): Promise<void> => {
const appBugs = packageJson.bugs?.url;
const appName = packageJson.name;
const appSupport = packageJson.support?.url;
const appMinNodeMajorVersion = getNodeMajorVersion(packageJson.engines?.node);
const envNodeMajorVersion = getNodeMajorVersion(process.versions?.node || process.version);

// Exit the process on error.
const processExit = (message: string, error: unknown): never => {
const errorMsg = error instanceof Error ? error.message : error;
const msg = [message];

if (errorMsg) {
msg.push(String(errorMsg));
}

if (appSupport) {
msg.push(`For help, visit the Troubleshooting Guide:\n${appSupport}`);
}

if (appBugs) {
msg.push(`To report bugs:\n${appBugs}`);
}

const finalMsg = msg.filter(Boolean).join('\n\n').trim();

if (finalMsg) {
console.error(`\n${finalMsg}\n`);
}

process.exit(1);
};

// Node.js confirmations
if (!envNodeMajorVersion || !appMinNodeMajorVersion || envNodeMajorVersion < appMinNodeMajorVersion) {
let error;

if (!envNodeMajorVersion) {
// Environment not broadcasting version. Missing or falsy
error = new Error('Unable to determine environment Node.js version. Update Node.js and try again.');
} else if (!appMinNodeMajorVersion) {
// Options or package.json engine been modified. Missing or falsy
error = new Error('Unable to determine server engine Node.js version requirements. Confirm engine available.');
} else {
// Everything else
error = new Error(
`Node.js version ${envNodeMajorVersion} found but ${appMinNodeMajorVersion} or higher is required. Update Node.js and try again.`
);
}

processExit(`${appName} failed to start. Engine requirements not met.`, error);

// Unreachable, processExit exits. Kept for readability.
return;
}

let main: typeof import('./index').main;

try {
const module = await import('./index');

main = module.main;
} catch (error) {
processExit(`Failed to load ${appName}`, error);

// Unreachable, processExit exits. Kept for type satisfaction.
return;
}

try {
await main({ mode: 'cli' });
} catch (error) {
processExit(`${appName} encountered a runtime error`, error);
}
};

run();
Loading
Loading