Skip to content

Commit 1b606b4

Browse files
committed
feat: allow meta resource responses
1 parent 09563dd commit 1b606b4

16 files changed

+515
-43
lines changed

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

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,24 @@ exports[`runServer should allow server to be stopped, http stop server: diagnost
2121
[
2222
"Registered resource: patternfly-components-index",
2323
],
24+
[
25+
"Registered resource: patternfly-components-index-meta",
26+
],
2427
[
2528
"Registered resource: patternfly-docs-index",
2629
],
30+
[
31+
"Registered resource: patternfly-docs-index-meta",
32+
],
2733
[
2834
"Registered resource: patternfly-docs-template",
2935
],
3036
[
3137
"Registered resource: patternfly-schemas-index",
3238
],
39+
[
40+
"Registered resource: patternfly-schemas-index-meta",
41+
],
3342
[
3443
"Registered resource: patternfly-schemas-template",
3544
],
@@ -77,15 +86,24 @@ exports[`runServer should allow server to be stopped, stdio stop server: diagnos
7786
[
7887
"Registered resource: patternfly-components-index",
7988
],
89+
[
90+
"Registered resource: patternfly-components-index-meta",
91+
],
8092
[
8193
"Registered resource: patternfly-docs-index",
8294
],
95+
[
96+
"Registered resource: patternfly-docs-index-meta",
97+
],
8398
[
8499
"Registered resource: patternfly-docs-template",
85100
],
86101
[
87102
"Registered resource: patternfly-schemas-index",
88103
],
104+
[
105+
"Registered resource: patternfly-schemas-index-meta",
106+
],
89107
[
90108
"Registered resource: patternfly-schemas-template",
91109
],
@@ -133,15 +151,24 @@ exports[`runServer should attempt to run server, create transport, connect, and
133151
[
134152
"Registered resource: patternfly-components-index",
135153
],
154+
[
155+
"Registered resource: patternfly-components-index-meta",
156+
],
136157
[
137158
"Registered resource: patternfly-docs-index",
138159
],
160+
[
161+
"Registered resource: patternfly-docs-index-meta",
162+
],
139163
[
140164
"Registered resource: patternfly-docs-template",
141165
],
142166
[
143167
"Registered resource: patternfly-schemas-index",
144168
],
169+
[
170+
"Registered resource: patternfly-schemas-index-meta",
171+
],
145172
[
146173
"Registered resource: patternfly-schemas-template",
147174
],
@@ -197,15 +224,24 @@ exports[`runServer should attempt to run server, disable SIGINT handler: diagnos
197224
[
198225
"Registered resource: patternfly-components-index",
199226
],
227+
[
228+
"Registered resource: patternfly-components-index-meta",
229+
],
200230
[
201231
"Registered resource: patternfly-docs-index",
202232
],
233+
[
234+
"Registered resource: patternfly-docs-index-meta",
235+
],
203236
[
204237
"Registered resource: patternfly-docs-template",
205238
],
206239
[
207240
"Registered resource: patternfly-schemas-index",
208241
],
242+
[
243+
"Registered resource: patternfly-schemas-index-meta",
244+
],
209245
[
210246
"Registered resource: patternfly-schemas-template",
211247
],
@@ -256,15 +292,24 @@ exports[`runServer should attempt to run server, enable SIGINT handler explicitl
256292
[
257293
"Registered resource: patternfly-components-index",
258294
],
295+
[
296+
"Registered resource: patternfly-components-index-meta",
297+
],
259298
[
260299
"Registered resource: patternfly-docs-index",
261300
],
301+
[
302+
"Registered resource: patternfly-docs-index-meta",
303+
],
262304
[
263305
"Registered resource: patternfly-docs-template",
264306
],
265307
[
266308
"Registered resource: patternfly-schemas-index",
267309
],
310+
[
311+
"Registered resource: patternfly-schemas-index-meta",
312+
],
268313
[
269314
"Registered resource: patternfly-schemas-template",
270315
],
@@ -320,15 +365,24 @@ exports[`runServer should attempt to run server, register a tool: diagnostics 1`
320365
[
321366
"Registered resource: patternfly-components-index",
322367
],
368+
[
369+
"Registered resource: patternfly-components-index-meta",
370+
],
323371
[
324372
"Registered resource: patternfly-docs-index",
325373
],
374+
[
375+
"Registered resource: patternfly-docs-index-meta",
376+
],
326377
[
327378
"Registered resource: patternfly-docs-template",
328379
],
329380
[
330381
"Registered resource: patternfly-schemas-index",
331382
],
383+
[
384+
"Registered resource: patternfly-schemas-index-meta",
385+
],
332386
[
333387
"Registered resource: patternfly-schemas-template",
334388
],
@@ -395,15 +449,24 @@ exports[`runServer should attempt to run server, register multiple tools: diagno
395449
[
396450
"Registered resource: patternfly-components-index",
397451
],
452+
[
453+
"Registered resource: patternfly-components-index-meta",
454+
],
398455
[
399456
"Registered resource: patternfly-docs-index",
400457
],
458+
[
459+
"Registered resource: patternfly-docs-index-meta",
460+
],
401461
[
402462
"Registered resource: patternfly-docs-template",
403463
],
404464
[
405465
"Registered resource: patternfly-schemas-index",
406466
],
467+
[
468+
"Registered resource: patternfly-schemas-index-meta",
469+
],
407470
[
408471
"Registered resource: patternfly-schemas-template",
409472
],
@@ -480,15 +543,24 @@ exports[`runServer should attempt to run server, use custom options: diagnostics
480543
[
481544
"Registered resource: patternfly-components-index",
482545
],
546+
[
547+
"Registered resource: patternfly-components-index-meta",
548+
],
483549
[
484550
"Registered resource: patternfly-docs-index",
485551
],
552+
[
553+
"Registered resource: patternfly-docs-index-meta",
554+
],
486555
[
487556
"Registered resource: patternfly-docs-template",
488557
],
489558
[
490559
"Registered resource: patternfly-schemas-index",
491560
],
561+
[
562+
"Registered resource: patternfly-schemas-index-meta",
563+
],
492564
[
493565
"Registered resource: patternfly-schemas-template",
494566
],
@@ -544,15 +616,24 @@ exports[`runServer should attempt to run server, use default tools, http: diagno
544616
[
545617
"Registered resource: patternfly-components-index",
546618
],
619+
[
620+
"Registered resource: patternfly-components-index-meta",
621+
],
547622
[
548623
"Registered resource: patternfly-docs-index",
549624
],
625+
[
626+
"Registered resource: patternfly-docs-index-meta",
627+
],
550628
[
551629
"Registered resource: patternfly-docs-template",
552630
],
553631
[
554632
"Registered resource: patternfly-schemas-index",
555633
],
634+
[
635+
"Registered resource: patternfly-schemas-index-meta",
636+
],
556637
[
557638
"Registered resource: patternfly-schemas-template",
558639
],
@@ -621,15 +702,24 @@ exports[`runServer should attempt to run server, use default tools, stdio: diagn
621702
[
622703
"Registered resource: patternfly-components-index",
623704
],
705+
[
706+
"Registered resource: patternfly-components-index-meta",
707+
],
624708
[
625709
"Registered resource: patternfly-docs-index",
626710
],
711+
[
712+
"Registered resource: patternfly-docs-index-meta",
713+
],
627714
[
628715
"Registered resource: patternfly-docs-template",
629716
],
630717
[
631718
"Registered resource: patternfly-schemas-index",
632719
],
720+
[
721+
"Registered resource: patternfly-schemas-index-meta",
722+
],
633723
[
634724
"Registered resource: patternfly-schemas-template",
635725
],

src/__tests__/__snapshots__/tool.searchPatternFlyDocs.test.ts.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ exports[`searchPatternFlyDocsTool, callback should have a specific markdown form
5353
5454
exports[`searchPatternFlyDocsTool, callback should parse parameters, default: search 1`] = `"# Search results for PatternFly version "v6" and "Button". Showing 2 exact matches."`;
5555
56-
exports[`searchPatternFlyDocsTool, callback should parse parameters, with "*" searchQuery all: search 1`] = `"# Search results for PatternFly version "v6" and "all" resources. Only showing the first 10 results. There are 806 potential match variations. Try searching with a more specific query."`;
56+
exports[`searchPatternFlyDocsTool, callback should parse parameters, with "*" searchQuery all: search 1`] = `"# Search results for PatternFly version "v6" and "all" resources. Only showing the first 10 results. There are 805 potential match variations. Try searching with a more specific query."`;
5757
58-
exports[`searchPatternFlyDocsTool, callback should parse parameters, with "all" searchQuery all: search 1`] = `"# Search results for PatternFly version "v6" and "all" resources. Only showing the first 10 results. There are 806 potential match variations. Try searching with a more specific query."`;
58+
exports[`searchPatternFlyDocsTool, callback should parse parameters, with "all" searchQuery all: search 1`] = `"# Search results for PatternFly version "v6" and "all" resources. Only showing the first 10 results. There are 805 potential match variations. Try searching with a more specific query."`;
5959
6060
exports[`searchPatternFlyDocsTool, callback should parse parameters, with explicit valid version: search 1`] = `"# Search results for PatternFly version "v6" and "Button". Showing 2 exact matches."`;
6161
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { distance } from 'fastest-levenshtein';
2+
import docsJson from '../docs.json';
3+
4+
describe('Documentation Data Integrity', () => {
5+
const allEntries = Object.values(docsJson.docs).flat();
6+
const uniqueCategories = [...new Set(allEntries.map(entry => entry.category).filter(Boolean))];
7+
const uniqueSections = [...new Set(allEntries.map(entry => (entry as any).section).filter(Boolean))];
8+
9+
const checkSimilarity = (list: string[], type: string) => {
10+
for (let i = 0; i < list.length; i++) {
11+
for (let j = i + 1; j < list.length; j++) {
12+
const str1 = list[i]!.toLowerCase();
13+
const str2 = list[j]!.toLowerCase();
14+
15+
// Check for near-duplicates using Levenshtein distance
16+
const dist = distance(str1, str2);
17+
18+
if (dist <= 2) {
19+
throw new Error(`Potential duplicate ${type} found: "${list[i]}" and "${list[j]}" (distance: ${dist})`);
20+
}
21+
22+
// Check if one is a substring of another (e.g., "component" and "components")
23+
if (str1.includes(str2) || str2.includes(str1)) {
24+
throw new Error(`Potential overlapping ${type} found: "${list[i]}" and "${list[j]}"`);
25+
}
26+
}
27+
}
28+
};
29+
30+
test('categories should be unique and distinct', () => {
31+
expect(() => checkSimilarity(uniqueCategories as string[], 'category')).not.toThrow();
32+
});
33+
34+
test('sections should be unique and distinct', () => {
35+
expect(() => checkSimilarity(uniqueSections as string[], 'section')).not.toThrow();
36+
});
37+
38+
test('no section should be named "getting-started"', () => {
39+
const hasGettingStarted = allEntries.some(entry => (entry as any).section === 'getting-started');
40+
41+
expect(hasGettingStarted).toBe(false);
42+
});
43+
});
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2+
import { registerResourceMeta } from '../server.resourceMeta';
3+
import { type McpResource } from '../server';
4+
import { getOptions, initializeSession } from '../options.context';
5+
6+
describe('registerResourceMeta', () => {
7+
let server: McpServer;
8+
const options = getOptions();
9+
const session = initializeSession();
10+
11+
beforeEach(() => {
12+
server = new McpServer({ name: 'test', version: '1.0.0' });
13+
jest.spyOn(server, 'registerResource').mockImplementation(() => ({} as any));
14+
});
15+
16+
afterEach(() => {
17+
jest.restoreAllMocks();
18+
});
19+
20+
test('should return original resource if enableMeta is false', () => {
21+
const callback = jest.fn();
22+
const resource: McpResource = [
23+
'test-resource',
24+
'test://uri',
25+
{ title: 'Test', description: 'Test' },
26+
callback,
27+
{ enableMeta: false }
28+
];
29+
30+
const result = registerResourceMeta(server, ...resource, options, session);
31+
32+
expect(result).toEqual(resource);
33+
expect(server.registerResource).not.toHaveBeenCalled();
34+
});
35+
36+
test('should register meta resource and enhance callback if enableMeta is true', async () => {
37+
const callback = jest.fn().mockResolvedValue({ contents: [{ uri: 'test://uri', text: 'original' }] });
38+
const metaHandler = jest.fn().mockResolvedValue({
39+
title: 'Meta Title',
40+
description: 'Meta Desc',
41+
params: [],
42+
exampleUris: []
43+
});
44+
45+
const resource: McpResource = [
46+
'test-resource',
47+
new ResourceTemplate('test://uri{?a}', { list: undefined }),
48+
{ title: 'Test', description: 'Test' },
49+
callback,
50+
{ enableMeta: true, metaHandler }
51+
];
52+
53+
const result = registerResourceMeta(server, ...resource, options, session);
54+
55+
// Should have registered the meta resource
56+
expect(server.registerResource).toHaveBeenCalledWith(
57+
'test-resource-meta',
58+
expect.any(ResourceTemplate),
59+
expect.objectContaining({ title: 'Test Metadata' }),
60+
expect.any(Function)
61+
);
62+
63+
// Enhanced callback should return 2 contents
64+
const enhancedCallback = result[3];
65+
const callResult = await enhancedCallback(new URL('test://uri'), { a: 'val' });
66+
67+
expect(callResult.contents).toHaveLength(2);
68+
expect(callResult.contents[0].text).toBe('original');
69+
expect(callResult.contents[1].uri).toBe('test://uri/meta');
70+
expect(callResult.contents[1].text).toContain('# Resource Metadata: Meta Title');
71+
});
72+
});

src/__tests__/server.test.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ import { startHttpTransport, type HttpServerHandle } from '../server.http';
66
import { DEFAULT_OPTIONS } from '../options.defaults';
77

88
// Mock dependencies
9-
jest.mock('@modelcontextprotocol/sdk/server/mcp.js');
9+
jest.mock('@modelcontextprotocol/sdk/server/mcp.js', () => {
10+
const actual = jest.requireActual('@modelcontextprotocol/sdk/server/mcp.js');
11+
12+
return {
13+
...actual,
14+
McpServer: jest.fn()
15+
};
16+
});
1017
jest.mock('@modelcontextprotocol/sdk/server/stdio.js');
1118
jest.mock('../logger');
1219
jest.mock('../server.logger', () => ({

0 commit comments

Comments
 (0)