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
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,20 +201,26 @@ npx @modelcontextprotocol/inspector-cli \

## Programmatic usage (advanced)

The `runServer` function is exported via the package exports map. You can import it from the dedicated subpath or the package root.
The package provides programmatic access through the `start()` function (or `main()` as an alternative):

Example (ESM):
```typescript
import { start, main, type CliOptions } from '@patternfly/patternfly-mcp';

```js
// Prefer the public export subpath
import { runServer } from '@patternfly/patternfly-mcp/server';
// Use with default options (equivalent to CLI without flags)
await start();

// Or from the package root (index.ts re-exports it)
import { runServer } from '@patternfly/patternfly-mcp';
// Override CLI options programmatically
await start({ docsHost: true });

// Starts the MCP server and listens on stdio
await runServer();
// Server runs until interrupted (Ctrl+C)
// Multiple options can be overridden
await start({
docsHost: true,
// Future CLI options can be added here
});

// TypeScript users can use the CliOptions type for type safety
const options: Partial<CliOptions> = { docsHost: true };
await start(options);
```

## Returned content details
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"main": "dist/index.js",
"type": "module",
"exports": {
".": "./dist/index.js",
"./server": "./dist/server.js"
".": "./dist/index.js"
},
"bin": {
"patternfly-mcp": "dist/index.js",
Expand Down
92 changes: 91 additions & 1 deletion src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { main } from '../index';
import { main, start, type CliOptions } from '../index';
import { parseCliOptions, freezeOptions, type GlobalOptions } from '../options';
import { runServer } from '../server';

Expand Down Expand Up @@ -111,5 +111,95 @@ describe('main', () => {

expect(callOrder).toEqual(['parse', 'freeze', 'run']);
});

it('should merge programmatic options with CLI options', async () => {
const cliOptions = { docsHost: false };
const programmaticOptions = { docsHost: true };

mockParseCliOptions.mockReturnValue(cliOptions);

await main(programmaticOptions);

// Should merge CLI options with programmatic options (programmatic takes precedence)
expect(mockFreezeOptions).toHaveBeenCalledWith({ docsHost: true });
});

it('should work with empty programmatic options', async () => {
const cliOptions = { docsHost: true };

mockParseCliOptions.mockReturnValue(cliOptions);

await main({});

expect(mockFreezeOptions).toHaveBeenCalledWith({ docsHost: true });
});

it('should work with undefined programmatic options', async () => {
const cliOptions = { docsHost: false };

mockParseCliOptions.mockReturnValue(cliOptions);

await main();

expect(mockFreezeOptions).toHaveBeenCalledWith({ docsHost: false });
});
});

describe('start alias', () => {
let consoleErrorSpy: jest.SpyInstance;
let processExitSpy: jest.SpyInstance;

beforeEach(() => {
jest.clearAllMocks();

// Mock process.exit to prevent actual exit
processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);

// Mock console.error
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();

// Setup default mocks
mockParseCliOptions.mockReturnValue({ docsHost: false });
mockFreezeOptions.mockReturnValue({} as GlobalOptions);
mockRunServer.mockResolvedValue(undefined);
});

afterEach(() => {
consoleErrorSpy.mockRestore();
processExitSpy.mockRestore();
});

it('should be equivalent to main function', async () => {
const cliOptions = { docsHost: true };

mockParseCliOptions.mockReturnValue(cliOptions);

await start();

expect(mockParseCliOptions).toHaveBeenCalled();
expect(mockFreezeOptions).toHaveBeenCalledWith(cliOptions);
expect(mockRunServer).toHaveBeenCalled();
});

it('should accept programmatic options like main', async () => {
const cliOptions = { docsHost: false };
const programmaticOptions = { docsHost: true };

mockParseCliOptions.mockReturnValue(cliOptions);

await start(programmaticOptions);

expect(mockFreezeOptions).toHaveBeenCalledWith({ docsHost: true });
});
});

describe('type exports', () => {
it('should export CliOptions type', () => {
// This test ensures the type is properly exported
// TypeScript compilation will fail if the type is not available
const options: Partial<CliOptions> = { docsHost: true };

expect(options).toBeDefined();
});
});

17 changes: 11 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
#!/usr/bin/env node

import { freezeOptions, parseCliOptions } from './options';
import { freezeOptions, parseCliOptions, type CliOptions } from './options';
import { runServer } from './server';

/**
* Main function - CLI entry point
* Main function - CLI entry point with optional programmatic overrides
*
* @param programmaticOptions - Optional programmatic options that override CLI options
*/
const main = async (): Promise<void> => {
const main = async (programmaticOptions?: Partial<CliOptions>): Promise<void> => {
try {
// Temporary parse for CLI options until we move to yargs or commander
// Parse CLI options
const cliOptions = parseCliOptions();

// Merge programmatic options with CLI options (programmatic takes precedence)
const finalOptions = { ...cliOptions, ...programmaticOptions };

// Freeze options to prevent further changes
freezeOptions(cliOptions);
freezeOptions(finalOptions);

// Create and run the server
await runServer();
Expand All @@ -30,4 +35,4 @@ if (process.env.NODE_ENV !== 'local') {
});
}

export { main, runServer };
export { main, main as start, type CliOptions };
Loading