@stabgan/openrouter-mcp-multimodal 4.6.1 → 4.6.2
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.
- package/README.md +60 -52
- package/dist/errors.d.ts +3 -12
- package/dist/errors.js +2 -6
- package/dist/index.js +2 -10
- package/dist/logger.d.ts +1 -18
- package/dist/logger.js +0 -7
- package/dist/model-cache.d.ts +1 -20
- package/dist/model-cache.js +1 -20
- package/dist/openrouter-api.d.ts +4 -18
- package/dist/openrouter-api.js +4 -20
- package/dist/tool-definitions.d.ts +1 -0
- package/dist/tool-definitions.js +708 -0
- package/dist/tool-handlers/analyze-audio.d.ts +0 -5
- package/dist/tool-handlers/analyze-image.d.ts +0 -6
- package/dist/tool-handlers/analyze-image.js +1 -8
- package/dist/tool-handlers/analyze-video.d.ts +0 -5
- package/dist/tool-handlers/analyze-video.js +0 -7
- package/dist/tool-handlers/async-chat.d.ts +17 -13
- package/dist/tool-handlers/async-chat.js +42 -65
- package/dist/tool-handlers/audio-utils.d.ts +1 -4
- package/dist/tool-handlers/audio-utils.js +4 -14
- package/dist/tool-handlers/cache.d.ts +2 -18
- package/dist/tool-handlers/cache.js +1 -19
- package/dist/tool-handlers/chat-completion.d.ts +2 -30
- package/dist/tool-handlers/chat-completion.js +13 -33
- package/dist/tool-handlers/chat-request.d.ts +25 -0
- package/dist/tool-handlers/chat-request.js +38 -0
- package/dist/tool-handlers/completion-utils.d.ts +1 -18
- package/dist/tool-handlers/completion-utils.js +0 -2
- package/dist/tool-handlers/fetch-utils.d.ts +2 -18
- package/dist/tool-handlers/fetch-utils.js +3 -51
- package/dist/tool-handlers/generate-audio.js +11 -21
- package/dist/tool-handlers/generate-image-dedicated.js +16 -70
- package/dist/tool-handlers/generate-image-input.d.ts +0 -1
- package/dist/tool-handlers/generate-image-input.js +2 -18
- package/dist/tool-handlers/generate-image.js +8 -15
- package/dist/tool-handlers/generate-video.d.ts +1 -7
- package/dist/tool-handlers/generate-video.js +24 -109
- package/dist/tool-handlers/health-check.d.ts +1 -9
- package/dist/tool-handlers/health-check.js +1 -12
- package/dist/tool-handlers/image-source.d.ts +14 -0
- package/dist/tool-handlers/image-source.js +23 -0
- package/dist/tool-handlers/image-utils.d.ts +5 -0
- package/dist/tool-handlers/image-utils.js +23 -0
- package/dist/tool-handlers/openai-withresponse.d.ts +1 -10
- package/dist/tool-handlers/openai-withresponse.js +0 -7
- package/dist/tool-handlers/openrouter-errors.d.ts +2 -18
- package/dist/tool-handlers/openrouter-errors.js +2 -34
- package/dist/tool-handlers/path-safety.d.ts +10 -15
- package/dist/tool-handlers/path-safety.js +30 -55
- package/dist/tool-handlers/provider-routing.d.ts +0 -9
- package/dist/tool-handlers/provider-routing.js +1 -14
- package/dist/tool-handlers/rerank.js +0 -2
- package/dist/tool-handlers/search-models.d.ts +0 -6
- package/dist/tool-handlers/speech-to-text.js +3 -21
- package/dist/tool-handlers/structured-output.d.ts +1 -4
- package/dist/tool-handlers/structured-output.js +2 -16
- package/dist/tool-handlers/text-to-speech.js +8 -26
- package/dist/tool-handlers/video-utils.d.ts +1 -6
- package/dist/tool-handlers/video-utils.js +2 -17
- package/dist/tool-handlers.js +4 -734
- package/dist/tool-icons.d.ts +0 -6
- package/dist/tool-icons.js +1 -8
- package/dist/version.d.ts +1 -15
- package/dist/version.js +1 -15
- package/package.json +3 -2
package/dist/tool-handlers.js
CHANGED
|
@@ -18,7 +18,7 @@ import { handleGenerateImageDedicated } from './tool-handlers/generate-image-ded
|
|
|
18
18
|
import { handleTextToSpeech } from './tool-handlers/text-to-speech.js';
|
|
19
19
|
import { handleSpeechToText } from './tool-handlers/speech-to-text.js';
|
|
20
20
|
import { handleStartChatCompletion, handleGetChatCompletionStatus, } from './tool-handlers/async-chat.js';
|
|
21
|
-
import {
|
|
21
|
+
import { TOOL_DEFINITIONS } from './tool-definitions.js';
|
|
22
22
|
import { TOOL_ICONS } from './tool-icons.js';
|
|
23
23
|
function wrapToolArgs(a) {
|
|
24
24
|
return { params: { arguments: a ?? {} } };
|
|
@@ -26,21 +26,9 @@ function wrapToolArgs(a) {
|
|
|
26
26
|
function buildProgressHook(server, progressToken) {
|
|
27
27
|
if (progressToken === undefined)
|
|
28
28
|
return undefined;
|
|
29
|
-
// MCP
|
|
30
|
-
// monotonically increasing within a single progressToken. OpenRouter
|
|
31
|
-
// returns `progress: 0..100` on some ticks and omits it on others, so
|
|
32
|
-
// we anchor on a per-hook attempt counter and use the upstream number
|
|
33
|
-
// only as an informational `message`. This guarantees monotonicity
|
|
34
|
-
// regardless of what the upstream does (drops, duplicates, decreases).
|
|
35
|
-
//
|
|
36
|
-
// See MCP spec 2025-06-18 utilities/progress §Behavior Requirements:
|
|
37
|
-
// "The progress value MUST increase with each notification, even if
|
|
38
|
-
// the total is unknown."
|
|
29
|
+
// MCP progress must monotonically increase; upstream values can drop or be omitted.
|
|
39
30
|
let lastSent = -1;
|
|
40
31
|
return ({ status, progress, attempt, video_id }) => {
|
|
41
|
-
// Always monotonic: at least attempt+1 (so initial attempt=0 → 0 stays
|
|
42
|
-
// reserved for the 'submitted' ping). If upstream has a real numeric
|
|
43
|
-
// progress that's higher than our counter, adopt that.
|
|
44
32
|
const candidate = typeof progress === 'number' ? Math.max(attempt, progress) : attempt;
|
|
45
33
|
const next = Math.max(lastSent + 1, candidate);
|
|
46
34
|
lastSent = next;
|
|
@@ -76,732 +64,14 @@ export class ToolHandlers {
|
|
|
76
64
|
}
|
|
77
65
|
register(server) {
|
|
78
66
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
79
|
-
tools:
|
|
80
|
-
{
|
|
81
|
-
name: 'chat_completion',
|
|
82
|
-
description: TOOL_DESCRIPTIONS.chat_completion,
|
|
83
|
-
annotations: {
|
|
84
|
-
title: 'Chat completion',
|
|
85
|
-
readOnlyHint: false,
|
|
86
|
-
destructiveHint: false,
|
|
87
|
-
idempotentHint: false,
|
|
88
|
-
openWorldHint: true,
|
|
89
|
-
},
|
|
90
|
-
inputSchema: {
|
|
91
|
-
type: 'object',
|
|
92
|
-
properties: {
|
|
93
|
-
model: {
|
|
94
|
-
type: 'string',
|
|
95
|
-
description: 'Model ID (optional, uses default). Append `:nitro` for the fastest variant, ' +
|
|
96
|
-
'`:floor` for the cheapest, `:free` for the free tier, `:online` for web search, ' +
|
|
97
|
-
'or `:exacto` for the best tool-calling accuracy. ' +
|
|
98
|
-
'Example: `openai/gpt-4o:nitro`. Or pass `online: true` for programmatic web search control.',
|
|
99
|
-
},
|
|
100
|
-
messages: {
|
|
101
|
-
type: 'array',
|
|
102
|
-
minItems: 1,
|
|
103
|
-
items: {
|
|
104
|
-
type: 'object',
|
|
105
|
-
properties: {
|
|
106
|
-
role: { type: 'string', enum: ['system', 'user', 'assistant'] },
|
|
107
|
-
content: {
|
|
108
|
-
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'object' } }],
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
required: ['role', 'content'],
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
temperature: { type: 'number', minimum: 0, maximum: 2 },
|
|
115
|
-
max_tokens: {
|
|
116
|
-
type: 'number',
|
|
117
|
-
minimum: 1,
|
|
118
|
-
description: 'Max completion tokens. Falls back to `OPENROUTER_MAX_TOKENS` env var if unset.',
|
|
119
|
-
},
|
|
120
|
-
provider: {
|
|
121
|
-
type: 'object',
|
|
122
|
-
description: 'OpenRouter provider-routing overrides. Merges on top of `OPENROUTER_PROVIDER_*` env defaults. ' +
|
|
123
|
-
'See https://openrouter.ai/docs/features/provider-routing',
|
|
124
|
-
properties: {
|
|
125
|
-
quantizations: {
|
|
126
|
-
type: 'array',
|
|
127
|
-
items: { type: 'string' },
|
|
128
|
-
description: 'Filter providers by quantization (e.g. `["fp16","int8"]`).',
|
|
129
|
-
},
|
|
130
|
-
ignore: {
|
|
131
|
-
type: 'array',
|
|
132
|
-
items: { type: 'string' },
|
|
133
|
-
description: 'Exclude these provider slugs.',
|
|
134
|
-
},
|
|
135
|
-
sort: {
|
|
136
|
-
type: 'string',
|
|
137
|
-
enum: ['price', 'throughput', 'latency'],
|
|
138
|
-
},
|
|
139
|
-
order: { type: 'array', items: { type: 'string' } },
|
|
140
|
-
require_parameters: { type: 'boolean' },
|
|
141
|
-
data_collection: { type: 'string', enum: ['allow', 'deny'] },
|
|
142
|
-
allow_fallbacks: { type: 'boolean' },
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
include_reasoning: {
|
|
146
|
-
type: 'boolean',
|
|
147
|
-
description: "Surface the model's chain-of-thought on `_meta.reasoning` for R1 / Opus 4.7 / Gemini Thinking.",
|
|
148
|
-
},
|
|
149
|
-
online: {
|
|
150
|
-
type: 'boolean',
|
|
151
|
-
description: "Enable OpenRouter's web-search plugin (Exa-backed, $4 / 1000 results).",
|
|
152
|
-
},
|
|
153
|
-
web_max_results: {
|
|
154
|
-
type: 'number',
|
|
155
|
-
minimum: 1,
|
|
156
|
-
description: 'Max web-search results when `online: true` (default 5).',
|
|
157
|
-
},
|
|
158
|
-
cache: {
|
|
159
|
-
type: 'boolean',
|
|
160
|
-
description: 'Enable OpenRouter response caching via `X-OpenRouter-Cache: true`. ' +
|
|
161
|
-
'Server-wide default settable via `OPENROUTER_CACHE_RESPONSES=1`.',
|
|
162
|
-
},
|
|
163
|
-
cache_ttl: {
|
|
164
|
-
type: 'string',
|
|
165
|
-
description: 'Cache TTL (e.g. `"5m"`, `"1h"`, `"24h"`; 1s-24h range).',
|
|
166
|
-
},
|
|
167
|
-
cache_clear: {
|
|
168
|
-
type: 'boolean',
|
|
169
|
-
description: 'Bust the cache entry for this exact request.',
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
required: ['messages'],
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
name: 'start_chat_completion',
|
|
177
|
-
description: TOOL_DESCRIPTIONS.start_chat_completion,
|
|
178
|
-
annotations: {
|
|
179
|
-
title: 'Start async chat completion',
|
|
180
|
-
readOnlyHint: false,
|
|
181
|
-
destructiveHint: false,
|
|
182
|
-
idempotentHint: false,
|
|
183
|
-
openWorldHint: true,
|
|
184
|
-
},
|
|
185
|
-
inputSchema: {
|
|
186
|
-
type: 'object',
|
|
187
|
-
properties: {
|
|
188
|
-
model: { type: 'string', description: 'Model ID (same as chat_completion).' },
|
|
189
|
-
messages: {
|
|
190
|
-
type: 'array',
|
|
191
|
-
minItems: 1,
|
|
192
|
-
items: {
|
|
193
|
-
type: 'object',
|
|
194
|
-
properties: {
|
|
195
|
-
role: { type: 'string', enum: ['system', 'user', 'assistant'] },
|
|
196
|
-
content: {
|
|
197
|
-
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'object' } }],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
required: ['role', 'content'],
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
temperature: { type: 'number', minimum: 0, maximum: 2 },
|
|
204
|
-
max_tokens: { type: 'number', minimum: 1 },
|
|
205
|
-
provider: { type: 'object' },
|
|
206
|
-
include_reasoning: { type: 'boolean' },
|
|
207
|
-
online: { type: 'boolean' },
|
|
208
|
-
web_max_results: { type: 'number', minimum: 1 },
|
|
209
|
-
cache: { type: 'boolean' },
|
|
210
|
-
cache_ttl: { type: 'string' },
|
|
211
|
-
cache_clear: { type: 'boolean' },
|
|
212
|
-
},
|
|
213
|
-
required: ['messages'],
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
name: 'get_chat_completion_status',
|
|
218
|
-
description: TOOL_DESCRIPTIONS.get_chat_completion_status,
|
|
219
|
-
annotations: {
|
|
220
|
-
title: 'Get async chat completion status',
|
|
221
|
-
readOnlyHint: true,
|
|
222
|
-
destructiveHint: false,
|
|
223
|
-
idempotentHint: true,
|
|
224
|
-
openWorldHint: false,
|
|
225
|
-
},
|
|
226
|
-
inputSchema: {
|
|
227
|
-
type: 'object',
|
|
228
|
-
properties: {
|
|
229
|
-
job_id: {
|
|
230
|
-
type: 'string',
|
|
231
|
-
description: 'The job_id returned by start_chat_completion.',
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
|
-
required: ['job_id'],
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
name: 'analyze_image',
|
|
239
|
-
description: TOOL_DESCRIPTIONS.analyze_image,
|
|
240
|
-
annotations: {
|
|
241
|
-
title: 'Analyze image',
|
|
242
|
-
readOnlyHint: true,
|
|
243
|
-
destructiveHint: false,
|
|
244
|
-
idempotentHint: false,
|
|
245
|
-
openWorldHint: true,
|
|
246
|
-
},
|
|
247
|
-
inputSchema: {
|
|
248
|
-
type: 'object',
|
|
249
|
-
properties: {
|
|
250
|
-
image_path: {
|
|
251
|
-
type: 'string',
|
|
252
|
-
description: 'Required. Local path (inside OPENROUTER_INPUT_DIR sandbox), https URL, or data URL. ' +
|
|
253
|
-
'Good: `"photo.jpg"`. Bad: `"url": "..."` (wrong key), `"/etc/passwd"` (UNSAFE_PATH).',
|
|
254
|
-
},
|
|
255
|
-
question: {
|
|
256
|
-
type: 'string',
|
|
257
|
-
description: 'Optional question about the image. Defaults to "What\'s in this image?" if omitted. ' +
|
|
258
|
-
'Good: `"List all text"`. Bad: using `prompt` key (wrong name for this tool).',
|
|
259
|
-
},
|
|
260
|
-
model: { type: 'string' },
|
|
261
|
-
cache_input: {
|
|
262
|
-
type: 'boolean',
|
|
263
|
-
description: 'Attach `cache_control: ephemeral` to the image block so Anthropic / Gemini prompt-cache it. ' +
|
|
264
|
-
'Repeat questions about the same image save ~10x on Anthropic.',
|
|
265
|
-
},
|
|
266
|
-
cache: { type: 'boolean' },
|
|
267
|
-
cache_ttl: { type: 'string' },
|
|
268
|
-
cache_clear: { type: 'boolean' },
|
|
269
|
-
},
|
|
270
|
-
required: ['image_path'],
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
name: 'analyze_audio',
|
|
275
|
-
description: TOOL_DESCRIPTIONS.analyze_audio,
|
|
276
|
-
annotations: {
|
|
277
|
-
title: 'Analyze audio',
|
|
278
|
-
readOnlyHint: true,
|
|
279
|
-
destructiveHint: false,
|
|
280
|
-
idempotentHint: false,
|
|
281
|
-
openWorldHint: true,
|
|
282
|
-
},
|
|
283
|
-
inputSchema: {
|
|
284
|
-
type: 'object',
|
|
285
|
-
properties: {
|
|
286
|
-
audio_path: {
|
|
287
|
-
type: 'string',
|
|
288
|
-
description: 'Local file path (sandboxed to OPENROUTER_INPUT_DIR / OPENROUTER_OUTPUT_DIR / cwd), ' +
|
|
289
|
-
'http(s) URL, or data URL (base64-encoded audio)',
|
|
290
|
-
},
|
|
291
|
-
question: {
|
|
292
|
-
type: 'string',
|
|
293
|
-
description: 'Question or instruction about the audio (default: transcribe)',
|
|
294
|
-
},
|
|
295
|
-
model: { type: 'string' },
|
|
296
|
-
cache_input: { type: 'boolean' },
|
|
297
|
-
cache: { type: 'boolean' },
|
|
298
|
-
cache_ttl: { type: 'string' },
|
|
299
|
-
cache_clear: { type: 'boolean' },
|
|
300
|
-
},
|
|
301
|
-
required: ['audio_path'],
|
|
302
|
-
},
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
name: 'analyze_video',
|
|
306
|
-
description: TOOL_DESCRIPTIONS.analyze_video,
|
|
307
|
-
annotations: {
|
|
308
|
-
title: 'Analyze video',
|
|
309
|
-
readOnlyHint: true,
|
|
310
|
-
destructiveHint: false,
|
|
311
|
-
idempotentHint: false,
|
|
312
|
-
openWorldHint: true,
|
|
313
|
-
},
|
|
314
|
-
inputSchema: {
|
|
315
|
-
type: 'object',
|
|
316
|
-
properties: {
|
|
317
|
-
video_path: {
|
|
318
|
-
type: 'string',
|
|
319
|
-
description: 'Local file path (sandboxed to OPENROUTER_INPUT_DIR / OPENROUTER_OUTPUT_DIR / cwd), ' +
|
|
320
|
-
'http(s) URL, or base64 data URL. Supported: mp4 / mpeg / mov / webm.',
|
|
321
|
-
},
|
|
322
|
-
question: { type: 'string' },
|
|
323
|
-
model: { type: 'string' },
|
|
324
|
-
cache_input: { type: 'boolean' },
|
|
325
|
-
cache: { type: 'boolean' },
|
|
326
|
-
cache_ttl: { type: 'string' },
|
|
327
|
-
cache_clear: { type: 'boolean' },
|
|
328
|
-
},
|
|
329
|
-
required: ['video_path'],
|
|
330
|
-
},
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
name: 'search_models',
|
|
334
|
-
description: TOOL_DESCRIPTIONS.search_models,
|
|
335
|
-
annotations: {
|
|
336
|
-
title: 'Search models',
|
|
337
|
-
readOnlyHint: true,
|
|
338
|
-
destructiveHint: false,
|
|
339
|
-
idempotentHint: true,
|
|
340
|
-
openWorldHint: true,
|
|
341
|
-
},
|
|
342
|
-
inputSchema: {
|
|
343
|
-
type: 'object',
|
|
344
|
-
properties: {
|
|
345
|
-
query: { type: 'string' },
|
|
346
|
-
provider: { type: 'string' },
|
|
347
|
-
capabilities: {
|
|
348
|
-
type: 'object',
|
|
349
|
-
properties: {
|
|
350
|
-
vision: { type: 'boolean' },
|
|
351
|
-
audio: { type: 'boolean' },
|
|
352
|
-
video: { type: 'boolean' },
|
|
353
|
-
},
|
|
354
|
-
},
|
|
355
|
-
limit: { type: 'number', minimum: 1, maximum: 50 },
|
|
356
|
-
offset: { type: 'number', minimum: 0 },
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
outputSchema: {
|
|
360
|
-
type: 'object',
|
|
361
|
-
properties: {
|
|
362
|
-
results: { type: 'array', items: { type: 'object' } },
|
|
363
|
-
offset: { type: 'number' },
|
|
364
|
-
limit: { type: 'number' },
|
|
365
|
-
total: { type: 'number' },
|
|
366
|
-
has_more: { type: 'boolean' },
|
|
367
|
-
next_offset: { type: ['number', 'null'] },
|
|
368
|
-
},
|
|
369
|
-
required: ['results', 'offset', 'limit', 'total', 'has_more', 'next_offset'],
|
|
370
|
-
},
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
name: 'get_model_info',
|
|
374
|
-
description: TOOL_DESCRIPTIONS.get_model_info,
|
|
375
|
-
annotations: {
|
|
376
|
-
title: 'Get model info',
|
|
377
|
-
readOnlyHint: true,
|
|
378
|
-
destructiveHint: false,
|
|
379
|
-
idempotentHint: true,
|
|
380
|
-
openWorldHint: true,
|
|
381
|
-
},
|
|
382
|
-
inputSchema: {
|
|
383
|
-
type: 'object',
|
|
384
|
-
properties: { model: { type: 'string' } },
|
|
385
|
-
required: ['model'],
|
|
386
|
-
},
|
|
387
|
-
outputSchema: {
|
|
388
|
-
type: 'object',
|
|
389
|
-
properties: {
|
|
390
|
-
id: { type: 'string' },
|
|
391
|
-
name: { type: 'string' },
|
|
392
|
-
context_length: { type: 'number' },
|
|
393
|
-
architecture: { type: 'object' },
|
|
394
|
-
},
|
|
395
|
-
required: ['id'],
|
|
396
|
-
},
|
|
397
|
-
},
|
|
398
|
-
{
|
|
399
|
-
name: 'validate_model',
|
|
400
|
-
description: TOOL_DESCRIPTIONS.validate_model,
|
|
401
|
-
annotations: {
|
|
402
|
-
title: 'Validate model',
|
|
403
|
-
readOnlyHint: true,
|
|
404
|
-
destructiveHint: false,
|
|
405
|
-
idempotentHint: true,
|
|
406
|
-
openWorldHint: true,
|
|
407
|
-
},
|
|
408
|
-
inputSchema: {
|
|
409
|
-
type: 'object',
|
|
410
|
-
properties: { model: { type: 'string' } },
|
|
411
|
-
required: ['model'],
|
|
412
|
-
},
|
|
413
|
-
outputSchema: {
|
|
414
|
-
type: 'object',
|
|
415
|
-
properties: {
|
|
416
|
-
valid: { type: 'boolean' },
|
|
417
|
-
model: { type: 'string' },
|
|
418
|
-
},
|
|
419
|
-
required: ['valid', 'model'],
|
|
420
|
-
},
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
name: 'generate_image',
|
|
424
|
-
description: TOOL_DESCRIPTIONS.generate_image,
|
|
425
|
-
annotations: {
|
|
426
|
-
title: 'Generate image',
|
|
427
|
-
readOnlyHint: false,
|
|
428
|
-
destructiveHint: false,
|
|
429
|
-
idempotentHint: false,
|
|
430
|
-
openWorldHint: true,
|
|
431
|
-
},
|
|
432
|
-
inputSchema: {
|
|
433
|
-
type: 'object',
|
|
434
|
-
properties: {
|
|
435
|
-
prompt: { type: 'string' },
|
|
436
|
-
model: { type: 'string' },
|
|
437
|
-
aspect_ratio: {
|
|
438
|
-
type: 'string',
|
|
439
|
-
enum: [
|
|
440
|
-
'1:1',
|
|
441
|
-
'2:3',
|
|
442
|
-
'3:2',
|
|
443
|
-
'3:4',
|
|
444
|
-
'4:3',
|
|
445
|
-
'4:5',
|
|
446
|
-
'5:4',
|
|
447
|
-
'9:16',
|
|
448
|
-
'16:9',
|
|
449
|
-
'21:9',
|
|
450
|
-
'1:4',
|
|
451
|
-
'4:1',
|
|
452
|
-
'1:8',
|
|
453
|
-
'8:1',
|
|
454
|
-
],
|
|
455
|
-
},
|
|
456
|
-
image_size: { type: 'string', enum: ['0.5K', '1K', '2K', '4K'] },
|
|
457
|
-
max_tokens: { type: 'number', minimum: 1 },
|
|
458
|
-
save_path: { type: 'string' },
|
|
459
|
-
input_images: { type: 'array', items: { type: 'string' } },
|
|
460
|
-
modalities: { type: 'array', items: { type: 'string' } },
|
|
461
|
-
},
|
|
462
|
-
required: ['prompt'],
|
|
463
|
-
},
|
|
464
|
-
},
|
|
465
|
-
{
|
|
466
|
-
name: 'generate_image_dedicated',
|
|
467
|
-
description: TOOL_DESCRIPTIONS.generate_image_dedicated,
|
|
468
|
-
annotations: {
|
|
469
|
-
title: 'Generate image (dedicated API)',
|
|
470
|
-
readOnlyHint: false,
|
|
471
|
-
destructiveHint: false,
|
|
472
|
-
idempotentHint: false,
|
|
473
|
-
openWorldHint: true,
|
|
474
|
-
},
|
|
475
|
-
inputSchema: {
|
|
476
|
-
type: 'object',
|
|
477
|
-
properties: {
|
|
478
|
-
prompt: {
|
|
479
|
-
type: 'string',
|
|
480
|
-
description: 'Text prompt describing the image to generate.',
|
|
481
|
-
},
|
|
482
|
-
model: {
|
|
483
|
-
type: 'string',
|
|
484
|
-
description: 'Image model ID. Default: google/gemini-2.5-flash-image. Browse available at https://openrouter.ai/collections/image-models',
|
|
485
|
-
},
|
|
486
|
-
resolution: {
|
|
487
|
-
type: 'string',
|
|
488
|
-
enum: ['512', '0.5K', '1K', '2K', '4K'],
|
|
489
|
-
description: 'Normalized resolution tier. Provider maps to closest supported size.',
|
|
490
|
-
},
|
|
491
|
-
aspect_ratio: {
|
|
492
|
-
type: 'string',
|
|
493
|
-
description: 'Aspect ratio (e.g. "1:1", "16:9", "9:16", "4:3", "21:9").',
|
|
494
|
-
},
|
|
495
|
-
quality: {
|
|
496
|
-
type: 'string',
|
|
497
|
-
enum: ['auto', 'low', 'medium', 'high'],
|
|
498
|
-
description: 'Image quality level.',
|
|
499
|
-
},
|
|
500
|
-
output_format: {
|
|
501
|
-
type: 'string',
|
|
502
|
-
enum: ['png', 'jpeg', 'webp', 'svg'],
|
|
503
|
-
description: 'Output image format.',
|
|
504
|
-
},
|
|
505
|
-
n: {
|
|
506
|
-
type: 'number',
|
|
507
|
-
minimum: 1,
|
|
508
|
-
maximum: 10,
|
|
509
|
-
description: 'Number of images to generate (model-dependent, default 1).',
|
|
510
|
-
},
|
|
511
|
-
input_references: {
|
|
512
|
-
type: 'array',
|
|
513
|
-
items: { type: 'string' },
|
|
514
|
-
description: 'Reference images for image-to-image workflows. Each entry: local path, http(s) URL, or data URL.',
|
|
515
|
-
},
|
|
516
|
-
save_path: { type: 'string', description: 'Save generated image to this path.' },
|
|
517
|
-
provider: {
|
|
518
|
-
type: 'object',
|
|
519
|
-
description: 'Provider routing overrides (order, sort, allow_fallbacks, etc.).',
|
|
520
|
-
},
|
|
521
|
-
cache: { type: 'boolean' },
|
|
522
|
-
cache_ttl: { type: 'string' },
|
|
523
|
-
cache_clear: { type: 'boolean' },
|
|
524
|
-
},
|
|
525
|
-
required: ['prompt'],
|
|
526
|
-
},
|
|
527
|
-
},
|
|
528
|
-
{
|
|
529
|
-
name: 'generate_audio',
|
|
530
|
-
description: TOOL_DESCRIPTIONS.generate_audio,
|
|
531
|
-
annotations: {
|
|
532
|
-
title: 'Generate audio',
|
|
533
|
-
readOnlyHint: false,
|
|
534
|
-
destructiveHint: false,
|
|
535
|
-
idempotentHint: false,
|
|
536
|
-
openWorldHint: true,
|
|
537
|
-
},
|
|
538
|
-
inputSchema: {
|
|
539
|
-
type: 'object',
|
|
540
|
-
properties: {
|
|
541
|
-
prompt: { type: 'string' },
|
|
542
|
-
model: { type: 'string' },
|
|
543
|
-
voice: { type: 'string' },
|
|
544
|
-
format: { type: 'string' },
|
|
545
|
-
save_path: { type: 'string' },
|
|
546
|
-
},
|
|
547
|
-
required: ['prompt'],
|
|
548
|
-
},
|
|
549
|
-
},
|
|
550
|
-
{
|
|
551
|
-
name: 'text_to_speech',
|
|
552
|
-
description: TOOL_DESCRIPTIONS.text_to_speech,
|
|
553
|
-
annotations: {
|
|
554
|
-
title: 'Text to speech (dedicated API)',
|
|
555
|
-
readOnlyHint: false,
|
|
556
|
-
destructiveHint: false,
|
|
557
|
-
idempotentHint: false,
|
|
558
|
-
openWorldHint: true,
|
|
559
|
-
},
|
|
560
|
-
inputSchema: {
|
|
561
|
-
type: 'object',
|
|
562
|
-
properties: {
|
|
563
|
-
input: {
|
|
564
|
-
type: 'string',
|
|
565
|
-
description: 'Text to convert to speech.',
|
|
566
|
-
},
|
|
567
|
-
model: {
|
|
568
|
-
type: 'string',
|
|
569
|
-
description: 'TTS model. Default: openai/gpt-4o-mini-tts-2025-12-15. Also: google/gemini-flash-tts, mistral/voxtral-mini-tts.',
|
|
570
|
-
},
|
|
571
|
-
voice: {
|
|
572
|
-
type: 'string',
|
|
573
|
-
description: 'Voice ID (model-specific). Default: alloy. OpenAI voices: alloy, echo, fable, onyx, nova, shimmer.',
|
|
574
|
-
},
|
|
575
|
-
response_format: {
|
|
576
|
-
type: 'string',
|
|
577
|
-
enum: ['mp3', 'opus', 'aac', 'flac', 'wav', 'pcm'],
|
|
578
|
-
description: 'Output audio format. Default: mp3.',
|
|
579
|
-
},
|
|
580
|
-
speed: {
|
|
581
|
-
type: 'number',
|
|
582
|
-
minimum: 0.25,
|
|
583
|
-
maximum: 4.0,
|
|
584
|
-
description: 'Speed of speech (0.25 to 4.0). Default: 1.0.',
|
|
585
|
-
},
|
|
586
|
-
instructions: {
|
|
587
|
-
type: 'string',
|
|
588
|
-
description: 'Tone/style instructions (e.g. "speak in a warm, friendly tone"). OpenAI models only.',
|
|
589
|
-
},
|
|
590
|
-
save_path: { type: 'string', description: 'Save audio to this path.' },
|
|
591
|
-
cache: { type: 'boolean' },
|
|
592
|
-
cache_ttl: { type: 'string' },
|
|
593
|
-
cache_clear: { type: 'boolean' },
|
|
594
|
-
},
|
|
595
|
-
required: ['input'],
|
|
596
|
-
},
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
name: 'speech_to_text',
|
|
600
|
-
description: TOOL_DESCRIPTIONS.speech_to_text,
|
|
601
|
-
annotations: {
|
|
602
|
-
title: 'Speech to text (dedicated API)',
|
|
603
|
-
readOnlyHint: true,
|
|
604
|
-
destructiveHint: false,
|
|
605
|
-
idempotentHint: false,
|
|
606
|
-
openWorldHint: true,
|
|
607
|
-
},
|
|
608
|
-
inputSchema: {
|
|
609
|
-
type: 'object',
|
|
610
|
-
properties: {
|
|
611
|
-
audio_path: {
|
|
612
|
-
type: 'string',
|
|
613
|
-
description: 'Audio file: local path (sandboxed), http(s) URL, or base64 data URL. Formats: mp3, wav, flac, ogg, webm, mp4.',
|
|
614
|
-
},
|
|
615
|
-
model: {
|
|
616
|
-
type: 'string',
|
|
617
|
-
description: 'STT model. Default: openai/whisper-1. Also: openai/gpt-4o-transcribe, openai/gpt-4o-mini-transcribe.',
|
|
618
|
-
},
|
|
619
|
-
language: {
|
|
620
|
-
type: 'string',
|
|
621
|
-
description: 'ISO-639-1 language code (e.g. "en", "es", "fr"). Improves accuracy.',
|
|
622
|
-
},
|
|
623
|
-
response_format: {
|
|
624
|
-
type: 'string',
|
|
625
|
-
enum: ['json', 'text', 'srt', 'verbose_json', 'vtt'],
|
|
626
|
-
description: 'Output format for transcription. Default: json.',
|
|
627
|
-
},
|
|
628
|
-
temperature: {
|
|
629
|
-
type: 'number',
|
|
630
|
-
minimum: 0,
|
|
631
|
-
maximum: 1,
|
|
632
|
-
description: 'Sampling temperature for transcription (0-1).',
|
|
633
|
-
},
|
|
634
|
-
cache: { type: 'boolean' },
|
|
635
|
-
cache_ttl: { type: 'string' },
|
|
636
|
-
cache_clear: { type: 'boolean' },
|
|
637
|
-
},
|
|
638
|
-
required: ['audio_path'],
|
|
639
|
-
},
|
|
640
|
-
},
|
|
641
|
-
{
|
|
642
|
-
name: 'generate_video',
|
|
643
|
-
description: TOOL_DESCRIPTIONS.generate_video,
|
|
644
|
-
annotations: {
|
|
645
|
-
title: 'Generate video',
|
|
646
|
-
readOnlyHint: false,
|
|
647
|
-
destructiveHint: false,
|
|
648
|
-
idempotentHint: false,
|
|
649
|
-
openWorldHint: true,
|
|
650
|
-
},
|
|
651
|
-
inputSchema: {
|
|
652
|
-
type: 'object',
|
|
653
|
-
properties: {
|
|
654
|
-
prompt: { type: 'string' },
|
|
655
|
-
model: { type: 'string' },
|
|
656
|
-
resolution: { type: 'string' },
|
|
657
|
-
aspect_ratio: { type: 'string' },
|
|
658
|
-
duration: { type: 'number', minimum: 1 },
|
|
659
|
-
seed: { type: 'number' },
|
|
660
|
-
first_frame_image: { type: 'string' },
|
|
661
|
-
last_frame_image: { type: 'string' },
|
|
662
|
-
reference_images: { type: 'array', items: { type: 'string' } },
|
|
663
|
-
provider: { type: 'object' },
|
|
664
|
-
save_path: { type: 'string' },
|
|
665
|
-
max_wait_ms: { type: 'number', minimum: 10000 },
|
|
666
|
-
poll_interval_ms: { type: 'number', minimum: 2000 },
|
|
667
|
-
},
|
|
668
|
-
required: ['prompt'],
|
|
669
|
-
},
|
|
670
|
-
},
|
|
671
|
-
{
|
|
672
|
-
name: 'generate_video_from_image',
|
|
673
|
-
description: TOOL_DESCRIPTIONS.generate_video_from_image,
|
|
674
|
-
annotations: {
|
|
675
|
-
title: 'Generate video from image',
|
|
676
|
-
readOnlyHint: false,
|
|
677
|
-
destructiveHint: false,
|
|
678
|
-
idempotentHint: false,
|
|
679
|
-
openWorldHint: true,
|
|
680
|
-
},
|
|
681
|
-
inputSchema: {
|
|
682
|
-
type: 'object',
|
|
683
|
-
properties: {
|
|
684
|
-
image: {
|
|
685
|
-
type: 'string',
|
|
686
|
-
description: 'First-frame image (path, URL, or data URL). Required.',
|
|
687
|
-
},
|
|
688
|
-
prompt: { type: 'string' },
|
|
689
|
-
model: { type: 'string' },
|
|
690
|
-
resolution: { type: 'string' },
|
|
691
|
-
aspect_ratio: { type: 'string' },
|
|
692
|
-
duration: { type: 'number', minimum: 1 },
|
|
693
|
-
seed: { type: 'number' },
|
|
694
|
-
save_path: { type: 'string' },
|
|
695
|
-
max_wait_ms: { type: 'number', minimum: 10000 },
|
|
696
|
-
poll_interval_ms: { type: 'number', minimum: 2000 },
|
|
697
|
-
},
|
|
698
|
-
required: ['image', 'prompt'],
|
|
699
|
-
},
|
|
700
|
-
},
|
|
701
|
-
{
|
|
702
|
-
name: 'get_video_status',
|
|
703
|
-
description: TOOL_DESCRIPTIONS.get_video_status,
|
|
704
|
-
annotations: {
|
|
705
|
-
title: 'Get video status',
|
|
706
|
-
readOnlyHint: true,
|
|
707
|
-
destructiveHint: false,
|
|
708
|
-
idempotentHint: true,
|
|
709
|
-
openWorldHint: true,
|
|
710
|
-
},
|
|
711
|
-
inputSchema: {
|
|
712
|
-
type: 'object',
|
|
713
|
-
properties: {
|
|
714
|
-
video_id: { type: 'string' },
|
|
715
|
-
save_path: { type: 'string' },
|
|
716
|
-
},
|
|
717
|
-
required: ['video_id'],
|
|
718
|
-
},
|
|
719
|
-
},
|
|
720
|
-
{
|
|
721
|
-
name: 'rerank_documents',
|
|
722
|
-
description: TOOL_DESCRIPTIONS.rerank_documents,
|
|
723
|
-
annotations: {
|
|
724
|
-
title: 'Rerank documents',
|
|
725
|
-
readOnlyHint: true,
|
|
726
|
-
destructiveHint: false,
|
|
727
|
-
idempotentHint: true,
|
|
728
|
-
openWorldHint: true,
|
|
729
|
-
},
|
|
730
|
-
inputSchema: {
|
|
731
|
-
type: 'object',
|
|
732
|
-
properties: {
|
|
733
|
-
query: { type: 'string' },
|
|
734
|
-
documents: { type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
735
|
-
model: { type: 'string' },
|
|
736
|
-
top_n: { type: 'number', minimum: 1 },
|
|
737
|
-
return_documents: { type: 'boolean' },
|
|
738
|
-
},
|
|
739
|
-
required: ['query', 'documents'],
|
|
740
|
-
},
|
|
741
|
-
outputSchema: {
|
|
742
|
-
type: 'object',
|
|
743
|
-
properties: {
|
|
744
|
-
model: { type: 'string' },
|
|
745
|
-
results: {
|
|
746
|
-
type: 'array',
|
|
747
|
-
items: {
|
|
748
|
-
type: 'object',
|
|
749
|
-
properties: {
|
|
750
|
-
index: { type: 'number' },
|
|
751
|
-
score: { type: 'number' },
|
|
752
|
-
document: { type: 'string' },
|
|
753
|
-
},
|
|
754
|
-
required: ['index', 'score'],
|
|
755
|
-
},
|
|
756
|
-
},
|
|
757
|
-
},
|
|
758
|
-
required: ['results'],
|
|
759
|
-
},
|
|
760
|
-
},
|
|
761
|
-
{
|
|
762
|
-
name: 'health_check',
|
|
763
|
-
description: TOOL_DESCRIPTIONS.health_check,
|
|
764
|
-
annotations: {
|
|
765
|
-
title: 'Health check',
|
|
766
|
-
readOnlyHint: true,
|
|
767
|
-
destructiveHint: false,
|
|
768
|
-
idempotentHint: true,
|
|
769
|
-
openWorldHint: true,
|
|
770
|
-
},
|
|
771
|
-
inputSchema: { type: 'object', properties: {} },
|
|
772
|
-
outputSchema: {
|
|
773
|
-
type: 'object',
|
|
774
|
-
properties: {
|
|
775
|
-
ok: { type: 'boolean' },
|
|
776
|
-
server_version: { type: 'string' },
|
|
777
|
-
protocol_version: { type: 'string' },
|
|
778
|
-
api_key_valid: { type: 'boolean' },
|
|
779
|
-
models_cached: { type: 'number' },
|
|
780
|
-
error: { type: 'string' },
|
|
781
|
-
},
|
|
782
|
-
required: [
|
|
783
|
-
'ok',
|
|
784
|
-
'server_version',
|
|
785
|
-
'protocol_version',
|
|
786
|
-
'api_key_valid',
|
|
787
|
-
'models_cached',
|
|
788
|
-
],
|
|
789
|
-
},
|
|
790
|
-
},
|
|
791
|
-
].map((tool) => ({
|
|
67
|
+
tools: TOOL_DEFINITIONS.map((tool) => ({
|
|
792
68
|
...tool,
|
|
793
69
|
icons: TOOL_ICONS[tool.name],
|
|
794
70
|
})),
|
|
795
71
|
}));
|
|
796
72
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
797
73
|
const { name, arguments: args } = request.params;
|
|
798
|
-
//
|
|
799
|
-
// open interface (content + optional _meta + optional isError +
|
|
800
|
-
// optional structuredContent). We cast through unknown because
|
|
801
|
-
// several handlers include server-specific _meta keys (e.g.
|
|
802
|
-
// server_version, cache, code) that aren't listed in the SDK's
|
|
803
|
-
// typed schema — the SDK accepts any extras thanks to the
|
|
804
|
-
// `[x: string]: unknown` index signature.
|
|
74
|
+
// Handlers return extra _meta keys not in the SDK type.
|
|
805
75
|
const dispatch = async () => {
|
|
806
76
|
switch (name) {
|
|
807
77
|
case 'chat_completion':
|