Skip to content

Commit 9fac7a0

Browse files
authored
Merge branch 'main' into feature/configurable-json-schema-spec
2 parents e9e8e41 + b28c297 commit 9fac7a0

File tree

9 files changed

+324
-13
lines changed

9 files changed

+324
-13
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/sdk",
3-
"version": "1.17.5",
3+
"version": "1.18.0",
44
"description": "Model Context Protocol implementation for TypeScript",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",

src/examples/server/simpleStreamableHttp.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ const strictOAuth = process.argv.includes('--oauth-strict');
2121
const getServer = () => {
2222
const server = new McpServer({
2323
name: 'simple-streamable-http-server',
24-
version: '1.0.0'
24+
version: '1.0.0',
25+
icons: [{src: './mcp.svg', sizes: '512x512', mimeType: 'image/svg+xml'}],
26+
websiteUrl: 'https://github.com/modelcontextprotocol/typescript-sdk',
2527
}, { capabilities: { logging: {} } });
2628

2729
// Register a simple tool that returns a greeting

src/server/index.test.ts

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
ListResourcesRequestSchema,
1616
ListToolsRequestSchema,
1717
SetLevelRequestSchema,
18-
ErrorCode
18+
ErrorCode,
19+
LoggingMessageNotification
1920
} from "../types.js";
2021
import { Transport } from "../shared/transport.js";
2122
import { InMemoryTransport } from "../inMemory.js";
@@ -569,7 +570,7 @@ test("should allow elicitation reject and cancel without validation", async () =
569570
action: "decline",
570571
});
571572

