Skip to content

Commit 0701caa

Browse files
committed
feat: add conformance scenario for SEP-2164 resource-not-found error code
Adds a draft-scoped scenario verifying servers return -32602 (Invalid Params) for non-existent resources instead of the legacy -32002 or an empty contents array. Includes a sep-2164.yaml traceability file mapping each MUST/MUST NOT/SHOULD to its check per the proposed SEP-2484 process.
1 parent cb0f4c3 commit 0701caa

3 files changed

Lines changed: 160 additions & 2 deletions

File tree

src/scenarios/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ import {
4040
ResourcesReadBinaryScenario,
4141
ResourcesTemplateReadScenario,
4242
ResourcesSubscribeScenario,
43-
ResourcesUnsubscribeScenario
43+
ResourcesUnsubscribeScenario,
44+
ResourcesNotFoundErrorScenario
4445
} from './server/resources';
4546

4647
import {
@@ -117,6 +118,9 @@ const allClientScenariosList: ClientScenario[] = [
117118
new ResourcesSubscribeScenario(),
118119
new ResourcesUnsubscribeScenario(),
119120

121+
// Resources error handling (SEP-2164)
122+
new ResourcesNotFoundErrorScenario(),
123+
120124
// Prompts scenarios
121125
new PromptsListScenario(),
122126
new PromptsGetSimpleScenario(),

src/scenarios/server/resources.ts

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { ClientScenario, ConformanceCheck, SpecVersion } from '../../types';
66
import { connectToServer } from './client-helper';
77
import {
88
TextResourceContents,
9-
BlobResourceContents
9+
BlobResourceContents,
10+
McpError
1011
} from '@modelcontextprotocol/sdk/types.js';
1112

1213
export class ResourcesListScenario implements ClientScenario {
@@ -435,6 +436,150 @@ Example request:
435436
}
436437
}
437438

439+
export class ResourcesNotFoundErrorScenario implements ClientScenario {
440+
name = 'sep-2164-resource-not-found';
441+
specVersions: SpecVersion[] = ['draft'];
442+
description = `Test error handling for non-existent resources (SEP-2164).
443+
444+
**Server Implementation Requirements:**
445+
446+
**Endpoint**: \`resources/read\`
447+
448+
When a client requests a URI that does not correspond to any resource, the server:
449+
450+
- **MUST** return a JSON-RPC error with code \`-32602\` (Invalid Params)
451+
- **MUST NOT** return a result with an empty \`contents\` array
452+
- **SHOULD** include the requested \`uri\` in the error \`data\` field
453+
454+
Example error response:
455+
456+
\`\`\`json
457+
{
458+
"jsonrpc": "2.0",
459+
"id": 2,
460+
"error": {
461+
"code": -32602,
462+
"message": "Resource not found",
463+
"data": {
464+
"uri": "test://nonexistent-resource-for-conformance-testing"
465+
}
466+
}
467+
}
468+
\`\`\`
469+
470+
This scenario does not require the server to register any specific resource — it tests behavior when reading a URI the server does not recognize.`;
471+
472+
async run(serverUrl: string): Promise<ConformanceCheck[]> {
473+
const checks: ConformanceCheck[] = [];
474+
const nonexistentUri =
475+
'test://nonexistent-resource-for-conformance-testing';
476+
const specReferences = [
477+
{
478+
id: 'SEP-2164',
479+
url: 'https://modelcontextprotocol.io/specification/draft/server/resources#error-handling'
480+
}
481+
];
482+
483+
let connection;
484+
try {
485+
connection = await connectToServer(serverUrl);
486+
} catch (error) {
487+
checks.push({
488+
id: 'sep-2164-error-code',
489+
name: 'ResourcesNotFoundErrorCode',
490+
description:
491+
'Server returns -32602 (Invalid Params) for non-existent resource',
492+
status: 'FAILURE',
493+
timestamp: new Date().toISOString(),
494+
errorMessage: `Failed to connect: ${error instanceof Error ? error.message : String(error)}`,
495+
specReferences
496+
});
497+
return checks;
498+
}
499+
500+
let caughtError: unknown;
501+
let result: { contents: unknown[] } | undefined;
502+
try {
503+
result = await connection.client.readResource({ uri: nonexistentUri });
504+
} catch (error) {
505+
caughtError = error;
506+
}
507+
508+
// Check 1: MUST return -32602 error code (not empty contents, not other codes)
509+
const errorCode =
510+
caughtError instanceof McpError ? caughtError.code : undefined;
511+
const errorCodeErrors: string[] = [];
512+
if (result !== undefined) {
513+
errorCodeErrors.push(
514+
`Server returned a result instead of an error (contents length: ${result.contents?.length ?? 'undefined'}). Servers MUST NOT return an empty contents array for non-existent resources.`
515+
);
516+
} else if (!(caughtError instanceof McpError)) {
517+
errorCodeErrors.push(
518+
`Expected a JSON-RPC error, got: ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`
519+
);
520+
} else if (errorCode !== -32602) {
521+
errorCodeErrors.push(
522+
`Expected error code -32602 (Invalid Params), got ${errorCode}. ` +
523+
(errorCode === -32002
524+
? 'Code -32002 was used in earlier spec versions but SEP-2164 standardizes on -32602.'
525+
: '')
526+
);
527+
}
528+
529+
checks.push({
530+
id: 'sep-2164-error-code',
531+
name: 'ResourcesNotFoundErrorCode',
532+
description:
533+
'Server returns -32602 (Invalid Params) for non-existent resource',
534+
status: errorCodeErrors.length === 0 ? 'SUCCESS' : 'FAILURE',
535+
timestamp: new Date().toISOString(),
536+
errorMessage:
537+
errorCodeErrors.length > 0 ? errorCodeErrors.join('; ') : undefined,
538+
specReferences,
539+
details: {
540+
requestedUri: nonexistentUri,
541+
receivedErrorCode: errorCode,
542+
receivedResult: result !== undefined
543+
}
544+
});
545+
546+
// Check 2: SHOULD include uri in error data field
547+
const errorData =
548+
caughtError instanceof McpError
549+
? (caughtError.data as { uri?: string } | undefined)
550+
: undefined;
551+
const dataUriMatches = errorData?.uri === nonexistentUri;
552+
553+
checks.push({
554+
id: 'sep-2164-data-uri',
555+
name: 'ResourcesNotFoundDataUri',
556+
description:
557+
'Server includes the requested URI in the error data field (SHOULD)',
558+
status:
559+
caughtError instanceof McpError
560+
? dataUriMatches
561+
? 'SUCCESS'
562+
: 'WARNING'
563+
: 'FAILURE',
564+
timestamp: new Date().toISOString(),
565+
errorMessage:
566+
caughtError instanceof McpError
567+
? dataUriMatches
568+
? undefined
569+
: `Error data.uri is ${JSON.stringify(errorData?.uri)}, expected "${nonexistentUri}". This is a SHOULD requirement.`
570+
: 'No JSON-RPC error received; cannot evaluate data field.',
571+
specReferences,
572+
details: {
573+
requestedUri: nonexistentUri,
574+
receivedDataUri: errorData?.uri
575+
}
576+
});
577+
578+
await connection.close();
579+
return checks;
580+
}
581+
}
582+
438583
export class ResourcesUnsubscribeScenario implements ClientScenario {
439584
name = 'resources-unsubscribe';
440585
specVersions: SpecVersion[] = ['2025-06-18', '2025-11-25'];

src/scenarios/server/sep-2164.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
sep: 2164
2+
spec_url: https://modelcontextprotocol.io/specification/draft/server/resources#error-handling
3+
requirements:
4+
- text: 'servers MUST return a JSON-RPC error with code -32602 (Invalid Params)'
5+
check: sep-2164-error-code
6+
- text: 'Servers MUST NOT return an empty contents array for a non-existent resource'
7+
check: sep-2164-error-code
8+
- text: 'The data field SHOULD include the uri that was not found'
9+
check: sep-2164-data-uri

0 commit comments

Comments
 (0)