Skip to content

Commit 02b0c0b

Browse files
Merge branch 'main' into improve-stateless-mode
2 parents 19cc793 + 466483f commit 02b0c0b

21 files changed

+1053
-92
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
## Overview
3939

4040
The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This TypeScript SDK implements
41-
[the full MCP specification](https://modelcontextprotocol.io/specification/latest), making it easy to:
41+
[the full MCP specification](https://modelcontextprotocol.io/specification/draft), making it easy to:
4242

4343
- Create MCP servers that expose resources, prompts and tools
4444
- Build MCP clients that can connect to any MCP server
@@ -158,7 +158,7 @@ const server = new McpServer({
158158

159159
### Tools
160160

161-
[Tools](https://modelcontextprotocol.io/specification/latest/server/tools) let LLMs take actions through your server. Tools can perform computation, fetch data and have side effects. Tools should be designed to be model-controlled - i.e. AI models will decide which tools to call,
161+
[Tools](https://modelcontextprotocol.io/specification/draft/server/tools) let LLMs take actions through your server. Tools can perform computation, fetch data and have side effects. Tools should be designed to be model-controlled - i.e. AI models will decide which tools to call,
162162
and the arguments.
163163

164164
```typescript
@@ -259,7 +259,7 @@ Tools can return `ResourceLink` objects to reference resources without embedding
259259

260260
### Resources
261261

262-
[Resources](https://modelcontextprotocol.io/specification/latest/server/resources) can also expose data to LLMs, but unlike tools shouldn't perform significant computation or have side effects.
262+
[Resources](https://modelcontextprotocol.io/specification/draft/server/resources) can also expose data to LLMs, but unlike tools shouldn't perform significant computation or have side effects.
263263

264264
Resources are designed to be used in an application-driven way, meaning MCP client applications can decide how to expose them. For example, a client could expose a resource picker to the human, or could expose them to the model directly.
265265

@@ -333,7 +333,7 @@ server.registerResource(
333333

334334
### Prompts
335335

336-
[Prompts](https://modelcontextprotocol.io/specification/latest/server/prompts) are reusable templates that help humans prompt models to interact with your server. They're designed to be user-driven, and might appear as slash commands in a chat interface.
336+
[Prompts](https://modelcontextprotocol.io/specification/draft/server/prompts) are reusable templates that help humans prompt models to interact with your server. They're designed to be user-driven, and might appear as slash commands in a chat interface.
337337

338338
```typescript
339339
import { completable } from '@modelcontextprotocol/sdk/server/completable.js';

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

src/client/auth.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,10 +625,12 @@ export async function discoverOAuthProtectedResourceMetadata(
625625
});
626626

627627
if (!response || response.status === 404) {
628+
await response?.body?.cancel();
628629
throw new Error(`Resource server does not implement OAuth 2.0 Protected Resource Metadata.`);
629630
}
630631

631632
if (!response.ok) {
633+
await response.body?.cancel();
632634
throw new Error(`HTTP ${response.status} trying to load well-known OAuth protected resource metadata.`);
633635
}
634636
return OAuthProtectedResourceMetadataSchema.parse(await response.json());
@@ -756,10 +758,12 @@ export async function discoverOAuthMetadata(
756758
});
757759

758760
if (!response || response.status === 404) {
761+
await response?.body?.cancel();
759762
return undefined;
760763
}
761764

762765
if (!response.ok) {
766+
await response.body?.cancel();
763767
throw new Error(`HTTP ${response.status} trying to load well-known OAuth metadata`);
764768
}
765769

@@ -869,6 +873,7 @@ export async function discoverAuthorizationServerMetadata(
869873
}
870874

871875
if (!response.ok) {
876+
await response.body?.cancel();
872877
// Continue looking for any 4xx response code.
873878
if (response.status >= 400 && response.status < 500) {
874879
continue; // Try next URL

src/client/index.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
CallToolRequestSchema,
1515
CreateMessageRequestSchema,
1616
ElicitRequestSchema,
17+
ElicitResultSchema,
1718
ListRootsRequestSchema,
1819
ErrorCode
1920
} from '../types.js';
@@ -917,6 +918,64 @@ test('should reject form-mode elicitation when client only supports URL mode', a
917918
await client.close();
918919
});
919920

921+
test('should reject missing-mode elicitation when client only supports URL mode', async () => {
922+
const server = new Server(
923+
{
924+
name: 'test server',
925+
version: '1.0'
926+
},
927+
{
928+
capabilities: {}
929+
}
930+
);
931+
932+
const client = new Client(
933+
{
934+
name: 'test client',
935+
version: '1.0'
936+
},
937+
{
938+
capabilities: {
939+
elicitation: {
940+
url: {}
941+
}
942+
}
943+
}
944+
);
945+
946+
const handler = vi.fn().mockResolvedValue({
947+
action: 'cancel'
948+
});
949+
client.setRequestHandler(ElicitRequestSchema, handler);
950+
951+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
952+
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
953+
954+
await expect(
955+
server.request(
956+
{
957+
method: 'elicitation/create',
958+
params: {
959+
message: 'Please provide data',
960+
requestedSchema: {
961+
type: 'object',
962+
properties: {
963+
username: {
964+
type: 'string'
965+
}
966+
}
967+
}
968+
}
969+
},
970+
ElicitResultSchema
971+
)
972+
).rejects.toThrow('Client does not support form-mode elicitation requests');
973+
974+
expect(handler).not.toHaveBeenCalled();
975+
976+
await Promise.all([client.close(), server.close()]);
977+
});
978+
920979
test('should reject URL-mode elicitation when client only supports form mode', async () => {
921980
const client = new Client(
922981
{

src/client/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,14 @@ export class Client<
267267
}
268268

269269
const { params } = validatedRequest.data;
270+
const mode = params.mode ?? 'form';
270271
const { supportsFormMode, supportsUrlMode } = getSupportedElicitationModes(this._capabilities.elicitation);
271272

272-
if (params.mode === 'form' && !supportsFormMode) {
273+
if (mode === 'form' && !supportsFormMode) {
273274
throw new McpError(ErrorCode.InvalidParams, 'Client does not support form-mode elicitation requests');
274275
}
275276

276-
if (params.mode === 'url' && !supportsUrlMode) {
277+
if (mode === 'url' && !supportsUrlMode) {
277278
throw new McpError(ErrorCode.InvalidParams, 'Client does not support URL-mode elicitation requests');
278279
}
279280

@@ -288,9 +289,9 @@ export class Client<
288289
}
289290

290291
const validatedResult = validationResult.data;
291-
const requestedSchema = params.mode === 'form' ? (params.requestedSchema as JsonSchemaType) : undefined;
292+
const requestedSchema = mode === 'form' ? (params.requestedSchema as JsonSchemaType) : undefined;
292293

293-
if (params.mode === 'form' && validatedResult.action === 'accept' && validatedResult.content && requestedSchema) {
294+
if (mode === 'form' && validatedResult.action === 'accept' && validatedResult.content && requestedSchema) {
294295
if (this._capabilities.elicitation?.form?.applyDefaults) {
295296
try {
296297
applyElicitationDefaults(requestedSchema, validatedResult.content);

src/client/sse.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ export class SSEClientTransport implements Transport {
253253

254254
const response = await (this._fetch ?? fetch)(this._endpoint, init);
255255
if (!response.ok) {
256+
const text = await response.text().catch(() => null);
257+
256258
if (response.status === 401 && this._authProvider) {
257259
const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response);
258260
this._resourceMetadataUrl = resourceMetadataUrl;
@@ -272,7 +274,6 @@ export class SSEClientTransport implements Transport {
272274
return this.send(message);
273275
}
274276

275-
const text = await response.text().catch(() => null);
276277
throw new Error(`Error POSTing to endpoint (HTTP ${response.status}): ${text}`);
277278
}
278279
} catch (error) {

0 commit comments

Comments
 (0)