@anyproto/anytype-mcp 1.1.2 → 1.2.0

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/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "mcp",
8
8
  "server"
9
9
  ],
10
- "version": "1.1.2",
10
+ "version": "1.2.0",
11
11
  "license": "MIT",
12
12
  "type": "module",
13
13
  "scripts": {
@@ -26,7 +26,7 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@modelcontextprotocol/sdk": "1.26.0",
29
- "axios": "1.13.4",
29
+ "axios": "1.13.5",
30
30
  "form-data": "4.0.5",
31
31
  "mustache": "4.2.0",
32
32
  "node-fetch": "3.3.2",
@@ -40,14 +40,14 @@
40
40
  "@eslint/js": "9.39.2",
41
41
  "@types/json-schema": "7.0.15",
42
42
  "@types/mustache": "4.2.6",
43
- "@types/node": "25.2.1",
43
+ "@types/node": "25.2.3",
44
44
  "@types/which": "3.0.4",
45
45
  "@vitest/coverage-v8": "4.0.18",
46
- "@typescript-eslint/eslint-plugin": "8.54.0",
47
- "@typescript-eslint/parser": "8.54.0",
46
+ "@typescript-eslint/eslint-plugin": "8.56.0",
47
+ "@typescript-eslint/parser": "8.56.0",
48
48
  "eslint": "9.39.2",
49
49
  "esbuild": "0.27.3",
50
- "openai": "6.18.0",
50
+ "openai": "6.22.0",
51
51
  "prettier": "3.8.1",
52
52
  "prettier-plugin-organize-imports": "4.3.0",
53
53
  "tsx": "4.21.0",
@@ -1,9 +1,10 @@
1
1
  import { AppKeyGenerator } from "../src/auth/get-key";
2
2
  import { initProxy, loadOpenApiSpec, ValidationError } from "../src/init-server";
3
+ import { determineBaseUrl } from "../src/utils/base-url";
3
4
 
4
5
  async function generateAppKey(specPath?: string) {
5
6
  const openApiSpec = await loadOpenApiSpec(specPath);
6
- const baseUrl = openApiSpec.servers?.[0]?.url || "http://127.0.0.1:31009";
7
+ const baseUrl = determineBaseUrl(openApiSpec);
7
8
  const generator = new AppKeyGenerator(baseUrl);
8
9
  await generator.generateAppKey();
9
10
  }
@@ -4,6 +4,7 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { OpenAPIV3 } from "openapi-types";
6
6
  import { MCPProxy } from "./mcp/proxy";
7
+ import { getDefaultSpecUrl } from "./utils/base-url";
7
8
 
8
9
  export class ValidationError extends Error {
9
10
  constructor(public errors: any[]) {
@@ -13,7 +14,7 @@ export class ValidationError extends Error {
13
14
  }
14
15
 
15
16
  export async function loadOpenApiSpec(specPath?: string): Promise<OpenAPIV3.Document> {
16
- const finalSpec = specPath || "http://127.0.0.1:31009/docs/openapi.json";
17
+ const finalSpec = specPath || getDefaultSpecUrl();
17
18
  let rawSpec: string;
18
19
 
19
20
  if (finalSpec.startsWith("http://") || finalSpec.startsWith("https://")) {
@@ -13,41 +13,37 @@ describe("MCPProxy", () => {
13
13
  let proxy: MCPProxy;
14
14
  let mockOpenApiSpec: OpenAPIV3.Document;
15
15
 
16
- beforeEach(() => {
17
- // Reset all mocks
18
- vi.clearAllMocks();
19
-
20
- // Setup minimal OpenAPI spec for testing
21
- mockOpenApiSpec = {
22
- openapi: "3.0.0",
23
- servers: [{ url: "http://localhost:3000" }],
24
- info: {
25
- title: "Test API",
26
- version: "1.0.0",
27
- },
28
- paths: {
29
- "/test": {
30
- get: {
31
- operationId: "getTest",
32
- responses: {
33
- "200": {
34
- description: "Success",
35
- },
36
- },
37
- },
16
+ const getHandlers = (proxy: MCPProxy) => {
17
+ const server = (proxy as any).server;
18
+ return server.setRequestHandler.mock.calls
19
+ .flatMap((x: unknown[]) => x)
20
+ .filter((x: unknown) => typeof x === "function");
21
+ };
22
+
23
+ const createMockOpenApiSpec = (overrides?: Partial<OpenAPIV3.Document>): OpenAPIV3.Document => ({
24
+ openapi: "3.0.0",
25
+ servers: [{ url: "http://localhost:3000" }],
26
+ info: { title: "Test API", version: "1.0.0" },
27
+ paths: {
28
+ "/test": {
29
+ get: {
30
+ operationId: "getTest",
31
+ responses: { "200": { description: "Success" } },
38
32
  },
39
33
  },
40
- };
34
+ },
35
+ ...overrides,
36
+ });
41
37
 
38
+ beforeEach(() => {
39
+ vi.clearAllMocks();
40
+ mockOpenApiSpec = createMockOpenApiSpec();
42
41
  proxy = new MCPProxy("test-proxy", mockOpenApiSpec);
43
42
  });
44
43
 
45
44
  describe("listTools handler", () => {
46
45
  it("should return converted tools from OpenAPI spec", async () => {
47
- const server = (proxy as any).server;
48
- const listToolsHandler = server.setRequestHandler.mock.calls[0].filter(
49
- (x: unknown) => typeof x === "function",
50
- )[0];
46
+ const [listToolsHandler] = getHandlers(proxy);
51
47
  const result = await listToolsHandler();
52
48
 
53
49
  expect(result).toHaveProperty("tools");
@@ -55,24 +51,18 @@ describe("MCPProxy", () => {
55
51
  });
56
52
 
57
53
  it("should truncate tool names exceeding 64 characters", async () => {
58
- // Setup OpenAPI spec with long tool names
59
- mockOpenApiSpec.paths = {
60
- "/test": {
61
- get: {
62
- operationId: "a".repeat(65),
63
- responses: {
64
- "200": {
65
- description: "Success",
66
- },
54
+ const specWithLongName = createMockOpenApiSpec({
55
+ paths: {
56
+ "/test": {
57
+ get: {
58
+ operationId: "a".repeat(65),
59
+ responses: { "200": { description: "Success" } },
67
60
  },
68
61
  },
69
62
  },
70
- };
71
- proxy = new MCPProxy("test-proxy", mockOpenApiSpec);
72
- const server = (proxy as any).server;
73
- const listToolsHandler = server.setRequestHandler.mock.calls[0].filter(
74
- (x: unknown) => typeof x === "function",
75
- )[0];
63
+ });
64
+ const testProxy = new MCPProxy("test-proxy", specWithLongName);
65
+ const [listToolsHandler] = getHandlers(testProxy);
76
66
  const result = await listToolsHandler();
77
67
 
78
68
  expect(result.tools[0].name.length).toBeLessThanOrEqual(64);
@@ -80,18 +70,15 @@ describe("MCPProxy", () => {
80
70
  });
81
71
 
82
72
  describe("callTool handler", () => {
73
+ const mockSuccessResponse = {
74
+ data: { message: "success" },
75
+ status: 200,
76
+ headers: new Headers({ "content-type": "application/json" }),
77
+ };
78
+
83
79
  it("should execute operation and return formatted response", async () => {
84
- // Mock HttpClient response
85
- const mockResponse = {
86
- data: { message: "success" },
87
- status: 200,
88
- headers: new Headers({
89
- "content-type": "application/json",
90
- }),
91
- };
92
- (HttpClient.prototype.executeOperation as ReturnType<typeof vi.fn>).mockResolvedValue(mockResponse);
80
+ (HttpClient.prototype.executeOperation as ReturnType<typeof vi.fn>).mockResolvedValue(mockSuccessResponse);
93
81
 
94
- // Set up the openApiLookup with our test operation
95
82
  (proxy as any).openApiLookup = {
96
83
  "API-getTest": {
97
84
  operationId: "getTest",
@@ -101,58 +88,25 @@ describe("MCPProxy", () => {
101
88
  },
102
89
  };
103
90
 
104
- const server = (proxy as any).server;
105
- const handlers = server.setRequestHandler.mock.calls
106
- .flatMap((x: unknown[]) => x)
107
- .filter((x: unknown) => typeof x === "function");
108
- const callToolHandler = handlers[1];
109
-
110
- const result = await callToolHandler({
111
- params: {
112
- name: "API-getTest",
113
- arguments: {},
114
- },
115
- });
91
+ const [, callToolHandler] = getHandlers(proxy);
92
+ const result = await callToolHandler({ params: { name: "API-getTest", arguments: {} } });
116
93
 
117
94
  expect(result).toEqual({
118
- content: [
119
- {
120
- type: "text",
121
- text: JSON.stringify({ message: "success" }),
122
- },
123
- ],
95
+ content: [{ type: "text", text: JSON.stringify({ message: "success" }) }],
124
96
  });
125
97
  });
126
98
 
127
99
  it("should throw error for non-existent operation", async () => {
128
- const server = (proxy as any).server;
129
- const handlers = server.setRequestHandler.mock.calls
130
- .flatMap((x: unknown[]) => x)
131
- .filter((x: unknown) => typeof x === "function");
132
- const callToolHandler = handlers[1];
133
-
134
- await expect(
135
- callToolHandler({
136
- params: {
137
- name: "nonExistentMethod",
138
- arguments: {},
139
- },
140
- }),
141
- ).rejects.toThrow("Method nonExistentMethod not found");
100
+ const [, callToolHandler] = getHandlers(proxy);
101
+
102
+ await expect(callToolHandler({ params: { name: "nonExistentMethod", arguments: {} } })).rejects.toThrow(
103
+ "Method nonExistentMethod not found",
104
+ );
142
105
  });
143
106
 
144
107
  it("should handle tool names exceeding 64 characters", async () => {
145
- // Mock HttpClient response
146
- const mockResponse = {
147
- data: { message: "success" },
148
- status: 200,
149
- headers: new Headers({
150
- "content-type": "application/json",
151
- }),
152
- };
153
- (HttpClient.prototype.executeOperation as ReturnType<typeof vi.fn>).mockResolvedValue(mockResponse);
108
+ (HttpClient.prototype.executeOperation as ReturnType<typeof vi.fn>).mockResolvedValue(mockSuccessResponse);
154
109
 
155
- // Set up the openApiLookup with a long tool name
156
110
  const longToolName = "a".repeat(65);
157
111
  const truncatedToolName = longToolName.slice(0, 64);
158
112
  (proxy as any).openApiLookup = {
@@ -164,26 +118,11 @@ describe("MCPProxy", () => {
164
118
  },
165
119
  };
166
120
 
167
- const server = (proxy as any).server;
168
- const handlers = server.setRequestHandler.mock.calls
169
- .flatMap((x: unknown[]) => x)
170
- .filter((x: unknown) => typeof x === "function");
171
- const callToolHandler = handlers[1];
172
-
173
- const result = await callToolHandler({
174
- params: {
175
- name: truncatedToolName,
176
- arguments: {},
177
- },
178
- });
121
+ const [, callToolHandler] = getHandlers(proxy);
122
+ const result = await callToolHandler({ params: { name: truncatedToolName, arguments: {} } });
179
123
 
180
124
  expect(result).toEqual({
181
- content: [
182
- {
183
- type: "text",
184
- text: JSON.stringify({ message: "success" }),
185
- },
186
- ],
125
+ content: [{ type: "text", text: JSON.stringify({ message: "success" }) }],
187
126
  });
188
127
  });
189
128
  });
@@ -202,6 +141,9 @@ describe("MCPProxy", () => {
202
141
 
203
142
  describe("parseHeadersFromEnv", () => {
204
143
  const originalEnv = process.env;
144
+ const expectHeaders = (headers: Record<string, string>) => {
145
+ expect(HttpClient).toHaveBeenCalledWith(expect.objectContaining({ headers }), expect.anything());
146
+ };
205
147
 
206
148
  beforeEach(() => {
207
149
  process.env = { ...originalEnv };
@@ -216,42 +158,21 @@ describe("MCPProxy", () => {
216
158
  Authorization: "Bearer token123",
217
159
  "X-Custom-Header": "test",
218
160
  });
219
-
220
- const proxy = new MCPProxy("test-proxy", mockOpenApiSpec);
221
- expect(HttpClient).toHaveBeenCalledWith(
222
- expect.objectContaining({
223
- headers: {
224
- Authorization: "Bearer token123",
225
- "X-Custom-Header": "test",
226
- },
227
- }),
228
- expect.anything(),
229
- );
161
+ new MCPProxy("test-proxy", mockOpenApiSpec);
162
+ expectHeaders({ Authorization: "Bearer token123", "X-Custom-Header": "test" });
230
163
  });
231
164
 
232
165
  it("should return empty object when env var is not set", () => {
233
166
  delete process.env.OPENAPI_MCP_HEADERS;
234
-
235
- const proxy = new MCPProxy("test-proxy", mockOpenApiSpec);
236
- expect(HttpClient).toHaveBeenCalledWith(
237
- expect.objectContaining({
238
- headers: {},
239
- }),
240
- expect.anything(),
241
- );
167
+ new MCPProxy("test-proxy", mockOpenApiSpec);
168
+ expectHeaders({});
242
169
  });
243
170
 
244
171
  it("should return empty object and warn on invalid JSON", () => {
245
172
  const consoleSpy = vi.spyOn(console, "warn");
246
173
  process.env.OPENAPI_MCP_HEADERS = "invalid json";
247
-
248
- const proxy = new MCPProxy("test-proxy", mockOpenApiSpec);
249
- expect(HttpClient).toHaveBeenCalledWith(
250
- expect.objectContaining({
251
- headers: {},
252
- }),
253
- expect.anything(),
254
- );
174
+ new MCPProxy("test-proxy", mockOpenApiSpec);
175
+ expectHeaders({});
255
176
  expect(consoleSpy).toHaveBeenCalledWith(
256
177
  "Failed to parse OPENAPI_MCP_HEADERS environment variable:",
257
178
  expect.any(Error),
@@ -261,20 +182,48 @@ describe("MCPProxy", () => {
261
182
  it("should return empty object and warn on non-object JSON", () => {
262
183
  const consoleSpy = vi.spyOn(console, "warn");
263
184
  process.env.OPENAPI_MCP_HEADERS = '"string"';
264
-
265
- const proxy = new MCPProxy("test-proxy", mockOpenApiSpec);
266
- expect(HttpClient).toHaveBeenCalledWith(
267
- expect.objectContaining({
268
- headers: {},
269
- }),
270
- expect.anything(),
271
- );
185
+ new MCPProxy("test-proxy", mockOpenApiSpec);
186
+ expectHeaders({});
272
187
  expect(consoleSpy).toHaveBeenCalledWith(
273
188
  "OPENAPI_MCP_HEADERS environment variable must be a JSON object, got:",
274
189
  "string",
275
190
  );
276
191
  });
277
192
  });
193
+
194
+ describe("base URL integration", () => {
195
+ const originalEnv = process.env;
196
+ const expectBaseUrl = (url: string) => {
197
+ expect(HttpClient).toHaveBeenCalledWith(expect.objectContaining({ baseUrl: url }), expect.anything());
198
+ };
199
+
200
+ beforeEach(() => {
201
+ process.env = { ...originalEnv };
202
+ });
203
+
204
+ afterEach(() => {
205
+ process.env = originalEnv;
206
+ });
207
+
208
+ it("should use ANYTYPE_API_BASE_URL when set", () => {
209
+ process.env.ANYTYPE_API_BASE_URL = "http://localhost:31012";
210
+ new MCPProxy("test-proxy", mockOpenApiSpec);
211
+ expectBaseUrl("http://localhost:31012");
212
+ });
213
+
214
+ it("should use spec servers when env var not set", () => {
215
+ delete process.env.ANYTYPE_API_BASE_URL;
216
+ new MCPProxy("test-proxy", mockOpenApiSpec);
217
+ expectBaseUrl("http://localhost:3000");
218
+ });
219
+
220
+ it("should use default when neither env var nor spec servers available", () => {
221
+ delete process.env.ANYTYPE_API_BASE_URL;
222
+ new MCPProxy("test-proxy", createMockOpenApiSpec({ servers: undefined }));
223
+ expectBaseUrl("http://127.0.0.1:31009");
224
+ });
225
+ });
226
+
278
227
  describe("connect", () => {
279
228
  it("should connect to transport", async () => {
280
229
  const mockTransport = {} as Transport;
package/src/mcp/proxy.ts CHANGED
@@ -6,6 +6,7 @@ import { Headers } from "node-fetch";
6
6
  import { OpenAPIV3 } from "openapi-types";
7
7
  import { HttpClient, HttpClientError } from "../client/http-client";
8
8
  import { OpenAPIToMCPConverter } from "../openapi/parser";
9
+ import { determineBaseUrl } from "../utils/base-url";
9
10
 
10
11
  type PathItemObject = OpenAPIV3.PathItemObject & {
11
12
  get?: OpenAPIV3.OperationObject;
@@ -32,7 +33,7 @@ export class MCPProxy {
32
33
 
33
34
  constructor(name: string, openApiSpec: OpenAPIV3.Document) {
34
35
  this.server = new Server({ name, version: "1.0.0" }, { capabilities: { tools: {} } });
35
- const baseUrl = openApiSpec.servers?.[0].url || "http://127.0.0.1:31009";
36
+ const baseUrl = determineBaseUrl(openApiSpec);
36
37
  this.httpClient = new HttpClient(
37
38
  {
38
39
  baseUrl,
@@ -0,0 +1,136 @@
1
+ import { OpenAPIV3 } from "openapi-types";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { determineBaseUrl, getDefaultSpecUrl, parseBaseUrlFromEnv } from "../base-url";
4
+
5
+ describe("base-url utilities", () => {
6
+ const originalEnv = process.env;
7
+
8
+ beforeEach(() => {
9
+ process.env = { ...originalEnv };
10
+ });
11
+
12
+ afterEach(() => {
13
+ process.env = originalEnv;
14
+ });
15
+
16
+ describe("parseBaseUrlFromEnv", () => {
17
+ it("should parse valid HTTP URL from env", () => {
18
+ process.env.ANYTYPE_API_BASE_URL = "http://localhost:31012";
19
+ expect(parseBaseUrlFromEnv()).toBe("http://localhost:31012");
20
+ });
21
+
22
+ it("should parse valid HTTPS URL from env", () => {
23
+ process.env.ANYTYPE_API_BASE_URL = "https://api.example.com:8080";
24
+ expect(parseBaseUrlFromEnv()).toBe("https://api.example.com:8080");
25
+ });
26
+
27
+ it("should strip path from URL and return origin only", () => {
28
+ process.env.ANYTYPE_API_BASE_URL = "http://localhost:31012/api/v1";
29
+ expect(parseBaseUrlFromEnv()).toBe("http://localhost:31012");
30
+ });
31
+
32
+ it("should return null when env var is not set", () => {
33
+ delete process.env.ANYTYPE_API_BASE_URL;
34
+ expect(parseBaseUrlFromEnv()).toBeNull();
35
+ });
36
+
37
+ it("should return null and warn on invalid URL", () => {
38
+ const consoleSpy = vi.spyOn(console, "warn");
39
+ process.env.ANYTYPE_API_BASE_URL = "not-a-valid-url";
40
+
41
+ expect(parseBaseUrlFromEnv()).toBeNull();
42
+ expect(consoleSpy).toHaveBeenCalledWith(
43
+ "Failed to parse ANYTYPE_API_BASE_URL environment variable:",
44
+ expect.any(Error),
45
+ );
46
+ });
47
+
48
+ it("should return null and warn on unsupported protocol", () => {
49
+ const consoleSpy = vi.spyOn(console, "warn");
50
+ process.env.ANYTYPE_API_BASE_URL = "ftp://localhost:31012";
51
+
52
+ expect(parseBaseUrlFromEnv()).toBeNull();
53
+ expect(consoleSpy).toHaveBeenCalledWith(
54
+ "ANYTYPE_API_BASE_URL must use http:// or https:// protocol, got: ftp:. Ignoring and using fallback.",
55
+ );
56
+ });
57
+ });
58
+
59
+ describe("determineBaseUrl", () => {
60
+ const mockOpenApiSpec: OpenAPIV3.Document = {
61
+ openapi: "3.0.0",
62
+ servers: [{ url: "http://localhost:3000" }],
63
+ info: {
64
+ title: "Test API",
65
+ version: "1.0.0",
66
+ },
67
+ paths: {},
68
+ };
69
+
70
+ it("should prioritize ANYTYPE_API_BASE_URL over spec servers", () => {
71
+ const consoleSpy = vi.spyOn(console, "error");
72
+ process.env.ANYTYPE_API_BASE_URL = "http://localhost:31012";
73
+
74
+ expect(determineBaseUrl(mockOpenApiSpec)).toBe("http://localhost:31012");
75
+ expect(consoleSpy).toHaveBeenCalledWith("Using base URL from ANYTYPE_API_BASE_URL: http://localhost:31012");
76
+ });
77
+
78
+ it("should use spec servers[0].url when env var is not set", () => {
79
+ const consoleSpy = vi.spyOn(console, "error");
80
+ delete process.env.ANYTYPE_API_BASE_URL;
81
+
82
+ expect(determineBaseUrl(mockOpenApiSpec)).toBe("http://localhost:3000");
83
+ expect(consoleSpy).toHaveBeenCalledWith("Using base URL from OpenAPI spec: http://localhost:3000");
84
+ });
85
+
86
+ it("should use default fallback when neither env var nor spec servers are available", () => {
87
+ const consoleSpy = vi.spyOn(console, "error");
88
+ delete process.env.ANYTYPE_API_BASE_URL;
89
+ const specWithoutServers = {
90
+ ...mockOpenApiSpec,
91
+ servers: undefined,
92
+ };
93
+
94
+ expect(determineBaseUrl(specWithoutServers)).toBe("http://127.0.0.1:31009");
95
+ expect(consoleSpy).toHaveBeenCalledWith("Using default base URL: http://127.0.0.1:31009");
96
+ });
97
+
98
+ it("should use default fallback when spec is not provided", () => {
99
+ const consoleSpy = vi.spyOn(console, "error");
100
+ delete process.env.ANYTYPE_API_BASE_URL;
101
+
102
+ expect(determineBaseUrl()).toBe("http://127.0.0.1:31009");
103
+ expect(consoleSpy).toHaveBeenCalledWith("Using default base URL: http://127.0.0.1:31009");
104
+ });
105
+
106
+ it("should fallback to spec servers when env var is invalid", () => {
107
+ const consoleSpy = vi.spyOn(console, "error");
108
+ process.env.ANYTYPE_API_BASE_URL = "invalid-url";
109
+
110
+ expect(determineBaseUrl(mockOpenApiSpec)).toBe("http://localhost:3000");
111
+ expect(consoleSpy).toHaveBeenCalledWith("Using base URL from OpenAPI spec: http://localhost:3000");
112
+ });
113
+ });
114
+
115
+ describe("getDefaultSpecUrl", () => {
116
+ it("should use ANYTYPE_API_BASE_URL with /docs/openapi.json suffix when set", () => {
117
+ process.env.ANYTYPE_API_BASE_URL = "http://localhost:31012";
118
+ expect(getDefaultSpecUrl()).toBe("http://localhost:31012/docs/openapi.json");
119
+ });
120
+
121
+ it("should strip path from endpoint before adding suffix", () => {
122
+ process.env.ANYTYPE_API_BASE_URL = "http://localhost:31012/some/path";
123
+ expect(getDefaultSpecUrl()).toBe("http://localhost:31012/docs/openapi.json");
124
+ });
125
+
126
+ it("should return default URL when env var is not set", () => {
127
+ delete process.env.ANYTYPE_API_BASE_URL;
128
+ expect(getDefaultSpecUrl()).toBe("http://127.0.0.1:31009/docs/openapi.json");
129
+ });
130
+
131
+ it("should return default URL when env var is invalid", () => {
132
+ process.env.ANYTYPE_API_BASE_URL = "invalid-url";
133
+ expect(getDefaultSpecUrl()).toBe("http://127.0.0.1:31009/docs/openapi.json");
134
+ });
135
+ });
136
+ });
@@ -0,0 +1,67 @@
1
+ import { URL } from "node:url";
2
+ import { OpenAPIV3 } from "openapi-types";
3
+
4
+ /**
5
+ * Parses the ANYTYPE_API_BASE_URL environment variable and returns the origin.
6
+ * Returns null if not set, invalid, or uses an unsupported protocol.
7
+ */
8
+ export function parseBaseUrlFromEnv(): string | null {
9
+ const endpoint = process.env.ANYTYPE_API_BASE_URL;
10
+ if (!endpoint) {
11
+ return null;
12
+ }
13
+
14
+ try {
15
+ const url = new URL(endpoint);
16
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
17
+ console.warn(
18
+ `ANYTYPE_API_BASE_URL must use http:// or https:// protocol, got: ${url.protocol}. Ignoring and using fallback.`,
19
+ );
20
+ return null;
21
+ }
22
+ return url.origin;
23
+ } catch (error) {
24
+ console.warn("Failed to parse ANYTYPE_API_BASE_URL environment variable:", error);
25
+ return null;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Determines the base URL using priority order:
31
+ * 1. ANYTYPE_API_BASE_URL environment variable
32
+ * 2. OpenAPI spec servers[0].url
33
+ * 3. Default fallback: http://127.0.0.1:31009
34
+ */
35
+ export function determineBaseUrl(openApiSpec?: OpenAPIV3.Document): string {
36
+ // Priority 1: Environment variable
37
+ const envEndpoint = parseBaseUrlFromEnv();
38
+ if (envEndpoint) {
39
+ console.error(`Using base URL from ANYTYPE_API_BASE_URL: ${envEndpoint}`);
40
+ return envEndpoint;
41
+ }
42
+
43
+ // Priority 2: OpenAPI spec servers[0].url
44
+ const specUrl = openApiSpec?.servers?.[0]?.url;
45
+ if (specUrl) {
46
+ console.error(`Using base URL from OpenAPI spec: ${specUrl}`);
47
+ return specUrl;
48
+ }
49
+
50
+ // Priority 3: Default fallback
51
+ const defaultUrl = "http://127.0.0.1:31009";
52
+ console.error(`Using default base URL: ${defaultUrl}`);
53
+ return defaultUrl;
54
+ }
55
+
56
+ /**
57
+ * Gets the default OpenAPI spec URL.
58
+ * If ANYTYPE_API_BASE_URL is set, uses it with /docs/openapi.json suffix.
59
+ * Otherwise, returns the default spec URL.
60
+ */
61
+ export function getDefaultSpecUrl(): string {
62
+ const endpoint = parseBaseUrlFromEnv();
63
+ if (endpoint) {
64
+ return `${endpoint}/docs/openapi.json`;
65
+ }
66
+ return "http://127.0.0.1:31009/docs/openapi.json";
67
+ }