@utdk/mcp 0.1.0-dev.646adf4

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/dist/loader.js ADDED
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Dynamic provider loader for @utdk/mcp-server.
3
+ *
4
+ * Loads OpenAPI documents and auth configuration from @utdk/* provider packages.
5
+ * Converts each OpenAPI spec to MCP-compatible tool definitions using OpenApiConverter.
6
+ */
7
+ import { OpenApiConverter } from "@utcp/http";
8
+ import { withSpan, configureTelemetry } from "@utdk/common";
9
+ import { buildAuthProvider } from "./auth.js";
10
+ /**
11
+ * Extract path parameter keys from a route template like "/repos/{owner}/{repo}".
12
+ */
13
+ function extractPathParams(routeTemplate) {
14
+ const matches = routeTemplate.match(/\{([^}]+)\}/g) ?? [];
15
+ return matches.map((m) => m.slice(1, -1));
16
+ }
17
+ /**
18
+ * Convert a UTCP tool name to a safe MCP tool name.
19
+ * MCP tool names must match [a-zA-Z0-9_-]+ .
20
+ * e.g. "github.repos/list" → "github__repos_list"
21
+ */
22
+ function toMcpToolName(utcpName) {
23
+ return utcpName.replace(/\./g, "__").replace(/[^a-zA-Z0-9_-]/g, "_");
24
+ }
25
+ /**
26
+ * Load a provider package's OpenAPI document and package metadata.
27
+ * Returns null if the package cannot be found or loaded.
28
+ */
29
+ async function loadProviderPackage(providerName) {
30
+ const packageName = `@utdk/${providerName}`;
31
+ try {
32
+ // Dynamically import the provider's openapi.json via the package path
33
+ // Provider packages expose their openapi.json as a JSON import
34
+ const [openApiMod, pkgMod] = await Promise.all([
35
+ import(`${packageName}/openapi.json`, { with: { type: "json" } }).catch(() => null),
36
+ import(`${packageName}/package.json`, { with: { type: "json" } }).catch(() => null),
37
+ ]);
38
+ if (!openApiMod || !pkgMod) {
39
+ // Try loading the whole package and see if it exposes the doc
40
+ process.stderr.write(`[mcp-server] Warning: could not import openapi.json or package.json for ${packageName}\n`);
41
+ return null;
42
+ }
43
+ return {
44
+ openApiDoc: openApiMod.default,
45
+ packageJson: pkgMod.default,
46
+ };
47
+ }
48
+ catch (err) {
49
+ process.stderr.write(`[mcp-server] Warning: failed to load provider ${providerName}: ${err}\n`);
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * Convert a loaded provider's OpenAPI doc into MCP-ready tool definitions.
55
+ */
56
+ function buildProviderTools(providerName, openApiDoc, auth) {
57
+ const converter = new OpenApiConverter(openApiDoc, {
58
+ callTemplateName: providerName,
59
+ });
60
+ const manual = converter.convert();
61
+ const tools = [];
62
+ for (const tool of manual.tools) {
63
+ const template = tool.tool_call_template;
64
+ if (!template)
65
+ continue;
66
+ const routeTemplate = template.url ?? "/";
67
+ const method = (template.http_method ?? "GET").toUpperCase();
68
+ const contentType = template.content_type ?? "application/json";
69
+ const pathParamKeys = extractPathParams(routeTemplate);
70
+ // Determine which input properties go in query params vs body
71
+ // For GET/HEAD/DELETE, non-path params → query string
72
+ // For POST/PUT/PATCH, non-path params → body
73
+ const inputProperties = (tool.inputs?.properties ?? {});
74
+ const allInputKeys = Object.keys(inputProperties);
75
+ const isBodyMethod = ["POST", "PUT", "PATCH"].includes(method);
76
+ const queryParamKeys = isBodyMethod ? [] : allInputKeys.filter((k) => !pathParamKeys.includes(k));
77
+ tools.push({
78
+ mcpName: toMcpToolName(tool.name),
79
+ utcpName: tool.name,
80
+ description: tool.description ?? `${method} ${routeTemplate}`,
81
+ inputSchema: tool.inputs,
82
+ providerName,
83
+ tags: tool.tags ?? [],
84
+ method,
85
+ routeTemplate,
86
+ contentType,
87
+ pathParamKeys,
88
+ queryParamKeys,
89
+ auth,
90
+ });
91
+ }
92
+ return tools;
93
+ }
94
+ /**
95
+ * Load all tools for a list of provider names.
96
+ * Providers that fail to load are skipped with a warning.
97
+ */
98
+ export async function loadProviders(providerNames) {
99
+ const allTools = [];
100
+ await Promise.all(providerNames.map(async (providerName) => {
101
+ const pkg = await loadProviderPackage(providerName);
102
+ if (!pkg)
103
+ return;
104
+ const authConfigs = pkg.packageJson.utdk?.auth ?? [];
105
+ const auth = buildAuthProvider(providerName, authConfigs);
106
+ const tools = buildProviderTools(providerName, pkg.openApiDoc, auth);
107
+ process.stderr.write(`[mcp-server] Loaded ${tools.length} tools from @utdk/${providerName}\n`);
108
+ allTools.push(...tools);
109
+ }));
110
+ return allTools;
111
+ }
112
+ /**
113
+ * Parse the UTDK_PROVIDERS environment variable.
114
+ * Returns an array of provider names (e.g. ["github", "slack", "stripe"]).
115
+ */
116
+ export function parseProviderNames(envValue) {
117
+ if (!envValue?.trim())
118
+ return [];
119
+ return envValue
120
+ .split(",")
121
+ .map((name) => name.trim().toLowerCase())
122
+ .filter(Boolean);
123
+ }
124
+ /**
125
+ * Initialize telemetry if UTDK_OTEL_EXPORTER is set.
126
+ */
127
+ export async function initTelemetry() {
128
+ const exporter = process.env["UTDK_OTEL_EXPORTER"];
129
+ if (exporter === "otlp" || exporter === "console") {
130
+ await configureTelemetry({ enabled: true, exporter });
131
+ }
132
+ }
133
+ /**
134
+ * Execute a tool call with telemetry and auth.
135
+ */
136
+ export async function executeTool(tool, args) {
137
+ return withSpan({
138
+ provider: tool.providerName,
139
+ operation: tool.utcpName,
140
+ spanName: `utdk.mcp.tool_call`,
141
+ }, async (span) => {
142
+ span.setAttribute("utdk.provider", tool.providerName);
143
+ span.setAttribute("utdk.tool", tool.utcpName);
144
+ span.setAttribute("mcp.tool_name", tool.mcpName);
145
+ // Build URL by substituting path parameters
146
+ let url = tool.routeTemplate;
147
+ const remainingArgs = { ...args };
148
+ for (const pathKey of tool.pathParamKeys) {
149
+ const value = remainingArgs[pathKey];
150
+ if (value !== undefined) {
151
+ url = url.replace(`{${pathKey}}`, encodeURIComponent(String(value)));
152
+ delete remainingArgs[pathKey];
153
+ }
154
+ else {
155
+ url = url.replace(`{${pathKey}}`, "");
156
+ }
157
+ }
158
+ // Build query string for GET-style methods
159
+ const isBodyMethod = ["POST", "PUT", "PATCH"].includes(tool.method);
160
+ let body;
161
+ const requestHeaders = {
162
+ "Content-Type": tool.contentType,
163
+ "User-Agent": "@utdk/mcp-server/0.1.0",
164
+ };
165
+ if (isBodyMethod) {
166
+ const bodyObj = {};
167
+ for (const [k, v] of Object.entries(remainingArgs)) {
168
+ bodyObj[k] = v;
169
+ }
170
+ if (Object.keys(bodyObj).length > 0) {
171
+ body = JSON.stringify(bodyObj);
172
+ }
173
+ }
174
+ else {
175
+ // Query params
176
+ const urlObj = new URL(url);
177
+ for (const [k, v] of Object.entries(remainingArgs)) {
178
+ if (v !== undefined && v !== null) {
179
+ urlObj.searchParams.append(k, String(v));
180
+ }
181
+ }
182
+ url = urlObj.toString();
183
+ }
184
+ // Apply auth
185
+ if (tool.auth) {
186
+ await tool.auth.authenticate(requestHeaders);
187
+ }
188
+ span.setAttribute("http.method", tool.method);
189
+ span.setAttribute("http.url", url);
190
+ const response = await fetch(url, {
191
+ method: tool.method,
192
+ headers: requestHeaders,
193
+ body,
194
+ });
195
+ span.setAttribute("http.status_code", response.status);
196
+ if (!response.ok) {
197
+ const errorBody = await response.text();
198
+ throw new Error(`Tool call failed: ${response.status} ${response.statusText}${errorBody ? `\n${errorBody}` : ""}`);
199
+ }
200
+ // Parse response
201
+ if (response.status === 204 || response.status === 205) {
202
+ return null;
203
+ }
204
+ const contentType = response.headers.get("content-type") ?? "";
205
+ if (contentType.includes("json")) {
206
+ return response.json();
207
+ }
208
+ return response.text();
209
+ });
210
+ }
211
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
package/dist/search.js ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Search and tokenization utilities for the MCP server wrapper tools.
3
+ *
4
+ * Extracted into a separate module so they can be unit-tested without
5
+ * importing the full server entry-point (which starts stdio transport).
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Tokenization
9
+ // ---------------------------------------------------------------------------
10
+ /**
11
+ * Tokenize text into lowercase words, splitting on non-alphanumeric characters.
12
+ * Also splits camelCase boundaries for better matching of MCP tool names.
13
+ *
14
+ * Examples:
15
+ * "github__repos_list" → ["github", "repos", "list"]
16
+ * "List public repositories" → ["list", "public", "repositories"]
17
+ * "security-advisories" → ["security", "advisories"]
18
+ */
19
+ export function tokenize(text) {
20
+ return text
21
+ .replace(/([a-z])([A-Z])/g, "$1 $2") // split camelCase boundaries
22
+ .toLowerCase()
23
+ .split(/[^a-z0-9]+/)
24
+ .filter(Boolean);
25
+ }
26
+ // ---------------------------------------------------------------------------
27
+ // TF-IDF scoring
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * Build a TF-IDF scoring function over a corpus of tools.
31
+ *
32
+ * Field weights applied during term-frequency calculation:
33
+ * - name × 3 (most signal)
34
+ * - tags × 2 (category signal)
35
+ * - description × 1
36
+ *
37
+ * IDF formula: log((N + 1) / df) — smoothed to avoid division-by-zero.
38
+ *
39
+ * Returns a function `score(tool, queryWords) → number` that can be called
40
+ * for each candidate. Build once per search call, reuse across candidates.
41
+ */
42
+ export function buildTfIdf(tools) {
43
+ const N = tools.length;
44
+ if (N === 0)
45
+ return () => 0;
46
+ // Precompute weighted token bags for each tool
47
+ const bags = tools.map((tool) => {
48
+ const bag = new Map();
49
+ const add = (tokens, weight) => {
50
+ for (const tok of tokens) {
51
+ bag.set(tok, (bag.get(tok) ?? 0) + weight);
52
+ }
53
+ };
54
+ add(tokenize(tool.mcpName), 3);
55
+ for (const tag of tool.tags) {
56
+ add(tokenize(tag), 2);
57
+ }
58
+ add(tokenize(tool.description), 1);
59
+ return bag;
60
+ });
61
+ // Document frequency: how many tools contain each token
62
+ const df = new Map();
63
+ for (const bag of bags) {
64
+ for (const tok of bag.keys()) {
65
+ df.set(tok, (df.get(tok) ?? 0) + 1);
66
+ }
67
+ }
68
+ const bagIndex = new Map(tools.map((t, i) => [t, bags[i]]));
69
+ return (tool, words) => {
70
+ const bag = bagIndex.get(tool);
71
+ if (!bag)
72
+ return 0;
73
+ const totalWeight = [...bag.values()].reduce((s, v) => s + v, 0) || 1;
74
+ let score = 0;
75
+ for (const word of words) {
76
+ const tf = (bag.get(word) ?? 0) / totalWeight;
77
+ const docFreq = df.get(word) ?? 0;
78
+ if (docFreq === 0)
79
+ continue;
80
+ const idf = Math.log((N + 1) / docFreq);
81
+ score += tf * idf;
82
+ }
83
+ return score;
84
+ };
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // searchTools
88
+ // ---------------------------------------------------------------------------
89
+ /**
90
+ * Search tools by keyword using TF-IDF relevance ranking.
91
+ *
92
+ * Matching: ALL query words must appear in at least one of name, tags, or
93
+ * description (case-insensitive substring match used for filtering; TF-IDF
94
+ * token scoring used for ranking).
95
+ *
96
+ * Returns up to `limit` results in descending relevance order.
97
+ */
98
+ export function searchTools(tools, query, provider, limit) {
99
+ const words = tokenize(query);
100
+ if (words.length === 0)
101
+ return [];
102
+ const source = provider ? tools.filter((t) => t.providerName === provider) : tools;
103
+ const scoreFn = buildTfIdf(source);
104
+ const candidates = [];
105
+ for (const tool of source) {
106
+ const nameLower = tool.mcpName.toLowerCase();
107
+ const tagsText = tool.tags.join(" ").toLowerCase();
108
+ const descLower = tool.description.toLowerCase();
109
+ const allMatch = words.every((word) => nameLower.includes(word) || tagsText.includes(word) || descLower.includes(word));
110
+ if (allMatch) {
111
+ candidates.push({ tool, score: scoreFn(tool, words) });
112
+ }
113
+ }
114
+ candidates.sort((a, b) => b.score - a.score);
115
+ return candidates.slice(0, limit).map(({ tool }) => ({
116
+ name: tool.mcpName,
117
+ description: tool.description,
118
+ tags: tool.tags,
119
+ }));
120
+ }
121
+ // ---------------------------------------------------------------------------
122
+ // groupTools
123
+ // ---------------------------------------------------------------------------
124
+ /**
125
+ * Group tools by provider name or by OpenAPI tag.
126
+ * Tools with no tags fall back to grouping under their provider name.
127
+ */
128
+ export function groupTools(tools, by) {
129
+ const grouped = {};
130
+ for (const tool of tools) {
131
+ if (by === "provider") {
132
+ (grouped[tool.providerName] ??= []).push(tool.mcpName);
133
+ }
134
+ else {
135
+ const keys = tool.tags.length > 0 ? tool.tags : [tool.providerName];
136
+ for (const key of keys) {
137
+ (grouped[key] ??= []).push(tool.mcpName);
138
+ }
139
+ }
140
+ }
141
+ return grouped;
142
+ }
143
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"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"}
package/dist/server.js ADDED
@@ -0,0 +1,294 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @utdk/mcp-server — Unified MCP server with dynamic tool loading.
4
+ *
5
+ * Usage:
6
+ * UTDK_PROVIDERS=github,slack npx @utdk/mcp-server
7
+ *
8
+ * MCP config:
9
+ * { "command": "npx", "args": ["@utdk/mcp-server"], "env": { "UTDK_PROVIDERS": "github" } }
10
+ *
11
+ * Environment variables:
12
+ * UTDK_PROVIDERS Comma-separated list of @utdk providers to load (required)
13
+ * UTDK_OTEL_EXPORTER Telemetry exporter: "otlp" | "console" | unset (noop)
14
+ * <PROVIDER>_TOKEN Bearer token for a provider (e.g. GITHUB_TOKEN)
15
+ * <PROVIDER>_SECRET_KEY API key for a provider (e.g. STRIPE_SECRET_KEY)
16
+ *
17
+ * Hot-reload:
18
+ * Send SIGHUP to reload provider list from the current UTDK_PROVIDERS env value.
19
+ */
20
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
21
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
23
+ import { executeTool, initTelemetry, loadProviders, parseProviderNames, } from "./loader.js";
24
+ import { searchTools, groupTools } from "./search.js";
25
+ // ---------------------------------------------------------------------------
26
+ // Schema normalization
27
+ // ---------------------------------------------------------------------------
28
+ /**
29
+ * Ensure the input schema is a valid MCP tool inputSchema (type: "object").
30
+ */
31
+ function normalizeInputSchema(schema) {
32
+ if (schema["type"] === "object") {
33
+ return {
34
+ type: "object",
35
+ ...(schema["properties"]
36
+ ? { properties: schema["properties"] }
37
+ : {}),
38
+ ...(schema["required"] ? { required: schema["required"] } : {}),
39
+ };
40
+ }
41
+ return {
42
+ type: "object",
43
+ ...(schema["properties"]
44
+ ? { properties: schema["properties"] }
45
+ : {}),
46
+ ...(schema["required"] ? { required: schema["required"] } : {}),
47
+ };
48
+ }
49
+ // search_tools and groupTools are imported from ./search.js above.
50
+ // ---------------------------------------------------------------------------
51
+ // MCP Server factory
52
+ // ---------------------------------------------------------------------------
53
+ /**
54
+ * Create a low-level MCP Server that serves tools from the given ProviderTool list.
55
+ * Uses raw request handlers for tools/list and tools/call to support dynamic,
56
+ * JSON-schema-based tool definitions without requiring Zod schemas.
57
+ */
58
+ function createServer(tools) {
59
+ const server = new Server({ name: "@utdk/mcp-server", version: "0.1.0" }, { capabilities: { tools: {} } });
60
+ // Build a lookup map for fast dispatch
61
+ const toolMap = new Map(tools.map((t) => [t.mcpName, t]));
62
+ // tools/list — return exactly the 4 meta-tools (no direct provider tool exposure)
63
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
64
+ tools: [
65
+ {
66
+ name: "list_tools",
67
+ description: "List available tool names without full schemas. Pass an optional provider to filter results, or group_by to organize by category.",
68
+ inputSchema: {
69
+ type: "object",
70
+ properties: {
71
+ provider: {
72
+ type: "string",
73
+ description: "Filter results to tools from this provider name (e.g. 'github')",
74
+ },
75
+ group_by: {
76
+ type: "string",
77
+ enum: ["provider", "tag"],
78
+ description: "Group results by 'provider' or by OpenAPI 'tag'. When omitted, returns a flat list of names.",
79
+ },
80
+ },
81
+ },
82
+ },
83
+ {
84
+ name: "search_tools",
85
+ description: "Find tools by keyword. Searches tool names, OpenAPI tags, and descriptions using TF-IDF relevance ranking so the LLM can locate relevant tools without browsing the full catalog.",
86
+ inputSchema: {
87
+ type: "object",
88
+ properties: {
89
+ query: {
90
+ type: "string",
91
+ description: "Natural language or keyword search (all words must appear in name, tags, or description)",
92
+ },
93
+ provider: {
94
+ type: "string",
95
+ description: "Restrict search to tools from this provider name (e.g. 'github')",
96
+ },
97
+ limit: {
98
+ type: "number",
99
+ description: "Maximum number of results to return (default 10)",
100
+ },
101
+ },
102
+ required: ["query"],
103
+ },
104
+ },
105
+ {
106
+ name: "tool_info",
107
+ description: "Get the full schema and metadata for a specific tool by name. Use this after list_tools or search_tools to retrieve the complete input schema before calling the tool.",
108
+ inputSchema: {
109
+ type: "object",
110
+ properties: {
111
+ tool_name: {
112
+ type: "string",
113
+ description: "The MCP tool name (e.g. 'github__repos_list')",
114
+ },
115
+ },
116
+ required: ["tool_name"],
117
+ },
118
+ },
119
+ {
120
+ name: "call_tool",
121
+ description: "Execute any registered tool by name with the provided arguments. Use tool_info first to get the correct argument schema.",
122
+ inputSchema: {
123
+ type: "object",
124
+ properties: {
125
+ tool_name: {
126
+ type: "string",
127
+ description: "The MCP tool name to execute (e.g. 'github__repos_list')",
128
+ },
129
+ arguments: {
130
+ type: "object",
131
+ description: "Arguments to pass to the tool",
132
+ },
133
+ },
134
+ required: ["tool_name", "arguments"],
135
+ },
136
+ },
137
+ ],
138
+ }));
139
+ // tools/call — dispatch to the 4 meta-tools only
140
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
141
+ const toolName = request.params.name;
142
+ const args = (request.params.arguments ?? {});
143
+ // list_tools meta-tool: enumerate provider tool names without schemas
144
+ if (toolName === "list_tools") {
145
+ const providerFilter = typeof args["provider"] === "string" ? args["provider"] : undefined;
146
+ const groupBy = args["group_by"] === "provider" || args["group_by"] === "tag"
147
+ ? args["group_by"]
148
+ : undefined;
149
+ const filtered = providerFilter
150
+ ? tools.filter((t) => t.providerName === providerFilter)
151
+ : tools;
152
+ let result;
153
+ if (groupBy === "provider" || groupBy === "tag") {
154
+ result = groupTools(filtered, groupBy);
155
+ }
156
+ else {
157
+ result = filtered.map((t) => t.mcpName);
158
+ }
159
+ return {
160
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
161
+ };
162
+ }
163
+ // search_tools meta-tool: keyword search across tool names and descriptions
164
+ if (toolName === "search_tools") {
165
+ const query = typeof args["query"] === "string" ? args["query"] : "";
166
+ const providerFilter = typeof args["provider"] === "string" ? args["provider"] : undefined;
167
+ const limit = typeof args["limit"] === "number" && args["limit"] > 0 ? Math.floor(args["limit"]) : 10;
168
+ const results = searchTools(tools, query, providerFilter, limit);
169
+ return {
170
+ content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
171
+ };
172
+ }
173
+ // tool_info meta-tool: return full schema and metadata for a specific tool
174
+ if (toolName === "tool_info") {
175
+ const requestedName = typeof args["tool_name"] === "string" ? args["tool_name"] : "";
176
+ const tool = toolMap.get(requestedName);
177
+ if (!tool) {
178
+ return {
179
+ isError: true,
180
+ content: [{ type: "text", text: `Unknown tool: ${requestedName}` }],
181
+ };
182
+ }
183
+ const info = {
184
+ name: tool.mcpName,
185
+ description: tool.description,
186
+ provider: tool.providerName,
187
+ inputSchema: normalizeInputSchema(tool.inputSchema),
188
+ };
189
+ return {
190
+ content: [{ type: "text", text: JSON.stringify(info, null, 2) }],
191
+ };
192
+ }
193
+ // call_tool meta-tool: execute a registered tool by name
194
+ if (toolName === "call_tool") {
195
+ const requestedName = typeof args["tool_name"] === "string" ? args["tool_name"] : "";
196
+ const toolArgs = typeof args["arguments"] === "object" && args["arguments"] !== null
197
+ ? args["arguments"]
198
+ : {};
199
+ const tool = toolMap.get(requestedName);
200
+ if (!tool) {
201
+ return {
202
+ isError: true,
203
+ content: [{ type: "text", text: `Unknown tool: ${requestedName}` }],
204
+ };
205
+ }
206
+ try {
207
+ const result = await executeTool(tool, toolArgs);
208
+ const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
209
+ return { content: [{ type: "text", text }] };
210
+ }
211
+ catch (err) {
212
+ const message = err instanceof Error ? err.message : String(err);
213
+ return {
214
+ isError: true,
215
+ content: [{ type: "text", text: `Error calling ${requestedName}: ${message}` }],
216
+ };
217
+ }
218
+ }
219
+ return {
220
+ isError: true,
221
+ content: [
222
+ {
223
+ type: "text",
224
+ text: `Unknown tool: ${toolName}. Use list_tools or search_tools to discover tools, then call_tool to execute them.`,
225
+ },
226
+ ],
227
+ };
228
+ });
229
+ return server;
230
+ }
231
+ // ---------------------------------------------------------------------------
232
+ // Hot-reload support
233
+ // ---------------------------------------------------------------------------
234
+ /**
235
+ * Reload providers from the current UTDK_PROVIDERS env value.
236
+ * Returns the new tool list (the caller is responsible for reconnecting if needed).
237
+ */
238
+ async function reloadProviders() {
239
+ const providerNames = parseProviderNames(process.env["UTDK_PROVIDERS"]);
240
+ process.stderr.write(`[mcp-server] (Re)loading providers: ${providerNames.join(", ") || "(none)"}\n`);
241
+ const tools = await loadProviders(providerNames);
242
+ process.stderr.write(`[mcp-server] Loaded ${tools.length} total tool(s) from ${providerNames.length} provider(s)\n`);
243
+ return tools;
244
+ }
245
+ // ---------------------------------------------------------------------------
246
+ // Entry point
247
+ // ---------------------------------------------------------------------------
248
+ async function main() {
249
+ await initTelemetry();
250
+ const providerNames = parseProviderNames(process.env["UTDK_PROVIDERS"]);
251
+ if (providerNames.length === 0) {
252
+ process.stderr.write("[mcp-server] Warning: UTDK_PROVIDERS is not set — starting with no tools.\n" +
253
+ "[mcp-server] Set UTDK_PROVIDERS=github,slack,stripe to load providers.\n");
254
+ }
255
+ else {
256
+ process.stderr.write(`[mcp-server] Loading providers: ${providerNames.join(", ")}\n`);
257
+ }
258
+ let tools = await loadProviders(providerNames);
259
+ process.stderr.write(`[mcp-server] Ready with ${tools.length} tool(s). Starting MCP stdio server...\n`);
260
+ let server = createServer(tools);
261
+ const transport = new StdioServerTransport();
262
+ // SIGHUP → hot-reload provider list.
263
+ // Because the MCP protocol is stateful (initialized per connection), we rebuild
264
+ // the internal request handler registry. The client sees updated tools on the
265
+ // next tools/list request without needing to reconnect.
266
+ process.on("SIGHUP", () => {
267
+ process.stderr.write("[mcp-server] SIGHUP received — reloading providers...\n");
268
+ reloadProviders()
269
+ .then((newTools) => {
270
+ tools = newTools;
271
+ // Rebuild server handlers in-place by re-registering on the existing server.
272
+ // The transport connection is preserved; only the handler registry changes.
273
+ server = createServer(newTools);
274
+ // Reconnect the new server to the existing transport so future messages
275
+ // are routed to the updated handler set.
276
+ server.connect(transport).catch((err) => {
277
+ process.stderr.write(`[mcp-server] Reconnect error after SIGHUP: ${err}\n`);
278
+ });
279
+ })
280
+ .catch((err) => {
281
+ process.stderr.write(`[mcp-server] Reload error: ${err}\n`);
282
+ });
283
+ });
284
+ process.on("SIGTERM", () => {
285
+ process.stderr.write("[mcp-server] SIGTERM received — shutting down.\n");
286
+ process.exit(0);
287
+ });
288
+ await server.connect(transport);
289
+ }
290
+ main().catch((err) => {
291
+ process.stderr.write(`[mcp-server] Fatal: ${err}\n`);
292
+ process.exit(1);
293
+ });
294
+ //# sourceMappingURL=server.js.map