@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.
@@ -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
+ });
@@ -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.2",
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}\" && eslint . --ext .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": "^1.4.1",
29
- "axios": "^1.7.9",
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
- "@typescript-eslint/eslint-plugin": "^8.32.1",
45
- "@typescript-eslint/parser": "^8.32.1",
46
- "@vitest/coverage-v8": "2.1.8",
47
- "esbuild": "^0.24.2",
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.2",
53
- "typescript": "^5.7.3",
54
- "vitest": "^2.1.8"
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
+ });