Skip to content

Commit 2a22bd0

Browse files
committed
feat(tools-plugins): allow mcp tool plugins
* docs, readme updates for tools, minor refactor for readability * index, refine, alias typings for tools, server * options.context, avoid cloning toolModules * options.defaults, set default pluginIsolation to strict * options, add tool cli options * server.tools, prevent potential orphaned process * server.toolsUser, align typings, align annotations with docs * server, activate tool registration, shutdown
1 parent b34425f commit 2a22bd0

23 files changed

+870
-358
lines changed

README.md

Lines changed: 170 additions & 295 deletions
Large diffs are not rendered by default.

src/__tests__/__snapshots__/options.defaults.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ exports[`options defaults should return specific properties: defaults 1`] = `
3737
"invokeTimeoutMs": 10000,
3838
"loadTimeoutMs": 5000,
3939
},
40-
"pluginIsolation": "none",
40+
"pluginIsolation": "strict",
4141
"repoName": "patternfly-mcp",
4242
"resourceMemoOptions": {
4343
"default": {

src/__tests__/__snapshots__/options.test.ts.snap

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ exports[`parseCliOptions should attempt to parse args with --allowed-hosts 1`] =
1717
"stderr": false,
1818
"transport": "stdio",
1919
},
20+
"pluginIsolation": undefined,
21+
"toolModules": [],
2022
}
2123
`;
2224

@@ -37,6 +39,8 @@ exports[`parseCliOptions should attempt to parse args with --allowed-origins 1`]
3739
"stderr": false,
3840
"transport": "stdio",
3941
},
42+
"pluginIsolation": undefined,
43+
"toolModules": [],
4044
}
4145
`;
4246

@@ -52,6 +56,8 @@ exports[`parseCliOptions should attempt to parse args with --docs-host flag 1`]
5256
"stderr": false,
5357
"transport": "stdio",
5458
},
59+
"pluginIsolation": undefined,
60+
"toolModules": [],
5561
}
5662
`;
5763

@@ -69,6 +75,8 @@ exports[`parseCliOptions should attempt to parse args with --http and --host 1`]
6975
"stderr": false,
7076
"transport": "stdio",
7177
},
78+
"pluginIsolation": undefined,
79+
"toolModules": [],
7280
}
7381
`;
7482

@@ -86,6 +94,8 @@ exports[`parseCliOptions should attempt to parse args with --http and --port 1`]
8694
"stderr": false,
8795
"transport": "stdio",
8896
},
97+
"pluginIsolation": undefined,
98+
"toolModules": [],
8999
}
90100
`;
91101

@@ -101,6 +111,8 @@ exports[`parseCliOptions should attempt to parse args with --http and invalid --
101111
"stderr": false,
102112
"transport": "stdio",
103113
},
114+
"pluginIsolation": undefined,
115+
"toolModules": [],
104116
}
105117
`;
106118

@@ -116,6 +128,8 @@ exports[`parseCliOptions should attempt to parse args with --http flag 1`] = `
116128
"stderr": false,
117129
"transport": "stdio",
118130
},
131+
"pluginIsolation": undefined,
132+
"toolModules": [],
119133
}
120134
`;
121135

@@ -131,6 +145,8 @@ exports[`parseCliOptions should attempt to parse args with --log-level flag 1`]
131145
"stderr": false,
132146
"transport": "stdio",
133147
},
148+
"pluginIsolation": undefined,
149+
"toolModules": [],
134150
}
135151
`;
136152

@@ -146,6 +162,8 @@ exports[`parseCliOptions should attempt to parse args with --log-stderr flag and
146162
"stderr": true,
147163
"transport": "stdio",
148164
},
165+
"pluginIsolation": undefined,
166+
"toolModules": [],
149167
}
150168
`;
151169

@@ -161,6 +179,8 @@ exports[`parseCliOptions should attempt to parse args with --verbose flag 1`] =
161179
"stderr": false,
162180
"transport": "stdio",
163181
},
182+
"pluginIsolation": undefined,
183+
"toolModules": [],
164184
}
165185
`;
166186

