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
14 changes: 14 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,20 @@ Basic code style guidelines are generally enforced by ESLint, but there are addi
- Typings exposed to consumers should always attempt to maintain consistency.
- Typings for tests are less of a focus than functionality checks.

#### Public API and deprecations

Public API changes use this lifecycle:

1. **Introduce** the replacement in a minor release (or a patch when appropriate).
2. **Deprecate** the old export with `@deprecated` JSDoc, example updates, and add a row to the deprecations table in [development.md](./docs/development.md#deprecations).
3. **Remove** deprecated exports only in a **major** release.

Deprecated APIs remain functional through minor releases. Do not remove them in minors unless the export was never public or keeping it poses a security or correctness risk.

Before each major release, audit `src/*.ts` and update deprecations that were announced in the prior cycle.

See [development.md](./docs/development.md#public-api-and-imports) for current deprecations, import paths and authoring guidance.

### Testing
Current testing is based on Jest.

Expand Down
32 changes: 25 additions & 7 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,15 +177,32 @@ const server: PfMcpInstance = await start({

### Public API and imports

To ensure stability and a predictable developer experience, this package currently enforces a strict public API. All supported programmatic functions and types are exported directly from the root entry point:
This package exposes a curated public API through `package.json` exports:

| Entry | Import | Purpose |
|---|---|---|
| Root | `@patternfly/patternfly-mcp` | `start()`, server instance types, programmatic options |
| Tools | `@patternfly/patternfly-mcp/tools` | `createMcpTool` and tool-authoring types for plugins and inline tools |

```typescript
import { start, type PfMcpInstance } from '@patternfly/patternfly-mcp';
import { createMcpTool, type ToolModule } from '@patternfly/patternfly-mcp/tools';
```

**Deep imports are not supported.** Accessing internal modules (e.g., `@patternfly/patternfly-mcp/dist/server`) is restricted by our package configuration. This "flattened" export strategy allows us to refactor internal code and move logic between files without impacting your programmatic integrations, as long as the root exports remain stable.
**Deep imports are not supported.** Accessing internal modules (e.g., `@patternfly/patternfly-mcp/dist/server`) is restricted by our package configuration. This export strategy allows us to refactor internal code without impacting supported integrations, as long as documented entry points remain stable.

If you require access to a type or utility that is not currently exported from a supported entry, please open an issue to discuss your use case.

#### Deprecations

If you require access to a type or utility that is not currently exported from the root, please open an issue to discuss your use case for extending the public API.
Deprecated APIs remain available through minor releases. **Removal is targeted for major releases** (semver). See [CONTRIBUTING.md](../CONTRIBUTING.md#public-api-and-deprecations).

Current consumer deprecations:

| Deprecated | Replacement (Use instead) | Removal |
|-------------------------------------|------------------------------------|-----------------|
| `createMcpTool` from the root entry | `@patternfly/patternfly-mcp/tools` | Planned **3.0** |
| `CliOptions` type alias | `PfMcpCliOptions` | Planned **3.0** |

### Server instance

Expand Down Expand Up @@ -269,7 +286,8 @@ Reference typings are exported from the package. The full listing can be found i
You can embed the MCP server inside your application using the `start()` function and provide **Tool Modules** directly.

```ts
import { start, createMcpTool, type PfMcpInstance, type ToolModule } from '@patternfly/patternfly-mcp';
import { start, type PfMcpInstance } from '@patternfly/patternfly-mcp';
import { createMcpTool, type ToolModule } from '@patternfly/patternfly-mcp/tools';

const echoTool: ToolModule = createMcpTool({
name: 'echoAMessage',
Expand Down Expand Up @@ -321,7 +339,7 @@ You can extend the server's capabilities by loading **Tool Plugins** at startup.

- **Node.js >= 22**: Loading external tool plugins (`--tool`) requires Node.js version 22 or higher due to the use of advanced process isolation and ESM module loading features.
- **ESM**: Plugins MUST be authored as ECMAScript Modules.
- **Dependency Resolution**: Plugins importing from `@patternfly/patternfly-mcp` require the package to be resolvable in the execution environment. This may require a local `npm install` in the plugin's directory or project root if the package is not available globally.
- **Dependency Resolution**: Plugins importing from `@patternfly/patternfly-mcp/tools` require the package to be resolvable in the execution environment. This may require a local `npm install` in the plugin's directory or project root if the package is not available globally.

### Security & isolation

Expand All @@ -339,7 +357,7 @@ We recommend using the `createMcpTool` helper to define tools. It ensures your t
#### Authoring a single tool module

```ts
import { createMcpTool } from '@patternfly/patternfly-mcp';
import { createMcpTool } from '@patternfly/patternfly-mcp/tools';

export default createMcpTool({
name: 'hello',
Expand All @@ -360,7 +378,7 @@ export default createMcpTool({
#### Authoring multiple tools in one module

```ts
import { createMcpTool } from '@patternfly/patternfly-mcp';
import { createMcpTool } from '@patternfly/patternfly-mcp/tools';

export default createMcpTool([
{ name: 'hi', description: 'Greets', inputSchema: {}, handler: () => ({ content: [{ type: 'text', text: 'hi' }] }) },
Expand Down
7 changes: 4 additions & 3 deletions docs/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ Examples should follow the basic guidelines:
2. Filenames are lowerCamelCased
3. Keep examples short; this is an introduction to the project
4. Examples are either JS or TS with ESM import/exports
5. Comments/annotations are used to explain key concepts
6. Examples are linted from the project's linting configs with
5. Tool plugins import `createMcpTool` from `@patternfly/patternfly-mcp/tools`; use the root entry for `start()` and server types
6. Comments/annotations are used to explain key concepts
7. Examples are linted from the project's linting configs with
- `npm run test:lint`
- `npm run test:types`
- `npm run test:spell-docs`
7. Examples are tested and can be run without errors
8. Examples are tested and can be run without errors
4 changes: 3 additions & 1 deletion docs/examples/embeddedInlineTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* inside your application with custom tools.
*/
// @ts-expect-error: Cannot find module '@patternfly/patternfly-mcp' - Remove this line if you're copying this example
import { start, createMcpTool, type PfMcpInstance, type PfMcpLogEvent, type PfMcpStats, type ToolModule } from '@patternfly/patternfly-mcp';
import { start, type PfMcpInstance, type PfMcpLogEvent, type PfMcpStats } from '@patternfly/patternfly-mcp';
// @ts-expect-error: Cannot find module '@patternfly/patternfly-mcp/tools' - Remove this line if you're copying this example
import { createMcpTool, type ToolModule } from '@patternfly/patternfly-mcp/tools';

/**
* Echo tool - A custom tool that echoes back the provided user message.
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/toolPluginGitStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* - Requires ESM default export.
*/
import { spawn } from 'node:child_process';
import { createMcpTool } from '@patternfly/patternfly-mcp';
import { createMcpTool } from '@patternfly/patternfly-mcp/tools';

/**
* Helper, execute a command using spawn with argument handling.
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/toolPluginHelloWorld.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* - JS support only. TypeScript is only supported for embedding the server.
* - Requires ESM default export.
*/
import { createMcpTool } from '@patternfly/patternfly-mcp';
import { createMcpTool } from '@patternfly/patternfly-mcp/tools';

export default createMcpTool({
name: 'helloWorld',
Expand Down
6 changes: 3 additions & 3 deletions guidelines/agent_coding.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ All tools and resources MUST follow the **Creator Pattern** for dependency injec
- **Options Injection Pattern**: Environment-dependent helpers should accept an optional `options` parameter that defaults to `getOptions()`. This allows for explicit dependency injection in tests while maintaining ergonomics via `AsyncLocalStorage` in production. Pure transforms should remain option-agnostic.
- **Internal Tools**: `(options = getOptions()): McpTool` -> Returns `[name, schema, handler]`.
- **Internal Resources**: `(options = getOptions()): McpResource` -> Returns `[name, uri, config, handler]`.
- **External Tool Plugins**: Authored using the `createMcpTool` helper with an object configuration, exported as `default`.
- **External Tool Plugins**: Authored with `createMcpTool` from `@patternfly/patternfly-mcp/tools`, using an object configuration, exported as `default`.
- **Testing**: Creators allow easy mocking: `const tool = usePatternFlyDocsTool(mockOptions)`.

### 2.2 Module Organization and Exports
Expand All @@ -78,7 +78,7 @@ All tools and resources MUST follow the **Creator Pattern** for dependency injec
External tool plugins should follow this basic structure:

```javascript
import { createMcpTool } from '@patternfly/patternfly-mcp';
import { createMcpTool } from '@patternfly/patternfly-mcp/tools';

export default createMcpTool({
name: 'myTool',
Expand Down Expand Up @@ -208,7 +208,7 @@ While the codebase emphasizes pragmatism, **public APIs require comprehensive JS
/**
* Exposed options for CLI use. A focused options interface.
*
* Alias of {@link CliOptions} (Internal type).
* Alias of {@link CliOptions} (Internal type). `CliOptions` is deprecated; use {@link PfMcpCliOptions}.
*/
type PfMcpCliOptions = CliOptions;

Expand Down
9 changes: 8 additions & 1 deletion jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export default {
'src/**/*.ts',
'!src/**/.*/**',
'!src/cli.ts',
'!src/declarations*'
'!src/declarations*',
'!src/server.workerEntry.ts'
],
coverageThreshold: {
global: {
Expand Down Expand Up @@ -62,6 +63,12 @@ export default {
]
}
},
{
displayName: 'package',
roots: ['<rootDir>/tests/package'],
testMatch: ['<rootDir>/tests/package/**/*.test.ts'],
...baseConfig
},
{
displayName: 'e2e',
roots: ['<rootDir>/tests/e2e'],
Expand Down
18 changes: 18 additions & 0 deletions jest.setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,24 @@ jest.mock('child_process', () => ({
execSync: (...args: unknown[]) => `<execSync>${JSON.stringify(args)}</execSync>`
}));

/**
* Note: Mock worker_threads to avoid issues with Worker in tests.
*
* Heads-up: If you think you need real worker_threads in a UNIT test, ask yourself:
* "Why are you using real worker threads in a unit test?"
*
* - Unit tests should be fast and deterministic; prefer the default mock.
* - If you truly need real workers, that likely belongs in tests/package or tests/e2e.
*
* Still certain you need real workers in a unit test? Choosing to `jest.unmock`
* signals non-trivial behavior. Your PR/work will be reviewed thoroughly, AND there
* is a higher likelihood you'll be asked to rethink the tests.
*
* @example
* jest.unmock('worker_threads');
*/
jest.mock('worker_threads');

/**
* Note: Mock pid-port to avoid ES module import issues in Jest
* - Returns undefined to simulate port is free (no process found)
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
"type": "module",
"imports": {
"~docsCatalog": "./src/docs.json",
"#toolsHost": "./dist/server.toolsHost.js"
"#toolsHost": "./dist/server.toolsHost.js",
"#workerEntry": "./dist/server.workerEntry.js"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./tools": {
"types": "./dist/tools.d.ts",
"default": "./dist/tools.js"
}
},
"bin": {
Expand All @@ -38,10 +43,11 @@
"test:audit-container": "npm run container:build && jest --selectProjects audit:container",
"test:ci": "npm test -- --coverage",
"test:dev": "npm test -- --watchAll",
"test:integration": "npm run build && NODE_OPTIONS='--experimental-vm-modules' jest --selectProjects e2e",
"test:integration": "npm run build && jest --selectProjects package && NODE_OPTIONS='--experimental-vm-modules' jest --selectProjects e2e",
"test:integration-dev": "npm run test:integration -- --watchAll",
"test:lint": "eslint .",
"test:lint-fix": "eslint . --fix",
"test:package": "npm run build && jest --selectProjects package",
"test:spell-docs": "cspell './README.md' './CONTRIBUTING.md' './GOVERNANCE.md' './SECURITY.md' './docs/**/*.md' './guidelines/**/*.md' './docs/**/*.ts' './docs/**/*.js' --config ./cspell.config.json --fail-fast",
"test:spell": "cspell './src/**/*.ts' './tests/**/*.ts' --exclude './src/**/*test*' --exclude './tests/**/*test*' --config ./cspell.config.json --fail-fast",
"test:types": "tsc --noEmit",
Expand Down
66 changes: 66 additions & 0 deletions src/__tests__/__snapshots__/server.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ exports[`runServer should allow server to be stopped, http stop server: diagnost
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down Expand Up @@ -77,6 +83,12 @@ exports[`runServer should allow server to be stopped, stdio stop server: diagnos
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down Expand Up @@ -139,6 +151,12 @@ exports[`runServer should attempt to run server, create transport, connect, and
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down Expand Up @@ -212,6 +230,12 @@ exports[`runServer should attempt to run server, disable SIGINT handler: diagnos
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down Expand Up @@ -280,6 +304,12 @@ exports[`runServer should attempt to run server, enable SIGINT handler explicitl
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down Expand Up @@ -353,6 +383,12 @@ exports[`runServer should attempt to run server, log warnings for experimental o
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down Expand Up @@ -441,6 +477,12 @@ exports[`runServer should attempt to run server, register a tool: diagnostics 1`
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down Expand Up @@ -522,6 +564,12 @@ exports[`runServer should attempt to run server, register multiple tools: diagno
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down Expand Up @@ -610,6 +658,12 @@ exports[`runServer should attempt to run server, use custom options: diagnostics
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down Expand Up @@ -683,6 +737,12 @@ exports[`runServer should attempt to run server, use default tools, http: diagno
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down Expand Up @@ -765,6 +825,12 @@ exports[`runServer should attempt to run server, use default tools, stdio: diagn
[
"No external tools loaded.",
],
[
"Registered collection: patternfly-docs",
],
[
"Registered collection: patternfly-component-schemas",
],
[
"Registered resource: patternfly-context",
],
Expand Down
Loading
Loading