-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbin.ts
More file actions
118 lines (106 loc) · 4.02 KB
/
bin.ts
File metadata and controls
118 lines (106 loc) · 4.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/usr/bin/env node
import { createRequire } from "node:module";
import { Command } from "commander";
import { startStdioServer } from "./stdio.js";
import { startHttpServer } from "./http.js";
import { createDocsMcpServerFactory } from "./create.js";
const require = createRequire(import.meta.url);
const SERVER_VERSION = readPackageVersion();
interface ServerCliOptions {
indexDir: string;
name: string;
toolPrefix?: string;
version: string;
queryEmbeddingApiKey?: string;
queryEmbeddingBaseUrl?: string;
queryEmbeddingBatchSize?: number;
proximityWeight?: number;
phraseSlop?: number;
vectorWeight?: number;
transport: "stdio" | "http";
port: number;
customToolsJson?: string;
}
const program = new Command();
program
.name("docs-mcp-server")
.description("Run @speakeasy-api/docs-mcp-server")
.requiredOption("--index-dir <path>", "Directory containing chunks.json and metadata.json")
.option("--name <value>", "MCP server name", "@speakeasy-api/docs-mcp-server")
.option("--tool-prefix <value>", "Tool name prefix (e.g. 'acme' produces acme_search_docs)")
.option("--version <value>", "MCP server version", SERVER_VERSION)
.option("--query-embedding-api-key <value>", "Query embedding API key (or set OPENAI_API_KEY)")
.option("--query-embedding-base-url <value>", "Query embedding API base URL")
.option("--query-embedding-batch-size <number>", "Query embedding batch size", parseIntOption)
.option("--proximity-weight <number>", "Lexical phrase blend weight", parseNumberOption)
.option("--phrase-slop <number>", "Phrase query slop (0-5)", parseNumberOption)
.option("--vector-weight <number>", "Vector rank blend weight", parseNumberOption)
.option("--transport <type>", "Transport type: stdio or http", "stdio")
.option(
"--port <number>",
"HTTP server port (only used with --transport http)",
parseIntOption,
20310,
)
.option(
"--custom-tools-json <json>",
"JSON array of custom tool definitions [{name, description, inputSchema}], each registered with an echo handler",
)
.action(async (options: ServerCliOptions) => {
const customTools = options.customToolsJson
? (
JSON.parse(options.customToolsJson) as Array<{
name: string;
description: string;
inputSchema: Record<string, unknown>;
}>
).map((def) => ({
...def,
handler: async (args: unknown) => ({
content: [{ type: "text" as const, text: JSON.stringify(args) }],
isError: false,
}),
}))
: [];
const serverName =
options.name === "@speakeasy-api/docs-mcp-server" && options.toolPrefix
? `${options.toolPrefix}-docs-server`
: options.name;
const mcpServerFactory = await createDocsMcpServerFactory({
serverName,
serverVersion: options.version,
indexDir: options.indexDir,
toolPrefix: options.toolPrefix,
queryEmbeddingApiKey: options.queryEmbeddingApiKey,
queryEmbeddingBaseUrl: options.queryEmbeddingBaseUrl,
queryEmbeddingBatchSize: options.queryEmbeddingBatchSize,
proximityWeight: options.proximityWeight,
phraseSlop: options.phraseSlop,
vectorWeight: options.vectorWeight,
...(customTools.length > 0 ? { customTools } : {}),
});
if (options.transport === "http") {
await startHttpServer(mcpServerFactory, { port: options.port });
} else {
await startStdioServer(mcpServerFactory);
}
});
function readPackageVersion(): string {
const pkg = require("../package.json");
return typeof pkg?.version === "string" ? pkg.version : "0.0.0";
}
void program.parseAsync(process.argv);
function parseNumberOption(value: string): number {
const parsed = Number(value);
if (!Number.isFinite(parsed)) {
throw new Error(`invalid numeric value '${value}'`);
}
return parsed;
}
function parseIntOption(value: string): number {
const parsed = Number.parseInt(value, 10);
if (!Number.isFinite(parsed)) {
throw new Error(`invalid integer value '${value}'`);
}
return parsed;
}