corent-mcp 0.5.2 → 0.6.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.
Files changed (3) hide show
  1. package/README.md +16 -1
  2. package/dist/server.js +109 -9
  3. package/package.json +5 -2
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Corent MCP Server
2
2
 
3
- Give any AI agent the ability to generate images and videos through the [Corent](https://corent.tech) API — one key, automatic model routing, provider fallback built in.
3
+ Give any AI agent the ability to generate images, videos, voice, and text through the [Corent](https://corent.tech) API — one key, automatic model routing, provider fallback built in.
4
4
 
5
5
  ## Tools
6
6
 
@@ -10,6 +10,9 @@ Give any AI agent the ability to generate images and videos through the [Corent]
10
10
  | `create` | Describe what you want + a spend ceiling; Corent decides everything and generates it (the "zero decisions" path) |
11
11
  | `generate_image` | Text → image, synchronous, returns a permanent URL |
12
12
  | `generate_video` | Text (or image) → video, async job |
13
+ | `generate_speech` | Text → spoken audio, synchronous |
14
+ | `generate_text` | Prompt → text from a frontier language model, synchronous, billed per token |
15
+ | `list_models` | The direct-access menu: every model that can be pinned by name, with quality and live status |
13
16
  | `get_job` | Poll a job until completed |
14
17
  | `get_balance` | Remaining account balance |
15
18
  | `get_status` | Live tier health |
@@ -20,6 +23,18 @@ image-vs-video, the tier, aspect ratio, and duration — the agent never manages
20
23
  models. `create` enforces a per-call spend ceiling so an autonomous agent can't
21
24
  overspend.
22
25
 
26
+ ### When the user names a specific model
27
+
28
+ All four `generate_*` tools also take an optional **`model`**: pin an exact
29
+ model from `list_models` and Corent runs that one, never a substitute (if it
30
+ can't deliver, the call fails and nothing is billed). Use it only when the user
31
+ asked for a particular model by name — otherwise omit it and let Corent route
32
+ to the best fit for the prompt, which is what the tiers are for.
33
+
34
+ `generate_text` is the language-model lane: it puts every frontier lab on the
35
+ same key and the same bill, so an agent can get a named model's answer or a
36
+ second opinion from another lab without the user holding that lab's account.
37
+
23
38
  ## Setup
24
39
 
25
40
  Get an API key at [corent.tech](https://corent.tech), then add to your MCP client config:
package/dist/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Shared Corent MCP server definition — the 8 tools, used by both the
2
+ * Shared Corent MCP server definition — the 10 tools, used by both the
3
3
  * stdio entrypoint (index.ts, for local `npx` use) and the hosted HTTP
4
4
  * entrypoint (http.ts). Keeping the tools in one place means the two
5
5
  * transports can never drift apart.
@@ -17,7 +17,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
17
  import { z } from "zod";
18
18
  import { MEDIA_WIDGET_HTML } from "./widget-html.js";
19
19
  export const DEFAULT_API_URL = "https://api.corent.tech";
20
- export const SERVER_VERSION = "0.5.2";
20
+ export const SERVER_VERSION = "0.6.0";
21
21
  // --- MCP Apps (SEP-1865): the media tools render an inline widget in hosts
22
22
  // that support it (claude.ai web/desktop, others). Hosts without the extension
23
23
  // ignore the _meta and fall back to plain text results, so this is additive.
@@ -51,6 +51,11 @@ function errorCode(err) {
51
51
  return "invalid_api_key";
52
52
  if (err.status === 402)
53
53
  return "insufficient_balance";
54
+ // Scoped keys (image/video/voice/llm): the key is valid, this lane isn't
55
+ // enabled on it. Distinct from invalid_api_key -- retrying won't help, the
56
+ // user has to enable the capability on the key.
57
+ if (err.status === 403)
58
+ return "capability_not_enabled";
54
59
  if (err.status === 404)
55
60
  return "not_found";
56
61
  if (err.status === 422 && detail.includes("content"))
@@ -180,6 +185,10 @@ export function createCorentServer(config = {}) {
180
185
  .enum(["air", "lite", "premium", "pro", "max_pro"])
181
186
  .optional()
182
187
  .describe("Quality tier: air=cheapest (best value, not the quickest), lite=everyday, premium=production, pro=advanced, max_pro=maximum fidelity (flagship models). Omit for a sensible default."),
188
+ model: z
189
+ .string()
190
+ .optional()
191
+ .describe("Direct model access: pin an exact model by name (list_models shows the menu). Bypasses Corent's routing -- never substituted, flat cost-plus price. Mutually exclusive with tier; prefer tier unless the user named a specific model."),
183
192
  style: z
184
193
  .enum(["photorealistic", "artistic", "anime", "logo", "text_focused"])
185
194
  .optional()
@@ -188,9 +197,9 @@ export function createCorentServer(config = {}) {
188
197
  },
189
198
  outputSchema: imageOutput,
190
199
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
191
- }, wrap(async ({ prompt, tier, style, aspect_ratio }) => corent("/v1/images/generate", {
200
+ }, wrap(async ({ prompt, tier, model, style, aspect_ratio }) => corent("/v1/images/generate", {
192
201
  method: "POST",
193
- body: JSON.stringify({ prompt, tier, style, aspect_ratio }),
202
+ body: JSON.stringify({ prompt, tier, model, style, aspect_ratio }),
194
203
  })));
195
204
  server.registerTool("generate_video", {
196
205
  title: "Generate video",
@@ -202,19 +211,26 @@ export function createCorentServer(config = {}) {
202
211
  .enum(["air", "lite", "premium", "pro", "max_pro"])
203
212
  .optional()
204
213
  .describe("Quality tier: air=cheapest (best value, not the quickest), lite=everyday, premium=production, pro=advanced, max_pro=maximum fidelity (flagship models). Omit for a sensible default."),
205
- aspect_ratio: z.enum(["16:9", "9:16", "1:1"]).default("16:9"),
214
+ aspect_ratio: z
215
+ .enum(["16:9", "9:16", "1:1"])
216
+ .optional()
217
+ .describe("Shape of the clip. OMIT IT when animating a source image: those models render the still's own shape and cannot be given a different one, so naming a shape is refused rather than silently ignored. Omitted for text-to-video means the model's own default (landscape)."),
206
218
  duration_s: z.number().int().min(1).max(30).optional().describe("Requested duration in seconds"),
207
219
  resolution: z
208
220
  .enum(["720p", "1080p", "4k"])
209
221
  .optional()
210
222
  .describe("Pixel resolution (default 720p). Tier-capped: pro allows up to 1080p, max_pro up to 4k; above the tier's cap it is clamped down, never rejected. Higher resolutions cost more (1080p ~2.5x, 4k ~5.5x). GET /v1/tiers lists each tier's menu with prices."),
211
223
  image_url: z.string().url().optional().describe("If set, animates this image instead of pure text-to-video"),
224
+ model: z
225
+ .string()
226
+ .optional()
227
+ .describe("Direct model access: pin an exact model by name (list_models shows the menu). Bypasses routing -- never substituted; duration and resolution snap to THAT model's own menu rather than a tier cap. Mutually exclusive with tier."),
212
228
  },
213
229
  outputSchema: jobOutput,
214
230
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
215
- }, wrap(async ({ prompt, tier, aspect_ratio, duration_s, resolution, image_url }) => corent("/v1/videos/generate", {
231
+ }, wrap(async ({ prompt, tier, model, aspect_ratio, duration_s, resolution, image_url }) => corent("/v1/videos/generate", {
216
232
  method: "POST",
217
- body: JSON.stringify({ prompt, tier, aspect_ratio, duration_s, resolution, image_url }),
233
+ body: JSON.stringify({ prompt, tier, model, aspect_ratio, duration_s, resolution, image_url }),
218
234
  })));
219
235
  server.registerTool("generate_speech", {
220
236
  title: "Generate speech (voice)",
@@ -226,6 +242,10 @@ export function createCorentServer(config = {}) {
226
242
  .regex(/^[A-Za-z0-9]{8,64}$/)
227
243
  .optional()
228
244
  .describe("Optional provider voice id; omit for the default narration voice"),
245
+ model: z
246
+ .string()
247
+ .optional()
248
+ .describe("Direct model access: pin an exact speech model by name (list_models shows the menu)."),
229
249
  },
230
250
  outputSchema: {
231
251
  id: z.string().optional(),
@@ -235,10 +255,90 @@ export function createCorentServer(config = {}) {
235
255
  error: z.string().optional(),
236
256
  },
237
257
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
238
- }, wrap(async ({ text, voice_id }) => corent("/v1/audio/speech", {
258
+ }, wrap(async ({ text, voice_id, model }) => corent("/v1/audio/speech", {
239
259
  method: "POST",
240
- body: JSON.stringify({ text, voice_id }),
260
+ body: JSON.stringify({ text, voice_id, model }),
241
261
  })));
262
+ server.registerTool("generate_text", {
263
+ title: "Generate text (language model)",
264
+ description: "Run a prompt through a frontier language model on the user's Corent account. Synchronous: returns the finished text, the token usage and the exact charge. Billed per token (a short answer is a fraction of a cent). Use this when the user wants a SPECIFIC model's answer, a second opinion from another lab, or work billed to their Corent balance -- not for your own reasoning, which costs them nothing. Failed generations are never billed.",
265
+ inputSchema: {
266
+ prompt: z.string().min(1).describe("What to ask, in plain language"),
267
+ system: z.string().optional().describe("Optional system instruction that frames the request"),
268
+ tier: z
269
+ .enum(["air", "lite", "premium", "pro", "max_pro"])
270
+ .optional()
271
+ .describe("Quality tier: air=cheapest, lite=everyday, premium=production, pro=advanced, max_pro=frontier flagships. Omit for a sensible default."),
272
+ model: z
273
+ .string()
274
+ .optional()
275
+ .describe("Direct model access: pin an exact text model by name (list_models shows the menu, kind='text'). Bypasses routing -- never substituted, flat cost-plus price. Mutually exclusive with tier."),
276
+ max_tokens: z
277
+ .number()
278
+ .int()
279
+ .min(1)
280
+ .max(8192)
281
+ .optional()
282
+ .describe("Cap on the reply length in tokens (default 1024). The balance gate reserves against this, so keep it realistic."),
283
+ temperature: z.number().min(0).max(2).optional().describe("Sampling temperature; omit for the model's default"),
284
+ },
285
+ outputSchema: {
286
+ text: z.string().optional(),
287
+ model: z.string().optional(),
288
+ finish_reason: z.string().optional(),
289
+ usage: z
290
+ .object({
291
+ prompt_tokens: z.number().optional(),
292
+ completion_tokens: z.number().optional(),
293
+ total_tokens: z.number().optional(),
294
+ })
295
+ .passthrough()
296
+ .optional(),
297
+ cost_cents: z.number().nullable().optional(),
298
+ error: z.string().optional(),
299
+ },
300
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
301
+ }, wrap(async ({ prompt, system, tier, model, max_tokens, temperature }) => {
302
+ const messages = system
303
+ ? [{ role: "system", content: system }, { role: "user", content: prompt }]
304
+ : [{ role: "user", content: prompt }];
305
+ // The API's `model` field is required and carries both modes: a pinned
306
+ // catalog name, or "corent/text-<tier>" for the brain-routed lane. lite
307
+ // matches the router's own default for an unrecognised tier.
308
+ const r = await corent("/v1/chat/completions", {
309
+ method: "POST",
310
+ body: JSON.stringify({
311
+ model: model ?? `corent/text-${tier ?? "lite"}`,
312
+ messages,
313
+ max_tokens,
314
+ temperature,
315
+ }),
316
+ });
317
+ const choice = (r.choices ?? [{}])[0] ?? {};
318
+ return {
319
+ text: choice.message?.content ?? "",
320
+ model: r.model,
321
+ finish_reason: choice.finish_reason,
322
+ usage: r.usage,
323
+ cost_cents: r.corent?.cost_cents,
324
+ job_id: r.corent?.job_id,
325
+ };
326
+ }));
327
+ server.registerTool("list_models", {
328
+ title: "List available models",
329
+ description: "The direct-access menu: every model that can be pinned by name on generate_image / generate_video / generate_speech / generate_text, with its kind (image, video, speech, text), quality score and live status. Carries NO price -- do not promise the user a per-model rate from this; billing is flat cost-plus and the exact charge is returned on each generation. Read-only and free. Use this only when the user wants a SPECIFIC model -- otherwise omit `model` and let Corent route to the best one for the prompt.",
330
+ inputSchema: {},
331
+ outputSchema: {
332
+ models: z
333
+ .array(z
334
+ .object({ model: z.string(), kind: z.string(), quality: z.number().optional() })
335
+ .passthrough())
336
+ .optional(),
337
+ count: z.number().optional(),
338
+ error: z.string().optional(),
339
+ },
340
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
341
+ }, wrap(async () => corent("/v1/models")));
242
342
  server.registerTool("get_job", {
243
343
  title: "Check job status",
244
344
  _meta: WIDGET_TOOL_META,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "corent-mcp",
3
- "version": "0.5.2",
4
- "description": "MCP server for the Corent media generation API — give any AI agent the ability to generate images and videos.",
3
+ "version": "0.6.0",
4
+ "description": "MCP server for the Corent API — give any AI agent the ability to generate images, videos, voice, and text.",
5
5
  "mcpName": "io.github.gg13121/corent-mcp",
6
6
  "license": "MIT",
7
7
  "type": "module",
@@ -18,6 +18,7 @@
18
18
  ],
19
19
  "scripts": {
20
20
  "build": "node scripts/build-widget.mjs && tsc && chmod +x dist/index.js",
21
+ "test": "npm run build && node --test test/*.test.js",
21
22
  "start": "node dist/index.js"
22
23
  },
23
24
  "keywords": [
@@ -25,6 +26,8 @@
25
26
  "modelcontextprotocol",
26
27
  "image-generation",
27
28
  "video-generation",
29
+ "text-to-speech",
30
+ "llm",
28
31
  "ai-agents",
29
32
  "corent"
30
33
  ],