Skip to content

Commit

Permalink
Create ResourceTemplate class and move listCallback into it
Browse files Browse the repository at this point in the history
  • Loading branch information
jspahrsummers committed Jan 8, 2025
1 parent 45f99e6 commit 8356cb9
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 55 deletions.
42 changes: 35 additions & 7 deletions src/server/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -1032,7 +1061,7 @@ describe("Server.resource", () => {

server.resource(
"test",
new UriTemplate("test://resource/{id}"),
new ResourceTemplate("test://resource/{id}", undefined),
async () => ({
contents: [
{
Expand Down Expand Up @@ -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",
Expand All @@ -1089,7 +1117,7 @@ describe("Server.resource", () => {
uri: "test://resource/2",
},
],
}),
})),
async (uri) => ({
contents: [
{
Expand Down Expand Up @@ -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: [
{
Expand Down Expand Up @@ -1201,7 +1229,7 @@ describe("Server.resource", () => {

server.resource(
"test",
new UriTemplate("test://resource/{id}"),
new ResourceTemplate("test://resource/{id}", undefined),
async () => ({
contents: [
{
Expand All @@ -1215,7 +1243,7 @@ describe("Server.resource", () => {
expect(() => {
server.resource(
"test",
new UriTemplate("test://resource/{id}"),
new ResourceTemplate("test://resource/{id}", undefined),
async () => ({
contents: [
{
Expand Down
101 changes: 53 additions & 48 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,8 @@ export type ReadResourceTemplateCallback = (
) => ReadResourceResult | Promise<ReadResourceResult>;

type RegisteredResourceTemplate = {
uriTemplate: UriTemplate;
resourceTemplate: ResourceTemplate;
metadata?: ResourceMetadata;
listCallback?: ListResourcesCallback;
readCallback: ReadResourceTemplateCallback;
};

Expand Down Expand Up @@ -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,
Expand All @@ -579,7 +578,7 @@ export class Server<
this._registeredResourceTemplates,
).map(([name, template]) => ({
name,
uriTemplate: template.uriTemplate.toString(),
uriTemplate: template.resourceTemplate.uriTemplate.toString(),
...template.metadata,
}));

Expand All @@ -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);
}
Expand All @@ -623,76 +624,51 @@ 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;
if (typeof rest[0] === "object") {
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,
});
}
}
Expand Down Expand Up @@ -723,28 +699,57 @@ 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]) {
throw new Error(`Resource template ${name} is already registered`);
}

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;
}
}

0 comments on commit 8356cb9

Please sign in to comment.