572-
// Test cancel - should not validate
573+
// Test cancel - should not validate
573574
await expect(
574575
server.elicitInput({
575576
message: "Please provide your name",
@@ -861,3 +862,154 @@ test("should handle request timeout", async () => {
861862
code: ErrorCode.RequestTimeout,
862863
});
863864
});
865+
866+
/*
867+
Test automatic log level handling for transports with and without sessionId
868+
*/
869+
test("should respect log level for transport without sessionId", async () => {
870+
871+
const server = new Server(
872+
{
873+
name: "test server",
874+
version: "1.0",
875+
},
876+
{
877+
capabilities: {
878+
prompts: {},
879+
resources: {},
880+
tools: {},
881+
logging: {},
882+
},
883+
enforceStrictCapabilities: true,
884+
},
885+
);
886+
887+
const client = new Client(
888+
{
889+
name: "test client",
890+
version: "1.0",
891+
},
892+
);
893+
894+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
895+
896+
await Promise.all([
897+
client.connect(clientTransport),
898+
server.connect(serverTransport),
899+
]);
900+
901+
expect(clientTransport.sessionId).toEqual(undefined);
902+
903+
// Client sets logging level to warning
904+
await client.setLoggingLevel("warning");
905+
906+
// This one will make it through
907+
const warningParams: LoggingMessageNotification["params"] = {
908+
level: "warning",
909+
logger: "test server",
910+
data: "Warning message",
911+
};
912+
913+
// This one will not
914+
const debugParams: LoggingMessageNotification["params"] = {
915+
level: "debug",
916+
logger: "test server",
917+
data: "Debug message",
918+
};
919+
920+
// Test the one that makes it through
921+
clientTransport.onmessage = jest.fn().mockImplementation((message) => {
922+
expect(message).toEqual({
923+
jsonrpc: "2.0",
924+
method: "notifications/message",
925+
params: warningParams
926+
});
927+
});
928+
929+
// This one will not make it through
930+
await server.sendLoggingMessage(debugParams);
931+
expect(clientTransport.onmessage).not.toHaveBeenCalled();
932+
933+
// This one will, triggering the above test in clientTransport.onmessage
934+
await server.sendLoggingMessage(warningParams);
935+
expect(clientTransport.onmessage).toHaveBeenCalled();
936+
937+
});
938+
939+
test("should respect log level for transport with sessionId", async () => {
940+
941+
const server = new Server(
942+
{
943+
name: "test server",
944+
version: "1.0",
945+
},
946+
{
947+
capabilities: {
948+
prompts: {},
949+
resources: {},
950+
tools: {},
951+
logging: {},
952+
},
953+
enforceStrictCapabilities: true,
954+
},
955+
);
956+
957+
const client = new Client(
958+
{
959+
name: "test client",
960+
version: "1.0",
961+
},
962+
);
963+
964+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
965+
966+
// Add a session id to the transports
967+
const SESSION_ID = "test-session-id";
968+
clientTransport.sessionId = SESSION_ID;
969+
serverTransport.sessionId = SESSION_ID;
970+
971+
expect(clientTransport.sessionId).toBeDefined();
972+
expect(serverTransport.sessionId).toBeDefined();
973+
974+
await Promise.all([
975+
client.connect(clientTransport),
976+
server.connect(serverTransport),
977+
]);
978+
979+
980+
// Client sets logging level to warning
981+
await client.setLoggingLevel("warning");
982+
983+
// This one will make it through
984+
const warningParams: LoggingMessageNotification["params"] = {
985+
level: "warning",
986+
logger: "test server",
987+
data: "Warning message",
988+
};
989+
990+
// This one will not
991+
const debugParams: LoggingMessageNotification["params"] = {
992+
level: "debug",
993+
logger: "test server",
994+
data: "Debug message",
995+
};
996+
997+
// Test the one that makes it through
998+
clientTransport.onmessage = jest.fn().mockImplementation((message) => {
999+
expect(message).toEqual({
1000+
jsonrpc: "2.0",
1001+
method: "notifications/message",
1002+
params: warningParams
1003+
});
1004+
});
1005+
1006+
// This one will not make it through
1007+
await server.sendLoggingMessage(debugParams, SESSION_ID);
1008+
expect(clientTransport.onmessage).not.toHaveBeenCalled();
1009+
1010+
// This one will, triggering the above test in clientTransport.onmessage
1011+
await server.sendLoggingMessage(warningParams, SESSION_ID);
1012+
expect(clientTransport.onmessage).toHaveBeenCalled();
1013+
1014+
});
1015+

src/server/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export class Server<
125125
const transportSessionId: string | undefined = extra.sessionId || extra.requestInfo?.headers['mcp-session-id'] as string || undefined;
126126
const { level } = request.params;
127127
const parseResult = LoggingLevelSchema.safeParse(level);
128-
if (transportSessionId && parseResult.success) {
128+
if (parseResult.success) {
129129
this._loggingLevels.set(transportSessionId, parseResult.data);
130130
}
131131
return {};
@@ -134,15 +134,15 @@ export class Server<
134134
}
135135

136136
// Map log levels by session id
137-
private _loggingLevels = new Map<string, LoggingLevel>();
137+
private _loggingLevels = new Map<string | undefined, LoggingLevel>();
138138

139139
// Map LogLevelSchema to severity index
140140
private readonly LOG_LEVEL_SEVERITY = new Map(
141141
LoggingLevelSchema.options.map((level, index) => [level, index])
142142
);
143143

144144
// Is a message with the given level ignored in the log level set for the given session id?
145-
private isMessageIgnored = (level: LoggingLevel, sessionId: string): boolean => {
145+
private isMessageIgnored = (level: LoggingLevel, sessionId?: string): boolean => {
146146
const currentLevel = this._loggingLevels.get(sessionId);
147147
return (currentLevel)
148148
? this.LOG_LEVEL_SEVERITY.get(level)! < this.LOG_LEVEL_SEVERITY.get(currentLevel)!
@@ -406,7 +406,7 @@ export class Server<
406406
*/
407407
async sendLoggingMessage(params: LoggingMessageNotification["params"], sessionId?: string) {
408408
if (this._capabilities.logging) {
409-
if (!sessionId || !this.isMessageIgnored(params.level, sessionId)) {
409+
if (!this.isMessageIgnored(params.level, sessionId)) {
410410
return this.notification({method: "notifications/message", params})
411411
}
412412
}

src/server/mcp.test.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,6 +1633,99 @@ describe("tool()", () => {
16331633
),
16341634
).rejects.toThrow(/Tool nonexistent-tool not found/);
16351635
});
1636+
1637+
/***
1638+
* Test: Tool Registration with _meta field
1639+
*/
1640+
test("should register tool with _meta field and include it in list response", async () => {
1641+
const mcpServer = new McpServer({
1642+
name: "test server",
1643+
version: "1.0",
1644+
});
1645+
const client = new Client({
1646+
name: "test client",
1647+
version: "1.0",
1648+
});
1649+
1650+
const metaData = {
1651+
author: "test-author",
1652+
version: "1.2.3",
1653+
category: "utility",
1654+
tags: ["test", "example"]
1655+
};
1656+
1657+
mcpServer.registerTool(
1658+
"test-with-meta",
1659+
{
1660+
description: "A tool with _meta field",
1661+
inputSchema: { name: z.string() },
1662+
_meta: metaData,
1663+
},
1664+
async ({ name }) => ({
1665+
content: [{ type: "text", text: `Hello, ${name}!` }]
1666+
})
1667+
);
1668+
1669+
const [clientTransport, serverTransport] =
1670+
InMemoryTransport.createLinkedPair();
1671+
1672+
await Promise.all([
1673+
client.connect(clientTransport),
1674+
mcpServer.server.connect(serverTransport),
1675+
]);
1676+
1677+
const result = await client.request(
1678+
{ method: "tools/list" },
1679+
ListToolsResultSchema,
1680+
);
1681+
1682+
expect(result.tools).toHaveLength(1);
1683+
expect(result.tools[0].name).toBe("test-with-meta");
1684+
expect(result.tools[0].description).toBe("A tool with _meta field");
1685+
expect(result.tools[0]._meta).toEqual(metaData);
1686+
});
1687+
1688+
/***
1689+
* Test: Tool Registration without _meta field should have undefined _meta
1690+
*/
1691+
test("should register tool without _meta field and have undefined _meta in response", async () => {
1692+
const mcpServer = new McpServer({
1693+
name: "test server",
1694+
version: "1.0",
1695+
});
1696+
const client = new Client({
1697+
name: "test client",
1698+
version: "1.0",
1699+
});
1700+
1701+
mcpServer.registerTool(
1702+
"test-without-meta",
1703+
{
1704+
description: "A tool without _meta field",
1705+
inputSchema: { name: z.string() },
1706+
},
1707+
async ({ name }) => ({
1708+
content: [{ type: "text", text: `Hello, ${name}!` }]
1709+
})
1710+
);
1711+
1712+
const [clientTransport, serverTransport] =
1713+
InMemoryTransport.createLinkedPair();
1714+
1715+
await Promise.all([
1716+
client.connect(clientTransport),
1717+
mcpServer.server.connect(serverTransport),
1718+
]);
1719+
1720+
const result = await client.request(
1721+
{ method: "tools/list" },
1722+
ListToolsResultSchema,
1723+
);
1724+
1725+
expect(result.tools).toHaveLength(1);
1726+
expect(result.tools[0].name).toBe("test-without-meta");
1727+
expect(result.tools[0]._meta).toBeUndefined();
1728+
});
16361729
});
16371730

16381731
describe("resource()", () => {

0 commit comments

Comments
 (0)