@anyproto/anytype-mcp 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +64 -0
- package/.github/workflows/release.yml +40 -0
- package/.prettierrc +5 -0
- package/bin/cli.mjs +53 -87
- package/cli/__tests__/openapi-client.test.ts +119 -0
- package/cli/openapi-client.ts +91 -0
- package/eslint.config.js +49 -0
- package/package.json +12 -26
- package/scripts/__tests__/parse-openapi.test.ts +189 -0
- package/scripts/__tests__/start-server.test.ts +205 -0
- package/scripts/build-cli.js +30 -0
- package/scripts/build.ts +24 -0
- package/scripts/openapi.json +5888 -0
- package/scripts/parse-openapi.ts +30 -0
- package/scripts/start-server.ts +31 -0
- package/scripts/tools.json +5657 -0
- package/src/init-server.ts +51 -0
- package/test_cases/ebay-api.json +2555 -0
- package/test_cases/ebay-api.json.tools +3710 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { loadSpec } from "../openapi-client";
|
|
5
|
+
|
|
6
|
+
vi.mock("fs/promises");
|
|
7
|
+
vi.mock("axios");
|
|
8
|
+
|
|
9
|
+
const validOpenApiSpec = {
|
|
10
|
+
openapi: "3.1.0",
|
|
11
|
+
info: {
|
|
12
|
+
title: "Test API",
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
},
|
|
15
|
+
servers: [{ url: "http://localhost:3000" }],
|
|
16
|
+
paths: {
|
|
17
|
+
"/pets": {
|
|
18
|
+
get: {
|
|
19
|
+
operationId: "getPets",
|
|
20
|
+
responses: {
|
|
21
|
+
"200": { description: "A list of pets" },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe("loadSpec", () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("Local file loading", () => {
|
|
34
|
+
it("should load a valid OpenAPI spec from local JSON file", async () => {
|
|
35
|
+
// Mock fs.readFile to return a valid spec
|
|
36
|
+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(validOpenApiSpec));
|
|
37
|
+
|
|
38
|
+
const result = await loadSpec("./test-spec.json");
|
|
39
|
+
|
|
40
|
+
expect(result).toEqual(validOpenApiSpec);
|
|
41
|
+
expect(fs.readFile).toHaveBeenCalledWith("./test-spec.json", "utf-8");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should load a valid OpenAPI spec from local YAML file", async () => {
|
|
45
|
+
// Mock fs.readFile to return a valid YAML spec
|
|
46
|
+
const yamlSpec = JSON.stringify(validOpenApiSpec);
|
|
47
|
+
vi.mocked(fs.readFile).mockResolvedValue(yamlSpec);
|
|
48
|
+
|
|
49
|
+
const result = await loadSpec("./test-spec.yaml");
|
|
50
|
+
|
|
51
|
+
expect(result).toEqual(validOpenApiSpec);
|
|
52
|
+
expect(fs.readFile).toHaveBeenCalledWith("./test-spec.yaml", "utf-8");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should handle file not found error", async () => {
|
|
56
|
+
// Mock fs.readFile to throw ENOENT
|
|
57
|
+
vi.mocked(fs.readFile).mockImplementation(() => {
|
|
58
|
+
throw new Error("ENOENT: no such file or directory");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await expect(loadSpec("./non-existent.json")).rejects.toThrow("ENOENT: no such file or directory");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should handle invalid JSON", async () => {
|
|
65
|
+
// Mock fs.readFile to return invalid JSON
|
|
66
|
+
vi.mocked(fs.readFile).mockResolvedValue("invalid json");
|
|
67
|
+
|
|
68
|
+
await expect(loadSpec("./invalid.json")).rejects.toThrow("Unexpected token i in JSON at position 0");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should handle invalid YAML", async () => {
|
|
72
|
+
// Mock fs.readFile to return invalid YAML
|
|
73
|
+
vi.mocked(fs.readFile).mockResolvedValue("invalid: yaml: :");
|
|
74
|
+
|
|
75
|
+
await expect(loadSpec("./invalid.yaml")).rejects.toThrow(
|
|
76
|
+
"end of the stream or a document separator is expected at line 1, column 15:",
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("URL loading", () => {
|
|
82
|
+
it("should load a valid OpenAPI spec from URL", async () => {
|
|
83
|
+
// Mock axios.get to return a valid spec
|
|
84
|
+
vi.mocked(axios.get).mockResolvedValue({ data: validOpenApiSpec });
|
|
85
|
+
|
|
86
|
+
const result = await loadSpec("http://example.com/api-spec.json");
|
|
87
|
+
|
|
88
|
+
expect(result).toEqual(validOpenApiSpec);
|
|
89
|
+
expect(axios.get).toHaveBeenCalledWith("http://example.com/api-spec.json");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should load a valid OpenAPI spec from YAML URL", async () => {
|
|
93
|
+
// Mock axios.get to return a valid YAML spec
|
|
94
|
+
const yamlSpec = JSON.stringify(validOpenApiSpec);
|
|
95
|
+
vi.mocked(axios.get).mockResolvedValue({ data: yamlSpec });
|
|
96
|
+
|
|
97
|
+
const result = await loadSpec("http://example.com/api-spec.yaml");
|
|
98
|
+
|
|
99
|
+
expect(result).toEqual(validOpenApiSpec);
|
|
100
|
+
expect(axios.get).toHaveBeenCalledWith("http://example.com/api-spec.yaml");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should handle network errors", async () => {
|
|
104
|
+
// Mock axios.get to throw network error
|
|
105
|
+
vi.mocked(axios.get).mockRejectedValue(new Error("Network Error"));
|
|
106
|
+
|
|
107
|
+
await expect(loadSpec("http://example.com/api-spec.json")).rejects.toThrow("Network Error");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should handle invalid response data", async () => {
|
|
111
|
+
// Mock axios.get to return invalid data
|
|
112
|
+
vi.mocked(axios.get).mockResolvedValue({ data: "invalid data" });
|
|
113
|
+
|
|
114
|
+
await expect(loadSpec("http://example.com/api-spec.json")).rejects.toThrow(
|
|
115
|
+
"Unexpected token i in JSON at position 0",
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import axios from "axios";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import { OpenAPIV3 } from "openapi-types";
|
|
6
|
+
import { HttpClient, OpenAPIToMCPConverter } from "../src";
|
|
7
|
+
|
|
8
|
+
export async function loadSpec(specPath: string): Promise<OpenAPIV3.Document> {
|
|
9
|
+
let content: string;
|
|
10
|
+
if (specPath.startsWith("http://") || specPath.startsWith("https://")) {
|
|
11
|
+
const response = await axios.get(specPath);
|
|
12
|
+
content = JSON.stringify(response.data);
|
|
13
|
+
} else {
|
|
14
|
+
content = await fs.readFile(specPath, "utf-8");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return JSON.parse(content);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
const [, , specPath, command, ...args] = process.argv;
|
|
22
|
+
|
|
23
|
+
if (!specPath || !command) {
|
|
24
|
+
console.log("Usage: openapi-client <spec-path> <command> [args]");
|
|
25
|
+
console.log("\nCommands:");
|
|
26
|
+
console.log(" list List all available methods");
|
|
27
|
+
console.log(" call <method> [params] Call a method with optional JSON params");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const spec = await loadSpec(specPath);
|
|
32
|
+
const converter = new OpenAPIToMCPConverter(spec);
|
|
33
|
+
const baseUrl = spec.servers?.[0]?.url;
|
|
34
|
+
if (!baseUrl) {
|
|
35
|
+
throw new Error("No base URL found in OpenAPI spec");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const httpClient = new HttpClient({ baseUrl }, spec);
|
|
39
|
+
|
|
40
|
+
if (command === "list") {
|
|
41
|
+
// List all available methods
|
|
42
|
+
const { tools } = converter.convertToMCPTools();
|
|
43
|
+
console.log("\nAvailable methods:");
|
|
44
|
+
Object.entries(tools).forEach(([name, def]) => {
|
|
45
|
+
def.methods.forEach((method) => {
|
|
46
|
+
console.log(`\n${name}-${method.name}:`);
|
|
47
|
+
console.log(` Description: ${method.description}`);
|
|
48
|
+
console.log(" Parameters:");
|
|
49
|
+
const params = method.inputSchema.properties || {};
|
|
50
|
+
Object.entries(params).forEach(([paramName, schema]) => {
|
|
51
|
+
const required = method.inputSchema.required?.includes(paramName) ? " (required)" : "";
|
|
52
|
+
console.log(` - ${paramName}${required}: ${(schema as any).type}`);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
} else if (command === "call") {
|
|
57
|
+
const [methodName, ...paramArgs] = args;
|
|
58
|
+
if (!methodName) {
|
|
59
|
+
console.error("Error: Method name required");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Parse parameters if provided
|
|
64
|
+
const params = paramArgs.length ? JSON.parse(paramArgs[0]) : {};
|
|
65
|
+
|
|
66
|
+
// Get operation details
|
|
67
|
+
const { openApiLookup } = converter.convertToMCPTools();
|
|
68
|
+
const operation = openApiLookup[methodName];
|
|
69
|
+
if (!operation) {
|
|
70
|
+
console.error(`Error: Method ${methodName} not found`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Execute the operation
|
|
76
|
+
const response = await httpClient.executeOperation(operation, params);
|
|
77
|
+
console.log("Response:", JSON.stringify(response.data, null, 2));
|
|
78
|
+
} catch (error: any) {
|
|
79
|
+
console.error("Error:", error.response?.data || error.message);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
console.error(`Error: Unknown command ${command}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
main().catch((error) => {
|
|
89
|
+
console.error("Error:", error.message);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import eslint from "@eslint/js";
|
|
2
|
+
import tseslint from "@typescript-eslint/eslint-plugin";
|
|
3
|
+
import tsparser from "@typescript-eslint/parser";
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
ignores: ["build/**/*", "bin/**/*"],
|
|
8
|
+
},
|
|
9
|
+
eslint.configs.recommended,
|
|
10
|
+
{
|
|
11
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
12
|
+
languageOptions: {
|
|
13
|
+
parser: tsparser,
|
|
14
|
+
parserOptions: {
|
|
15
|
+
ecmaVersion: "latest",
|
|
16
|
+
sourceType: "module",
|
|
17
|
+
},
|
|
18
|
+
globals: {
|
|
19
|
+
process: "readonly",
|
|
20
|
+
console: "readonly",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
plugins: {
|
|
24
|
+
"@typescript-eslint": tseslint,
|
|
25
|
+
},
|
|
26
|
+
rules: {
|
|
27
|
+
...tseslint.configs.recommended.rules,
|
|
28
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
29
|
+
"@typescript-eslint/no-unused-vars": "off",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
files: ["**/*.js", "**/*.jsx"],
|
|
34
|
+
languageOptions: {
|
|
35
|
+
parserOptions: {
|
|
36
|
+
ecmaVersion: "latest",
|
|
37
|
+
sourceType: "module",
|
|
38
|
+
},
|
|
39
|
+
globals: {
|
|
40
|
+
process: "readonly",
|
|
41
|
+
console: "readonly",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
rules: {
|
|
45
|
+
...eslint.configs.recommended.rules,
|
|
46
|
+
"no-unused-vars": "off",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
];
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"mcp",
|
|
7
7
|
"server"
|
|
8
8
|
],
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.3",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"scripts": {
|
|
@@ -17,18 +17,16 @@
|
|
|
17
17
|
"dev": "tsx watch scripts/start-server.ts",
|
|
18
18
|
"parse-openapi": "tsx scripts/parse-openapi.ts",
|
|
19
19
|
"format": "prettier --write \"src/**/*.{ts,tsx}\" \"scripts/**/*.{ts,tsx}\"",
|
|
20
|
-
"lint": "prettier --check \"src/**/*.{ts,tsx}\" \"scripts/**/*.{ts,tsx}\"
|
|
21
|
-
"fix-lint": "eslint . --ext .ts,.tsx --fix",
|
|
20
|
+
"lint": "prettier --check \"src/**/*.{ts,tsx}\" \"scripts/**/*.{ts,tsx}\"",
|
|
22
21
|
"type-check": "tsc --noEmit"
|
|
23
22
|
},
|
|
24
23
|
"bin": {
|
|
25
24
|
"anytype-mcp": "bin/cli.mjs"
|
|
26
25
|
},
|
|
27
26
|
"dependencies": {
|
|
28
|
-
"@modelcontextprotocol/sdk": "
|
|
29
|
-
"axios": "^1.
|
|
27
|
+
"@modelcontextprotocol/sdk": "1.8.0",
|
|
28
|
+
"axios": "^1.8.4",
|
|
30
29
|
"form-data": "^4.0.1",
|
|
31
|
-
"js-yaml": "^4.1.0",
|
|
32
30
|
"mustache": "^4.2.0",
|
|
33
31
|
"openapi-client-axios": "^7.5.5",
|
|
34
32
|
"openapi-schema-validator": "^12.1.3",
|
|
@@ -37,21 +35,18 @@
|
|
|
37
35
|
},
|
|
38
36
|
"devDependencies": {
|
|
39
37
|
"@anthropic-ai/sdk": "^0.33.1",
|
|
40
|
-
"@types/js-yaml": "^4.0.9",
|
|
41
38
|
"@types/json-schema": "^7.0.15",
|
|
42
39
|
"@types/mustache": "^4.2.5",
|
|
43
40
|
"@types/node": "^20.17.16",
|
|
44
|
-
"@
|
|
45
|
-
"@
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"eslint": "^9.26.0",
|
|
49
|
-
"openai": "^4.80.1",
|
|
41
|
+
"@types/which": "^3.0.4",
|
|
42
|
+
"@vitest/coverage-v8": "3.1.1",
|
|
43
|
+
"esbuild": "^0.25.2",
|
|
44
|
+
"openai": "^4.91.1",
|
|
50
45
|
"prettier": "^3.5.3",
|
|
51
46
|
"prettier-plugin-organize-imports": "^4.1.0",
|
|
52
|
-
"tsx": "^4.19.
|
|
53
|
-
"typescript": "^5.
|
|
54
|
-
"vitest": "^
|
|
47
|
+
"tsx": "^4.19.3",
|
|
48
|
+
"typescript": "^5.8.2",
|
|
49
|
+
"vitest": "^3.1.1"
|
|
55
50
|
},
|
|
56
51
|
"description": "Official MCP server for Anytype API",
|
|
57
52
|
"repository": {
|
|
@@ -62,14 +57,5 @@
|
|
|
62
57
|
"bugs": {
|
|
63
58
|
"url": "https://github.com/anyproto/anytype-mcp/issues"
|
|
64
59
|
},
|
|
65
|
-
"homepage": "https://github.com/anyproto/anytype-mcp#readme"
|
|
66
|
-
"files": [
|
|
67
|
-
"bin",
|
|
68
|
-
"dist",
|
|
69
|
-
"src",
|
|
70
|
-
"README.md"
|
|
71
|
-
],
|
|
72
|
-
"engines": {
|
|
73
|
-
"node": ">=16"
|
|
74
|
-
}
|
|
60
|
+
"homepage": "https://github.com/anyproto/anytype-mcp#readme"
|
|
75
61
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { OpenAPIV3 } from "openapi-types";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { OpenAPIToMCPConverter } from "../../src/openapi/parser";
|
|
4
|
+
|
|
5
|
+
describe("OpenAPI Tool Conversion", () => {
|
|
6
|
+
const sampleSpec: OpenAPIV3.Document = {
|
|
7
|
+
openapi: "3.0.0",
|
|
8
|
+
info: {
|
|
9
|
+
title: "Test API",
|
|
10
|
+
version: "1.0.0",
|
|
11
|
+
},
|
|
12
|
+
paths: {
|
|
13
|
+
"/pets/{petId}": {
|
|
14
|
+
get: {
|
|
15
|
+
operationId: "getPet",
|
|
16
|
+
summary: "Get a pet by ID",
|
|
17
|
+
parameters: [
|
|
18
|
+
{
|
|
19
|
+
name: "petId",
|
|
20
|
+
in: "path",
|
|
21
|
+
required: true,
|
|
22
|
+
description: "The ID of the pet",
|
|
23
|
+
schema: {
|
|
24
|
+
type: "integer",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
responses: {
|
|
29
|
+
"200": {
|
|
30
|
+
description: "Pet found",
|
|
31
|
+
content: {
|
|
32
|
+
"application/json": {
|
|
33
|
+
schema: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
id: { type: "integer" },
|
|
37
|
+
name: { type: "string" },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
describe("convertToOpenAITools", () => {
|
|
50
|
+
it("converts OpenAPI spec to OpenAI tools format", () => {
|
|
51
|
+
const converter = new OpenAPIToMCPConverter(sampleSpec);
|
|
52
|
+
const tools = converter.convertToOpenAITools();
|
|
53
|
+
|
|
54
|
+
expect(tools).toHaveLength(1);
|
|
55
|
+
const tool = tools[0];
|
|
56
|
+
|
|
57
|
+
expect(tool.type).toBe("function");
|
|
58
|
+
expect(tool.function.name).toBe("getPet");
|
|
59
|
+
expect(tool.function.description).toBe("Get a pet by ID");
|
|
60
|
+
expect(tool.function.parameters).toEqual({
|
|
61
|
+
$defs: {},
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
petId: {
|
|
65
|
+
type: "integer",
|
|
66
|
+
description: "The ID of the pet",
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
required: ["petId"],
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("convertToAnthropicTools", () => {
|
|
75
|
+
it("converts OpenAPI spec to Anthropic tools format", () => {
|
|
76
|
+
const converter = new OpenAPIToMCPConverter(sampleSpec);
|
|
77
|
+
const tools = converter.convertToAnthropicTools();
|
|
78
|
+
|
|
79
|
+
expect(tools).toHaveLength(1);
|
|
80
|
+
const tool = tools[0];
|
|
81
|
+
|
|
82
|
+
expect(tool.name).toBe("getPet");
|
|
83
|
+
expect(tool.description).toBe("Get a pet by ID");
|
|
84
|
+
expect(tool.input_schema).toEqual({
|
|
85
|
+
$defs: {},
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
petId: {
|
|
89
|
+
type: "integer",
|
|
90
|
+
description: "The ID of the pet",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
required: ["petId"],
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("Complex API Conversion", () => {
|
|
99
|
+
const complexSpec: OpenAPIV3.Document = {
|
|
100
|
+
openapi: "3.0.0",
|
|
101
|
+
info: { title: "Complex API", version: "1.0.0" },
|
|
102
|
+
paths: {
|
|
103
|
+
"/pets": {
|
|
104
|
+
post: {
|
|
105
|
+
operationId: "createPet",
|
|
106
|
+
summary: "Create a pet",
|
|
107
|
+
requestBody: {
|
|
108
|
+
required: true,
|
|
109
|
+
content: {
|
|
110
|
+
"application/json": {
|
|
111
|
+
schema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
required: ["name", "type"],
|
|
114
|
+
properties: {
|
|
115
|
+
name: { type: "string", description: "The name of the pet" },
|
|
116
|
+
type: { type: "string", description: "The type of pet", enum: ["dog", "cat", "bird"] },
|
|
117
|
+
age: { type: "integer", description: "The age of the pet in years" },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
responses: {
|
|
124
|
+
"201": {
|
|
125
|
+
description: "Pet created successfully",
|
|
126
|
+
content: {
|
|
127
|
+
"application/json": {
|
|
128
|
+
schema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
id: { type: "integer" },
|
|
132
|
+
name: { type: "string" },
|
|
133
|
+
type: { type: "string" },
|
|
134
|
+
age: { type: "integer" },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
it("converts complex OpenAPI spec to OpenAI tools format", () => {
|
|
147
|
+
const converter = new OpenAPIToMCPConverter(complexSpec);
|
|
148
|
+
const tools = converter.convertToOpenAITools();
|
|
149
|
+
|
|
150
|
+
expect(tools).toHaveLength(1);
|
|
151
|
+
const tool = tools[0];
|
|
152
|
+
|
|
153
|
+
expect(tool.type).toBe("function");
|
|
154
|
+
expect(tool.function.name).toBe("createPet");
|
|
155
|
+
expect(tool.function.description).toBe("Create a pet");
|
|
156
|
+
expect(tool.function.parameters).toEqual({
|
|
157
|
+
$defs: {},
|
|
158
|
+
type: "object",
|
|
159
|
+
properties: {
|
|
160
|
+
name: { type: "string", description: "The name of the pet" },
|
|
161
|
+
type: { type: "string", description: "The type of pet", enum: ["dog", "cat", "bird"] },
|
|
162
|
+
age: { type: "integer", description: "The age of the pet in years" },
|
|
163
|
+
},
|
|
164
|
+
required: ["name", "type"],
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("converts complex OpenAPI spec to Anthropic tools format", () => {
|
|
169
|
+
const converter = new OpenAPIToMCPConverter(complexSpec);
|
|
170
|
+
const tools = converter.convertToAnthropicTools();
|
|
171
|
+
|
|
172
|
+
expect(tools).toHaveLength(1);
|
|
173
|
+
const tool = tools[0];
|
|
174
|
+
|
|
175
|
+
expect(tool.name).toBe("createPet");
|
|
176
|
+
expect(tool.description).toBe("Create a pet");
|
|
177
|
+
expect(tool.input_schema).toEqual({
|
|
178
|
+
$defs: {},
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
name: { type: "string", description: "The name of the pet" },
|
|
182
|
+
type: { type: "string", description: "The type of pet", enum: ["dog", "cat", "bird"] },
|
|
183
|
+
age: { type: "integer", description: "The age of the pet in years" },
|
|
184
|
+
},
|
|
185
|
+
required: ["name", "type"],
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|