Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions SERVER_REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,97 @@ If no progress token provided, just execute with delays.

**Reference**: [SEP-1034](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1034)

#### `test_elicitation_sep1330_enums`

**Arguments**: None

**Behavior**: Request user input from the client using `elicitation/create` with enum schema improvements (SEP-1330)

**Elicitation Request**:

```json
{
"method": "elicitation/create",
"params": {
"message": "Please select options from the enum fields",
"requestedSchema": {
"type": "object",
"properties": {
"untitledSingle": {
"type": "string",
"description": "Select one option",
"enum": ["option1", "option2", "option3"]
},
"titledSingle": {
"type": "string",
"description": "Select one option with titles",
"oneOf": [
{ "const": "value1", "title": "First Option" },
{ "const": "value2", "title": "Second Option" },
{ "const": "value3", "title": "Third Option" }
]
},
"legacyEnum": {
"type": "string",
"description": "Select one option (legacy)",
"enum": ["opt1", "opt2", "opt3"],
"enumNames": ["Option One", "Option Two", "Option Three"]
},
"untitledMulti": {
"type": "array",
"description": "Select multiple options",
"minItems": 1,
"maxItems": 3,
"items": {
"type": "string",
"enum": ["option1", "option2", "option3"]
}
},
"titledMulti": {
"type": "array",
"description": "Select multiple options with titles",
"minItems": 1,
"maxItems": 3,
"items": {
"anyOf": [
{ "const": "value1", "title": "First Choice" },
{ "const": "value2", "title": "Second Choice" },
{ "const": "value3", "title": "Third Choice" }
]
}
}
},
"required": []
}
}
}
```

**Returns**: Text content with the elicitation result

```json
{
"content": [
{
"type": "text",
"text": "Elicitation completed: action=<accept/decline/cancel>, content={...}"
}
]
}
```

**Implementation Note**: This tool tests SEP-1330 support for enum schema improvements including:

- Untitled single-select enums (type: string with enum array)
- Titled single-select enums (using oneOf with const/title objects)
- Legacy titled enums (using deprecated enumNames array)
- Untitled multi-select enums (type: array with items.enum)
- Titled multi-select enums (using items.anyOf with const/title objects)

If the client doesn't support elicitation (no `elicitation` capability), return an error.

**Reference**: [SEP-1330](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330)

---

## 3. Resources Requirements
Expand Down
99 changes: 99 additions & 0 deletions examples/servers/typescript/everything-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,105 @@ function createMcpServer() {
}
);

