OpenHue MCP Server
by lsemenenko
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-constant-binary-expression */
/* eslint-disable @typescript-eslint/no-unused-expressions */
import { Server } from "./index.js";
import { z } from "zod";
import { RequestSchema, NotificationSchema, ResultSchema, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, CreateMessageRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, SetLevelRequestSchema, ErrorCode, } from "../types.js";
import { InMemoryTransport } from "../inMemory.js";
import { Client } from "../client/index.js";
test("should accept latest protocol version", async () => {
var _a;
let sendPromiseResolve;
const sendPromise = new Promise((resolve) => {
sendPromiseResolve = resolve;
});
const serverTransport = {
start: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
send: jest.fn().mockImplementation((message) => {
if (message.id === 1 && message.result) {
expect(message.result).toEqual({
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: expect.any(Object),
serverInfo: {
name: "test server",
version: "1.0",
},
});
sendPromiseResolve(undefined);
}
return Promise.resolve();
}),
};
const server = new Server({
name: "test server",
version: "1.0",
}, {
capabilities: {
prompts: {},
resources: {},
tools: {},
logging: {},
},
});
await server.connect(serverTransport);
// Simulate initialize request with latest version
(_a = serverTransport.onmessage) === null || _a === void 0 ? void 0 : _a.call(serverTransport, {
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: {},
clientInfo: {
name: "test client",
version: "1.0",
},
},
});
await expect(sendPromise).resolves.toBeUndefined();
});
test("should accept supported older protocol version", async () => {
var _a;
const OLD_VERSION = SUPPORTED_PROTOCOL_VERSIONS[1];
let sendPromiseResolve;
const sendPromise = new Promise((resolve) => {
sendPromiseResolve = resolve;
});
const serverTransport = {
start: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
send: jest.fn().mockImplementation((message) => {
if (message.id === 1 && message.result) {
expect(message.result).toEqual({
protocolVersion: OLD_VERSION,
capabilities: expect.any(Object),
serverInfo: {
name: "test server",
version: "1.0",
},
});
sendPromiseResolve(undefined);
}
return Promise.resolve();
}),
};
const server = new Server({
name: "test server",
version: "1.0",
}, {
capabilities: {
prompts: {},
resources: {},
tools: {},
logging: {},
},
});
await server.connect(serverTransport);
// Simulate initialize request with older version
(_a = serverTransport.onmessage) === null || _a === void 0 ? void 0 : _a.call(serverTransport, {
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: OLD_VERSION,
capabilities: {},
clientInfo: {
name: "test client",
version: "1.0",
},
},
});
await expect(sendPromise).resolves.toBeUndefined();
});
test("should handle unsupported protocol version", async () => {
var _a;
let sendPromiseResolve;
const sendPromise = new Promise((resolve) => {
sendPromiseResolve = resolve;
});
const serverTransport = {
start: jest.fn().mockResolvedValue(undefined),
close: jest.fn().mockResolvedValue(undefined),
send: jest.fn().mockImplementation((message) => {
if (message.id === 1 && message.result) {
expect(message.result).toEqual({
protocolVersion: LATEST_PROTOCOL_VERSION,
capabilities: expect.any(Object),
serverInfo: {
name: "test server",
version: "1.0",
},
});
sendPromiseResolve(undefined);
}
return Promise.resolve();
}),
};
const server = new Server({
name: "test server",
version: "1.0",
}, {
capabilities: {
prompts: {},
resources: {},
tools: {},
logging: {},
},
});
await server.connect(serverTransport);
// Simulate initialize request with unsupported version
(_a = serverTransport.onmessage) === null || _a === void 0 ? void 0 : _a.call(serverTransport, {
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: "invalid-version",
capabilities: {},
clientInfo: {
name: "test client",
version: "1.0",
},
},
});
await expect(sendPromise).resolves.toBeUndefined();
});
test("should respect client capabilities", async () => {
const server = new Server({
name: "test server",
version: "1.0",
}, {
capabilities: {
prompts: {},
resources: {},
tools: {},
logging: {},
},
enforceStrictCapabilities: true,
});
const client = new Client({
name: "test client",
version: "1.0",
}, {
capabilities: {
sampling: {},
},
});
// Implement request handler for sampling/createMessage
client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
// Mock implementation of createMessage
return {
model: "test-model",
role: "assistant",
content: {
type: "text",
text: "This is a test response",
},
};
});
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([
client.connect(clientTransport),
server.connect(serverTransport),
]);
expect(server.getClientCapabilities()).toEqual({ sampling: {} });
// This should work because sampling is supported by the client
await expect(server.createMessage({
messages: [],
maxTokens: 10,
})).resolves.not.toThrow();
// This should still throw because roots are not supported by the client
await expect(server.listRoots()).rejects.toThrow(/^Client does not support/);
});
test("should respect server notification capabilities", async () => {
const server = new Server({
name: "test server",
version: "1.0",
}, {
capabilities: {
logging: {},
},
enforceStrictCapabilities: true,
});
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
// This should work because logging is supported by the server
await expect(server.sendLoggingMessage({
level: "info",
data: "Test log message",
})).resolves.not.toThrow();
// This should throw because resource notificaitons are not supported by the server
await expect(server.sendResourceUpdated({ uri: "test://resource" })).rejects.toThrow(/^Server does not support/);
});
test("should only allow setRequestHandler for declared capabilities", () => {
const server = new Server({
name: "test server",
version: "1.0",
}, {
capabilities: {
prompts: {},
resources: {},
},
});
// These should work because the capabilities are declared
expect(() => {
server.setRequestHandler(ListPromptsRequestSchema, () => ({ prompts: [] }));
}).not.toThrow();
expect(() => {
server.setRequestHandler(ListResourcesRequestSchema, () => ({
resources: [],
}));
}).not.toThrow();
// These should throw because the capabilities are not declared
expect(() => {
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: [] }));
}).toThrow(/^Server does not support tools/);
expect(() => {
server.setRequestHandler(SetLevelRequestSchema, () => ({}));
}).toThrow(/^Server does not support logging/);
});
/*
Test that custom request/notification/result schemas can be used with the Server class.
*/
test("should typecheck", () => {
const GetWeatherRequestSchema = RequestSchema.extend({
method: z.literal("weather/get"),
params: z.object({
city: z.string(),
}),
});
const GetForecastRequestSchema = RequestSchema.extend({
method: z.literal("weather/forecast"),
params: z.object({
city: z.string(),
days: z.number(),
}),
});
const WeatherForecastNotificationSchema = NotificationSchema.extend({
method: z.literal("weather/alert"),
params: z.object({
severity: z.enum(["warning", "watch"]),
message: z.string(),
}),
});
const WeatherRequestSchema = GetWeatherRequestSchema.or(GetForecastRequestSchema);
const WeatherNotificationSchema = WeatherForecastNotificationSchema;
const WeatherResultSchema = ResultSchema.extend({
temperature: z.number(),
conditions: z.string(),
});
// Create a typed Server for weather data
const weatherServer = new Server({
name: "WeatherServer",
version: "1.0.0",
}, {
capabilities: {
prompts: {},
resources: {},
tools: {},
logging: {},
},
});
// Typecheck that only valid weather requests/notifications/results are allowed
weatherServer.setRequestHandler(GetWeatherRequestSchema, (request) => {
return {
temperature: 72,
conditions: "sunny",
};
});
weatherServer.setNotificationHandler(WeatherForecastNotificationSchema, (notification) => {
console.log(`Weather alert: ${notification.params.message}`);
});
});
test("should handle server cancelling a request", async () => {
const server = new Server({
name: "test server",
version: "1.0",
}, {
capabilities: {
sampling: {},
},
});
const client = new Client({
name: "test client",
version: "1.0",
}, {
capabilities: {
sampling: {},
},
});
// Set up client to delay responding to createMessage
client.setRequestHandler(CreateMessageRequestSchema, async (_request, extra) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return {
model: "test",
role: "assistant",
content: {
type: "text",
text: "Test response",
},
};
});
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([
client.connect(clientTransport),
server.connect(serverTransport),
]);
// Set up abort controller
const controller = new AbortController();
// Issue request but cancel it immediately
const createMessagePromise = server.createMessage({
messages: [],
maxTokens: 10,
}, {
signal: controller.signal,
});
controller.abort("Cancelled by test");
// Request should be rejected
await expect(createMessagePromise).rejects.toBe("Cancelled by test");
});
test("should handle request timeout", async () => {
const server = new Server({
name: "test server",
version: "1.0",
}, {
capabilities: {
sampling: {},
},
});
// Set up client that delays responses
const client = new Client({
name: "test client",
version: "1.0",
}, {
capabilities: {
sampling: {},
},
});
client.setRequestHandler(CreateMessageRequestSchema, async (_request, extra) => {
await new Promise((resolve, reject) => {
const timeout = setTimeout(resolve, 100);
extra.signal.addEventListener("abort", () => {
clearTimeout(timeout);
reject(extra.signal.reason);
});
});
return {
model: "test",
role: "assistant",
content: {
type: "text",
text: "Test response",
},
};
});
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await Promise.all([
client.connect(clientTransport),
server.connect(serverTransport),
]);
// Request with 0 msec timeout should fail immediately
await expect(server.createMessage({
messages: [],
maxTokens: 10,
}, { timeout: 0 })).rejects.toMatchObject({
code: ErrorCode.RequestTimeout,
});
});
//# sourceMappingURL=index.test.js.map