chat
Send a chat history to a local Ollama model and receive the assistant's reply along with timing details. Supports system, user, and assistant messages.
Instructions
Run a chat completion against a local model with message history (non-streaming). Returns the assistant's reply plus timing.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| model | Yes | Model name. | |
| messages | Yes | Chat history. Each item: {role: "system"|"user"|"assistant", content: string}. | |
| options | No | Ollama sampling/decoding options. |
Implementation Reference
- server.js:210-245 (handler)The main handler function for the 'chat' tool. Validates args (model string, messages array with {role,content}), calls Ollama's POST /api/chat endpoint, and returns assistant reply with timing metrics.
// ─── Tool: chat ─────────────────────────────────────────────────────────── async function chat(args) { const badModel = requireString(args, 'model'); if (badModel) return errorResult(badModel); if (!Array.isArray(args.messages) || !args.messages.length) { return errorResult('messages is required (non-empty array of {role, content} objects)'); } for (const m of args.messages) { if (!m || typeof m !== 'object' || typeof m.role !== 'string' || typeof m.content !== 'string') { return errorResult('each message must be {role: "system"|"user"|"assistant", content: string}'); } } const body = { model: args.model, messages: args.messages, stream: false, }; if (args.options && typeof args.options === 'object') body.options = args.options; const r = await httpRequest('POST', '/api/chat', body); if (r.error) return errorResult(r.error); const d = r.data || {}; return textResult({ model: d.model || args.model, message: d.message || null, done_reason: d.done_reason || null, eval_count: d.eval_count || null, eval_duration_ms: d.eval_duration ? Math.round(d.eval_duration / 1e6) : null, prompt_eval_count: d.prompt_eval_count || null, total_duration_ms: d.total_duration ? Math.round(d.total_duration / 1e6) : null, tokens_per_second: d.eval_count && d.eval_duration ? Math.round((d.eval_count / (d.eval_duration / 1e9)) * 100) / 100 : null, }); } - server.js:327-356 (schema)Schema registration for the 'chat' tool in the TOOLS array. Defines name, description, annotations, and inputSchema requiring 'model' (string) and 'messages' (array of {role: system|user|assistant, content: string}) with optional 'options' object.
{ name: 'chat', description: 'Run a chat completion against a local model with message history (non-streaming). Returns the assistant\'s reply plus timing.', annotations: { title: 'Chat completion', readOnlyHint: false, destructiveHint: false, openWorldHint: true }, inputSchema: { type: 'object', properties: { model: { type: 'string', description: 'Model name.' }, messages: { type: 'array', description: 'Chat history. Each item: {role: "system"|"user"|"assistant", content: string}.', items: { type: 'object', properties: { role: { type: 'string', enum: ['system', 'user', 'assistant'] }, content: { type: 'string' }, }, required: ['role', 'content'], }, }, options: { type: 'object', description: 'Ollama sampling/decoding options.', additionalProperties: true, }, }, required: ['model', 'messages'], additionalProperties: false, }, }, - server.js:385-394 (registration)The HANDLERS map that binds the tool name 'chat' to the chat function, enabling JSON-RPC dispatch at line 414.
const HANDLERS = { ollama_status: ollamaStatus, list_models: listModels, list_running: listRunning, show_model: showModel, generate: generate, chat: chat, pull_model: pullModel, delete_model: deleteModel, }; - server.js:57-107 (helper)The httpRequest helper used by the chat handler to make the POST request to Ollama's /api/chat endpoint.
function httpRequest(method, path, body) { return new Promise((resolve) => { let url; try { url = new URL(path, OLLAMA_URL); } catch (e) { resolve({ error: `invalid URL: ${e.message}` }); return; } const lib = url.protocol === 'https:' ? https : http; const opts = { method, hostname: url.hostname, port: url.port || (url.protocol === 'https:' ? 443 : 80), path: url.pathname + url.search, headers: { 'accept': 'application/json' }, }; let bodyBuf = null; if (body !== undefined) { bodyBuf = Buffer.from(JSON.stringify(body), 'utf8'); opts.headers['content-type'] = 'application/json'; opts.headers['content-length'] = bodyBuf.length; } const req = lib.request(opts, (res) => { let chunks = Buffer.alloc(0); res.on('data', (d) => { chunks = Buffer.concat([chunks, d]); }); res.on('end', () => { const text = chunks.toString('utf8'); if (res.statusCode >= 400) { resolve({ status: res.statusCode, error: `HTTP ${res.statusCode}: ${text.slice(0, 500)}` }); return; } // Some endpoints return text/plain (e.g. GET /); try JSON first, fall back to text. try { resolve({ status: res.statusCode, data: JSON.parse(text) }); } catch (_) { resolve({ status: res.statusCode, data: null, text }); } }); }); req.setTimeout(REQUEST_TIMEOUT_MS, () => { req.destroy(new Error(`request timed out after ${REQUEST_TIMEOUT_MS}ms`)); }); req.on('error', (e) => { // Give a friendly connection-refused message. const msg = /ECONNREFUSED|ENOTFOUND/.test(e.code || e.message) ? `cannot reach Ollama at ${OLLAMA_URL} — is the server running? Start it with \`ollama serve\` or open the Ollama app.` : e.message; resolve({ error: msg }); }); if (bodyBuf) req.write(bodyBuf); req.end(); }); } - server.js:109-114 (helper)The requireString helper used by the chat handler to validate the 'model' argument.
function requireString(args, field) { if (typeof args[field] !== 'string' || !args[field].trim()) { return `${field} is required (non-empty string)`; } return null; }