SEP-1330: Compatibility with SEP-1034 (#1100)
Co-authored-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
ce420f8f94
commit
9f06b6b499
5 changed files with 785 additions and 242 deletions
|
|
@ -70,6 +70,19 @@ function applyElicitationDefaults(schema: JsonSchemaType | undefined, data: unkn
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(schema.anyOf)) {
|
||||
for (const sub of schema.anyOf) {
|
||||
applyElicitationDefaults(sub, data);
|
||||
}
|
||||
}
|
||||
|
||||
// Combine schemas
|
||||
if (Array.isArray(schema.oneOf)) {
|
||||
for (const sub of schema.oneOf) {
|
||||
applyElicitationDefaults(sub, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ClientOptions = ProtocolOptions & {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import { Client } from '../client/index.js';
|
||||
import { InMemoryTransport } from '../inMemory.js';
|
||||
import { ElicitRequestSchema } from '../types.js';
|
||||
import { ElicitRequestParams, ElicitRequestSchema } from '../types.js';
|
||||
import { AjvJsonSchemaValidator } from '../validation/ajv-provider.js';
|
||||
import { CfWorkerJsonSchemaValidator } from '../validation/cfworker-provider.js';
|
||||
import { Server } from './index.js';
|
||||
|
|
@ -17,36 +17,58 @@ import { Server } from './index.js';
|
|||
const ajvProvider = new AjvJsonSchemaValidator();
|
||||
const cfWorkerProvider = new CfWorkerJsonSchemaValidator();
|
||||
|
||||
let server: Server;
|
||||
let client: Client;
|
||||
|
||||
describe('Elicitation Flow', () => {
|
||||
describe('with AJV validator', () => {
|
||||
beforeEach(async () => {
|
||||
server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: ajvProvider
|
||||
}
|
||||
);
|
||||
|
||||
client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
});
|
||||
|
||||
testElicitationFlow(ajvProvider, 'AJV');
|
||||
});
|
||||
|
||||
describe('with CfWorker validator', () => {
|
||||
beforeEach(async () => {
|
||||
server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: cfWorkerProvider
|
||||
}
|
||||
);
|
||||
|
||||
client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
});
|
||||
|
||||
testElicitationFlow(cfWorkerProvider, 'CfWorker');
|
||||
});
|
||||
});
|
||||
|
||||
function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWorkerProvider, validatorName: string) {
|
||||
test(`${validatorName}: should elicit simple object with string field`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: { name: 'John Doe' }
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
const result = await server.elicitInput({
|
||||
message: 'What is your name?',
|
||||
requestedSchema: {
|
||||
|
|
@ -65,24 +87,11 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should elicit object with integer field`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: { age: 42 }
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
const result = await server.elicitInput({
|
||||
message: 'What is your age?',
|
||||
requestedSchema: {
|
||||
|
|
@ -101,24 +110,11 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should elicit object with boolean field`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: { agree: true }
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
const result = await server.elicitInput({
|
||||
message: 'Do you agree?',
|
||||
requestedSchema: {
|
||||
|
|
@ -137,16 +133,6 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should elicit complex object with multiple fields`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
const userData = {
|
||||
name: 'Jane Smith',
|
||||
email: 'jane@example.com',
|
||||
|
|
@ -163,9 +149,6 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
content: userData
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
const result = await server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
|
|
@ -192,16 +175,6 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should reject invalid object (missing required field)`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
|
|
@ -210,9 +183,6 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
}
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
|
|
@ -229,16 +199,6 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should reject invalid field type`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
|
|
@ -247,9 +207,6 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
}
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
|
|
@ -266,24 +223,11 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should reject invalid string (too short)`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: { name: '' } // Too short
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'What is your name?',
|
||||
|
|
@ -299,24 +243,11 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should reject invalid integer (out of range)`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: { age: 200 } // Too high
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'What is your age?',
|
||||
|
|
@ -332,24 +263,11 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should reject invalid pattern`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: { zipCode: 'ABC123' } // Doesn't match pattern
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Enter a 5-digit zip code',
|
||||
|
|
@ -366,23 +284,10 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should allow decline action without validation`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'decline'
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
const result = await server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
|
|
@ -400,23 +305,10 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should allow cancel action without validation`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'cancel'
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
const result = await server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
|
|
@ -434,16 +326,6 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should handle multiple sequential elicitation requests`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
let requestCount = 0;
|
||||
client.setRequestHandler(ElicitRequestSchema, request => {
|
||||
requestCount++;
|
||||
|
|
@ -457,9 +339,6 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
return { action: 'decline' };
|
||||
});
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
const nameResult = await server.elicitInput({
|
||||
message: 'What is your name?',
|
||||
requestedSchema: {
|
||||
|
|
@ -500,24 +379,11 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should validate with optional fields present`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: { name: 'John', nickname: 'Johnny' }
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
const result = await server.elicitInput({
|
||||
message: 'Enter your name',
|
||||
requestedSchema: {
|
||||
|
|
@ -537,24 +403,11 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should validate with optional fields absent`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: { name: 'John' }
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
const result = await server.elicitInput({
|
||||
message: 'Enter your name',
|
||||
requestedSchema: {
|
||||
|
|
@ -574,24 +427,11 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
});
|
||||
|
||||
test(`${validatorName}: should validate email format`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: { email: 'user@example.com' }
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
const result = await server.elicitInput({
|
||||
message: 'Enter your email',
|
||||
requestedSchema: {
|
||||
|
|
@ -629,18 +469,80 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
}
|
||||
);
|
||||
|
||||
const testSchemaProperties: ElicitRequestParams['requestedSchema'] = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
subscribe: { type: 'boolean', default: true },
|
||||
nickname: { type: 'string', default: 'Guest' },
|
||||
age: { type: 'integer', minimum: 0, maximum: 150, default: 18 },
|
||||
color: { type: 'string', enum: ['red', 'green'], default: 'green' },
|
||||
untitledSingleSelectEnum: {
|
||||
type: 'string',
|
||||
title: 'Untitled Single Select Enum',
|
||||
description: 'Choose your favorite color',
|
||||
enum: ['red', 'green', 'blue'],
|
||||
default: 'green'
|
||||
},
|
||||
untitledMultipleSelectEnum: {
|
||||
type: 'array',
|
||||
title: 'Untitled Multiple Select Enum',
|
||||
description: 'Choose your favorite colors',
|
||||
minItems: 1,
|
||||
maxItems: 3,
|
||||
items: { type: 'string', enum: ['red', 'green', 'blue'] },
|
||||
default: ['green', 'blue']
|
||||
},
|
||||
titledSingleSelectEnum: {
|
||||
type: 'string',
|
||||
title: 'Single Select Enum',
|
||||
description: 'Choose your favorite color',
|
||||
oneOf: [
|
||||
{ const: 'red', title: 'Red' },
|
||||
{ const: 'green', title: 'Green' },
|
||||
{ const: 'blue', title: 'Blue' }
|
||||
],
|
||||
default: 'green'
|
||||
},
|
||||
titledMultipleSelectEnum: {
|
||||
type: 'array',
|
||||
title: 'Multiple Select Enum',
|
||||
description: 'Choose your favorite colors',
|
||||
minItems: 1,
|
||||
maxItems: 3,
|
||||
items: {
|
||||
anyOf: [
|
||||
{ const: 'red', title: 'Red' },
|
||||
{ const: 'green', title: 'Green' },
|
||||
{ const: 'blue', title: 'Blue' }
|
||||
]
|
||||
},
|
||||
default: ['green', 'blue']
|
||||
},
|
||||
legacyTitledEnum: {
|
||||
type: 'string',
|
||||
title: 'Legacy Titled Enum',
|
||||
description: 'Choose your favorite color',
|
||||
enum: ['red', 'green', 'blue'],
|
||||
enumNames: ['Red', 'Green', 'Blue'],
|
||||
default: 'green'
|
||||
},
|
||||
optionalWithADefault: { type: 'string', default: 'default value' }
|
||||
},
|
||||
required: [
|
||||
'subscribe',
|
||||
'nickname',
|
||||
'age',
|
||||
'color',
|
||||
'titledSingleSelectEnum',
|
||||
'titledMultipleSelectEnum',
|
||||
'untitledSingleSelectEnum',
|
||||
'untitledMultipleSelectEnum'
|
||||
]
|
||||
};
|
||||
|
||||
// Client returns no values; SDK should apply defaults automatically (and validate)
|
||||
client.setRequestHandler(ElicitRequestSchema, request => {
|
||||
expect(request.params.requestedSchema).toEqual({
|
||||
type: 'object',
|
||||
properties: {
|
||||
subscribe: { type: 'boolean', default: true },
|
||||
nickname: { type: 'string', default: 'Guest' },
|
||||
age: { type: 'integer', minimum: 0, maximum: 150, default: 18 },
|
||||
color: { type: 'string', enum: ['red', 'green'], default: 'green' }
|
||||
},
|
||||
required: ['subscribe', 'nickname', 'age', 'color']
|
||||
});
|
||||
expect(request.params.requestedSchema).toEqual(testSchemaProperties);
|
||||
return {
|
||||
action: 'accept',
|
||||
content: {}
|
||||
|
|
@ -652,16 +554,7 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
|
||||
const result = await server.elicitInput({
|
||||
message: 'Provide your preferences',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
subscribe: { type: 'boolean', default: true },
|
||||
nickname: { type: 'string', default: 'Guest' },
|
||||
age: { type: 'integer', minimum: 0, maximum: 150, default: 18 },
|
||||
color: { type: 'string', enum: ['red', 'green'], default: 'green' }
|
||||
},
|
||||
required: ['subscribe', 'nickname', 'age', 'color']
|
||||
}
|
||||
requestedSchema: testSchemaProperties
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
|
|
@ -670,30 +563,23 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
subscribe: true,
|
||||
nickname: 'Guest',
|
||||
age: 18,
|
||||
color: 'green'
|
||||
color: 'green',
|
||||
untitledSingleSelectEnum: 'green',
|
||||
untitledMultipleSelectEnum: ['green', 'blue'],
|
||||
titledSingleSelectEnum: 'green',
|
||||
titledMultipleSelectEnum: ['green', 'blue'],
|
||||
legacyTitledEnum: 'green',
|
||||
optionalWithADefault: 'default value'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test(`${validatorName}: should reject invalid email format`, async () => {
|
||||
const server = new Server(
|
||||
{ name: 'test-server', version: '1.0.0' },
|
||||
{
|
||||
capabilities: {},
|
||||
jsonSchemaValidator: validatorProvider
|
||||
}
|
||||
);
|
||||
|
||||
const client = new Client({ name: 'test-client', version: '1.0.0' }, { capabilities: { elicitation: {} } });
|
||||
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: { email: 'not-an-email' }
|
||||
}));
|
||||
|
||||
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
||||
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
|
||||
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Enter your email',
|
||||
|
|
@ -707,4 +593,369 @@ function testElicitationFlow(validatorProvider: typeof ajvProvider | typeof cfWo
|
|||
})
|
||||
).rejects.toThrow(/does not match requested schema/);
|
||||
});
|
||||
|
||||
// Enums - Valid - Single Select - Untitled / Titled
|
||||
|
||||
test(`${validatorName}: should succeed with valid selection in single-select untitled enum`, async () => {
|
||||
// Set up client to return valid response
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
color: 'Red'
|
||||
}
|
||||
}));
|
||||
|
||||
// Test with valid response
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
color: {
|
||||
type: 'string',
|
||||
title: 'Color Selection',
|
||||
description: 'Choose your favorite color',
|
||||
enum: ['Red', 'Green', 'Blue'],
|
||||
default: 'Green'
|
||||
}
|
||||
},
|
||||
required: ['color']
|
||||
}
|
||||
})
|
||||
).resolves.toEqual({
|
||||
action: 'accept',
|
||||
content: {
|
||||
color: 'Red'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test(`${validatorName}: should succeed with valid selection in single-select titled enum`, async () => {
|
||||
// Set up client to return valid response
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
color: '#FF0000'
|
||||
}
|
||||
}));
|
||||
|
||||
// Test with valid response
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
color: {
|
||||
type: 'string',
|
||||
title: 'Color Selection',
|
||||
description: 'Choose your favorite color',
|
||||
oneOf: [
|
||||
{ const: '#FF0000', title: 'Red' },
|
||||
{ const: '#00FF00', title: 'Green' },
|
||||
{ const: '#0000FF', title: 'Blue' }
|
||||
],
|
||||
default: '#00FF00'
|
||||
}
|
||||
},
|
||||
required: ['color']
|
||||
}
|
||||
})
|
||||
).resolves.toEqual({
|
||||
action: 'accept',
|
||||
content: {
|
||||
color: '#FF0000'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test(`${validatorName}: should succeed with valid selection in single-select titled legacy enum`, async () => {
|
||||
// Set up client to return valid response
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
color: '#FF0000'
|
||||
}
|
||||
}));
|
||||
|
||||
// Test with valid response
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
color: {
|
||||
type: 'string',
|
||||
title: 'Color Selection',
|
||||
description: 'Choose your favorite color',
|
||||
enum: ['#FF0000', '#00FF00', '#0000FF'],
|
||||
enumNames: ['Red', 'Green', 'Blue'],
|
||||
default: '#00FF00'
|
||||
}
|
||||
},
|
||||
required: ['color']
|
||||
}
|
||||
})
|
||||
).resolves.toEqual({
|
||||
action: 'accept',
|
||||
content: {
|
||||
color: '#FF0000'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Enums - Valid - Multi Select - Untitled / Titled
|
||||
|
||||
test(`${validatorName}: should succeed with valid selection in multi-select untitled enum`, async () => {
|
||||
// Set up client to return valid response
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
colors: ['Red', 'Blue']
|
||||
}
|
||||
}));
|
||||
|
||||
// Test with valid response
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
colors: {
|
||||
type: 'array',
|
||||
title: 'Color Selection',
|
||||
description: 'Choose your favorite colors',
|
||||
minItems: 1,
|
||||
maxItems: 3,
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: ['Red', 'Green', 'Blue']
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['colors']
|
||||
}
|
||||
})
|
||||
).resolves.toEqual({
|
||||
action: 'accept',
|
||||
content: {
|
||||
colors: ['Red', 'Blue']
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test(`${validatorName}: should succeed with valid selection in multi-select titled enum`, async () => {
|
||||
// Set up client to return valid response
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
colors: ['#FF0000', '#0000FF']
|
||||
}
|
||||
}));
|
||||
|
||||
// Test with valid response
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
colors: {
|
||||
type: 'array',
|
||||
title: 'Color Selection',
|
||||
description: 'Choose your favorite colors',
|
||||
minItems: 1,
|
||||
maxItems: 3,
|
||||
items: {
|
||||
anyOf: [
|
||||
{ const: '#FF0000', title: 'Red' },
|
||||
{ const: '#00FF00', title: 'Green' },
|
||||
{ const: '#0000FF', title: 'Blue' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['colors']
|
||||
}
|
||||
})
|
||||
).resolves.toEqual({
|
||||
action: 'accept',
|
||||
content: {
|
||||
colors: ['#FF0000', '#0000FF']
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Enums - Invalid - Single Select - Untitled / Titled
|
||||
|
||||
test(`${validatorName}: should reject invalid selection in single-select untitled enum`, async () => {
|
||||
// Set up client to return valid response
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
color: 'Black' // Color not in enum list
|
||||
}
|
||||
}));
|
||||
|
||||
// Test with valid response
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
color: {
|
||||
type: 'string',
|
||||
title: 'Color Selection',
|
||||
description: 'Choose your favorite color',
|
||||
enum: ['Red', 'Green', 'Blue'],
|
||||
default: 'Green'
|
||||
}
|
||||
},
|
||||
required: ['color']
|
||||
}
|
||||
})
|
||||
).rejects.toThrow(/^MCP error -32602: Elicitation response content does not match requested schema/);
|
||||
});
|
||||
|
||||
test(`${validatorName}: should reject invalid selection in single-select titled enum`, async () => {
|
||||
// Set up client to return valid response
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
color: 'Red' // Should be "#FF0000" (const not title)
|
||||
}
|
||||
}));
|
||||
|
||||
// Test with valid response
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
color: {
|
||||
type: 'string',
|
||||
title: 'Color Selection',
|
||||
description: 'Choose your favorite color',
|
||||
oneOf: [
|
||||
{ const: '#FF0000', title: 'Red' },
|
||||
{ const: '#00FF00', title: 'Green' },
|
||||
{ const: '#0000FF', title: 'Blue' }
|
||||
],
|
||||
default: '#00FF00'
|
||||
}
|
||||
},
|
||||
required: ['color']
|
||||
}
|
||||
})
|
||||
).rejects.toThrow(/^MCP error -32602: Elicitation response content does not match requested schema/);
|
||||
});
|
||||
|
||||
test(`${validatorName}: should reject invalid selection in single-select titled legacy enum`, async () => {
|
||||
// Set up client to return valid response
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
color: 'Red' // Should be "#FF0000" (enum not enumNames)
|
||||
}
|
||||
}));
|
||||
|
||||
// Test with valid response
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
color: {
|
||||
type: 'string',
|
||||
title: 'Color Selection',
|
||||
description: 'Choose your favorite color',
|
||||
enum: ['#FF0000', '#00FF00', '#0000FF'],
|
||||
enumNames: ['Red', 'Green', 'Blue'],
|
||||
default: '#00FF00'
|
||||
}
|
||||
},
|
||||
required: ['color']
|
||||
}
|
||||
})
|
||||
).rejects.toThrow(/^MCP error -32602: Elicitation response content does not match requested schema/);
|
||||
});
|
||||
|
||||
// Enums - Invalid - Multi Select - Untitled / Titled
|
||||
|
||||
test(`${validatorName}: should reject invalid selection in multi-select untitled enum`, async () => {
|
||||
// Set up client to return valid response
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
color: 'Red' // Should be array, not string
|
||||
}
|
||||
}));
|
||||
|
||||
// Test with valid response
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
color: {
|
||||
type: 'array',
|
||||
title: 'Color Selection',
|
||||
description: 'Choose your favorite colors',
|
||||
minItems: 1,
|
||||
maxItems: 3,
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: ['Red', 'Green', 'Blue']
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['color']
|
||||
}
|
||||
})
|
||||
).rejects.toThrow(/^MCP error -32602: Elicitation response content does not match requested schema/);
|
||||
});
|
||||
|
||||
test(`${validatorName}: should reject invalid selection in multi-select titled enum`, async () => {
|
||||
// Set up client to return valid response
|
||||
client.setRequestHandler(ElicitRequestSchema, _request => ({
|
||||
action: 'accept',
|
||||
content: {
|
||||
colors: ['Red', 'Blue'] // Should be ["#FF0000", "#0000FF"] (const not title)
|
||||
}
|
||||
}));
|
||||
|
||||
// Test with valid response
|
||||
await expect(
|
||||
server.elicitInput({
|
||||
message: 'Please provide your information',
|
||||
requestedSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
colors: {
|
||||
type: 'array',
|
||||
title: 'Color Selection',
|
||||
description: 'Choose your favorite colors',
|
||||
minItems: 1,
|
||||
maxItems: 3,
|
||||
items: {
|
||||
anyOf: [
|
||||
{ const: '#FF0000', title: 'Red' },
|
||||
{ const: '#00FF00', title: 'Green' },
|
||||
{ const: '#0000FF', title: 'Blue' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['colors']
|
||||
}
|
||||
})
|
||||
).rejects.toThrow(/^MCP error -32602: Elicitation response content does not match requested schema/);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -480,6 +480,34 @@ const sdkTypeChecks = {
|
|||
sdk = spec;
|
||||
spec = sdk;
|
||||
},
|
||||
UntitledSingleSelectEnumSchema: (sdk: SDKTypes.UntitledSingleSelectEnumSchema, spec: SpecTypes.UntitledSingleSelectEnumSchema) => {
|
||||
sdk = spec;
|
||||
spec = sdk;
|
||||
},
|
||||
TitledSingleSelectEnumSchema: (sdk: SDKTypes.TitledSingleSelectEnumSchema, spec: SpecTypes.TitledSingleSelectEnumSchema) => {
|
||||
sdk = spec;
|
||||
spec = sdk;
|
||||
},
|
||||
SingleSelectEnumSchema: (sdk: SDKTypes.SingleSelectEnumSchema, spec: SpecTypes.SingleSelectEnumSchema) => {
|
||||
sdk = spec;
|
||||
spec = sdk;
|
||||
},
|
||||
UntitledMultiSelectEnumSchema: (sdk: SDKTypes.UntitledMultiSelectEnumSchema, spec: SpecTypes.UntitledMultiSelectEnumSchema) => {
|
||||
sdk = spec;
|
||||
spec = sdk;
|
||||
},
|
||||
TitledMultiSelectEnumSchema: (sdk: SDKTypes.TitledMultiSelectEnumSchema, spec: SpecTypes.TitledMultiSelectEnumSchema) => {
|
||||
sdk = spec;
|
||||
spec = sdk;
|
||||
},
|
||||
MultiSelectEnumSchema: (sdk: SDKTypes.MultiSelectEnumSchema, spec: SpecTypes.MultiSelectEnumSchema) => {
|
||||
sdk = spec;
|
||||
spec = sdk;
|
||||
},
|
||||
LegacyTitledEnumSchema: (sdk: SDKTypes.LegacyTitledEnumSchema, spec: SpecTypes.LegacyTitledEnumSchema) => {
|
||||
sdk = spec;
|
||||
spec = sdk;
|
||||
},
|
||||
PrimitiveSchemaDefinition: (sdk: SDKTypes.PrimitiveSchemaDefinition, spec: SpecTypes.PrimitiveSchemaDefinition) => {
|
||||
sdk = spec;
|
||||
spec = sdk;
|
||||
|
|
@ -587,7 +615,7 @@ describe('Spec Types', () => {
|
|||
it('should define some expected types', () => {
|
||||
expect(specTypes).toContain('JSONRPCNotification');
|
||||
expect(specTypes).toContain('ElicitResult');
|
||||
expect(specTypes).toHaveLength(112);
|
||||
expect(specTypes).toHaveLength(119);
|
||||
});
|
||||
|
||||
it('should have up to date list of missing sdk types', () => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Source: https://github.com/modelcontextprotocol/modelcontextprotocol
|
||||
* Pulled from: https://raw.githubusercontent.com/modelcontextprotocol/modelcontextprotocol/main/schema/draft/schema.ts
|
||||
* Last updated from commit: bcdd3363e6472b645f196e7ec6988abe3b9799e2
|
||||
* Last updated from commit: 11ad2a720d8e2f54881235f734121db0bda39052
|
||||
*
|
||||
* DO NOT EDIT THIS FILE MANUALLY. Changes will be overwritten by automated updates.
|
||||
* To update this file, run: npm run fetch:spec-types
|
||||
|
|
@ -55,6 +55,7 @@ export interface RequestParams {
|
|||
export interface Request {
|
||||
method: string;
|
||||
// Allow unofficial extensions of `Request.params` without impacting `RequestParams`.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
params?: { [key: string]: any };
|
||||
}
|
||||
|
||||
|
|
@ -66,11 +67,11 @@ export interface NotificationParams {
|
|||
_meta?: { [key: string]: unknown };
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
export interface Notification {
|
||||
method: string;
|
||||
// Allow unofficial extensions of `Notification.params` without impacting `NotificationParams`.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
params?: { [key: string]: any };
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +96,7 @@ export interface Error {
|
|||
* Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.).
|
||||
*/
|
||||
data?: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* A uniquely identifying ID for a request in JSON-RPC.
|
||||
|
|
@ -360,7 +361,7 @@ export interface Icon {
|
|||
*
|
||||
* If not provided, the client should assume the icon can be used with any theme.
|
||||
*/
|
||||
theme?: 'light' | 'dark';
|
||||
theme?: "light" | "dark";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -427,7 +428,7 @@ export interface Implementation extends BaseMetadata, Icons {
|
|||
*/
|
||||
export interface PingRequest extends JSONRPCRequest {
|
||||
method: "ping";
|
||||
params?: RequestParams
|
||||
params?: RequestParams;
|
||||
}
|
||||
|
||||
/* Progress notifications */
|
||||
|
|
@ -552,6 +553,7 @@ export interface ResourceRequestParams extends RequestParams {
|
|||
*
|
||||
* @category resources/read
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface ReadResourceRequestParams extends ResourceRequestParams {}
|
||||
|
||||
/**
|
||||
|
|
@ -588,6 +590,7 @@ export interface ResourceListChangedNotification extends JSONRPCNotification {
|
|||
*
|
||||
* @category resources/subscribe
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface SubscribeRequestParams extends ResourceRequestParams {}
|
||||
|
||||
/**
|
||||
|
|
@ -605,6 +608,7 @@ export interface SubscribeRequest extends JSONRPCRequest {
|
|||
*
|
||||
* @category resources/unsubscribe
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface UnsubscribeRequestParams extends ResourceRequestParams {}
|
||||
|
||||
/**
|
||||
|
|
@ -1647,15 +1651,176 @@ export interface BooleanSchema {
|
|||
default?: boolean;
|
||||
}
|
||||
|
||||
export interface EnumSchema {
|
||||
/**
|
||||
* Schema for single-selection enumeration without display titles for options.
|
||||
*/
|
||||
export interface UntitledSingleSelectEnumSchema {
|
||||
type: "string";
|
||||
/**
|
||||
* Optional title for the enum field.
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* Optional description for the enum field.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* Array of enum values to choose from.
|
||||
*/
|
||||
enum: string[];
|
||||
/**
|
||||
* Optional default value.
|
||||
*/
|
||||
default?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for single-selection enumeration with display titles for each option.
|
||||
*/
|
||||
export interface TitledSingleSelectEnumSchema {
|
||||
type: "string";
|
||||
/**
|
||||
* Optional title for the enum field.
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* Optional description for the enum field.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* Array of enum options with values and display labels.
|
||||
*/
|
||||
oneOf: Array<{
|
||||
/**
|
||||
* The enum value.
|
||||
*/
|
||||
const: string;
|
||||
/**
|
||||
* Display label for this option.
|
||||
*/
|
||||
title: string;
|
||||
}>;
|
||||
/**
|
||||
* Optional default value.
|
||||
*/
|
||||
default?: string;
|
||||
}
|
||||
|
||||
// Combined single selection enumeration
|
||||
export type SingleSelectEnumSchema =
|
||||
| UntitledSingleSelectEnumSchema
|
||||
| TitledSingleSelectEnumSchema;
|
||||
|
||||
/**
|
||||
* Schema for multiple-selection enumeration without display titles for options.
|
||||
*/
|
||||
export interface UntitledMultiSelectEnumSchema {
|
||||
type: "array";
|
||||
/**
|
||||
* Optional title for the enum field.
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* Optional description for the enum field.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* Minimum number of items to select.
|
||||
*/
|
||||
minItems?: number;
|
||||
/**
|
||||
* Maximum number of items to select.
|
||||
*/
|
||||
maxItems?: number;
|
||||
/**
|
||||
* Schema for the array items.
|
||||
*/
|
||||
items: {
|
||||
type: "string";
|
||||
/**
|
||||
* Array of enum values to choose from.
|
||||
*/
|
||||
enum: string[];
|
||||
};
|
||||
/**
|
||||
* Optional default value.
|
||||
*/
|
||||
default?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for multiple-selection enumeration with display titles for each option.
|
||||
*/
|
||||
export interface TitledMultiSelectEnumSchema {
|
||||
type: "array";
|
||||
/**
|
||||
* Optional title for the enum field.
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* Optional description for the enum field.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* Minimum number of items to select.
|
||||
*/
|
||||
minItems?: number;
|
||||
/**
|
||||
* Maximum number of items to select.
|
||||
*/
|
||||
maxItems?: number;
|
||||
/**
|
||||
* Schema for array items with enum options and display labels.
|
||||
*/
|
||||
items: {
|
||||
/**
|
||||
* Array of enum options with values and display labels.
|
||||
*/
|
||||
anyOf: Array<{
|
||||
/**
|
||||
* The constant enum value.
|
||||
*/
|
||||
const: string;
|
||||
/**
|
||||
* Display title for this option.
|
||||
*/
|
||||
title: string;
|
||||
}>;
|
||||
};
|
||||
/**
|
||||
* Optional default value.
|
||||
*/
|
||||
default?: string[];
|
||||
}
|
||||
|
||||
// Combined multiple selection enumeration
|
||||
export type MultiSelectEnumSchema =
|
||||
| UntitledMultiSelectEnumSchema
|
||||
| TitledMultiSelectEnumSchema;
|
||||
|
||||
/**
|
||||
* Use TitledSingleSelectEnumSchema instead.
|
||||
* This interface will be removed in a future version.
|
||||
*/
|
||||
export interface LegacyTitledEnumSchema {
|
||||
type: "string";
|
||||
title?: string;
|
||||
description?: string;
|
||||
enum: string[];
|
||||
enumNames?: string[]; // Display names for enum values
|
||||
/**
|
||||
* (Legacy) Display names for enum values.
|
||||
* Non-standard according to JSON schema 2020-12.
|
||||
*/
|
||||
enumNames?: string[];
|
||||
default?: string;
|
||||
}
|
||||
|
||||
// Union type for all enum schemas
|
||||
export type EnumSchema =
|
||||
| SingleSelectEnumSchema
|
||||
| MultiSelectEnumSchema
|
||||
| LegacyTitledEnumSchema;
|
||||
|
||||
/**
|
||||
* The client's response to an elicitation request.
|
||||
*
|
||||
|
|
@ -1674,7 +1839,7 @@ export interface ElicitResult extends Result {
|
|||
* The submitted form data, only present when action is "accept".
|
||||
* Contains values matching the requested schema.
|
||||
*/
|
||||
content?: { [key: string]: string | number | boolean };
|
||||
content?: { [key: string]: string | number | boolean | string[] };
|
||||
}
|
||||
|
||||
/* Client messages */
|
||||
|
|
|
|||
92
src/types.ts
92
src/types.ts
|
|
@ -1239,9 +1239,37 @@ export const NumberSchemaSchema = z.object({
|
|||
});
|
||||
|
||||
/**
|
||||
* Primitive schema definition for enum fields.
|
||||
* Schema for single-selection enumeration without display titles for options.
|
||||
*/
|
||||
export const EnumSchemaSchema = z.object({
|
||||
export const UntitledSingleSelectEnumSchemaSchema = z.object({
|
||||
type: z.literal('string'),
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
enum: z.array(z.string()),
|
||||
default: z.string().optional()
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for single-selection enumeration with display titles for each option.
|
||||
*/
|
||||
export const TitledSingleSelectEnumSchemaSchema = z.object({
|
||||
type: z.literal('string'),
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
oneOf: z.array(
|
||||
z.object({
|
||||
const: z.string(),
|
||||
title: z.string()
|
||||
})
|
||||
),
|
||||
default: z.string().optional()
|
||||
});
|
||||
|
||||
/**
|
||||
* Use TitledSingleSelectEnumSchema instead.
|
||||
* This interface will be removed in a future version.
|
||||
*/
|
||||
export const LegacyTitledEnumSchemaSchema = z.object({
|
||||
type: z.literal('string'),
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
|
|
@ -1250,6 +1278,55 @@ export const EnumSchemaSchema = z.object({
|
|||
default: z.string().optional()
|
||||
});
|
||||
|
||||
// Combined single selection enumeration
|
||||
export const SingleSelectEnumSchemaSchema = z.union([UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema]);
|
||||
|
||||
/**
|
||||
* Schema for multiple-selection enumeration without display titles for options.
|
||||
*/
|
||||
export const UntitledMultiSelectEnumSchemaSchema = z.object({
|
||||
type: z.literal('array'),
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
minItems: z.number().optional(),
|
||||
maxItems: z.number().optional(),
|
||||
items: z.object({
|
||||
type: z.literal('string'),
|
||||
enum: z.array(z.string())
|
||||
}),
|
||||
default: z.array(z.string()).optional()
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for multiple-selection enumeration with display titles for each option.
|
||||
*/
|
||||
export const TitledMultiSelectEnumSchemaSchema = z.object({
|
||||
type: z.literal('array'),
|
||||
title: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
minItems: z.number().optional(),
|
||||
maxItems: z.number().optional(),
|
||||
items: z.object({
|
||||
anyOf: z.array(
|
||||
z.object({
|
||||
const: z.string(),
|
||||
title: z.string()
|
||||
})
|
||||
)
|
||||
}),
|
||||
default: z.array(z.string()).optional()
|
||||
});
|
||||
|
||||
/**
|
||||
* Combined schema for multiple-selection enumeration
|
||||
*/
|
||||
export const MultiSelectEnumSchemaSchema = z.union([UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema]);
|
||||
|
||||
/**
|
||||
* Primitive schema definition for enum fields.
|
||||
*/
|
||||
export const EnumSchemaSchema = z.union([LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema]);
|
||||
|
||||
/**
|
||||
* Union of all primitive schema definitions.
|
||||
*/
|
||||
|
|
@ -1298,7 +1375,7 @@ export const ElicitResultSchema = ResultSchema.extend({
|
|||
* The submitted form data, only present when action is "accept".
|
||||
* Contains values matching the requested schema.
|
||||
*/
|
||||
content: z.record(z.union([z.string(), z.number(), z.boolean()])).optional()
|
||||
content: z.record(z.union([z.string(), z.number(), z.boolean(), z.array(z.string())])).optional()
|
||||
});
|
||||
|
||||
/* Autocomplete */
|
||||
|
|
@ -1667,7 +1744,16 @@ export type CreateMessageResult = Infer<typeof CreateMessageResultSchema>;
|
|||
export type BooleanSchema = Infer<typeof BooleanSchemaSchema>;
|
||||
export type StringSchema = Infer<typeof StringSchemaSchema>;
|
||||
export type NumberSchema = Infer<typeof NumberSchemaSchema>;
|
||||
|
||||
export type EnumSchema = Infer<typeof EnumSchemaSchema>;
|
||||
export type UntitledSingleSelectEnumSchema = Infer<typeof UntitledSingleSelectEnumSchemaSchema>;
|
||||
export type TitledSingleSelectEnumSchema = Infer<typeof TitledSingleSelectEnumSchemaSchema>;
|
||||
export type LegacyTitledEnumSchema = Infer<typeof LegacyTitledEnumSchemaSchema>;
|
||||
export type UntitledMultiSelectEnumSchema = Infer<typeof UntitledMultiSelectEnumSchemaSchema>;
|
||||
export type TitledMultiSelectEnumSchema = Infer<typeof TitledMultiSelectEnumSchemaSchema>;
|
||||
export type SingleSelectEnumSchema = Infer<typeof SingleSelectEnumSchemaSchema>;
|
||||
export type MultiSelectEnumSchema = Infer<typeof MultiSelectEnumSchemaSchema>;
|
||||
|
||||
export type PrimitiveSchemaDefinition = Infer<typeof PrimitiveSchemaDefinitionSchema>;
|
||||
export type ElicitRequestParams = Infer<typeof ElicitRequestParamsSchema>;
|
||||
export type ElicitRequest = Infer<typeof ElicitRequestSchema>;
|
||||
|
|
|
|||
Loading…
Reference in a new issue