Create ResourceTemplate class and move listCallback into it

This commit is contained in:
Justin Spahr-Summers 2025-01-08 13:44:12 +00:00
commit 8356cb900c
No known key found for this signature in database
2 changed files with 88 additions and 55 deletions

View file

@ -25,6 +25,7 @@ import { Transport } from "../shared/transport.js";
import { InMemoryTransport } from "../inMemory.js";
import { Client } from "../client/index.js";
import { UriTemplate } from "../shared/uriTemplate.js";
import { ResourceTemplate } from "./index.js";
test("should accept latest protocol version", async () => {
let sendPromiseResolve: (value: unknown) => void;
@ -553,6 +554,34 @@ test("should handle request timeout", async () => {
});
});
describe("ResourceTemplate", () => {
test("should create ResourceTemplate with string pattern", () => {
const template = new ResourceTemplate("test://{category}/{id}", undefined);
expect(template.uriTemplate.toString()).toBe("test://{category}/{id}");
expect(template.listCallback).toBeUndefined();
});
test("should create ResourceTemplate with UriTemplate", () => {
const uriTemplate = new UriTemplate("test://{category}/{id}");
const template = new ResourceTemplate(uriTemplate, undefined);
expect(template.uriTemplate).toBe(uriTemplate);
expect(template.listCallback).toBeUndefined();
});
test("should create ResourceTemplate with list callback", async () => {
const listCallback = jest.fn().mockResolvedValue({
resources: [{ name: "Test", uri: "test://example" }],
});
const template = new ResourceTemplate("test://{id}", listCallback);
expect(template.listCallback).toBe(listCallback);
const result = await template.listCallback?.();
expect(result?.resources).toHaveLength(1);
expect(listCallback).toHaveBeenCalled();
});
});
describe("Server.tool", () => {
test("should register zero-argument tool", async () => {
const server = new Server({
@ -1032,7 +1061,7 @@ describe("Server.resource", () => {
server.resource(
"test",
new UriTemplate("test://resource/{id}"),
new ResourceTemplate("test://resource/{id}", undefined),
async () => ({
contents: [
{
@ -1077,8 +1106,7 @@ describe("Server.resource", () => {
server.resource(
"test",
new UriTemplate("test://resource/{id}"),
async () => ({
new ResourceTemplate("test://resource/{id}", async () => ({
resources: [
{
name: "Resource 1",
@ -1089,7 +1117,7 @@ describe("Server.resource", () => {
uri: "test://resource/2",
},
],
}),
})),
async (uri) => ({
contents: [
{
@ -1134,7 +1162,7 @@ describe("Server.resource", () => {
server.resource(
"test",
new UriTemplate("test://resource/{category}/{id}"),
new ResourceTemplate("test://resource/{category}/{id}", undefined),
async (uri, { category, id }) => ({
contents: [
{
@ -1201,7 +1229,7 @@ describe("Server.resource", () => {
server.resource(
"test",
new UriTemplate("test://resource/{id}"),
new ResourceTemplate("test://resource/{id}", undefined),
async () => ({
contents: [
{
@ -1215,7 +1243,7 @@ describe("Server.resource", () => {
expect(() => {
server.resource(
"test",
new UriTemplate("test://resource/{id}"),
new ResourceTemplate("test://resource/{id}", undefined),
async () => ({
contents: [
{

View file

@ -98,9 +98,8 @@ export type ReadResourceTemplateCallback = (
) => ReadResourceResult | Promise<ReadResourceResult>;
type RegisteredResourceTemplate = {
uriTemplate: UriTemplate;
resourceTemplate: ResourceTemplate;
metadata?: ResourceMetadata;
listCallback?: ListResourcesCallback;
readCallback: ReadResourceTemplateCallback;
};
@ -558,11 +557,11 @@ export class Server<
const templateResources: Resource[] = [];
for (const template of Object.values(this._registeredResourceTemplates)) {
if (!template.listCallback) {
if (!template.resourceTemplate.listCallback) {
continue;
}
const result = await template.listCallback();
const result = await template.resourceTemplate.listCallback();
for (const resource of result.resources) {
templateResources.push({
...resource,
@ -579,7 +578,7 @@ export class Server<
this._registeredResourceTemplates,
).map(([name, template]) => ({
name,
uriTemplate: template.uriTemplate.toString(),
uriTemplate: template.resourceTemplate.uriTemplate.toString(),
...template.metadata,
}));
@ -597,7 +596,9 @@ export class Server<
// Then check templates
for (const template of Object.values(this._registeredResourceTemplates)) {
const variables = template.uriTemplate.match(uri.toString());
const variables = template.resourceTemplate.uriTemplate.match(
uri.toString(),
);
if (variables) {
return template.readCallback(uri, variables);
}
@ -623,48 +624,27 @@ export class Server<
): void;
/**
* Registers a resource `name` with a URI template pattern, which will use the given callback to respond to read requests.
* Registers a resource `name` with a template pattern, which will use the given callback to respond to read requests.
*/
resource(
name: string,
uriTemplate: UriTemplate,
template: ResourceTemplate,
readCallback: ReadResourceTemplateCallback,
): void;
/**
* Registers a resource `name` with a URI template pattern and metadata, which will use the given callback to respond to read requests.
* Registers a resource `name` with a template pattern and metadata, which will use the given callback to respond to read requests.
*/
resource(
name: string,
uriTemplate: UriTemplate,
template: ResourceTemplate,
metadata: ResourceMetadata,
readCallback: ReadResourceTemplateCallback,
): void;
/**
* Registers a resource `name` with a URI template pattern, which will use the list callback to enumerate matching resources and read callback to respond to read requests.
*/
resource(
name: string,
uriTemplate: UriTemplate,
listCallback: ListResourcesCallback,
readCallback: ReadResourceTemplateCallback,
): void;
/**
* Registers a resource `name` with a URI template pattern and metadata, which will use the list callback to enumerate matching resources and read callback to respond to read requests.
*/
resource(
name: string,
uriTemplate: UriTemplate,
metadata: ResourceMetadata,
listCallback: ListResourcesCallback,
readCallback: ReadResourceTemplateCallback,
): void;
resource(
name: string,
uriOrTemplate: string | UriTemplate,
uriOrTemplate: string | ResourceTemplate,
...rest: unknown[]
): void {
let metadata: ResourceMetadata | undefined;
@ -672,27 +652,23 @@ export class Server<
metadata = rest.shift() as ResourceMetadata;
}
let listCallback: ListResourcesCallback | undefined;
if (rest.length > 1) {
listCallback = rest.shift() as ListResourcesCallback;
}
const readCallback = rest[0] as
| ReadResourceCallback
| ReadResourceTemplateCallback;
if (typeof uriOrTemplate === "string") {
const readCallback = rest[0] as ReadResourceCallback;
this.registerResource({
name,
uri: uriOrTemplate,
metadata,
readCallback,
readCallback: readCallback as ReadResourceCallback,
});
} else {
const readCallback = rest[0] as ReadResourceTemplateCallback;
this.registerResourceTemplate({
name,
uriTemplate: uriOrTemplate,
resourceTemplate: uriOrTemplate,
metadata,
listCallback,
readCallback,
readCallback: readCallback as ReadResourceTemplateCallback,
});
}
}
@ -723,15 +699,13 @@ export class Server<
private registerResourceTemplate({
name,
uriTemplate,
resourceTemplate,
metadata,
listCallback,
readCallback,
}: {
name: string;
uriTemplate: UriTemplate;
resourceTemplate: ResourceTemplate;
metadata?: ResourceMetadata;
listCallback?: ListResourcesCallback;
readCallback: ReadResourceTemplateCallback;
}): void {
if (this._registeredResourceTemplates[name]) {
@ -739,12 +713,43 @@ export class Server<
}
this._registeredResourceTemplates[name] = {
uriTemplate,
resourceTemplate,
metadata,
listCallback,
readCallback,
};
this.setResourceRequestHandlers();
}
}
/**
* A resource template combines a URI pattern with optional functionality to enumerate
* all resources matching that pattern.
*/
export class ResourceTemplate {
private _uriTemplate: UriTemplate;
constructor(
uriTemplate: string | UriTemplate,
private _listCallback: ListResourcesCallback | undefined,
) {
this._uriTemplate =
typeof uriTemplate === "string"
? new UriTemplate(uriTemplate)
: uriTemplate;
}
/**
* Gets the URI template pattern.
*/
get uriTemplate(): UriTemplate {
return this._uriTemplate;
}
/**
* Gets the list callback, if one was provided.
*/
get listCallback(): ListResourcesCallback | undefined {
return this._listCallback;
}
}