332 lines
9 KiB
TypeScript
332 lines
9 KiB
TypeScript
import {
|
|
mergeCapabilities,
|
|
Protocol,
|
|
ProtocolOptions,
|
|
RequestOptions,
|
|
} from "../shared/protocol.js";
|
|
import {
|
|
ClientCapabilities,
|
|
CreateMessageRequest,
|
|
CreateMessageResultSchema,
|
|
EmptyResultSchema,
|
|
Implementation,
|
|
InitializedNotificationSchema,
|
|
InitializeRequest,
|
|
InitializeRequestSchema,
|
|
InitializeResult,
|
|
LATEST_PROTOCOL_VERSION,
|
|
ListRootsRequest,
|
|
ListRootsResultSchema,
|
|
LoggingMessageNotification,
|
|
Notification,
|
|
Request,
|
|
ResourceUpdatedNotification,
|
|
Result,
|
|
ServerCapabilities,
|
|
ServerNotification,
|
|
ServerRequest,
|
|
ServerResult,
|
|
SUPPORTED_PROTOCOL_VERSIONS,
|
|
} from "../types.js";
|
|
|
|
export type ServerOptions = ProtocolOptions & {
|
|
/**
|
|
* Capabilities to advertise as being supported by this server.
|
|
*/
|
|
capabilities?: ServerCapabilities;
|
|
|
|
/**
|
|
* Optional instructions describing how to use the server and its features.
|
|
*/
|
|
instructions?: string;
|
|
};
|
|
|
|
/**
|
|
* An MCP server on top of a pluggable transport.
|
|
*
|
|
* This server will automatically respond to the initialization flow as initiated from the client.
|
|
*
|
|
* To use with custom types, extend the base Request/Notification/Result types and pass them as type parameters:
|
|
*
|
|
* ```typescript
|
|
* // Custom schemas
|
|
* const CustomRequestSchema = RequestSchema.extend({...})
|
|
* const CustomNotificationSchema = NotificationSchema.extend({...})
|
|
* const CustomResultSchema = ResultSchema.extend({...})
|
|
*
|
|
* // Type aliases
|
|
* type CustomRequest = z.infer<typeof CustomRequestSchema>
|
|
* type CustomNotification = z.infer<typeof CustomNotificationSchema>
|
|
* type CustomResult = z.infer<typeof CustomResultSchema>
|
|
*
|
|
* // Create typed server
|
|
* const server = new Server<CustomRequest, CustomNotification, CustomResult>({
|
|
* name: "CustomServer",
|
|
* version: "1.0.0"
|
|
* })
|
|
* ```
|
|
*/
|
|
export class Server<
|
|
RequestT extends Request = Request,
|
|
NotificationT extends Notification = Notification,
|
|
ResultT extends Result = Result,
|
|
> extends Protocol<
|
|
ServerRequest | RequestT,
|
|
ServerNotification | NotificationT,
|
|
ServerResult | ResultT
|
|
> {
|
|
private _clientCapabilities?: ClientCapabilities;
|
|
private _clientVersion?: Implementation;
|
|
private _capabilities: ServerCapabilities;
|
|
private _instructions?: string;
|
|
|
|
/**
|
|
* Callback for when initialization has fully completed (i.e., the client has sent an `initialized` notification).
|
|
*/
|
|
oninitialized?: () => void;
|
|
|
|
/**
|
|
* Initializes this server with the given name and version information.
|
|
*/
|
|
constructor(
|
|
private _serverInfo: Implementation,
|
|
options?: ServerOptions,
|
|
) {
|
|
super(options);
|
|
this._capabilities = options?.capabilities ?? {};
|
|
this._instructions = options?.instructions;
|
|
|
|
this.setRequestHandler(InitializeRequestSchema, (request) =>
|
|
this._oninitialize(request),
|
|
);
|
|
this.setNotificationHandler(InitializedNotificationSchema, () =>
|
|
this.oninitialized?.(),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Registers new capabilities. This can only be called before connecting to a transport.
|
|
*
|
|
* The new capabilities will be merged with any existing capabilities previously given (e.g., at initialization).
|
|
*/
|
|
public registerCapabilities(capabilities: ServerCapabilities): void {
|
|
if (this.transport) {
|
|
throw new Error(
|
|
"Cannot register capabilities after connecting to transport",
|
|
);
|
|
}
|
|
|
|
this._capabilities = mergeCapabilities(this._capabilities, capabilities);
|
|
}
|
|
|
|
protected assertCapabilityForMethod(method: RequestT["method"]): void {
|
|
switch (method as ServerRequest["method"]) {
|
|
case "sampling/createMessage":
|
|
if (!this._clientCapabilities?.sampling) {
|
|
throw new Error(
|
|
`Client does not support sampling (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "roots/list":
|
|
if (!this._clientCapabilities?.roots) {
|
|
throw new Error(
|
|
`Client does not support listing roots (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "ping":
|
|
// No specific capability required for ping
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected assertNotificationCapability(
|
|
method: (ServerNotification | NotificationT)["method"],
|
|
): void {
|
|
switch (method as ServerNotification["method"]) {
|
|
case "notifications/message":
|
|
if (!this._capabilities.logging) {
|
|
throw new Error(
|
|
`Server does not support logging (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "notifications/resources/updated":
|
|
case "notifications/resources/list_changed":
|
|
if (!this._capabilities.resources) {
|
|
throw new Error(
|
|
`Server does not support notifying about resources (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "notifications/tools/list_changed":
|
|
if (!this._capabilities.tools) {
|
|
throw new Error(
|
|
`Server does not support notifying of tool list changes (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "notifications/prompts/list_changed":
|
|
if (!this._capabilities.prompts) {
|
|
throw new Error(
|
|
`Server does not support notifying of prompt list changes (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "notifications/cancelled":
|
|
// Cancellation notifications are always allowed
|
|
break;
|
|
|
|
case "notifications/progress":
|
|
// Progress notifications are always allowed
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected assertRequestHandlerCapability(method: string): void {
|
|
switch (method) {
|
|
case "sampling/createMessage":
|
|
if (!this._capabilities.sampling) {
|
|
throw new Error(
|
|
`Server does not support sampling (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "logging/setLevel":
|
|
if (!this._capabilities.logging) {
|
|
throw new Error(
|
|
`Server does not support logging (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "prompts/get":
|
|
case "prompts/list":
|
|
if (!this._capabilities.prompts) {
|
|
throw new Error(
|
|
`Server does not support prompts (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "resources/list":
|
|
case "resources/templates/list":
|
|
case "resources/read":
|
|
if (!this._capabilities.resources) {
|
|
throw new Error(
|
|
`Server does not support resources (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "tools/call":
|
|
case "tools/list":
|
|
if (!this._capabilities.tools) {
|
|
throw new Error(
|
|
`Server does not support tools (required for ${method})`,
|
|
);
|
|
}
|
|
break;
|
|
|
|
case "ping":
|
|
case "initialize":
|
|
// No specific capability required for these methods
|
|
break;
|
|
}
|
|
}
|
|
|
|
private async _oninitialize(
|
|
request: InitializeRequest,
|
|
): Promise<InitializeResult> {
|
|
const requestedVersion = request.params.protocolVersion;
|
|
|
|
this._clientCapabilities = request.params.capabilities;
|
|
this._clientVersion = request.params.clientInfo;
|
|
|
|
return {
|
|
protocolVersion: SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion)
|
|
? requestedVersion
|
|
: LATEST_PROTOCOL_VERSION,
|
|
capabilities: this.getCapabilities(),
|
|
serverInfo: this._serverInfo,
|
|
...(this._instructions && { instructions: this._instructions }),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* After initialization has completed, this will be populated with the client's reported capabilities.
|
|
*/
|
|
getClientCapabilities(): ClientCapabilities | undefined {
|
|
return this._clientCapabilities;
|
|
}
|
|
|
|
/**
|
|
* After initialization has completed, this will be populated with information about the client's name and version.
|
|
*/
|
|
getClientVersion(): Implementation | undefined {
|
|
return this._clientVersion;
|
|
}
|
|
|
|
private getCapabilities(): ServerCapabilities {
|
|
return this._capabilities;
|
|
}
|
|
|
|
async ping() {
|
|
return this.request({ method: "ping" }, EmptyResultSchema);
|
|
}
|
|
|
|
async createMessage(
|
|
params: CreateMessageRequest["params"],
|
|
options?: RequestOptions,
|
|
) {
|
|
return this.request(
|
|
{ method: "sampling/createMessage", params },
|
|
CreateMessageResultSchema,
|
|
options,
|
|
);
|
|
}
|
|
|
|
async listRoots(
|
|
params?: ListRootsRequest["params"],
|
|
options?: RequestOptions,
|
|
) {
|
|
return this.request(
|
|
{ method: "roots/list", params },
|
|
ListRootsResultSchema,
|
|
options,
|
|
);
|
|
}
|
|
|
|
async sendLoggingMessage(params: LoggingMessageNotification["params"]) {
|
|
return this.notification({ method: "notifications/message", params });
|
|
}
|
|
|
|
async sendResourceUpdated(params: ResourceUpdatedNotification["params"]) {
|
|
return this.notification({
|
|
method: "notifications/resources/updated",
|
|
params,
|
|
});
|
|
}
|
|
|
|
async sendResourceListChanged() {
|
|
return this.notification({
|
|
method: "notifications/resources/list_changed",
|
|
});
|
|
}
|
|
|
|
async sendToolListChanged() {
|
|
return this.notification({ method: "notifications/tools/list_changed" });
|
|
}
|
|
|
|
async sendPromptListChanged() {
|
|
return this.notification({ method: "notifications/prompts/list_changed" });
|
|
}
|
|
}
|