Skip to content

Commit 1e56091

Browse files
authored
Merge branch 'main' into fix-import-path-in-readme
2 parents c2397b8 + bf7fd44 commit 1e56091

File tree

5 files changed

+86
-4
lines changed

5 files changed

+86
-4
lines changed

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.11.0",
3+
"version": "1.11.1",
44
"description": "Model Context Protocol implementation for TypeScript",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",

src/client/auth.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,31 @@ describe("OAuth Authorization", () => {
177177
expect(codeVerifier).toBe("test_verifier");
178178
});
179179

180+
it("includes scope parameter when provided", async () => {
181+
const { authorizationUrl } = await startAuthorization(
182+
"https://auth.example.com",
183+
{
184+
clientInformation: validClientInfo,
185+
redirectUrl: "http://localhost:3000/callback",
186+
scope: "read write profile",
187+
}
188+
);
189+
190+
expect(authorizationUrl.searchParams.get("scope")).toBe("read write profile");
191+
});
192+
193+
it("excludes scope parameter when not provided", async () => {
194+
const { authorizationUrl } = await startAuthorization(
195+
"https://auth.example.com",
196+
{
197+
clientInformation: validClientInfo,
198+
redirectUrl: "http://localhost:3000/callback",
199+
}
200+
);
201+
202+
expect(authorizationUrl.searchParams.has("scope")).toBe(false);
203+
});
204+
180205
it("uses metadata authorization_endpoint when provided", async () => {
181206
const { authorizationUrl } = await startAuthorization(
182207
"https://auth.example.com",

src/client/auth.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ export async function auth(
145145
const { authorizationUrl, codeVerifier } = await startAuthorization(serverUrl, {
146146
metadata,
147147
clientInformation,
148-
redirectUrl: provider.redirectUrl
148+
redirectUrl: provider.redirectUrl,
149+
scope: provider.clientMetadata.scope
149150
});
150151

151152
await provider.saveCodeVerifier(codeVerifier);
@@ -202,10 +203,12 @@ export async function startAuthorization(
202203
metadata,
203204
clientInformation,
204205
redirectUrl,
206+
scope,
205207
}: {
206208
metadata?: OAuthMetadata;
207209
clientInformation: OAuthClientInformation;
208210
redirectUrl: string | URL;
211+
scope?: string;
209212
},
210213
): Promise<{ authorizationUrl: URL; codeVerifier: string }> {
211214
const responseType = "code";
@@ -246,6 +249,10 @@ export async function startAuthorization(
246249
codeChallengeMethod,
247250
);
248251
authorizationUrl.searchParams.set("redirect_uri", String(redirectUrl));
252+
253+
if (scope) {
254+
authorizationUrl.searchParams.set("scope", scope);
255+
}
249256

250257
return { authorizationUrl, codeVerifier };
251258
}

src/server/mcp.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,53 @@ describe("tool()", () => {
622622
});
623623
});
624624

625+
test("should register tool with description, empty params, and annotations", async () => {
626+
const mcpServer = new McpServer({
627+
name: "test server",
628+
version: "1.0",
629+
});
630+
const client = new Client({
631+
name: "test client",
632+
version: "1.0",
633+
});
634+
635+
mcpServer.tool(
636+
"test",
637+
"A tool with everything but empty params",
638+
{},
639+
{ title: "Complete Test Tool with empty params", readOnlyHint: true, openWorldHint: false },
640+
async () => ({
641+
content: [{ type: "text", text: "Test response" }]
642+
})
643+
);
644+
645+
const [clientTransport, serverTransport] =
646+
InMemoryTransport.createLinkedPair();
647+
648+
await Promise.all([
649+
client.connect(clientTransport),
650+
mcpServer.server.connect(serverTransport),
651+
]);
652+
653+
const result = await client.request(
654+
{ method: "tools/list" },
655+
ListToolsResultSchema,
656+
);
657+
658+
expect(result.tools).toHaveLength(1);
659+
expect(result.tools[0].name).toBe("test");
660+
expect(result.tools[0].description).toBe("A tool with everything but empty params");
661+
expect(result.tools[0].inputSchema).toMatchObject({
662+
type: "object",
663+
properties: {}
664+
});
665+
expect(result.tools[0].annotations).toEqual({
666+
title: "Complete Test Tool with empty params",
667+
readOnlyHint: true,
668+
openWorldHint: false
669+
});
670+
});
671+
625672
test("should validate tool args", async () => {
626673
const mcpServer = new McpServer({
627674
name: "test server",

src/server/mcp.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -663,8 +663,11 @@ export class McpServer {
663663
// Helper to check if an object is a Zod schema (ZodRawShape)
664664
const isZodRawShape = (obj: unknown): obj is ZodRawShape => {
665665
if (typeof obj !== "object" || obj === null) return false;
666-
// Check that at least one property is a ZodType instance
667-
return Object.values(obj as object).some(v => v instanceof ZodType);
666+
667+
const isEmptyObject = z.object({}).strict().safeParse(obj).success;
668+
669+
// Check if object is empty or at least one property is a ZodType instance
670+
return isEmptyObject || Object.values(obj as object).some(v => v instanceof ZodType);
668671
};
669672

670673
let description: string | undefined;

0 commit comments

Comments
 (0)