Kobold MCP Server

  • dist
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; import fetch from 'node-fetch'; // Base configuration schema that all requests can include const BaseConfigSchema = z.object({ apiUrl: z.string().default('http://localhost:5001'), }); // Core API schemas (api/v1) const MaxContextLengthSchema = BaseConfigSchema; const MaxLengthSchema = BaseConfigSchema; const GenerateSchema = BaseConfigSchema.extend({ prompt: z.string(), max_length: z.number().optional(), max_context_length: z.number().optional(), temperature: z.number().optional(), top_p: z.number().optional(), top_k: z.number().optional(), repetition_penalty: z.number().optional(), stop_sequence: z.array(z.string()).optional(), seed: z.number().optional(), }); // Multiplayer schemas const MultiplayerStatusSchema = BaseConfigSchema; const MultiplayerGetStorySchema = BaseConfigSchema; const MultiplayerSetStorySchema = BaseConfigSchema.extend({ story: z.string() }); // Store chat history in memory const chatHistory = []; // Generate check schemas const GenerateCheckSchema = BaseConfigSchema; const GenerateCheckMultiuserSchema = BaseConfigSchema; // Extra API schemas (api/extra) const TokenCountSchema = BaseConfigSchema.extend({ text: z.string(), }); const DetokenizeSchema = BaseConfigSchema.extend({ tokens: z.array(z.number()), }); const TranscribeSchema = BaseConfigSchema.extend({ audio: z.string(), language: z.string().optional(), }); const WebSearchSchema = BaseConfigSchema.extend({ query: z.string(), }); const TTSSchema = BaseConfigSchema.extend({ text: z.string(), voice: z.string().optional(), speed: z.number().optional(), }); const AbortSchema = BaseConfigSchema; const PerfInfoSchema = BaseConfigSchema; const ModelInfoSchema = BaseConfigSchema; const VersionInfoSchema = BaseConfigSchema; const PreloadStorySchema = BaseConfigSchema; const LastLogProbsSchema = BaseConfigSchema; // Stable Diffusion API schemas (sdapi/v1) const Txt2ImgSchema = BaseConfigSchema.extend({ prompt: z.string(), negative_prompt: z.string().optional(), width: z.number().optional(), height: z.number().optional(), steps: z.number().optional(), cfg_scale: z.number().optional(), sampler_name: z.string().optional(), seed: z.number().optional(), }); const Img2ImgSchema = Txt2ImgSchema.extend({ init_images: z.array(z.string()), denoising_strength: z.number().optional(), }); const InterrogateSchema = BaseConfigSchema.extend({ image: z.string(), }); const SDModelsSchema = BaseConfigSchema; const SDSamplersSchema = BaseConfigSchema; const SDOptionsSchema = BaseConfigSchema; // OpenAI compatible API schemas (v1) const ChatCompletionSchema = BaseConfigSchema.extend({ messages: z.array(z.object({ role: z.enum(['system', 'user', 'assistant']), content: z.string(), })), temperature: z.number().optional(), top_p: z.number().optional(), max_tokens: z.number().optional(), stop: z.array(z.string()).optional(), }); const CompletionSchema = BaseConfigSchema.extend({ prompt: z.string(), max_tokens: z.number().optional(), temperature: z.number().optional(), top_p: z.number().optional(), stop: z.array(z.string()).optional(), }); const ModelsSchema = BaseConfigSchema; const AudioTranscriptionSchema = BaseConfigSchema.extend({ file: z.string(), model: z.string().optional(), language: z.string().optional(), }); const AudioSpeechSchema = BaseConfigSchema.extend({ input: z.string(), voice: z.string().optional(), speed: z.number().optional(), }); // Server setup const server = new Server({ name: "kobold-server", version: "0.1.0", }, { capabilities: { tools: {}, }, }); // Helper function for HTTP requests async function makeRequest(url, method = 'GET', body = null) { const options = { method, headers: body ? { 'Content-Type': 'application/json' } : undefined, }; if (body && method !== 'GET') { options.body = JSON.stringify(body); } const response = await fetch(url, options); if (!response.ok) { throw new Error(`KoboldAI API error: ${response.statusText}`); } return response.json(); } server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ // Core API tools { name: "kobold_max_context_length", description: "Get current max context length setting", inputSchema: zodToJsonSchema(MaxContextLengthSchema), }, { name: "kobold_max_length", description: "Get current max length setting", inputSchema: zodToJsonSchema(MaxLengthSchema), }, { name: "kobold_generate", description: "Generate text with KoboldAI", inputSchema: zodToJsonSchema(GenerateSchema), }, // Extra API tools { name: "kobold_model_info", description: "Get current model information", inputSchema: zodToJsonSchema(ModelInfoSchema), }, { name: "kobold_version", description: "Get KoboldAI version information", inputSchema: zodToJsonSchema(VersionInfoSchema), }, { name: "kobold_perf_info", description: "Get performance information", inputSchema: zodToJsonSchema(PerfInfoSchema), }, { name: "kobold_token_count", description: "Count tokens in text", inputSchema: zodToJsonSchema(TokenCountSchema), }, { name: "kobold_detokenize", description: "Convert token IDs to text", inputSchema: zodToJsonSchema(DetokenizeSchema), }, { name: "kobold_transcribe", description: "Transcribe audio using Whisper", inputSchema: zodToJsonSchema(TranscribeSchema), }, { name: "kobold_web_search", description: "Search the web via DuckDuckGo", inputSchema: zodToJsonSchema(WebSearchSchema), }, { name: "kobold_tts", description: "Generate text-to-speech audio", inputSchema: zodToJsonSchema(TTSSchema), }, { name: "kobold_abort", description: "Abort the currently ongoing generation", inputSchema: zodToJsonSchema(AbortSchema), }, { name: "kobold_last_logprobs", description: "Get token logprobs from the last request", inputSchema: zodToJsonSchema(LastLogProbsSchema), }, // Stable Diffusion API tools { name: "kobold_sd_models", description: "List available Stable Diffusion models", inputSchema: zodToJsonSchema(SDModelsSchema), }, { name: "kobold_sd_samplers", description: "List available Stable Diffusion samplers", inputSchema: zodToJsonSchema(SDSamplersSchema), }, { name: "kobold_txt2img", description: "Generate image from text prompt", inputSchema: zodToJsonSchema(Txt2ImgSchema), }, { name: "kobold_img2img", description: "Transform existing image using prompt", inputSchema: zodToJsonSchema(Img2ImgSchema), }, { name: "kobold_interrogate", description: "Generate caption for image", inputSchema: zodToJsonSchema(InterrogateSchema), }, // OpenAI compatible API tools { name: "kobold_chat", description: "Chat completion (OpenAI-compatible)", inputSchema: zodToJsonSchema(ChatCompletionSchema), }, { name: "kobold_complete", description: "Text completion (OpenAI-compatible)", inputSchema: zodToJsonSchema(CompletionSchema), }, ], })); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; const { apiUrl = 'http://localhost:5001', ...requestData } = args; // Special handling for chat completions if (name === 'kobold_chat') { // Add new messages to chat history const newMessages = requestData.messages || []; chatHistory.push(...newMessages); // Get last 4 messages const recentMessages = chatHistory.slice(-4); console.error('Last 4 messages in chat:'); console.error(JSON.stringify(recentMessages, null, 2)); // Make the API request with all context const result = await makeRequest(`${apiUrl}/v1/chat/completions`, 'POST', { ...requestData, messages: chatHistory }); // Add assistant's response to history const typedResult = result; if (typedResult.choices?.[0]?.message) { chatHistory.push(typedResult.choices[0].message); } return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], isError: false, }; } // Handle GET requests differently const getEndpoints = { kobold_max_context_length: '/api/v1/config/max_context_length', kobold_max_length: '/api/v1/config/max_length', kobold_generate_check: '/api/extra/generate/check', kobold_model_info: '/api/v1/model', kobold_version: '/api/v1/info/version', kobold_perf_info: '/api/extra/perf', kobold_sd_models: '/sdapi/v1/sd-models', kobold_sd_samplers: '/sdapi/v1/samplers', }; if (getEndpoints[name]) { const result = await makeRequest(`${apiUrl}${getEndpoints[name]}`); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], isError: false, }; } // Handle POST endpoints const postEndpoints = { kobold_multiplayer_status: { endpoint: '/api/extra/multiplayer/status', schema: MultiplayerStatusSchema }, kobold_multiplayer_get_story: { endpoint: '/api/extra/multiplayer/getstory', schema: MultiplayerGetStorySchema }, kobold_multiplayer_set_story: { endpoint: '/api/extra/multiplayer/setstory', schema: MultiplayerSetStorySchema }, kobold_generate_check_multiuser: { endpoint: '/api/extra/generate/check', schema: GenerateCheckMultiuserSchema }, kobold_generate: { endpoint: '/api/v1/generate', schema: GenerateSchema }, kobold_token_count: { endpoint: '/api/extra/tokencount', schema: TokenCountSchema }, kobold_detokenize: { endpoint: '/api/extra/detokenize', schema: DetokenizeSchema }, kobold_transcribe: { endpoint: '/api/extra/transcribe', schema: TranscribeSchema }, kobold_web_search: { endpoint: '/api/extra/websearch', schema: WebSearchSchema }, kobold_tts: { endpoint: '/api/extra/tts', schema: TTSSchema }, kobold_abort: { endpoint: '/api/extra/abort', schema: AbortSchema }, kobold_last_logprobs: { endpoint: '/api/extra/last_logprobs', schema: LastLogProbsSchema }, kobold_txt2img: { endpoint: '/sdapi/v1/txt2img', schema: Txt2ImgSchema }, kobold_img2img: { endpoint: '/sdapi/v1/img2img', schema: Img2ImgSchema }, kobold_interrogate: { endpoint: '/sdapi/v1/interrogate', schema: InterrogateSchema }, kobold_complete: { endpoint: '/v1/completions', schema: CompletionSchema }, }; if (postEndpoints[name]) { const { endpoint, schema } = postEndpoints[name]; const parsed = schema.safeParse(args); if (!parsed.success) { throw new Error(`Invalid arguments: ${parsed.error}`); } const result = await makeRequest(`${apiUrl}${endpoint}`, 'POST', requestData); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], isError: false, }; } throw new Error(`Unknown tool: ${name}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error: ${errorMessage}` }], isError: true, }; } }); // Start server async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("KoboldAI MCP server running on stdio"); } runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); });