Skip to content

Commit ad96c74

Browse files
authored
feat: programmatic option override (#5)
* index, expose convenience method, allow options override * test, check merging options, confirm exports defined
1 parent c1e13eb commit ad96c74

File tree

4 files changed

+119
-19
lines changed

4 files changed

+119
-19
lines changed

README.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -201,20 +201,26 @@ npx @modelcontextprotocol/inspector-cli \
201201

202202
## Programmatic usage (advanced)
203203

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

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

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

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

215-
// Starts the MCP server and listens on stdio
216-
await runServer();
217-
// Server runs until interrupted (Ctrl+C)
215+
// Multiple options can be overridden
216+
await start({
217+
docsHost: true,
218+
// Future CLI options can be added here
219+
});
220+
221+
// TypeScript users can use the CliOptions type for type safety
222+
const options: Partial<CliOptions> = { docsHost: true };
223+
await start(options);
218224
```
219225

220226
## Returned content details

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
"main": "dist/index.js",
66
"type": "module",
77
"exports": {
8-
".": "./dist/index.js",
9-
"./server": "./dist/server.js"
8+
".": "./dist/index.js"
109
},
1110
"bin": {
1211
"patternfly-mcp": "dist/index.js",

src/__tests__/index.test.ts

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { main } from '../index';
1+
import { main, start, type CliOptions } from '../index';
22
import { parseCliOptions, freezeOptions, type GlobalOptions } from '../options';
33
import { runServer } from '../server';
44

@@ -111,5 +111,95 @@ describe('main', () => {
111111

112112
expect(callOrder).toEqual(['parse', 'freeze', 'run']);
113113
});
114+
115+
it('should merge programmatic options with CLI options', async () => {
116+
const cliOptions = { docsHost: false };
117+
const programmaticOptions = { docsHost: true };
118+
119+
mockParseCliOptions.mockReturnValue(cliOptions);
120+
121+
await main(programmaticOptions);
122+
123+
// Should merge CLI options with programmatic options (programmatic takes precedence)
124+
expect(mockFreezeOptions).toHaveBeenCalledWith({ docsHost: true });
125+
});
126+
127+
it('should work with empty programmatic options', async () => {
128+
const cliOptions = { docsHost: true };
129+
130+
mockParseCliOptions.mockReturnValue(cliOptions);
131+
132+
await main({});
133+
134+
expect(mockFreezeOptions).toHaveBeenCalledWith({ docsHost: true });
135+
});
136+
137+
it('should work with undefined programmatic options', async () => {
138+
const cliOptions = { docsHost: false };
139+
140+
mockParseCliOptions.mockReturnValue(cliOptions);
141+
142+
await main();
143+
144+
expect(mockFreezeOptions).toHaveBeenCalledWith({ docsHost: false });
145+
});
146+
});
147+
148+
describe('start alias', () => {
149+
let consoleErrorSpy: jest.SpyInstance;
150+
let processExitSpy: jest.SpyInstance;
151+
152+
beforeEach(() => {
153+
jest.clearAllMocks();
154+
155+
// Mock process.exit to prevent actual exit
156+
processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
157+
158+
// Mock console.error
159+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
160+
161+
// Setup default mocks
162+
mockParseCliOptions.mockReturnValue({ docsHost: false });
163+
mockFreezeOptions.mockReturnValue({} as GlobalOptions);
164+
mockRunServer.mockResolvedValue(undefined);
165+
});
166+
167+
afterEach(() => {
168+
consoleErrorSpy.mockRestore();
169+
processExitSpy.mockRestore();
170+
});
171+
172+
it('should be equivalent to main function', async () => {
173+
const cliOptions = { docsHost: true };
174+
175+
mockParseCliOptions.mockReturnValue(cliOptions);
176+
177+
await start();
178+
179+
expect(mockParseCliOptions).toHaveBeenCalled();
180+
expect(mockFreezeOptions).toHaveBeenCalledWith(cliOptions);
181+
expect(mockRunServer).toHaveBeenCalled();
182+
});
183+
184+
it('should accept programmatic options like main', async () => {
185+
const cliOptions = { docsHost: false };
186+
const programmaticOptions = { docsHost: true };
187+
188+
mockParseCliOptions.mockReturnValue(cliOptions);
189+
190+
await start(programmaticOptions);
191+
192+
expect(mockFreezeOptions).toHaveBeenCalledWith({ docsHost: true });
193+
});
194+
});
195+
196+
describe('type exports', () => {
197+
it('should export CliOptions type', () => {
198+
// This test ensures the type is properly exported
199+
// TypeScript compilation will fail if the type is not available
200+
const options: Partial<CliOptions> = { docsHost: true };
201+
202+
expect(options).toBeDefined();
203+
});
114204
});
115205

src/index.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
#!/usr/bin/env node
22

3-
import { freezeOptions, parseCliOptions } from './options';
3+
import { freezeOptions, parseCliOptions, type CliOptions } from './options';
44
import { runServer } from './server';
55

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

16+
// Merge programmatic options with CLI options (programmatic takes precedence)
17+
const finalOptions = { ...cliOptions, ...programmaticOptions };
18+
1419
// Freeze options to prevent further changes
15-
freezeOptions(cliOptions);
20+
freezeOptions(finalOptions);
1621

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

33-
export { main, runServer };
38+
export { main, main as start, type CliOptions };

0 commit comments

Comments
 (0)