// SEP-1330: Elicitation with enum schema improvements
mcpServer.registerTool(
'test_elicitation_sep1330_enums',
{
description:
'Tests elicitation with enum schema improvements per SEP-1330',
inputSchema: {}
},
async () => {
try {
// Request user input with all 5 enum schema variants
const result = await mcpServer.server.request(
{
method: 'elicitation/create',
params: {
message: 'Please select options from the enum fields',
requestedSchema: {
type: 'object',
properties: {
// Untitled single-select enum (basic)
untitledSingle: {
type: 'string',
description: 'Select one option',
enum: ['option1', 'option2', 'option3']
},
// Titled single-select enum (using oneOf with const/title)
titledSingle: {
type: 'string',
description: 'Select one option with titles',
oneOf: [
{ const: 'value1', title: 'First Option' },
{ const: 'value2', title: 'Second Option' },
{ const: 'value3', title: 'Third Option' }
]
},
// Legacy titled enum (using enumNames - deprecated)
legacyEnum: {
type: 'string',
description: 'Select one option (legacy)',
enum: ['opt1', 'opt2', 'opt3'],
enumNames: ['Option One', 'Option Two', 'Option Three']
},
// Untitled multi-select enum
untitledMulti: {
type: 'array',
description: 'Select multiple options',
minItems: 1,
maxItems: 3,
items: {
type: 'string',
enum: ['option1', 'option2', 'option3']
}
},
// Titled multi-select enum (using anyOf with const/title)
titledMulti: {
type: 'array',
description: 'Select multiple options with titles',
minItems: 1,
maxItems: 3,
items: {
anyOf: [
{ const: 'value1', title: 'First Choice' },
{ const: 'value2', title: 'Second Choice' },
{ const: 'value3', title: 'Third Choice' }
]
}
}
},
required: []
}
}
},
z
.object({ method: z.literal('elicitation/create') })
.passthrough() as any
);

const elicitResult = result as any;
return {
content: [
{
type: 'text',
text: `Elicitation completed: action=${elicitResult.action}, content=${JSON.stringify(elicitResult.content || {})}`
}
]
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Elicitation not supported or error: ${error.message}`
}
]
};
}
}
);

// Dynamic tool (registered later via timer)

// ===== RESOURCES =====
Expand Down
10 changes: 7 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import {
printServerSummary,
runInteractiveMode
} from './runner';
import { listScenarios, listClientScenarios } from './scenarios';
import {
listScenarios,
listClientScenarios,
listActiveClientScenarios
} from './scenarios';
import { ConformanceCheck } from './types';
import { ClientOptionsSchema, ServerOptionsSchema } from './schemas';
import packageJson from '../package.json';
Expand Down Expand Up @@ -97,8 +101,8 @@ program
const { failed } = printServerResults(result.checks);
process.exit(failed > 0 ? 1 : 0);
} else {
// Run all scenarios
const scenarios = listClientScenarios();
// Run all active scenarios
const scenarios = listActiveClientScenarios();
console.log(
`Running ${scenarios.length} scenarios against ${validated.url}\n`
);
Expand Down
2 changes: 1 addition & 1 deletion src/scenarios/client/auth/basic-dcr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ServerLifecycle } from './helpers/serverLifecycle.js';
import { Request, Response } from 'express';

export class AuthBasicDCRScenario implements Scenario {
name = 'auth-basic-dcr';
name = 'auth/basic-dcr';
description =
'Tests Basic OAuth flow with DCR, PRM at path-based location, OAuth metadata at root location, and no scopes required';
private authServer = new ServerLifecycle(() => this.authBaseUrl);
Expand Down
2 changes: 1 addition & 1 deletion src/scenarios/client/auth/basic-metadata-var1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ServerLifecycle } from './helpers/serverLifecycle.js';

export class AuthBasicMetadataVar1Scenario implements Scenario {
// TODO: name should match what we put in the scenario map
name = 'auth-basic-metadata-var1';
name = 'auth/basic-metadata-var1';
description =
'Tests Basic OAuth flow with DCR, PRM at root location, OAuth metadata at OpenID discovery path, and no scopes required';
private authServer = new ServerLifecycle(() => this.authBaseUrl);
Expand Down
110 changes: 71 additions & 39 deletions src/scenarios/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from './server/tools.js';

import { ElicitationDefaultsScenario } from './server/elicitation-defaults.js';
import { ElicitationEnumsScenario } from './server/elicitation-enums.js';

import {
ResourcesListScenario,
Expand All @@ -46,56 +47,83 @@ import {
PromptsGetWithImageScenario
} from './server/prompts.js';

export const scenarios = new Map<string, Scenario>([
['initialize', new InitializeScenario()],
['tools-call', new ToolsCallScenario()],
['auth/basic-dcr', new AuthBasicDCRScenario()],
['auth/basic-metadata-var1', new AuthBasicMetadataVar1Scenario()],
[
'elicitation-sep1034-client-defaults',
new ElicitationClientDefaultsScenario()
]
]);

export const clientScenarios = new Map<string, ClientScenario>([
// Pending client scenarios (not yet fully tested/implemented)
const pendingClientScenariosList: ClientScenario[] = [
// Elicitation scenarios (SEP-1330)
new ElicitationEnumsScenario()
];

// All client scenarios
const allClientScenariosList: ClientScenario[] = [
// Lifecycle scenarios
['server-initialize', new ServerInitializeScenario()],
new ServerInitializeScenario(),

// Utilities scenarios
['logging-set-level', new LoggingSetLevelScenario()],
['completion-complete', new CompletionCompleteScenario()],
new LoggingSetLevelScenario(),
new CompletionCompleteScenario(),

// Tools scenarios
['tools-list', new ToolsListScenario()],
['tools-call-simple-text', new ToolsCallSimpleTextScenario()],
['tools-call-image', new ToolsCallImageScenario()],
['tools-call-audio', new ToolsCallAudioScenario()],
['tools-call-embedded-resource', new ToolsCallEmbeddedResourceScenario()],
['tools-call-mixed-content', new ToolsCallMultipleContentTypesScenario()],
['tools-call-with-logging', new ToolsCallWithLoggingScenario()],
['tools-call-error', new ToolsCallErrorScenario()],
['tools-call-with-progress', new ToolsCallWithProgressScenario()],
['tools-call-sampling', new ToolsCallSamplingScenario()],
['tools-call-elicitation', new ToolsCallElicitationScenario()],
new ToolsListScenario(),
new ToolsCallSimpleTextScenario(),
new ToolsCallImageScenario(),
new ToolsCallAudioScenario(),
new ToolsCallEmbeddedResourceScenario(),
new ToolsCallMultipleContentTypesScenario(),
new ToolsCallWithLoggingScenario(),
new ToolsCallErrorScenario(),
new ToolsCallWithProgressScenario(),
new ToolsCallSamplingScenario(),
new ToolsCallElicitationScenario(),

// Elicitation scenarios (SEP-1034)
['elicitation-sep1034-defaults', new ElicitationDefaultsScenario()],
new ElicitationDefaultsScenario(),

// Elicitation scenarios (SEP-1330) - pending
...pendingClientScenariosList,

// Resources scenarios
['resources-list', new ResourcesListScenario()],
['resources-read-text', new ResourcesReadTextScenario()],
['resources-read-binary', new ResourcesReadBinaryScenario()],
['resources-templates-read', new ResourcesTemplateReadScenario()],
['resources-subscribe', new ResourcesSubscribeScenario()],
['resources-unsubscribe', new ResourcesUnsubscribeScenario()],
new ResourcesListScenario(),
new ResourcesReadTextScenario(),
new ResourcesReadBinaryScenario(),
new ResourcesTemplateReadScenario(),
new ResourcesSubscribeScenario(),
new ResourcesUnsubscribeScenario(),

// Prompts scenarios
['prompts-list', new PromptsListScenario()],
['prompts-get-simple', new PromptsGetSimpleScenario()],
['prompts-get-with-args', new PromptsGetWithArgsScenario()],
['prompts-get-embedded-resource', new PromptsGetEmbeddedResourceScenario()],
['prompts-get-with-image', new PromptsGetWithImageScenario()]
]);
new PromptsListScenario(),
new PromptsGetSimpleScenario(),
new PromptsGetWithArgsScenario(),
new PromptsGetEmbeddedResourceScenario(),
new PromptsGetWithImageScenario()
];

// Active client scenarios (excludes pending)
const activeClientScenariosList: ClientScenario[] =
allClientScenariosList.filter(
(scenario) =>
!pendingClientScenariosList.some(
(pending) => pending.name === scenario.name
)
);

// Client scenarios map - built from list
export const clientScenarios = new Map<string, ClientScenario>(
allClientScenariosList.map((scenario) => [scenario.name, scenario])
);

// Scenario scenarios
const scenariosList: Scenario[] = [
new InitializeScenario(),
new ToolsCallScenario(),
new AuthBasicDCRScenario(),
new AuthBasicMetadataVar1Scenario(),
new ElicitationClientDefaultsScenario()
];

// Scenarios map - built from list
export const scenarios = new Map<string, Scenario>(
scenariosList.map((scenario) => [scenario.name, scenario])
);

export function registerScenario(name: string, scenario: Scenario): void {
scenarios.set(name, scenario);
Expand All @@ -116,3 +144,7 @@ export function listScenarios(): string[] {
export function listClientScenarios(): string[] {
return Array.from(clientScenarios.keys());
}

export function listActiveClientScenarios(): string[] {
return activeClientScenariosList.map((scenario) => scenario.name);
}
Loading
Loading