File size: 3,290 Bytes
4e23b01 | 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 | import { getCoreVersion } from '#/_base/version';
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
import type { MCPClient, MCPToolDefinition, MCPToolResult } from './types';
export const KIMI_MCP_CLIENT_NAME = 'kimi-code';
export const KIMI_MCP_CLIENT_VERSION = getCoreVersion();
export interface UnexpectedCloseReason {
readonly error?: Error;
readonly stderr?: string;
}
export type UnexpectedCloseListener = (reason: UnexpectedCloseReason) => void;
export function isMcpConnectionClosedError(error: unknown): boolean {
return (
error instanceof Error &&
(error as Error & { readonly code?: unknown }).code === ErrorCode.ConnectionClosed
);
}
export function isMcpTransportFailure(error: unknown): boolean {
if (!(error instanceof Error)) return false;
if (isMcpConnectionClosedError(error)) return true;
return !(error instanceof McpError);
}
export const MCP_LIVENESS_PROBE_TIMEOUT_MS = 5_000;
export function isMcpMalformedResultError(error: unknown): boolean {
return error instanceof Error && error.name === 'ZodError';
}
export async function probeMcpLiveness(client: MCPClient, signal: AbortSignal): Promise<boolean> {
try {
await client.ping(signal);
return true;
} catch (error) {
if (isMcpConnectionClosedError(error)) return false;
if (isMcpMalformedResultError(error)) return true;
if (error instanceof McpError) {
return (error as Error & { readonly code?: unknown }).code !== ErrorCode.RequestTimeout;
}
return false;
}
}
export interface McpRequestOptions {
readonly timeout?: number;
readonly signal?: AbortSignal;
}
export function buildRequestOptions(
timeoutMs: number | undefined,
signal: AbortSignal | undefined,
): McpRequestOptions | undefined {
if (timeoutMs === undefined && signal === undefined) return undefined;
return { timeout: timeoutMs, signal };
}
interface SdkListedTool {
readonly name: string;
readonly description?: string;
readonly inputSchema: Record<string, unknown>;
}
export function toMcpToolDefinition(tool: SdkListedTool): MCPToolDefinition {
return {
name: tool.name,
description: tool.description ?? '',
inputSchema: tool.inputSchema,
};
}
export function toMcpToolResult(result: unknown): MCPToolResult {
if (typeof result === 'object' && result !== null && 'content' in result) {
const typed = result as {
content: unknown;
isError?: unknown;
structuredContent?: unknown;
_meta?: unknown;
};
if (Array.isArray(typed.content)) {
return {
content: typed.content as MCPToolResult['content'],
isError: typed.isError === true,
structuredContent: typed.structuredContent,
_meta:
typeof typed._meta === 'object' && typed._meta !== null
? (typed._meta as Record<string, unknown>)
: undefined,
};
}
}
if (typeof result === 'object' && result !== null && 'toolResult' in result) {
const legacy = (result as { toolResult: unknown }).toolResult;
return {
content: [
{
type: 'text',
text: typeof legacy === 'string' ? legacy : JSON.stringify(legacy),
},
],
isError: false,
};
}
return { content: [], isError: false };
}
|