local-lemonade 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,442 @@
1
+ # llama.cpp-Specific API
2
+
3
+ This page documents Lemonade's llama.cpp-specific compatibility surface.
4
+
5
+ ## Summary
6
+
7
+ | Method | Endpoint | Description | Modality |
8
+ |--------|----------|-------------|----------|
9
+ | `POST` | [`/v1/rerank`](#post-v1rerank) | Reranking | query + documents -> relevance-scored documents |
10
+ | `GET` | [`/v1/slots`](#get-v1slots) | Returns the current slots processing state | slots state |
11
+ | `POST` | [`/v1/slots/{id}?action=save`](#post-v1slotsidactionsave) | Save the prompt cache of the specified slot to a file | prompt cache |
12
+ | `POST` | [`/v1/slots/{id}?action=restore`](#post-v1slotsidactionrestore) | Restore the prompt cache of the specified slot from a file | prompt cache |
13
+ | `POST` | [`/v1/slots/{id}?action=erase`](#post-v1slotsidactionerase) | Erase the prompt cache of the specified slot | prompt cache |
14
+ | `POST` | [`/v1/tokenize`](#post-v1tokenize) | Tokenize a given text | tokenization |
15
+
16
+ ## `POST /v1/rerank`
17
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
18
+
19
+ Reranking API for llama.cpp-compatible reranker models. You provide a query and a list of documents, and receive relevance scores for each document. Lemonade will load the requested model automatically if it is not already loaded.
20
+
21
+ > **Note:** This endpoint is part of Lemonade's llama.cpp compatibility layer. Internally, Lemonade forwards the request to llama.cpp's `/v1/rerank` endpoint.
22
+
23
+ > **Note:** Lemonade also accepts `/reranking` and `/reranker` as aliases — all three route to the same handler and behave identically.
24
+
25
+ > **Note:** The endpoint is available under all four path prefixes: `/api/v0/`, `/api/v1/`, `/v0/`, and `/v1/`.
26
+
27
+ > **Note:** This endpoint is only available for reranker-specific models using the `llamacpp` recipe, such as `bge-reranker-v2-m3-GGUF`.
28
+
29
+ ### Parameters
30
+
31
+ | Parameter | Required | Description | Status |
32
+ |-----------|----------|-------------|--------|
33
+ | `query` | Yes | The search query text. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
34
+ | `documents` | Yes | Array of document strings to score against the query. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
35
+ | `model` | Yes | The reranking model to use. If not already loaded, Lemonade loads it before forwarding the request. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
36
+
37
+ ### Example request
38
+
39
+ === "PowerShell"
40
+
41
+ ```powershell
42
+ Invoke-WebRequest `
43
+ -Uri "http://localhost:13305/v1/rerank" `
44
+ -Method POST `
45
+ -Headers @{ "Content-Type" = "application/json" } `
46
+ -Body '{
47
+ "model": "bge-reranker-v2-m3-GGUF",
48
+ "query": "What is the capital of France?",
49
+ "documents": [
50
+ "Paris is the capital of France.",
51
+ "Berlin is the capital of Germany.",
52
+ "Madrid is the capital of Spain."
53
+ ]
54
+ }' -UseBasicParsing
55
+ ```
56
+
57
+ === "Bash"
58
+
59
+ ```bash
60
+ curl -X POST http://localhost:13305/v1/rerank \
61
+ -H "Content-Type: application/json" \
62
+ -d '{
63
+ "model": "bge-reranker-v2-m3-GGUF",
64
+ "query": "What is the capital of France?",
65
+ "documents": [
66
+ "Paris is the capital of France.",
67
+ "Berlin is the capital of Germany.",
68
+ "Madrid is the capital of Spain."
69
+ ]
70
+ }'
71
+ ```
72
+
73
+ ### Response format
74
+
75
+ ```json
76
+ {
77
+ "model": "bge-reranker-v2-m3-GGUF",
78
+ "object": "list",
79
+ "results": [
80
+ {
81
+ "index": 0,
82
+ "relevance_score": 8.60673713684082
83
+ },
84
+ {
85
+ "index": 1,
86
+ "relevance_score": -5.3886260986328125
87
+ },
88
+ {
89
+ "index": 2,
90
+ "relevance_score": -3.555561065673828
91
+ }
92
+ ],
93
+ "usage": {
94
+ "prompt_tokens": 51,
95
+ "total_tokens": 51
96
+ }
97
+ }
98
+ ```
99
+
100
+ **Field Descriptions:**
101
+
102
+ - `model` - Model identifier used for reranking
103
+ - `object` - Type of response object, always `"list"`
104
+ - `results` - Array of all input documents with relevance scores
105
+ - `index` - Original index of the document in the input array
106
+ - `relevance_score` - Relevance score assigned by the model; higher means more relevant
107
+ - `usage` - Token usage statistics
108
+ - `prompt_tokens` - Number of tokens in the input
109
+ - `total_tokens` - Total tokens processed
110
+
111
+ > **Note:** Results are returned in input order. To rank documents by relevance, sort `results` by `relevance_score` in descending order on the client side.
112
+
113
+ ## `GET /v1/slots`
114
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
115
+
116
+ Returns the current state of all processing slots in the llama.cpp server. Slots are parallel processing contexts that can handle multiple requests concurrently.
117
+
118
+ > **Note:** This endpoint is part of Lemonade's llama.cpp compatibility layer. Internally, Lemonade forwards the request to llama.cpp's `/slots` endpoint.
119
+
120
+ > **Note:** This endpoint is only available when a llama.cpp model is loaded.
121
+
122
+ > **Note:** This endpoint supports all four path prefixes: `/api/v0/slots`, `/api/v1/slots`, `/v0/slots`, and `/v1/slots`.
123
+
124
+ ### Parameters
125
+
126
+ This endpoint accepts no parameters.
127
+
128
+ ### Example request
129
+
130
+ === "PowerShell"
131
+
132
+ ```powershell
133
+ Invoke-WebRequest `
134
+ -Uri "http://localhost:13305/v1/slots" `
135
+ -Method GET -UseBasicParsing
136
+ ```
137
+
138
+ === "Bash"
139
+
140
+ ```bash
141
+ curl http://localhost:13305/v1/slots
142
+ ```
143
+
144
+ ### Response format
145
+
146
+ ```json
147
+ [
148
+ {
149
+ "id": 0,
150
+ "state": "idle",
151
+ "next_token": {
152
+ "has_next_token": false,
153
+ "n_remain": 0,
154
+ "n_decoded": 0
155
+ },
156
+ "task_id": -1,
157
+ "cache_tokens": 1024
158
+ },
159
+ {
160
+ "id": 1,
161
+ "state": "processing",
162
+ "next_token": {
163
+ "has_next_token": true,
164
+ "n_remain": 42,
165
+ "n_decoded": 15
166
+ },
167
+ "task_id": 123,
168
+ "cache_tokens": 512
169
+ }
170
+ ]
171
+ ```
172
+
173
+ **Field Descriptions:**
174
+
175
+ - `id` - Unique identifier for the slot
176
+ - `state` - Current processing state ("idle", "processing", etc.)
177
+ - `next_token` - Information about token generation state
178
+ - `has_next_token` - Whether more tokens are expected
179
+ - `n_remain` - Number of tokens remaining to generate
180
+ - `n_decoded` - Number of tokens already decoded
181
+ - `task_id` - Identifier of the current task being processed (-1 if idle)
182
+ - `cache_tokens` - Number of cached tokens in the slot's prompt cache
183
+
184
+ ## `POST /v1/slots/{id}?action=save`
185
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
186
+
187
+ Save the prompt cache of a specific slot to a file. This allows you to persist the current context state for later restoration.
188
+
189
+ > **Note:** This endpoint is part of Lemonade's llama.cpp compatibility layer. Internally, Lemonade forwards the request to llama.cpp's `/slots/{id}?action=save` endpoint.
190
+
191
+ > **Note:** The llama.cpp server must be started with the `--slot-save-path` argument for save operations to work. See [Server Configuration](../guide/configuration/README.md) for details on configuring backend arguments.
192
+ >
193
+ > Example configuration:
194
+ > ```bash
195
+ > lemonade config set llamacpp.args="--slot-save-path /path/to/slot/saves"
196
+ > ```
197
+
198
+ > **Note:** This endpoint supports all four path prefixes: `/api/v0/slots/{id}`, `/api/v1/slots/{id}`, `/v0/slots/{id}`, and `/v1/slots/{id}`.
199
+
200
+ ### Parameters
201
+
202
+ | Parameter | Required | Description | Status |
203
+ |-----------|----------|-------------|--------|
204
+ | `id` | Yes | The slot ID to save (path parameter). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
205
+ | `filename` | Yes | The filename where the slot cache should be saved (JSON body). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
206
+
207
+ ### Example request
208
+
209
+ === "PowerShell"
210
+
211
+ ```powershell
212
+ Invoke-WebRequest `
213
+ -Uri "http://localhost:13305/v1/slots/0?action=save" `
214
+ -Method POST `
215
+ -Headers @{ "Content-Type" = "application/json" } `
216
+ -Body '{"filename": "my_conversation_cache.bin"}' -UseBasicParsing
217
+ ```
218
+
219
+ === "PowerShell (/api/v1)"
220
+
221
+ ```powershell
222
+ Invoke-WebRequest `
223
+ -Uri "http://localhost:13305/api/v1/slots/0?action=save" `
224
+ -Method POST `
225
+ -Headers @{ "Content-Type" = "application/json" } `
226
+ -Body '{"filename": "my_conversation_cache.bin"}' -UseBasicParsing
227
+ ```
228
+
229
+ === "Bash"
230
+
231
+ ```bash
232
+ curl -X POST "http://localhost:13305/v1/slots/0?action=save" \
233
+ -H "Content-Type: application/json" \
234
+ -d '{"filename": "my_conversation_cache.bin"}'
235
+ ```
236
+
237
+ ### Response format
238
+
239
+ ```json
240
+ {
241
+ "id_slot": 0,
242
+ "filename": "my_conversation_cache.bin",
243
+ "n_saved": 1024
244
+ }
245
+ ```
246
+
247
+ **Field Descriptions:**
248
+
249
+ - `id_slot` - The slot ID that was saved
250
+ - `filename` - The filename where the cache was saved
251
+ - `n_saved` - Number of tokens saved to the cache file
252
+
253
+ ## `POST /v1/slots/{id}?action=restore`
254
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
255
+
256
+ Restore the prompt cache of a specific slot from a previously saved file. This allows you to resume a conversation or context from where you left off.
257
+
258
+ > **Note:** This endpoint is part of Lemonade's llama.cpp compatibility layer. Internally, Lemonade forwards the request to llama.cpp's `/slots/{id}?action=restore` endpoint.
259
+
260
+ > **Note:** The llama.cpp server must be started with the `--slot-save-path` argument for restore operations to work.
261
+
262
+ > **Note:** This endpoint supports all four path prefixes: `/api/v0/slots/{id}`, `/api/v1/slots/{id}`, `/v0/slots/{id}`, and `/v1/slots/{id}`.
263
+
264
+ ### Parameters
265
+
266
+ | Parameter | Required | Description | Status |
267
+ |-----------|----------|-------------|--------|
268
+ | `id` | Yes | The slot ID to restore to (path parameter). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
269
+ | `filename` | Yes | The filename from which to restore the slot cache (JSON body). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
270
+
271
+ ### Example request
272
+
273
+ === "PowerShell"
274
+
275
+ ```powershell
276
+ Invoke-WebRequest `
277
+ -Uri "http://localhost:13305/v1/slots/0?action=restore" `
278
+ -Method POST `
279
+ -Headers @{ "Content-Type" = "application/json" } `
280
+ -Body '{"filename": "my_conversation_cache.bin"}' -UseBasicParsing
281
+ ```
282
+
283
+ === "PowerShell (/api/v1)"
284
+
285
+ ```powershell
286
+ Invoke-WebRequest `
287
+ -Uri "http://localhost:13305/api/v1/slots/0?action=restore" `
288
+ -Method POST `
289
+ -Headers @{ "Content-Type" = "application/json" } `
290
+ -Body '{"filename": "my_conversation_cache.bin"}' -UseBasicParsing
291
+ ```
292
+
293
+ === "Bash"
294
+
295
+ ```bash
296
+ curl -X POST "http://localhost:13305/v1/slots/0?action=restore" \
297
+ -H "Content-Type: application/json" \
298
+ -d '{"filename": "my_conversation_cache.bin"}'
299
+ ```
300
+
301
+ ### Response format
302
+
303
+ ```json
304
+ {
305
+ "id_slot": 0,
306
+ "filename": "my_conversation_cache.bin",
307
+ "n_restored": 1024
308
+ }
309
+ ```
310
+
311
+ **Field Descriptions:**
312
+
313
+ - `id_slot` - The slot ID that was restored
314
+ - `filename` - The filename from which the cache was restored
315
+ - `n_restored` - Number of tokens restored from the cache file
316
+
317
+ ## `POST /v1/slots/{id}?action=erase`
318
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
319
+
320
+ Erase (clear) the prompt cache of a specific slot. This removes all cached context from the slot, resetting it to an empty state.
321
+
322
+ > **Note:** This endpoint is part of Lemonade's llama.cpp compatibility layer. Internally, Lemonade forwards the request to llama.cpp's `/slots/{id}?action=erase` endpoint.
323
+
324
+ > **Note:** This endpoint supports all four path prefixes: `/api/v0/slots/{id}`, `/api/v1/slots/{id}`, `/v0/slots/{id}`, and `/v1/slots/{id}`.
325
+
326
+ ### Parameters
327
+
328
+ | Parameter | Required | Description | Status |
329
+ |-----------|----------|-------------|--------|
330
+ | `id` | Yes | The slot ID to erase (path parameter). | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
331
+
332
+ ### Example request
333
+
334
+ === "PowerShell"
335
+
336
+ ```powershell
337
+ Invoke-WebRequest `
338
+ -Uri "http://localhost:13305/v1/slots/0?action=erase" `
339
+ -Method POST -UseBasicParsing
340
+ ```
341
+
342
+ === "PowerShell (/api/v1)"
343
+
344
+ ```powershell
345
+ Invoke-WebRequest `
346
+ -Uri "http://localhost:13305/api/v1/slots/0?action=erase" `
347
+ -Method POST -UseBasicParsing
348
+ ```
349
+
350
+ === "Bash"
351
+
352
+ ```bash
353
+ curl -X POST "http://localhost:13305/v1/slots/0?action=erase"
354
+ ```
355
+
356
+ ### Response format
357
+
358
+ ```json
359
+ {
360
+ "id_slot": 0
361
+ }
362
+ ```
363
+
364
+ **Field Descriptions:**
365
+
366
+ - `id_slot` - The slot ID that was erased
367
+
368
+ > **Note:** If the server returns an error, it may indicate that the slot was not found or that the operation failed.
369
+
370
+ ## `POST /v1/tokenize`
371
+ <sub>![Status](https://img.shields.io/badge/status-fully_available-green)</sub>
372
+
373
+ Tokenize a given text. Does not count towards the current model's context window.
374
+
375
+ > **Note:** This endpoint is part of Lemonade's llama.cpp compatibility layer. Internally, Lemonade forwards the request to llama.cpp's `/tokenize` endpoint.
376
+
377
+ > **Note:** This endpoint supports all four path prefixes: `/api/v0/tokenize`, `/api/v1/tokenize`, `/v0/tokenize`, and `/v1/tokenize`.
378
+
379
+ > **Note:** Actual response values may vary for the same string across different models if the models do not share the same tokenizer.
380
+
381
+ ### Parameters
382
+
383
+ | Parameter | Required | Description | Status |
384
+ |-----------|----------|-------------|--------|
385
+ | `content` | Yes | The text to tokenize. | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
386
+ | `add_special` | No | Boolean indicating if special tokens, i.e. `BOS`, should be inserted. Default: `false` | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
387
+ | `parse_special` | No | Boolean indicating if special tokens should be tokenized. When `false` special tokens are treated as plaintext. Default: `true` | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
388
+ | `with_pieces` | No | Boolean indicating whether to return token pieces along with IDs. Default: `false` | <sub>![Status](https://img.shields.io/badge/available-green)</sub> |
389
+
390
+ ### Example request
391
+
392
+ === "PowerShell"
393
+
394
+ ```powershell
395
+ Invoke-WebRequest `
396
+ -Uri "http://localhost:13305/v1/tokenize" `
397
+ -Method POST `
398
+ -Headers @{ "Content-Type" = "application/json" } `
399
+ -Body '{"content": "This is a string to tokenize"}' -UseBasicParsing
400
+ ```
401
+
402
+ === "PowerShell (/api/v1)"
403
+
404
+ ```powershell
405
+ Invoke-WebRequest `
406
+ -Uri "http://localhost:13305/api/v1/tokenize" `
407
+ -Method POST `
408
+ -Headers @{ "Content-Type" = "application/json" } `
409
+ -Body '{"content": "This is a string to tokenize"}' -UseBasicParsing
410
+ ```
411
+
412
+ === "Bash"
413
+
414
+ ```bash
415
+ curl -X POST "http://localhost:13305/v1/tokenize" \
416
+ -H "Content-Type: application/json" \
417
+ -d '{"content": "This is a string to tokenize"}'
418
+ ```
419
+
420
+ ### Response format
421
+
422
+ ```json
423
+ {
424
+ "tokens": [1919,369,264,886,310,74995]
425
+ }
426
+ ```
427
+
428
+ If `with_pieces` is `true`:
429
+
430
+ ```json
431
+ {
432
+ "tokens": [
433
+ {"id": 123, "piece": "Hello"},
434
+ {"id": 456, "piece": " world"},
435
+ {"id": 789, "piece": "!"}
436
+ ]
437
+ }
438
+ ```
439
+
440
+ **Field Descriptions:**
441
+
442
+ - `tokens` - Array of token IDs
package/docs/mcp.md ADDED
@@ -0,0 +1,199 @@
1
+ # MCP Gateway
2
+
3
+ Lemonade exposes its inference capabilities as a Model Context Protocol (MCP) server, so any MCP-compatible client (GitHub Copilot, Claude Desktop, MCP Inspector, Cursor, the `mcp` Python client, etc.) can call your locally running models as tools.
4
+
5
+ The gateway implements the **MCP "Streamable HTTP" transport** (spec version `2025-06-18`) with the `tools` capability only. All traffic flows through a single endpoint:
6
+
7
+ | Endpoint | Status | Notes |
8
+ |----------|--------|-------|
9
+ | `POST /mcp` | Supported | JSON-RPC 2.0 envelope. Accepts a single message or a batch array. |
10
+ | `GET /mcp` | `405 Method Not Allowed` | Server-initiated SSE channel is not supported. |
11
+
12
+ > **Why a single path?** The MCP specification mandates one endpoint URL per server, so `/mcp` is an intentional exception to Lemonade's quad-prefix convention.
13
+
14
+ ## Authentication
15
+
16
+ `/mcp` is treated as a regular API route, so it honors `LEMONADE_API_KEY` exactly like `/api/v1/chat/completions`:
17
+
18
+ ```bash
19
+ curl -s http://localhost:13305/mcp \
20
+ -H "Authorization: Bearer $LEMONADE_API_KEY" \
21
+ -H "Content-Type: application/json" \
22
+ -d '{"jsonrpc":"2.0","id":1,"method":"ping"}'
23
+ ```
24
+
25
+ ## Supported methods
26
+
27
+ | Method | Purpose |
28
+ |--------|---------|
29
+ | `initialize` | Negotiate protocol version, return server identity and capabilities. |
30
+ | `notifications/initialized` | Client acknowledgement; silently accepted. |
31
+ | `tools/list` | Return the catalogue of callable tools (with JSON Schemas). |
32
+ | `tools/call` | Invoke one of the tools below. |
33
+ | `ping` | Liveness probe; returns `{}`. |
34
+
35
+ ## Tools
36
+
37
+ All tools auto-load (and download, if missing) the requested model on first call, exactly like `POST /v1/chat/completions`. Errors are returned as MCP results with `"isError": true` rather than JSON-RPC errors, matching the spec's guidance for tool failures.
38
+
39
+ ### `lemonade_list_models`
40
+
41
+ Discover what's loaded, what's downloaded, and what's recommended. Call this first if you don't already know the exact model name to pass to the other tools — passing a wrong name may trigger a multi-GB download.
42
+
43
+ ```json
44
+ {
45
+ "name": "lemonade_list_models",
46
+ "arguments": {
47
+ "include_available": true,
48
+ "include_suggested": true
49
+ }
50
+ }
51
+ ```
52
+
53
+ Returns a summary text block plus a JSON-stringified text block with `{loaded, available, suggested_to_pull, recommended_chat_model}`.
54
+
55
+ ### `lemonade_chat`
56
+
57
+ Chat completion against any LLM in the registry.
58
+
59
+ ```json
60
+ {
61
+ "name": "lemonade_chat",
62
+ "arguments": {
63
+ "model": "Qwen3-1.7B-GGUF",
64
+ "messages": [
65
+ {"role": "system", "content": "You are concise."},
66
+ {"role": "user", "content": "Summarize MCP in one line."}
67
+ ],
68
+ "max_tokens": 64,
69
+ "temperature": 0.2
70
+ }
71
+ }
72
+ ```
73
+
74
+ Returns one text block with the assistant content. If the model emits tool calls, a second text block containing `tool_calls: <json>` is appended.
75
+
76
+ Reasoning models (Qwen3, DeepSeek-R1, ...) have the `<think>` block disabled by default to keep small `max_tokens` budgets from being consumed by reasoning. Pass `"chat_template_kwargs": {"enable_thinking": true}` to opt back in.
77
+
78
+ > **Picking a portable model.** The example above uses `Qwen3-1.7B-GGUF` because GGUF (llama.cpp) runs everywhere lemonade does — Windows, Linux/Docker, macOS, CPU and Vulkan/ROCm/Metal GPUs. Hybrid/NPU variants such as `*-Hybrid` (recipe `ryzenai-llm`, **Windows + AMD RyzenAI** only) or `*-FLM` (recipe `flm`, **AMD Ryzen AI NPU** only) are faster on supported hardware but unavailable on others. If your client picks one that isn't supported, the tool returns a structured error suggesting a portable alternative — prefer `lemonade_list_models` to discover what's actually available on the running server.
79
+
80
+ ### `lemonade_transcribe_audio`
81
+
82
+ Transcribe a local audio file with a Whisper-class model. Prefer `audio_path` (the server runs on the same machine as the caller); use `audio_base64` only when you genuinely have bytes in memory.
83
+
84
+ ```json
85
+ {
86
+ "name": "lemonade_transcribe_audio",
87
+ "arguments": {
88
+ "model": "Whisper-Large-v3-Turbo",
89
+ "audio_path": "C:/clips/meeting.wav",
90
+ "response_format": "verbose_json"
91
+ }
92
+ }
93
+ ```
94
+
95
+ Returns two text blocks: the bare transcript, followed by the full OpenAI-shaped response (for callers that need timestamps or segments).
96
+
97
+ ### `lemonade_generate_image`
98
+
99
+ Generate one or more PNGs from a prompt. **Prefer writing to disk** via `output_path` (single image) or `output_dir` (one or more) — base64 image content blocks cost tens of thousands of tokens per image and some clients surface them as opaque resource URIs.
100
+
101
+ ```json
102
+ {
103
+ "name": "lemonade_generate_image",
104
+ "arguments": {
105
+ "model": "SDXL-Turbo",
106
+ "prompt": "a lemon-shaped car driving across the moon",
107
+ "size": "512x512",
108
+ "output_path": "lemon-car.png"
109
+ }
110
+ }
111
+ ```
112
+
113
+ When disk paths are provided, returns text block(s) with the absolute path(s). Otherwise, returns one inline image content block per image (`{"type":"image", "data":"<base64>", "mimeType":"image/png"}`).
114
+
115
+ **Sandboxed disk writes.** To prevent a cross-origin or unauthenticated caller from overwriting arbitrary files, `output_path` and `output_dir` are confined to a sandbox directory:
116
+
117
+ - Default: `<cache_dir>/mcp-images`.
118
+ - Override with the `LEMONADE_MCP_IMAGE_DIR` environment variable (absolute path).
119
+ - Relative paths resolve against the sandbox root; absolute paths must stay within it. Paths that escape the sandbox (via `..` or symlinks) are rejected.
120
+ - `output_dir` writes use auto-generated, unique filenames (`image_<token>_<i>.png`), so concurrent callers never clobber one another's images — the returned `paths` tell you the exact names. Use `output_path` when you need an exact, caller-chosen filename (it is written as named, replacing any existing file at that path).
121
+
122
+ ### `lemonade_omni`
123
+
124
+ One-shot multimodal turn against a **Lemonade Omni collection** (a model bundle that pairs a planner LLM with an image model, an image-edit model, and a TTS voice under a single `collection.omni` recipe — see [the Omni docs](../dev/lemonade-omni.md)). The server runs the orchestrator's internal tool-calling loop, executes the collection's `generate_image` / `edit_image` / `text_to_speech` tools by routing to the bundled components, and returns the result as a text block plus native MCP `image` / `audio` content blocks — one per artifact, in the order they were produced.
125
+
126
+ `model` is **optional** and defaults to `LMX-Omni-5.5B-Lite` (smaller and faster). Pass `model` explicitly to opt into a larger collection (e.g. `LMX-Omni-52B-Halo` on capable hardware) or any other `collection.omni` model surfaced by `lemonade_list_models`. The collection is downloaded on first use and may be multi-GB.
127
+
128
+ Use `lemonade_chat` instead when you only need plain-text LLM output and don't want the planner-loop overhead.
129
+
130
+ ```json
131
+ {
132
+ "name": "lemonade_omni",
133
+ "arguments": {
134
+ "messages": [
135
+ {"role": "user", "content": "Generate an image of a lemon car, then read out a one-line description."}
136
+ ],
137
+ "output_dir": "omni"
138
+ }
139
+ }
140
+ ```
141
+
142
+ **Disk vs. inline output.** A single Omni turn can produce both images and audio in arbitrary order. Pass an `output_dir` to write each artifact to disk under a unique auto-generated name (`omni_<token>_<i>.<ext>`) — the tool returns one text block per artifact with its absolute path, plus a JSON-stringified `paths` array. This is strongly preferred over inline base64 for the same reasons documented under `lemonade_generate_image` — and is the **only** way to get audio out on clients that don't render `audio` content blocks. Like `lemonade_generate_image`, `output_dir` is confined to the MCP image sandbox (see **Sandboxed disk writes** above): relative paths resolve against the sandbox root, paths escaping it are rejected, and unique filenames mean concurrent callers never clobber each other.
143
+
144
+ When `output_dir` is omitted, artifacts are inlined as MCP content blocks: `{"type":"image", "data":"<base64>", "mimeType":"image/png"}` and `{"type":"audio", "data":"<base64>", "mimeType":"audio/mpeg"}`.
145
+
146
+ If the planner emits app-defined tool calls (those you passed in via `tools`/`tool_choice`), an extra text block `tool_calls: <json>` is appended, matching `lemonade_chat`'s passthrough semantics.
147
+
148
+ Passing a non-collection model (e.g. a plain LLM) returns `isError: true` with a hint to use `lemonade_chat`.
149
+
150
+ ### `lemonade_docs`
151
+
152
+ Read the server's own API reference. Call with no arguments to list the pages this server ships, then pass `page` to read one as markdown. The pages are bundled with the server, so they match the running version and work offline.
153
+
154
+ ```json
155
+ {
156
+ "name": "lemonade_docs",
157
+ "arguments": {
158
+ "page": "api/lemonade"
159
+ }
160
+ }
161
+ ```
162
+
163
+ The listing returns a summary text block plus a JSON-stringified block with `{pages: [{id, title, bytes}]}`. `page` takes an `id` from that listing; unknown pages return `"isError": true`. The same content is available over HTTP at [`GET /v1/docs`](./lemonade.md#get-v1docs).
164
+
165
+ ## Error model
166
+
167
+ | Code | Meaning |
168
+ |------|---------|
169
+ | `-32700` | Body was not valid JSON. |
170
+ | `-32600` | Request was not a JSON-RPC object (or batch was empty / missing `method`). |
171
+ | `-32601` | Unknown JSON-RPC method (e.g. `resources/list`). |
172
+ | `-32602` | Invalid `params` for a known method. |
173
+ | `-32603` | Internal server error (an exception escaped a handler). |
174
+
175
+ Tool-level failures (bad arguments, model load errors, backend exceptions) are returned as **successful** JSON-RPC results with `"isError": true` and a text content block describing the failure, so MCP-aware models can self-correct.
176
+
177
+ ## Limitations (MVP)
178
+
179
+ - No server-initiated SSE (GET /mcp returns 405). Tools return their full result in the POST response.
180
+ - No session resumption (`Mcp-Session-Id` header is not issued).
181
+ - `resources/*` and `prompts/*` capabilities are not implemented.
182
+ - Streaming chat output is not exposed via MCP — `stream=true` is ignored. Use `POST /v1/chat/completions` directly for streamed tokens.
183
+ - Embeddings and text-to-speech are not currently exposed as MCP tools; use the OpenAI-compatible endpoints (`/v1/embeddings`, `/v1/audio/speech`) for those.
184
+
185
+ ## Quick test with curl
186
+
187
+ ```bash
188
+ # 1. Initialize
189
+ curl -s http://localhost:13305/mcp -H "Content-Type: application/json" \
190
+ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}'
191
+
192
+ # 2. List tools
193
+ curl -s http://localhost:13305/mcp -H "Content-Type: application/json" \
194
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
195
+
196
+ # 3. Call lemonade_chat
197
+ curl -s http://localhost:13305/mcp -H "Content-Type: application/json" \
198
+ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"lemonade_chat","arguments":{"model":"Qwen3-1.7B-GGUF","messages":[{"role":"user","content":"hi"}],"max_tokens":16}}}'
199
+ ```
package/docs/ollama.md ADDED
@@ -0,0 +1,21 @@
1
+ # Ollama-Compatible API
2
+
3
+ Lemonade supports the [Ollama API](https://github.com/ollama/ollama/blob/main/docs/api.md), allowing applications built for Ollama to work with Lemonade without modification.
4
+
5
+ To enable auto-detection by Ollama-integrated apps, configure the server to use the Ollama default port `11434`. See [Server Configuration](../guide/configuration/README.md#settings-reference) for how to change the port.
6
+
7
+ | Endpoint | Status | Notes |
8
+ |----------|--------|-------|
9
+ | `POST /api/chat` | Supported | Streaming and non-streaming |
10
+ | `POST /api/generate` | Supported | Text completion + image generation |
11
+ | `GET /api/tags` | Supported | Lists downloaded models |
12
+ | `POST /api/show` | Supported | Model details |
13
+ | `DELETE /api/delete` | Supported | |
14
+ | `POST /api/pull` | Supported | Download with progress |
15
+ | `POST /api/embed` | Supported | New embeddings format |
16
+ | `POST /api/embeddings` | Supported | Legacy embeddings |
17
+ | `GET /api/ps` | Supported | Running models |
18
+ | `GET /api/version` | Supported | |
19
+ | `POST /api/create` | Not supported | Returns 501 |
20
+ | `POST /api/copy` | Not supported | Returns 501 |
21
+ | `POST /api/push` | Not supported | Returns 501 |