-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathserver.ts
More file actions
124 lines (104 loc) · 3.7 KB
/
server.ts
File metadata and controls
124 lines (104 loc) · 3.7 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
119
120
121
122
123
124
/**
* Hidrix Tools — MCP Server
*
* Auto-discovers tools from the tools directory and registers them.
* To add a new tool: create tools/your-tool/index.ts and export `definition`.
*
* Usage:
* bun run server.ts # stdio transport (default)
* bun run server.ts --http # Streamable HTTP transport (port 3100)
* PORT=8080 bun run server.ts --http # custom port
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { loadTools, checkEnvVars } from "./lib/tool-registry.js";
import { join } from "path";
const VERSION = "2.0.0";
const DEFAULT_HTTP_PORT = 3100;
function createServer(): McpServer {
return new McpServer({
name: "hidrix-tools",
version: VERSION,
});
}
async function registerTools(server: McpServer) {
const toolsDir = join(import.meta.dir, "tools");
const tools = await loadTools(toolsDir);
let registered = 0;
for (const tool of tools) {
const missing = checkEnvVars(tool);
if (missing.length > 0) {
console.error(`[hidrix-tools] ${tool.name}: skipped (missing env: ${missing.join(", ")})`);
continue;
}
server.tool(
tool.name,
tool.description,
tool.params,
async (params: Record<string, any>) => {
try {
const result = await tool.execute(params);
return { content: [{ type: "text" as const, text: result }] };
} catch (e: any) {
return { content: [{ type: "text" as const, text: `Error: ${e.message}` }], isError: true };
}
},
);
console.error(`[hidrix-tools] ✓ ${tool.name}`);
registered++;
}
console.error(`[hidrix-tools] ${registered}/${tools.length} tools loaded`);
return registered;
}
async function startStdio(server: McpServer) {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("[hidrix-tools] MCP server running on stdio");
}
async function startHttp(server: McpServer) {
const { createServer: createHttpServer } = await import("http");
const port = parseInt(process.env.PORT || String(DEFAULT_HTTP_PORT));
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => crypto.randomUUID() });
await server.connect(transport);
const httpServer = createHttpServer(async (req, res) => {
const url = req.url || "/";
if (url === "/mcp" && (req.method === "POST" || req.method === "GET" || req.method === "DELETE")) {
// Parse body for POST
if (req.method === "POST") {
const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(chunk);
const body = JSON.parse(Buffer.concat(chunks).toString());
await transport.handleRequest(req, res, body);
} else {
await transport.handleRequest(req, res);
}
return;
}
if (url === "/health") {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ status: "ok", version: VERSION }));
return;
}
res.writeHead(404);
res.end("Not Found");
});
httpServer.listen(port, () => {
console.error(`[hidrix-tools] MCP server running on http://localhost:${port}/mcp`);
console.error(`[hidrix-tools] Health check: http://localhost:${port}/health`);
});
}
async function main() {
const useHttp = process.argv.includes("--http");
const server = createServer();
await registerTools(server);
if (useHttp) {
await startHttp(server);
} else {
await startStdio(server);
}
}
main().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});