@@ -176,6 +196,8 @@ exports[`parseCliOptions should attempt to parse args with --verbose flag and --
176196
"stderr": false,
177197
"transport": "stdio",
178198
},
199+
"pluginIsolation": undefined,
200+
"toolModules": [],
179201
}
180202
`;
181203

@@ -191,6 +213,8 @@ exports[`parseCliOptions should attempt to parse args with other arguments 1`] =
191213
"stderr": false,
192214
"transport": "stdio",
193215
},
216+
"pluginIsolation": undefined,
217+
"toolModules": [],
194218
}
195219
`;
196220

@@ -206,5 +230,7 @@ exports[`parseCliOptions should attempt to parse args without --docs-host flag 1
206230
"stderr": false,
207231
"transport": "stdio",
208232
},
233+
"pluginIsolation": undefined,
234+
"toolModules": [],
209235
}
210236
`;

src/__tests__/__snapshots__/server.test.ts.snap

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ exports[`runServer should allow server to be stopped, http stop server: diagnost
66
[
77
"Server logging enabled.",
88
],
9+
[
10+
"No external tools loaded.",
11+
],
912
[
1013
"Registered tool: usePatternFlyDocs",
1114
],
@@ -35,6 +38,9 @@ exports[`runServer should allow server to be stopped, stdio stop server: diagnos
3538
[
3639
"Server logging enabled.",
3740
],
41+
[
42+
"No external tools loaded.",
43+
],
3844
[
3945
"Registered tool: usePatternFlyDocs",
4046
],
@@ -64,6 +70,9 @@ exports[`runServer should attempt to run server, create transport, connect, and
6470
[
6571
"Server logging enabled.",
6672
],
73+
[
74+
"No external tools loaded.",
75+
],
6776
[
6877
"test-server-4 server running on stdio transport",
6978
],
@@ -98,6 +107,9 @@ exports[`runServer should attempt to run server, disable SIGINT handler: diagnos
98107
[
99108
"Server logging enabled.",
100109
],
110+
[
111+
"No external tools loaded.",
112+
],
101113
[
102114
"test-server-7 server running on stdio transport",
103115
],
@@ -127,6 +139,9 @@ exports[`runServer should attempt to run server, enable SIGINT handler explicitl
127139
[
128140
"Server logging enabled.",
129141
],
142+
[
143+
"No external tools loaded.",
144+
],
130145
[
131146
"test-server-8 server running on stdio transport",
132147
],
@@ -161,12 +176,18 @@ exports[`runServer should attempt to run server, register a tool: diagnostics 1`
161176
[
162177
"Server logging enabled.",
163178
],
179+
[
180+
"No external tools loaded.",
181+
],
164182
[
165183
"Registered tool: loremIpsum",
166184
],
167185
[
168186
"test-server-5 server running on stdio transport",
169187
],
188+
[
189+
"Built-in tool at index 0 is missing the static name property, "toolName"",
190+
],
170191
[
171192
"Tool "loremIpsum" has a non Zod inputSchema. This may cause unexpected issues.",
172193
],
@@ -203,6 +224,9 @@ exports[`runServer should attempt to run server, register multiple tools: diagno
203224
[
204225
"Server logging enabled.",
205226
],
227+
[
228+
"No external tools loaded.",
229+
],
206230
[
207231
"Registered tool: loremIpsum",
208232
],
@@ -212,6 +236,12 @@ exports[`runServer should attempt to run server, register multiple tools: diagno
212236
[
213237
"test-server-6 server running on stdio transport",
214238
],
239+
[
240+
"Built-in tool at index 0 is missing the static name property, "toolName"",
241+
],
242+
[
243+
"Built-in tool at index 1 is missing the static name property, "toolName"",
244+
],
215245
[
216246
"Tool "loremIpsum" has a non Zod inputSchema. This may cause unexpected issues.",
217247
],
@@ -252,6 +282,9 @@ exports[`runServer should attempt to run server, use custom options: diagnostics
252282
[
253283
"Server logging enabled.",
254284
],
285+
[
286+
"No external tools loaded.",
287+
],
255288
[
256289
"test-server-3 server running on stdio transport",
257290
],
@@ -286,6 +319,9 @@ exports[`runServer should attempt to run server, use default tools, http: diagno
286319
[
287320
"Server logging enabled.",
288321
],
322+
[
323+
"No external tools loaded.",
324+
],
289325
[
290326
"Registered tool: usePatternFlyDocs",
291327
],
@@ -333,6 +369,9 @@ exports[`runServer should attempt to run server, use default tools, stdio: diagn
333369
[
334370
"Server logging enabled.",
335371
],
372+
[
373+
"No external tools loaded.",
374+
],
336375
[
337376
"Registered tool: usePatternFlyDocs",
338377
],

src/__tests__/__snapshots__/server.tools.test.ts.snap

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ exports[`composeTools should attempt to setup creators, file package creators 1`
1717
exports[`composeTools should attempt to setup creators, file package creators, Node.js 20 1`] = `
1818
{
1919
"log": [
20+
[
21+
"Existing Tools Host session detected test-session-id. Shutting down the existing host before creating a new one.",
22+
],
2023
[
2124
"External tool plugins require Node >= 22; skipping file-based tools.",
2225
],
@@ -27,14 +30,21 @@ exports[`composeTools should attempt to setup creators, file package creators, N
2730

2831
exports[`composeTools should attempt to setup creators, file package creators, Node.js 24 1`] = `
2932
{
30-
"log": [],
33+
"log": [
34+
[
35+
"Existing Tools Host session detected test-session-id. Shutting down the existing host before creating a new one.",
36+
],
37+
],
3138
"toolsCount": 5,
3239
}
3340
`;
3441

3542
exports[`composeTools should attempt to setup creators, file package creators, Node.js undefined 1`] = `
3643
{
3744
"log": [
45+
[
46+
"Existing Tools Host session detected test-session-id. Shutting down the existing host before creating a new one.",
47+
],
3848
[
3949
"External tool plugins require Node >= 22; skipping file-based tools.",
4050
],
@@ -46,6 +56,9 @@ exports[`composeTools should attempt to setup creators, file package creators, N
4656
exports[`composeTools should attempt to setup creators, file package duplicate creators 1`] = `
4757
{
4858
"log": [
59+
[
60+
"Existing Tools Host session detected test-session-id. Shutting down the existing host before creating a new one.",
61+
],
4962
[
5063
"Skipping tool plugin "@patternfly/tools" – name already used by built-in/inline tool.",
5164
],
@@ -56,14 +69,21 @@ exports[`composeTools should attempt to setup creators, file package duplicate c
5669

5770
exports[`composeTools should attempt to setup creators, inline and file package creators 1`] = `
5871
{
59-
"log": [],
72+
"log": [
73+
[
74+
"Existing Tools Host session detected test-session-id. Shutting down the existing host before creating a new one.",
75+
],
76+
],
6077
"toolsCount": 7,
6178
}
6279
`;
6380

6481
exports[`composeTools should attempt to setup creators, inline and file package creators duplicate builtin creators 1`] = `
6582
{
6683
"log": [
84+
[
85+
"Existing Tools Host session detected test-session-id. Shutting down the existing host before creating a new one.",
86+
],
6787
[
6888
"Skipping inline tool "loremipsum" because a tool with the same name is already provided (built-in or earlier).",
6989
],
@@ -78,6 +98,9 @@ exports[`composeTools should attempt to setup creators, inline and file package
7898
exports[`composeTools should attempt to setup creators, inline and file package creators, duplicates 1`] = `
7999
{
80100
"log": [
101+
[
102+
"Existing Tools Host session detected test-session-id. Shutting down the existing host before creating a new one.",
103+
],
81104
[
82105
"Skipping tool plugin "@patternfly/tools" – name already used by built-in/inline tool.",
83106
],
@@ -92,6 +115,9 @@ exports[`composeTools should attempt to setup creators, inline and file package
92115
exports[`composeTools should attempt to setup creators, inline and file package creators, duplicates, Node.js 20 1`] = `
93116
{
94117
"log": [
118+
[
119+
"Existing Tools Host session detected test-session-id. Shutting down the existing host before creating a new one.",
120+
],
95121
[
96122
"External tool plugins require Node >= 22; skipping file-based tools.",
97123
],

src/__tests__/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { runServer } from '../server';
88
jest.mock('../options');
99
jest.mock('../options.context');
1010
jest.mock('../server');
11+
jest.mock('../server.tools');
1112

1213
const mockParseCliOptions = parseCliOptions as jest.MockedFunction<typeof parseCliOptions>;
1314
const mockSetOptions = setOptions as jest.MockedFunction<typeof setOptions>;

src/__tests__/options.context.test.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,33 @@ const MockStdioServerTransport = StdioServerTransport as jest.MockedClass<typeof
1313

1414
describe('setOptions', () => {
1515
it('should ignore valid but incorrect options for merged options', () => {
16-
const updatedOptions = setOptions({ logging: 'oops' as any, resourceMemoOptions: 'gotcha' as any, toolMemoOptions: 'really?' as any });
16+
const updatedOptions = setOptions({
17+
logging: 'oops' as any,
18+
resourceMemoOptions: 'gotcha' as any,
19+
toolMemoOptions: 'really?' as any,
20+
pluginIsolation: 'fun' as any
21+
});
1722

1823
expect(updatedOptions.logging.protocol).toBe(DEFAULT_OPTIONS.logging.protocol);
1924
expect(updatedOptions.resourceMemoOptions?.readFile?.expire).toBe(DEFAULT_OPTIONS.resourceMemoOptions?.readFile?.expire);
2025
expect(updatedOptions.toolMemoOptions?.fetchDocs?.expire).toBe(DEFAULT_OPTIONS.toolMemoOptions?.fetchDocs?.expire);
26+
expect(updatedOptions.pluginIsolation).toBe(DEFAULT_OPTIONS.pluginIsolation);
2127
});
2228

2329
it('should ignore null/invalid nested overrides safely', () => {
24-
const updatedOptions = setOptions({ logging: null as any, resourceMemoOptions: null as any });
30+
const updatedOptions = setOptions({ logging: null as any, resourceMemoOptions: null as any, pluginIsolation: null as any });
2531

26-
expect(typeof updatedOptions.logging.protocol === 'boolean').toBe(true);
32+
expect(typeof updatedOptions.logging.protocol).toBe('boolean');
2733
expect(updatedOptions.logging.protocol).toBe(DEFAULT_OPTIONS.logging.protocol);
2834

29-
expect(typeof updatedOptions.resourceMemoOptions?.readFile?.expire === 'number').toBe(true);
35+
expect(typeof updatedOptions.resourceMemoOptions?.readFile?.expire).toBe('number');
3036
expect(updatedOptions.resourceMemoOptions?.readFile?.expire).toBe(DEFAULT_OPTIONS.resourceMemoOptions?.readFile?.expire);
3137

32-
expect(typeof updatedOptions.toolMemoOptions?.fetchDocs?.expire === 'number').toBe(true);
38+
expect(typeof updatedOptions.toolMemoOptions?.fetchDocs?.expire).toBe('number');
3339
expect(updatedOptions.toolMemoOptions?.fetchDocs?.expire).toBe(DEFAULT_OPTIONS.toolMemoOptions?.fetchDocs?.expire);
40+
41+
expect(typeof updatedOptions.pluginIsolation).toBe('string');
42+
expect(updatedOptions.pluginIsolation).toBe(DEFAULT_OPTIONS.pluginIsolation);
3443
});
3544
});
3645

src/__tests__/server.toolsUser.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
sanitizeStaticToolName,
1919
type Tool,
2020
type ToolCreator,
21-
type MultiToolConfig,
21+
type ToolMultiConfig,
2222
type ToolConfig
2323
} from '../server.toolsUser';
2424
import { isZodSchema } from '../server.schema';
@@ -710,7 +710,7 @@ describe('createMcpTool', () => {
710710
createMcpTool(['dolorSit', { description: 'dolor sit', inputSchema: { type: 'object', properties: {} } }, () => {}]),
711711
createMcpTool('@scope/pkg4'),
712712
'@scope/pkg5'
713-
] as MultiToolConfig
713+
] as ToolMultiConfig
714714
}
715715
])('should normalize configs, $description', ({ config }) => {
716716
const result = createMcpTool(config);

0 commit comments

Comments
 (0)