Skip to content

Commit 6354cef

Browse files
committed
chore: merge
1 parent 9bea052 commit 6354cef

File tree

4 files changed

+188
-388
lines changed

4 files changed

+188
-388
lines changed

src/app.ts

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { Command } from "commander";
2-
import type { StartServerOptions } from "./server/types.ts";
32
import { type ListToolsOptions } from "./commands/list-tools.ts";
4-
import { ZodError } from "zod";
53

64
const program = new Command("algolia-mcp");
75

@@ -64,20 +62,6 @@ const ALLOW_TOOLS_OPTIONS_TUPLE = [
6462
DEFAULT_ALLOW_TOOLS,
6563
] as const;
6664

67-
function formatErrorForCli(error: unknown): string {
68-
if (error instanceof ZodError) {
69-
return [...error.errors.map((e) => `- ${e.path.join(".") || "<root>"}: ${e.message}`)].join(
70-
"\n",
71-
);
72-
}
73-
74-
if (error instanceof Error) {
75-
return error.message;
76-
}
77-
78-
return "Unknown error";
79-
}
80-
8165
program
8266
.command("start-server", { isDefault: true })
8367
.description("Starts the Algolia MCP server")
@@ -96,27 +80,22 @@ program
9680

9781
.option("--transport [stdio|http]", "Transport type, either `stdio` (default) or `http`", "stdio")
9882
.action(async (opts) => {
99-
try {
100-
switch (opts.transport) {
101-
case "stdio": {
102-
console.info('Starting server with stdio transport');
103-
const { startServer } = await import("./commands/start-server.ts");
104-
await startServer(opts);
105-
break;
106-
}
107-
case "http": {
108-
console.info('Starting server with HTTP transport support');
109-
const { startHttpServer } = await import("./commands/start-http-server.ts");
110-
await startHttpServer(opts);
111-
break;
112-
}
113-
default:
114-
console.error(`Unknown transport type: ${opts.transport}\nAllowed values: stdio, http`);
115-
process.exit(1);
83+
switch (opts.transport) {
84+
case "stdio": {
85+
console.info('Starting server with stdio transport');
86+
const { startServer } = await import("./commands/start-server.ts");
87+
await startServer(opts);
88+
break;
89+
}
90+
case "http": {
91+
console.info('Starting server with HTTP transport support');
92+
const { startHttpServer } = await import("./commands/start-http-server.ts");
93+
await startHttpServer(opts);
94+
break;
11695
}
117-
} catch (error) {
118-
console.error(formatErrorForCli(error));
119-
process.exit(1);
96+
default:
97+
console.error(`Unknown transport type: ${opts.transport}\nAllowed values: stdio, http`);
98+
process.exit(1);
12099
}
121100
});
122101

src/commands/start-server.ts

Lines changed: 22 additions & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -1,227 +1,31 @@
11
#!/usr/bin/env -S node --experimental-strip-types
22

3-
import { authenticate } from "../authentication.ts";
4-
import { AppStateManager } from "../appState.ts";
5-
import { DashboardApi } from "../DashboardApi.ts";
6-
import {
7-
operationId as GetUserInfoOperationId,
8-
registerGetUserInfo,
9-
} from "../tools/registerGetUserInfo.ts";
10-
import {
11-
operationId as GetApplicationsOperationId,
12-
registerGetApplications,
13-
} from "../tools/registerGetApplications.ts";
143
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15-
import type {
16-
ProcessCallbackArguments,
17-
ProcessInputSchema,
18-
RequestMiddleware,
19-
} from "../tools/registerOpenApi.ts";
20-
import { registerOpenApiTools } from "../tools/registerOpenApi.ts";
21-
import { CONFIG } from "../config.ts";
22-
import {
23-
ABTestingSpec,
24-
AnalyticsSpec,
25-
CollectionsSpec,
26-
IngestionSpec,
27-
MonitoringSpec,
28-
QuerySuggestionsSpec,
29-
RecommendSpec,
30-
SearchSpec,
31-
UsageSpec,
32-
} from "../openApi.ts";
33-
import { CliFilteringOptionsSchema, getToolFilter, isToolAllowed } from "../toolFilters.ts";
34-
import {
35-
operationId as SetAttributesForFacetingOperationId,
36-
registerSetAttributesForFaceting,
37-
} from "../tools/registerSetAttributesForFaceting.ts";
38-
import {
39-
registerSetCustomRanking,
40-
operationId as SetCustomRankingOperationId,
41-
} from "../tools/registerSetCustomRanking.ts";
42-
43-
import { CustomMcpServer } from "../CustomMcpServer.ts";
44-
import { z } from "zod";
45-
46-
export const StartServerOptionsSchema = CliFilteringOptionsSchema.extend({
47-
credentials: z
48-
.object({
49-
applicationId: z.string(),
50-
apiKey: z.string(),
51-
})
52-
.optional(),
53-
});
54-
55-
type StartServerOptions = z.infer<typeof StartServerOptionsSchema>;
56-
57-
function makeRegionRequestMiddleware(dashboardApi: DashboardApi): RequestMiddleware {
58-
return async ({ request, params }) => {
59-
const application = await dashboardApi.getApplication(params.applicationId);
60-
const region = application.data.attributes.log_region === "de" ? "eu" : "us";
61-
62-
const url = new URL(request.url);
63-
const regionFromUrl = url.hostname.match(/data\.(.+)\.algolia.com/)?.[0];
64-
65-
if (regionFromUrl !== region) {
66-
console.error("Had to adjust region from", regionFromUrl, "to", region);
67-
url.hostname = `data.${region}.algolia.com`;
68-
return new Request(url, request.clone());
69-
}
70-
71-
return request;
72-
};
73-
}
74-
75-
export async function startServer(options: StartServerOptions): Promise<CustomMcpServer> {
76-
const { credentials, ...opts } = StartServerOptionsSchema.parse(options);
77-
const toolFilter = getToolFilter(opts);
78-
79-
const server = new CustomMcpServer({
80-
name: "algolia",
81-
version: CONFIG.version,
82-
capabilities: {
83-
resources: {},
84-
tools: {},
85-
},
86-
});
87-
88-
const regionHotFixMiddlewares: RequestMiddleware[] = [];
89-
let processCallbackArguments: ProcessCallbackArguments;
90-
const processInputSchema: ProcessInputSchema = (inputSchema) => {
91-
// If we got it from the options, we don't need it from the AI
92-
if (credentials && inputSchema.properties?.applicationId) {
93-
delete inputSchema.properties.applicationId;
94-
95-
if (Array.isArray(inputSchema.required)) {
96-
inputSchema.required = inputSchema.required.filter((item) => item !== "applicationId");
97-
}
98-
}
99-
100-
return inputSchema;
101-
};
102-
103-
if (credentials) {
104-
processCallbackArguments = async (params, securityKeys) => {
105-
const result = { ...params };
106-
107-
if (securityKeys.has("applicationId")) {
108-
result.applicationId = credentials.applicationId;
109-
}
110-
111-
if (securityKeys.has("apiKey")) {
112-
result.apiKey = credentials.apiKey;
113-
}
114-
115-
return result;
116-
};
117-
} else {
118-
const appState = await AppStateManager.load();
119-
120-
if (!appState.get("accessToken")) {
121-
const token = await authenticate();
122-
123-
await appState.update({
124-
accessToken: token.access_token,
125-
refreshToken: token.refresh_token,
126-
});
127-
}
128-
129-
const dashboardApi = new DashboardApi({ baseUrl: CONFIG.dashboardApiBaseUrl, appState });
130-
131-
processCallbackArguments = async (params, securityKeys) => {
132-
const result = { ...params };
133-
134-
if (securityKeys.has("apiKey")) {
135-
result.apiKey = await dashboardApi.getApiKey(params.applicationId);
136-
}
137-
138-
return result;
139-
};
140-
141-
regionHotFixMiddlewares.push(makeRegionRequestMiddleware(dashboardApi));
142-
143-
// Dashboard API Tools
144-
if (isToolAllowed(GetUserInfoOperationId, toolFilter)) {
145-
registerGetUserInfo(server, dashboardApi);
146-
}
147-
148-
if (isToolAllowed(GetApplicationsOperationId, toolFilter)) {
149-
registerGetApplications(server, dashboardApi);
150-
}
151-
152-
// TODO: Make it available when with applicationId+apiKey mode too
153-
if (isToolAllowed(SetAttributesForFacetingOperationId, toolFilter)) {
154-
registerSetAttributesForFaceting(server, dashboardApi);
155-
}
156-
157-
if (isToolAllowed(SetCustomRankingOperationId, toolFilter)) {
158-
registerSetCustomRanking(server, dashboardApi);
159-
}
4+
import { initMCPServer } from "../server/init-server.ts";
5+
import type { StartServerOptions } from "../server/types.ts";
6+
import { ZodError } from "zod";
7+
8+
function formatErrorForCli(error: unknown): string {
9+
if (error instanceof ZodError) {
10+
return [...error.errors.map((e) => `- ${e.path.join(".") || "<root>"}: ${e.message}`)].join(
11+
"\n",
12+
);
16013
}
16114

162-
for (const openApiSpec of [
163-
SearchSpec,
164-
AnalyticsSpec,
165-
RecommendSpec,
166-
ABTestingSpec,
167-
MonitoringSpec,
168-
CollectionsSpec,
169-
QuerySuggestionsSpec,
170-
]) {
171-
registerOpenApiTools({
172-
server,
173-
processInputSchema,
174-
processCallbackArguments,
175-
openApiSpec,
176-
toolFilter,
177-
});
15+
if (error instanceof Error) {
16+
return error.message;
17817
}
17918

180-
// Usage
181-
registerOpenApiTools({
182-
server,
183-
processInputSchema,
184-
processCallbackArguments,
185-
openApiSpec: UsageSpec,
186-
toolFilter,
187-
requestMiddlewares: [
188-
// The Usage API expects `name` parameter as multiple values
189-
// rather than comma-separated.
190-
async ({ request }) => {
191-
const url = new URL(request.url);
192-
const nameParams = url.searchParams.get("name");
193-
194-
if (!nameParams) {
195-
return new Request(url, request.clone());
196-
}
197-
198-
const nameValues = nameParams.split(",");
199-
200-
url.searchParams.delete("name");
201-
202-
nameValues.forEach((value) => {
203-
url.searchParams.append("name", value);
204-
});
205-
206-
return new Request(url, request.clone());
207-
},
208-
],
209-
});
210-
211-
// Ingestion API Tools
212-
registerOpenApiTools({
213-
server,
214-
processInputSchema,
215-
processCallbackArguments,
216-
openApiSpec: IngestionSpec,
217-
toolFilter,
218-
requestMiddlewares: [
219-
// Dirty fix for Claud hallucinating regions
220-
...regionHotFixMiddlewares,
221-
],
222-
});
19+
return "Unknown error";
20+
}
22321

224-
const transport = new StdioServerTransport();
225-
await server.connect(transport);
226-
return server;
22+
export async function startServer(options: StartServerOptions) {
23+
try {
24+
const server = await initMCPServer(options);
25+
const transport = new StdioServerTransport();
26+
await server.connect(transport);
27+
} catch (error) {
28+
console.error(formatErrorForCli(error));
29+
process.exit(1);
30+
}
22731
}

0 commit comments

Comments
 (0)