Skip to content

Commit 2ea5068

Browse files
committed
feat: have list changed for all prompts, tools and resources
1 parent 161d993 commit 2ea5068

File tree

3 files changed

+428
-118
lines changed

3 files changed

+428
-118
lines changed

src/client/index.test.ts

Lines changed: 269 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
ListResourcesRequestSchema,
1313
ListToolsRequestSchema,
1414
ListToolsResultSchema,
15+
ListPromptsRequestSchema,
1516
CallToolRequestSchema,
1617
CallToolResultSchema,
1718
CreateMessageRequestSchema,
@@ -21,7 +22,9 @@ import {
2122
ErrorCode,
2223
McpError,
2324
CreateTaskResultSchema,
24-
Tool
25+
Tool,
26+
Prompt,
27+
Resource
2528
} from '../types.js';
2629
import { Transport } from '../shared/transport.js';
2730
import { Server } from '../server/index.js';
@@ -1275,9 +1278,11 @@ test('should handle tool list changed notification with auto refresh', async ()
12751278
version: '1.0.0'
12761279
},
12771280
{
1278-
toolListChangedOptions: {
1279-
onToolListChanged: (err, tools) => {
1280-
notifications.push([err, tools]);
1281+
listChanged: {
1282+
tools: {
1283+
onChanged: (err, tools) => {
1284+
notifications.push([err, tools]);
1285+
}
12811286
}
12821287
}
12831288
}
@@ -1361,11 +1366,13 @@ test('should handle tool list changed notification with manual refresh', async (
13611366
version: '1.0.0'
13621367
},
13631368
{
1364-
toolListChangedOptions: {
1365-
autoRefresh: false,
1366-
debounceMs: 0,
1367-
onToolListChanged: (err, tools) => {
1368-
notifications.push([err, tools]);
1369+
listChanged: {
1370+
tools: {
1371+
autoRefresh: false,
1372+
debounceMs: 0,
1373+
onChanged: (err, tools) => {
1374+
notifications.push([err, tools]);
1375+
}
13691376
}
13701377
}
13711378
}
@@ -1394,15 +1401,266 @@ test('should handle tool list changed notification with manual refresh', async (
13941401
}));
13951402
await server.sendToolListChanged();
13961403

1397-
// Wait for the debounced notifications to be processed
1398-
await new Promise(resolve => setTimeout(resolve, 1000));
1404+
// Wait for the notifications to be processed (no debounce)
1405+
await new Promise(resolve => setTimeout(resolve, 100));
13991406

14001407
// Should be 1 notification with no tool data because autoRefresh is false
14011408
expect(notifications).toHaveLength(1);
14021409
expect(notifications[0][0]).toBeNull();
14031410
expect(notifications[0][1]).toBeNull();
14041411
});
14051412

1413+
/***
1414+
* Test: Handle Prompt List Changed Notifications
1415+
*/
1416+
test('should handle prompt list changed notification with auto refresh', async () => {
1417+
const notifications: [Error | null, Prompt[] | null][] = [];
1418+
1419+
const server = new Server(
1420+
{
1421+
name: 'test-server',
1422+
version: '1.0.0'
1423+
},
1424+
{
1425+
capabilities: {
1426+
prompts: {
1427+
listChanged: true
1428+
}
1429+
}
1430+
}
1431+
);
1432+
1433+
// Set up server handlers
1434+
server.setRequestHandler(InitializeRequestSchema, async request => ({
1435+
protocolVersion: request.params.protocolVersion,
1436+
capabilities: {
1437+
prompts: {
1438+
listChanged: true
1439+
}
1440+
},
1441+
serverInfo: {
1442+
name: 'test-server',
1443+
version: '1.0.0'
1444+
}
1445+
}));
1446+
1447+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
1448+
prompts: []
1449+
}));
1450+
1451+
// Configure listChanged handler in constructor
1452+
const client = new Client(
1453+
{
1454+
name: 'test-client',
1455+
version: '1.0.0'
1456+
},
1457+
{
1458+
listChanged: {
1459+
prompts: {
1460+
onChanged: (err, prompts) => {
1461+
notifications.push([err, prompts]);
1462+
}
1463+
}
1464+
}
1465+
}
1466+
);
1467+
1468+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
1469+
1470+
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
1471+
1472+
const result1 = await client.listPrompts();
1473+
expect(result1.prompts).toHaveLength(0);
1474+
1475+
// Update the prompts list
1476+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
1477+
prompts: [
1478+
{
1479+
name: 'test-prompt',
1480+
description: 'A test prompt'
1481+
}
1482+
]
1483+
}));
1484+
await server.sendPromptListChanged();
1485+
1486+
// Wait for the debounced notifications to be processed
1487+
await new Promise(resolve => setTimeout(resolve, 1000));
1488+
1489+
// Should be 1 notification with 1 prompt because autoRefresh is true
1490+
expect(notifications).toHaveLength(1);
1491+
expect(notifications[0][0]).toBeNull();
1492+
expect(notifications[0][1]).toHaveLength(1);
1493+
expect(notifications[0][1]?.[0].name).toBe('test-prompt');
1494+
});
1495+
1496+
/***
1497+
* Test: Handle Resource List Changed Notifications
1498+
*/
1499+
test('should handle resource list changed notification with auto refresh', async () => {
1500+
const notifications: [Error | null, Resource[] | null][] = [];
1501+
1502+
const server = new Server(
1503+
{
1504+
name: 'test-server',
1505+
version: '1.0.0'
1506+
},
1507+
{
1508+
capabilities: {
1509+
resources: {
1510+
listChanged: true
1511+
}
1512+
}
1513+
}
1514+
);
1515+
1516+
// Set up server handlers
1517+
server.setRequestHandler(InitializeRequestSchema, async request => ({
1518+
protocolVersion: request.params.protocolVersion,
1519+
capabilities: {
1520+
resources: {
1521+
listChanged: true
1522+
}
1523+
},
1524+
serverInfo: {
1525+
name: 'test-server',
1526+
version: '1.0.0'
1527+
}
1528+
}));
1529+
1530+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
1531+
resources: []
1532+
}));
1533+
1534+
// Configure listChanged handler in constructor
1535+
const client = new Client(
1536+
{
1537+
name: 'test-client',
1538+
version: '1.0.0'
1539+
},
1540+
{
1541+
listChanged: {
1542+
resources: {
1543+
onChanged: (err, resources) => {
1544+
notifications.push([err, resources]);
1545+
}
1546+
}
1547+
}
1548+
}
1549+
);
1550+
1551+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
1552+
1553+
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
1554+
1555+
const result1 = await client.listResources();
1556+
expect(result1.resources).toHaveLength(0);
1557+
1558+
// Update the resources list
1559+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
1560+
resources: [
1561+
{
1562+
uri: 'file:///test.txt',
1563+
name: 'test-resource',
1564+
description: 'A test resource'
1565+
}
1566+
]
1567+
}));
1568+
await server.sendResourceListChanged();
1569+
1570+
// Wait for the debounced notifications to be processed
1571+
await new Promise(resolve => setTimeout(resolve, 1000));
1572+
1573+
// Should be 1 notification with 1 resource because autoRefresh is true
1574+
expect(notifications).toHaveLength(1);
1575+
expect(notifications[0][0]).toBeNull();
1576+
expect(notifications[0][1]).toHaveLength(1);
1577+
expect(notifications[0][1]?.[0].name).toBe('test-resource');
1578+
});
1579+
1580+
/***
1581+
* Test: Handle Multiple List Changed Handlers
1582+
*/
1583+
test('should handle multiple list changed handlers configured together', async () => {
1584+
const toolNotifications: [Error | null, Tool[] | null][] = [];
1585+
const promptNotifications: [Error | null, Prompt[] | null][] = [];
1586+
1587+
const server = new Server(
1588+
{
1589+
name: 'test-server',
1590+
version: '1.0.0'
1591+
},
1592+
{
1593+
capabilities: {
1594+
tools: { listChanged: true },
1595+
prompts: { listChanged: true }
1596+
}
1597+
}
1598+
);
1599+
1600+
// Set up server handlers
1601+
server.setRequestHandler(InitializeRequestSchema, async request => ({
1602+
protocolVersion: request.params.protocolVersion,
1603+
capabilities: {
1604+
tools: { listChanged: true },
1605+
prompts: { listChanged: true }
1606+
},
1607+
serverInfo: {
1608+
name: 'test-server',
1609+
version: '1.0.0'
1610+
}
1611+
}));
1612+
1613+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
1614+
tools: [{ name: 'tool-1', inputSchema: { type: 'object' } }]
1615+
}));
1616+
1617+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
1618+
prompts: [{ name: 'prompt-1' }]
1619+
}));
1620+
1621+
// Configure multiple listChanged handlers in constructor
1622+
const client = new Client(
1623+
{
1624+
name: 'test-client',
1625+
version: '1.0.0'
1626+
},
1627+
{
1628+
listChanged: {
1629+
tools: {
1630+
debounceMs: 0,
1631+
onChanged: (err, tools) => {
1632+
toolNotifications.push([err, tools]);
1633+
}
1634+
},
1635+
prompts: {
1636+
debounceMs: 0,
1637+
onChanged: (err, prompts) => {
1638+
promptNotifications.push([err, prompts]);
1639+
}
1640+
}
1641+
}
1642+
}
1643+
);
1644+
1645+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
1646+
1647+
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
1648+
1649+
// Send both notifications
1650+
await server.sendToolListChanged();
1651+
await server.sendPromptListChanged();
1652+
1653+
// Wait for notifications to be processed
1654+
await new Promise(resolve => setTimeout(resolve, 100));
1655+
1656+
// Both handlers should have received their respective notifications
1657+
expect(toolNotifications).toHaveLength(1);
1658+
expect(toolNotifications[0][1]?.[0].name).toBe('tool-1');
1659+
1660+
expect(promptNotifications).toHaveLength(1);
1661+
expect(promptNotifications[0][1]?.[0].name).toBe('prompt-1');
1662+
});
1663+
14061664
describe('outputSchema validation', () => {
14071665
/***
14081666
* Test: Validate structuredContent Against outputSchema

0 commit comments

Comments
 (0)