diff --git a/src/schema.test.ts b/src/schema.test.ts new file mode 100644 index 0000000..c8f48d4 --- /dev/null +++ b/src/schema.test.ts @@ -0,0 +1,42 @@ +import { BuildListTool, BuildGetTool } from "./schema.js"; +import { Resource } from "./common/api/types.js"; + +describe("BuildListTool", () => { + it("should create a tool for listing resources", () => { + const resource: Resource = { + singular: "example", + plural: "examples", + parents: [], + children: [], + patternElems: [], + schema: { properties: {} }, + customMethods: [], + }; + const tool = BuildListTool(resource, "example"); + expect(tool.name).toBe("list-example"); + expect(tool.description).toBe("List all example resources"); + expect(tool.inputSchema).toEqual({ type: "object", properties: {} }); + }); +}); + +describe("BuildGetTool", () => { + it("should create a tool for getting a resource", () => { + const resource: Resource = { + singular: "example", + plural: "examples", + parents: [], + children: [], + patternElems: [], + schema: { properties: {} }, + customMethods: [], + }; + const tool = BuildGetTool(resource, "example"); + expect(tool.name).toBe("get-example"); + expect(tool.description).toBe("Get details of a specific example"); + expect(tool.inputSchema).toEqual({ + type: "object", + properties: { path: { type: "string" } }, + required: ["path"], + }); + }); +}); diff --git a/src/schema.ts b/src/schema.ts index 7da9401..baa7de6 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -95,6 +95,46 @@ export function BuildUpdateTool(resource: Resource, resourceName: string): Tool }; } +export function BuildListTool(resource: Resource, resourceName: string): Tool { + // get all path params except the last one, which is the resource name. For list + // we ignore it. + const parentFieldNames = resource.patternElems.filter((elem) => elem.startsWith("{")).slice(0, -1); + // strip the braces from the field names + for (let i = 0; i < parentFieldNames.length; i++) { + parentFieldNames[i] = parentFieldNames[i].slice(1, -1); + } + const properties: Record = {}; + // iterate parentFieldNames + for (const name of parentFieldNames) { + properties[name] = { + type: "string", + description: `The ${name} to filter the list of ${resourceName} resources`, + }; + } + return { + name: `list-${resourceName}`, + description: `List all ${resourceName} resources`, + inputSchema: { + type: "object", + properties: properties, + }, + }; +} + +export function BuildGetTool(resource: Resource, resourceName: string): Tool { + return { + name: `get-${resourceName}`, + description: `Get details of a specific ${resourceName}`, + inputSchema: { + type: "object", + properties: { + path: { type: "string" }, + }, + required: ["path"], + }, + }; +} + export function BuildResource(resource: Resource, resourceName: string, serverUrl: string, prefix: string) { return { uriTemplate: `${serverUrl}/${resource.patternElems.join('/')}`, diff --git a/src/server.ts b/src/server.ts index f83ea7a..805af1c 100755 --- a/src/server.ts +++ b/src/server.ts @@ -2,7 +2,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" import { fetchOpenAPI, OpenAPIImpl } from "./common/openapi/openapi.js"; import { APIClient } from "./common/api/api.js"; import { OpenAPI as OpenAPIType, Resource } from "./common/api/types.js"; -import { BuildCreateTool, BuildDeleteTool, BuildResource, BuildUpdateTool } from "./schema.js"; +import { BuildCreateTool, BuildDeleteTool, BuildResource, BuildUpdateTool, BuildListTool, BuildGetTool } from "./schema.js"; import axios from "axios"; import { Client } from "./common/client/client.js"; @@ -99,7 +99,9 @@ export async function main() { tools.push(BuildCreateTool(resource, resourceName)); tools.push(BuildDeleteTool(resource, resourceName)); tools.push(BuildUpdateTool(resource, resourceName)); - resourceList.push(BuildResource(resource, resourceName, a.serverUrl(), prefix)) + tools.push(BuildListTool(resource, resourceName)); + tools.push(BuildGetTool(resource, resourceName)); + resourceList.push(BuildResource(resource, resourceName, a.serverUrl(), prefix)); } // empty handlers @@ -179,6 +181,27 @@ export async function main() { }], isError: false }; + } else if (matchingTool.name.startsWith("list")) { + const resourceSingular = matchingTool.name.split("-")[1]; + const resource = a.resources()[resourceSingular]; + const resp = await client.list({}, resource, a.serverUrl(), request.params.arguments!); + return { + content: [{ + type: "text", + text: JSON.stringify(resp) + }], + isError: false + }; + } else if (matchingTool.name.startsWith("get")) { + const path = request.params.arguments!["path"]; + const resp = await client.get({}, a.serverUrl(), path); + return { + content: [{ + type: "text", + text: JSON.stringify(resp) + }], + isError: false + }; } } catch (error) { return {