Skip to content

Commit b33349f

Browse files
committed
refactor to use zod schemas and reduce any usage
1 parent 3d38b49 commit b33349f

File tree

9 files changed

+301
-239
lines changed

9 files changed

+301
-239
lines changed

package-lock.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"js-yaml": "^4.1.0",
3232
"jsonwebtoken": "^9.0.2",
3333
"node-fetch": "^3.3.2",
34-
"uuid": "^10.0.0"
34+
"uuid": "^10.0.0",
35+
"zod": "^3.25.76"
3536
},
3637
"devDependencies": {
3738
"@types/js-yaml": "^4.0.9",

src/config.ts

Lines changed: 52 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,67 @@
11
import { readFileSync } from 'fs';
22
import * as yaml from 'js-yaml';
33
import { config } from 'dotenv';
4+
import { z } from 'zod';
45

56
// Load environment variables
67
config();
78

8-
interface MCPServer {
9-
command: string;
10-
args?: string[];
11-
env?: Record<string, string>;
12-
}
13-
14-
interface AmpSettings {
15-
'amp.url': string;
16-
'amp.mcpServers': {
17-
[key: string]: MCPServer;
18-
};
19-
}
9+
// Single source of truth: Zod schema for config validation
10+
const ConfigSchema = z.object({
11+
github: z.object({
12+
base_url: z.string(),
13+
token: z.string().optional(),
14+
check_name: z.string(),
15+
development_mode: z.boolean(),
16+
bot_username: z.string(),
17+
webhook_secret: z.string().optional(),
18+
}),
19+
queue: z.object({
20+
max_workers: z.coerce.number(),
21+
max_queue_size: z.coerce.number(),
22+
retry_after_seconds: z.coerce.number(),
23+
}),
24+
diff_splitting: z.object({
25+
max_chunk_size: z.coerce.number(),
26+
max_concurrent: z.coerce.number(),
27+
}),
28+
server: z.object({
29+
port: z.string(),
30+
debug: z.string(),
31+
}),
32+
amp: z.object({
33+
timeout: z.string(),
34+
command: z.string(),
35+
server_url: z.string(),
36+
settings: z.object({
37+
'amp.url': z.string(),
38+
'amp.mcpServers': z.record(z.object({
39+
command: z.string(),
40+
args: z.array(z.string()).optional(),
41+
env: z.record(z.string()).optional(),
42+
})),
43+
}),
44+
prompt_template: z.string(),
45+
tools: z.array(z.object({
46+
name: z.string(),
47+
description: z.string(),
48+
instructions: z.array(z.string()),
49+
})),
50+
}),
51+
});
2052

21-
interface Tool {
22-
name: string;
23-
description: string;
24-
instructions: string[];
25-
}
26-
27-
export interface Config {
28-
github: {
29-
base_url: string;
30-
token: string;
31-
check_name: string;
32-
development_mode: boolean;
33-
bot_username: string;
34-
webhook_secret?: string;
35-
};
36-
queue: {
37-
max_workers: number;
38-
max_queue_size: number;
39-
retry_after_seconds: number;
40-
};
41-
diff_splitting: {
42-
max_chunk_size: number;
43-
max_concurrent: number;
44-
};
45-
server: {
46-
port: string;
47-
debug: string;
48-
};
49-
amp: {
50-
timeout: string;
51-
command: string;
52-
server_url: string;
53-
settings: AmpSettings;
54-
prompt_template: string;
55-
tools: Tool[];
56-
};
57-
}
53+
// Export the inferred type as the single source of truth
54+
export type Config = z.infer<typeof ConfigSchema>;
5855

5956
class ConfigLoader {
6057
private static instance: ConfigLoader;
6158
private config: Config;
6259

63-
private constructor() {
60+
private constructor() {
6461
const configFile = readFileSync('config.yml', 'utf8');
65-
const rawConfig = yaml.load(configFile) as any;
66-
this.config = this.processEnvVars(rawConfig);
62+
const rawConfig = yaml.load(configFile);
63+
const processedConfig = this.processEnvVars(rawConfig);
64+
this.config = ConfigSchema.parse(processedConfig);
6765
}
6866

6967
static getInstance(): ConfigLoader {
@@ -77,7 +75,7 @@ class ConfigLoader {
7775
return this.config;
7876
}
7977

80-
private processEnvVars(obj: any): any {
78+
private processEnvVars(obj: unknown): unknown {
8179
if (typeof obj === 'string') {
8280
return obj.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
8381
const value = process.env[envVar];
@@ -93,7 +91,7 @@ class ConfigLoader {
9391
return obj.map(item => this.processEnvVars(item));
9492
}
9593
if (obj && typeof obj === 'object') {
96-
const result: any = {};
94+
const result: Record<string, unknown> = {};
9795
for (const [key, value] of Object.entries(obj)) {
9896
result[key] = this.processEnvVars(value);
9997
}

src/mcp/http-server.ts

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Hono } from "hono";
1+
import { Hono, Context, Next } from "hono";
22
import { cors } from "hono/cors";
33
// Remove unused imports
44
import { getConfig, Config } from "../config.js";
@@ -33,7 +33,7 @@ export function createMCPRoutes(): Hono {
3333
);
3434

3535
// Middleware to validate Authorization header
36-
const authMiddleware = async (c: any, next: any) => {
36+
const authMiddleware = async (c: Context, next: Next) => {
3737
const authHeader = c.req.header("Authorization");
3838
const expectedToken = process.env.MCP_AUTH_TOKEN;
3939

@@ -83,18 +83,20 @@ export function createMCPRoutes(): Hono {
8383
const body = await c.req.json();
8484
console.log("🔍 Received MCP request:", JSON.stringify(body, null, 2));
8585

86+
const bodyObj = body as Record<string, unknown>;
87+
8688
// Handle JSON-RPC request
87-
if (body.jsonrpc !== "2.0") {
88-
console.log("❌ Invalid JSON-RPC version:", body.jsonrpc);
89+
if (bodyObj.jsonrpc !== "2.0") {
90+
console.log("❌ Invalid JSON-RPC version:", bodyObj.jsonrpc);
8991
return c.json({
9092
jsonrpc: "2.0",
9193
error: { code: -32600, message: "Invalid Request" },
92-
id: body.id || null
94+
id: bodyObj.id || null
9395
}, 400);
9496
}
9597

96-
console.log("📋 Method:", body.method, "ID:", body.id);
97-
switch (body.method) {
98+
console.log("📋 Method:", bodyObj.method, "ID:", bodyObj.id);
99+
switch (bodyObj.method) {
98100
case "initialize":
99101
console.log("🔧 Handling initialize");
100102
return handleInitialize(c, body);
@@ -112,7 +114,7 @@ export function createMCPRoutes(): Hono {
112114
return c.json({
113115
jsonrpc: "2.0",
114116
error: { code: -32601, message: "Method not found" },
115-
id: body.id || null
117+
id: bodyObj.id || null
116118
}, 400);
117119
}
118120
} catch (error) {
@@ -153,9 +155,10 @@ export function createMCPRoutes(): Hono {
153155
return app;
154156
}
155157

156-
function handleInitialize(c: any, body: any) {
158+
function handleInitialize(c: Context, body: unknown) {
157159
try {
158-
console.log("🚀 Initializing MCP server with params:", body.params);
160+
const bodyObj = body as Record<string, unknown>;
161+
console.log("🚀 Initializing MCP server with params:", bodyObj.params);
159162

160163
return c.json({
161164
jsonrpc: "2.0",
@@ -169,19 +172,19 @@ function handleInitialize(c: any, body: any) {
169172
version: "2.0.0"
170173
}
171174
},
172-
id: body.id
175+
id: bodyObj.id
173176
});
174177
} catch (error) {
175178
console.error("Error during initialization:", error);
176179
return c.json({
177180
jsonrpc: "2.0",
178181
error: { code: -32603, message: "Internal error" },
179-
id: body.id || null
182+
id: (body as Record<string, unknown>).id || null
180183
}, 500);
181184
}
182185
}
183186

184-
function handleInitializedNotification(c: any, body: any) {
187+
function handleInitializedNotification(c: Context, body: unknown) {
185188
try {
186189
console.log("📢 Received initialized notification");
187190
// Notifications don't require a response, but we return 202 Accepted
@@ -191,14 +194,14 @@ function handleInitializedNotification(c: any, body: any) {
191194
return c.json({
192195
jsonrpc: "2.0",
193196
error: { code: -32603, message: "Internal error" },
194-
id: body.id || null
197+
id: (body as Record<string, unknown>).id || null
195198
}, 500);
196199
}
197200
}
198201

199-
function handleToolsList(c: any, body: any) {
202+
function handleToolsList(c: Context, body: unknown) {
200203
try {
201-
console.log("📋 Listing tools for request ID:", body.id);
204+
console.log("📋 Listing tools for request ID:", (body as Record<string, unknown>).id);
202205
const tools = [
203206
{
204207
name: "leave_general_comment",
@@ -394,29 +397,30 @@ function handleToolsList(c: any, body: any) {
394397
return c.json({
395398
jsonrpc: "2.0",
396399
result: { tools },
397-
id: body.id
400+
id: (body as Record<string, unknown>).id
398401
});
399402
} catch (error) {
400403
console.error("Error listing tools:", error);
401404
return c.json({
402405
jsonrpc: "2.0",
403406
error: { code: -32603, message: "Internal error" },
404-
id: body.id || null
407+
id: (body as Record<string, unknown>).id || null
405408
}, 500);
406409
}
407410
}
408411

409-
async function handleToolsCall(c: any, body: any, config: Config, githubClient: GitHubClient) {
412+
async function handleToolsCall(c: Context, body: unknown, config: Config, githubClient: GitHubClient) {
413+
const bodyObj = body as Record<string, unknown>;
410414
try {
411-
console.log("🔧 Tool call params:", JSON.stringify(body.params, null, 2));
412-
const { name, arguments: args } = body.params || {};
415+
console.log("🔧 Tool call params:", JSON.stringify(bodyObj.params, null, 2));
416+
const { name, arguments: args } = (bodyObj.params as Record<string, unknown>) || {};
413417

414418
if (!name) {
415419
console.log("❌ Missing tool name in params");
416420
return c.json({
417421
jsonrpc: "2.0",
418422
error: { code: -32602, message: "Invalid params: missing tool name" },
419-
id: body.id || null
423+
id: bodyObj.id || null
420424
}, 400);
421425
}
422426

@@ -484,15 +488,15 @@ async function handleToolsCall(c: any, body: any, config: Config, githubClient:
484488
return c.json({
485489
jsonrpc: "2.0",
486490
error: { code: -32601, message: `Unknown tool: ${name}` },
487-
id: body.id || null
491+
id: bodyObj.id || null
488492
}, 400);
489493
}
490494

491495
console.log("✅ Tool call successful, result:", JSON.stringify(result, null, 2));
492496
return c.json({
493497
jsonrpc: "2.0",
494498
result,
495-
id: body.id
499+
id: bodyObj.id
496500
});
497501
} catch (error) {
498502
console.error("Error calling tool:", error);
@@ -502,7 +506,7 @@ async function handleToolsCall(c: any, body: any, config: Config, githubClient:
502506
code: -32603,
503507
message: error instanceof Error ? error.message : "Unknown error"
504508
},
505-
id: body.id || null
509+
id: bodyObj.id || null
506510
}, 500);
507511
}
508512
}

src/mcp/tools/get_pr_comments.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export async function getPRComments(
1313
githubClient: GitHubClient
1414
): Promise<{
1515
success: boolean;
16-
comments?: any[];
16+
comments?: unknown[];
1717
total_comments?: number;
1818
error?: string
1919
}> {

src/mcp/tools/get_pr_info.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ export async function getPRInfo(
1414
githubClient: GitHubClient
1515
): Promise<{
1616
success: boolean;
17-
pr_info?: any;
17+
pr_info?: unknown;
1818
diff?: string;
19-
repository_info?: any;
19+
repository_info?: unknown;
2020
error?: string
2121
}> {
2222
try {

0 commit comments

Comments
 (0)