Skip to content

Commit f62fe8a

Browse files
committed
strenghten the eslint rules
Remove to exception rules and enforce better type check
1 parent 9b0fc6b commit f62fe8a

6 files changed

Lines changed: 65 additions & 52 deletions

File tree

eslint.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ export default [
3636
caughtErrorsIgnorePattern: "^_",
3737
},
3838
],
39-
"@typescript-eslint/no-explicit-any": "off", // Allow any for now in existing codebase
40-
"@typescript-eslint/explicit-function-return-type": "off",
39+
"@typescript-eslint/explicit-function-return-type": "error", // Require explicit return types
40+
"@typescript-eslint/no-explicit-any": "error", // Disallow explicit any types
4141
"@typescript-eslint/explicit-module-boundary-types": "off",
4242

4343
// General JavaScript/TypeScript rules - most important ones

src/extract-tools.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class AAPOperationObject implements OpenAPIV3.OperationObject {
3030
public parameters?: OpenAPIV3.ParameterObject[];
3131
public responses: OpenAPIV3.ResponsesObject;
3232

33-
constructor(rawOperation: any) {
33+
constructor(rawOperation: Partial<OpenAPIV3.OperationObject>) {
3434
Object.assign(this, rawOperation);
3535
this.deprecated = rawOperation.deprecated || false;
3636
this.responses = rawOperation.responses || {};
@@ -41,7 +41,7 @@ export class AAPOperationObject implements OpenAPIV3.OperationObject {
4141
/**
4242
* Normalize a value to boolean if it looks like a boolean; otherwise undefined.
4343
*/
44-
export function normalizeBoolean(value: any): boolean | undefined {
44+
export function normalizeBoolean(value: unknown): boolean | undefined {
4545
if (typeof value === "boolean") return value;
4646
if (typeof value === "string") {
4747
const normalized = value.trim().toLowerCase();
@@ -62,7 +62,7 @@ export function shouldIncludeOperationForMcp(
6262
operation: AAPOperationObject,
6363
defaultInclude = true,
6464
): boolean {
65-
const opRaw = (operation as any)["x-mcp"];
65+
const opRaw = (operation as Record<string, unknown>)["x-mcp"];
6666
const opVal = normalizeBoolean(opRaw);
6767
if (typeof opVal !== "undefined") return opVal;
6868
if (typeof opRaw !== "undefined") {
@@ -72,7 +72,7 @@ export function shouldIncludeOperationForMcp(
7272
`-> expected boolean or 'true'/'false'. Falling back to path/root/default.`,
7373
);
7474
}
75-
const pathRaw = (pathItem as any)["x-mcp"];
75+
const pathRaw = (pathItem as Record<string, unknown>)["x-mcp"];
7676
const pathVal = normalizeBoolean(pathRaw);
7777
if (typeof pathVal !== "undefined") return pathVal;
7878
if (typeof pathRaw !== "undefined") {
@@ -82,7 +82,7 @@ export function shouldIncludeOperationForMcp(
8282
`-> expected boolean or 'true'/'false'. Falling back to root/default.`,
8383
);
8484
}
85-
const rootRaw = (api as any)["x-mcp"];
85+
const rootRaw = (api as Record<string, unknown>)["x-mcp"];
8686
const rootVal = normalizeBoolean(rootRaw);
8787
if (typeof rootVal !== "undefined") return rootVal;
8888
if (typeof rootRaw !== "undefined") {
@@ -148,14 +148,14 @@ export function mapOpenApiSchemaToJsonSchema(
148148
// Detect cycles
149149
if (seen.has(schema)) {
150150
console.warn(
151-
`Cycle detected in schema${(schema as any).title ? ` "${(schema as any).title}"` : ""}, returning generic object to break recursion.`,
151+
`Cycle detected in schema${(schema as Record<string, unknown>).title ? ` "${(schema as Record<string, unknown>).title}"` : ""}, returning generic object to break recursion.`,
152152
);
153153
return { type: "object" };
154154
}
155155
seen.add(schema);
156156
try {
157157
// Create a copy of the schema to modify
158-
const jsonSchema: any = { ...schema };
158+
const jsonSchema: Record<string, unknown> = { ...schema };
159159
// Convert integer type to number (JSON Schema compatible)
160160
if (schema.type === "integer") jsonSchema.type = "number";
161161
// Remove OpenAPI-specific properties that aren't in JSON Schema
@@ -167,7 +167,7 @@ export function mapOpenApiSchemaToJsonSchema(
167167
delete jsonSchema.readOnly;
168168
delete jsonSchema.writeOnly;
169169
// Handle nullable properties by adding null to the type
170-
if ((schema as any).nullable) {
170+
if ((schema as Record<string, unknown>).nullable) {
171171
if (Array.isArray(jsonSchema.type)) {
172172
if (!jsonSchema.type.includes("null")) jsonSchema.type.push("null");
173173
} else if (typeof jsonSchema.type === "string") {
@@ -178,11 +178,11 @@ export function mapOpenApiSchemaToJsonSchema(
178178
}
179179
// Recursively process object properties
180180
if (jsonSchema.type === "object" && jsonSchema.properties) {
181-
const mappedProps: any = {};
181+
const mappedProps: Record<string, JSONSchema7 | boolean> = {};
182182
for (const [key, propSchema] of Object.entries(jsonSchema.properties)) {
183183
if (typeof propSchema === "object" && propSchema !== null) {
184184
mappedProps[key] = mapOpenApiSchemaToJsonSchema(
185-
propSchema as any,
185+
propSchema as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
186186
seen,
187187
);
188188
} else if (typeof propSchema === "boolean") {
@@ -198,7 +198,7 @@ export function mapOpenApiSchemaToJsonSchema(
198198
jsonSchema.items !== null
199199
) {
200200
jsonSchema.items = mapOpenApiSchemaToJsonSchema(
201-
jsonSchema.items as any,
201+
jsonSchema.items as OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject,
202202
seen,
203203
);
204204
}
@@ -358,9 +358,9 @@ export function extractToolsFromApi(
358358
} catch (error) {
359359
const loc = operation.operationId || `${method} ${path}`;
360360
const extVal =
361-
(operation as any)["x-mcp"] ??
362-
(pathItem as any)["x-mcp"] ??
363-
(api as any)["x-mcp"];
361+
(operation as Record<string, unknown>)["x-mcp"] ??
362+
(pathItem as Record<string, unknown>)["x-mcp"] ??
363+
(api as Record<string, unknown>)["x-mcp"];
364364
let extPreview: string;
365365
try {
366366
extPreview = JSON.stringify(extVal);
@@ -438,7 +438,7 @@ export function extractToolsFromApi(
438438
(value) => value[1],
439439
);
440440
const missingDescriptions = propertiesEntries.filter(
441-
(p: any) => p && typeof p === "object" && !p.description,
441+
(p: JSONSchema7 | boolean) => p && typeof p === "object" && !(p as JSONSchema7).description,
442442
);
443443
if (missingDescriptions.length) {
444444
logs.push({

src/index.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,19 @@ if (ignoreCertificateErrors) {
139139
}
140140

141141
// TypeScript interfaces
142+
interface UserInfo {
143+
is_superuser: boolean;
144+
is_platform_auditor: boolean;
145+
}
146+
147+
interface UserApiResponse {
148+
results: UserInfo[];
149+
}
150+
151+
interface ExtendedTransport extends StreamableHTTPServerTransport {
152+
categoryOverride?: string;
153+
userAgent?: string;
154+
}
142155

143156
interface SessionData {
144157
[sessionId: string]: {
@@ -197,7 +210,7 @@ const validateTokenAndGetPermissions = async (
197210
);
198211
}
199212

200-
const data = (await response.json()) as any;
213+
const data = (await response.json()) as UserApiResponse;
201214

202215
if (
203216
!data.results ||
@@ -207,7 +220,7 @@ const validateTokenAndGetPermissions = async (
207220
throw new Error("Invalid response format from /api/gateway/v1/me/");
208221
}
209222

210-
const userInfo = data.results[0] as any;
223+
const userInfo = data.results[0];
211224
return {
212225
is_superuser: userInfo.is_superuser || false,
213226
is_platform_auditor: userInfo.is_platform_auditor || false,
@@ -307,7 +320,7 @@ const generateTools = async (): Promise<AAPMcpToolDefinition[]> => {
307320

308321
try {
309322
const tools = extractToolsFromApi(
310-
bundledSpec as any,
323+
bundledSpec as OpenAPIV3.Document,
311324
) as AAPMcpToolDefinition[];
312325
const filteredTools = tools.filter((tool) => {
313326
tool.service = spec.service; // Add service information to each tool
@@ -446,9 +459,7 @@ server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
446459

447460
// Get category override from transport if available
448461
const transport = sessionId ? transports[sessionId] : null;
449-
const categoryOverride = transport
450-
? (transport as any).categoryOverride
451-
: undefined;
462+
const categoryOverride = transport?.categoryOverride;
452463

453464
// Determine user category based on category override
454465
const category = getUserCategory(categoryOverride);
@@ -503,15 +514,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
503514
// Get user-agent from transport (if available)
504515
let userAgent = "unknown";
505516
if (sessionId && transports[sessionId]) {
506-
const transport = transports[sessionId] as any;
517+
const transport = transports[sessionId];
507518
userAgent = transport.userAgent || "unknown";
508519
}
509520

510521
// Get the Bearer token for this session
511522
const bearerToken = getBearerTokenForSession(sessionId);
512523

513524
// Execute the tool by making HTTP request
514-
let result: any;
525+
let result: unknown;
515526
let response: Response | undefined;
516527
let fullUrl: string = `${CONFIG.BASE_URL}${tool.pathTemplate}`;
517528
let requestOptions: RequestInit | undefined;
@@ -645,7 +656,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
645656
});
646657

647658
// Global state management
648-
const transports: Record<string, StreamableHTTPServerTransport> = {};
659+
const transports: Record<string, ExtendedTransport> = {};
649660
const sessionData: SessionData = {};
650661

651662
const app = express();
@@ -664,7 +675,7 @@ const mcpPostHandler = async (
664675
req: express.Request,
665676
res: express.Response,
666677
categoryOverride?: string,
667-
) => {
678+
): Promise<void> => {
668679
const sessionId = req.headers["mcp-session-id"] as string;
669680
const authHeader = req.headers["authorization"] as string;
670681

@@ -683,16 +694,18 @@ const mcpPostHandler = async (
683694
} else if (!sessionId && isInitializeRequest(req.body)) {
684695
// New initialization request
685696
transport = new StreamableHTTPServerTransport({
686-
sessionIdGenerator: () => randomUUID(),
687-
onsessioninitialized: async (sessionId: string) => {
697+
sessionIdGenerator: (): string => randomUUID(),
698+
onsessioninitialized: async (sessionId: string): Promise<void> => {
688699
console.log(
689700
`${getTimestamp()} Session initialized${categoryOverride ? ` with category override: ${categoryOverride}` : ""}`,
690701
);
691-
transports[sessionId] = transport;
692702

693703
// Store category override and user-agent in transport for later access
694-
(transport as any).categoryOverride = categoryOverride;
695-
(transport as any).userAgent = req.headers["user-agent"] || "unknown";
704+
const extendedTransport = transport as ExtendedTransport;
705+
extendedTransport.categoryOverride = categoryOverride;
706+
extendedTransport.userAgent = req.headers["user-agent"] || "unknown";
707+
708+
transports[sessionId] = extendedTransport;
696709

697710
// Extract and validate the bearer token
698711
const token = extractBearerToken(authHeader);
@@ -718,7 +731,7 @@ const mcpPostHandler = async (
718731
});
719732

720733
// Set up onclose handler to clean up transport when closed
721-
transport.onclose = () => {
734+
transport.onclose = (): void => {
722735
const sid = transport.sessionId;
723736
if (sid && transports[sid]) {
724737
console.log(
@@ -772,7 +785,7 @@ const mcpGetHandler = async (
772785
req: express.Request,
773786
res: express.Response,
774787
_categoryOverride?: string,
775-
) => {
788+
): Promise<void> => {
776789
const sessionId = req.headers["mcp-session-id"] as string;
777790
const _authHeader = req.headers["authorization"] as string;
778791

@@ -801,7 +814,7 @@ const mcpDeleteHandler = async (
801814
req: express.Request,
802815
res: express.Response,
803816
_categoryOverride?: string,
804-
) => {
817+
): Promise<void> => {
805818
const sessionId = req.headers["mcp-session-id"] as string;
806819

807820
if (!sessionId || !transports[sessionId]) {

src/logger.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ import { metricsService } from "./metrics.js";
55
export interface LogEntry {
66
timestamp: string;
77
endpoint: string;
8-
payload: any;
9-
response: any;
8+
payload: Record<string, unknown>;
9+
response: Record<string, unknown>;
1010
return_code: number;
1111
}
1212

1313
export interface Tool {
1414
name: string;
1515
service?: string;
1616
category?: string;
17-
[key: string]: any;
17+
[key: string]: unknown;
1818
}
1919

2020
export class ToolLogger {
@@ -36,8 +36,8 @@ export class ToolLogger {
3636
async logToolAccess(
3737
tool: Tool,
3838
endpoint: string,
39-
payload: any,
40-
response: any,
39+
payload: Record<string, unknown>,
40+
response: Record<string, unknown>,
4141
returnCode: number,
4242
startTime?: number,
4343
_sessionId?: string,

src/views/logs.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ interface LogsEntry {
55
toolName: string;
66
return_code: number;
77
endpoint: string;
8-
payload?: any;
8+
payload?: Record<string, unknown>;
99
userAgent?: string;
1010
}
1111

@@ -35,12 +35,12 @@ export const renderLogs = (data: LogsData): string => {
3535
} = data;
3636

3737
// Helper function to format timestamp for display
38-
const formatTimestamp = (timestamp: string) => {
38+
const formatTimestamp = (timestamp: string): string => {
3939
return new Date(timestamp).toLocaleString();
4040
};
4141

4242
// Helper function to get status color
43-
const getStatusColor = (code: number) => {
43+
const getStatusColor = (code: number): string => {
4444
if (code >= 200 && code < 300) return "#28a745"; // green
4545
if (code >= 300 && code < 400) return "#ffc107"; // yellow
4646
if (code >= 400 && code < 500) return "#fd7e14"; // orange

src/views/tool-details.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ interface LogEntry {
66
timestamp: string;
77
return_code: number;
88
endpoint: string;
9-
response?: any;
9+
response?: Record<string, unknown>;
1010
}
1111

1212
interface CategoryWithAccess {
@@ -35,12 +35,12 @@ export const renderToolDetails = (data: ToolDetailsData): string => {
3535
} = data;
3636

3737
// Helper function to format timestamp for display
38-
const formatTimestamp = (timestamp: string) => {
38+
const formatTimestamp = (timestamp: string): string => {
3939
return new Date(timestamp).toLocaleString();
4040
};
4141

4242
// Helper function to get status color
43-
const getStatusColor = (code: number) => {
43+
const getStatusColor = (code: number): string => {
4444
if (code >= 200 && code < 300) return "#28a745"; // green
4545
if (code >= 300 && code < 400) return "#ffc107"; // yellow
4646
if (code >= 400 && code < 500) return "#fd7e14"; // orange
@@ -49,7 +49,7 @@ export const renderToolDetails = (data: ToolDetailsData): string => {
4949
};
5050

5151
// Helper function to get status text
52-
const getStatusText = (code: number) => {
52+
const getStatusText = (code: number): string => {
5353
if (code >= 200 && code < 300) return "Success";
5454
if (code >= 300 && code < 400) return "Redirect";
5555
if (code >= 400 && code < 500) return "Client Error";
@@ -58,17 +58,17 @@ export const renderToolDetails = (data: ToolDetailsData): string => {
5858
};
5959

6060
// Format the input schema for display
61-
const formatSchema = (schema: any, level = 0): string => {
61+
const formatSchema = (schema: unknown, level = 0): string => {
6262
if (!schema) return "No schema defined";
6363

6464
const indent = " ".repeat(level);
6565
let result = "";
6666

67-
if (schema.type === "object" && schema.properties) {
67+
if ((schema as Record<string, unknown>).type === "object" && (schema as Record<string, unknown>).properties) {
6868
result += "{\n";
69-
for (const [key, value] of Object.entries(schema.properties)) {
70-
const prop = value as any;
71-
const required = schema.required?.includes(key) ? " (required)" : "";
69+
for (const [key, value] of Object.entries((schema as Record<string, unknown>).properties as Record<string, unknown>)) {
70+
const prop = value as Record<string, unknown>;
71+
const required = ((schema as Record<string, unknown>).required as string[])?.includes(key) ? " (required)" : "";
7272
result += `${indent} "${key}"${required}: `;
7373
if (prop.type === "object") {
7474
result += formatSchema(prop, level + 1);

0 commit comments

Comments
 (0)