corent-mcp 0.3.0 → 0.4.1
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/LICENSE +21 -0
- package/README.md +11 -2
- package/dist/brand.js +10 -0
- package/dist/http.js +77 -0
- package/dist/oauth.js +678 -0
- package/dist/server.js +68 -8
- package/dist/widget-html.js +2 -0
- package/package.json +10 -6
package/dist/server.js
CHANGED
|
@@ -15,8 +15,26 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
17
|
import { z } from "zod";
|
|
18
|
+
import { MEDIA_WIDGET_HTML } from "./widget-html.js";
|
|
18
19
|
export const DEFAULT_API_URL = "https://api.corent.tech";
|
|
19
|
-
export const SERVER_VERSION = "0.
|
|
20
|
+
export const SERVER_VERSION = "0.4.0";
|
|
21
|
+
// --- MCP Apps (SEP-1865): the media tools render an inline widget in hosts
|
|
22
|
+
// that support it (claude.ai web/desktop, others). Hosts without the extension
|
|
23
|
+
// ignore the _meta and fall back to plain text results, so this is additive.
|
|
24
|
+
const WIDGET_URI = "ui://corent/media";
|
|
25
|
+
const WIDGET_MIME = "text/html;profile=mcp-app";
|
|
26
|
+
// Both the canonical nested key and the legacy flat key — the spec tells hosts
|
|
27
|
+
// to check both, and older host builds only know the flat one.
|
|
28
|
+
const WIDGET_TOOL_META = {
|
|
29
|
+
ui: { resourceUri: WIDGET_URI },
|
|
30
|
+
"ui/resourceUri": WIDGET_URI,
|
|
31
|
+
};
|
|
32
|
+
// Generated media lives on Supabase storage; the logo and any brand assets on
|
|
33
|
+
// corent.tech. Everything else stays blocked by the host's CSP.
|
|
34
|
+
const WIDGET_CSP = {
|
|
35
|
+
connectDomains: [],
|
|
36
|
+
resourceDomains: ["https://hzokyylcctfybulkgmcb.supabase.co", "https://corent.tech"],
|
|
37
|
+
};
|
|
20
38
|
class CorentApiError extends Error {
|
|
21
39
|
status;
|
|
22
40
|
detail;
|
|
@@ -33,6 +51,8 @@ function errorCode(err) {
|
|
|
33
51
|
return "invalid_api_key";
|
|
34
52
|
if (err.status === 402)
|
|
35
53
|
return "insufficient_balance";
|
|
54
|
+
if (err.status === 404)
|
|
55
|
+
return "not_found";
|
|
36
56
|
if (err.status === 422 && detail.includes("content"))
|
|
37
57
|
return "prompt_not_allowed";
|
|
38
58
|
if (err.status === 422)
|
|
@@ -60,7 +80,7 @@ export function createCorentServer(config = {}) {
|
|
|
60
80
|
// can advertise its tools to catalogs/agents without a key configured.
|
|
61
81
|
if (!apiKey) {
|
|
62
82
|
throw new CorentApiError(401, {
|
|
63
|
-
message: "
|
|
83
|
+
message: "No Corent credentials. On claude.ai or other remote MCP clients, reconnect the Corent connector and approve access (OAuth). For local/stdio use, set CORENT_API_KEY in your MCP client config. Keys: https://corent.tech/dashboard/api-keys",
|
|
64
84
|
});
|
|
65
85
|
}
|
|
66
86
|
const res = await fetch(`${apiUrl}${path}`, {
|
|
@@ -104,10 +124,38 @@ export function createCorentServer(config = {}) {
|
|
|
104
124
|
}
|
|
105
125
|
};
|
|
106
126
|
}
|
|
107
|
-
|
|
127
|
+
// icons/websiteUrl: spec-standard server identity — MCP clients that support
|
|
128
|
+
// it (claude.ai included) show the square Corent mark instead of a letter tile.
|
|
129
|
+
const server = new McpServer({
|
|
130
|
+
name: "corent",
|
|
131
|
+
title: "Corent",
|
|
132
|
+
version: SERVER_VERSION,
|
|
133
|
+
icons: [{ src: "https://corent.tech/brand/intro-mark.png", mimeType: "image/png", sizes: ["512x512"] }],
|
|
134
|
+
websiteUrl: "https://corent.tech",
|
|
135
|
+
});
|
|
136
|
+
server.registerResource("corent-media-widget", WIDGET_URI, {
|
|
137
|
+
title: "Corent media widget",
|
|
138
|
+
description: "Inline card that tracks a generation and plays the result.",
|
|
139
|
+
mimeType: WIDGET_MIME,
|
|
140
|
+
_meta: { ui: { csp: WIDGET_CSP } },
|
|
141
|
+
}, async () => ({
|
|
142
|
+
contents: [
|
|
143
|
+
{
|
|
144
|
+
uri: WIDGET_URI,
|
|
145
|
+
mimeType: WIDGET_MIME,
|
|
146
|
+
text: MEDIA_WIDGET_HTML,
|
|
147
|
+
_meta: { ui: { csp: WIDGET_CSP } },
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
}));
|
|
108
151
|
// Loose output shapes: every field optional so an additive API change can
|
|
109
152
|
// never break a client's schema validation.
|
|
110
|
-
|
|
153
|
+
// model is null while a video job is still processing (no provider chosen
|
|
154
|
+
// yet) -- must be nullable, not just optional, or the SDK's output validator
|
|
155
|
+
// throws on the normal poll-loop response.
|
|
156
|
+
const mediaMeta = z
|
|
157
|
+
.object({ model: z.string().nullable().optional(), cost_cents: z.number().nullable().optional() })
|
|
158
|
+
.passthrough();
|
|
111
159
|
const imageOutput = {
|
|
112
160
|
id: z.string().optional(),
|
|
113
161
|
status: z.string().optional(),
|
|
@@ -124,9 +172,14 @@ export function createCorentServer(config = {}) {
|
|
|
124
172
|
};
|
|
125
173
|
server.registerTool("generate_image", {
|
|
126
174
|
title: "Generate image",
|
|
175
|
+
_meta: WIDGET_TOOL_META,
|
|
127
176
|
description: "Generate an image from a text prompt. Returns a permanent public URL. Synchronous: typically completes in 2-20 seconds. Costs a few cents, billed to the Corent account. Failed generations are never billed.",
|
|
128
177
|
inputSchema: {
|
|
129
178
|
prompt: z.string().describe("What to generate, in plain language"),
|
|
179
|
+
tier: z
|
|
180
|
+
.enum(["air", "lite", "premium", "pro", "max_pro"])
|
|
181
|
+
.optional()
|
|
182
|
+
.describe("Quality tier (preferred): air=cheapest/fastest, lite=everyday, premium=production, pro=advanced, max_pro=maximum fidelity (flagship models). Overrides preference."),
|
|
130
183
|
preference: z
|
|
131
184
|
.enum(["fast", "cheap", "quality", "balanced"])
|
|
132
185
|
.default("balanced")
|
|
@@ -139,15 +192,20 @@ export function createCorentServer(config = {}) {
|
|
|
139
192
|
},
|
|
140
193
|
outputSchema: imageOutput,
|
|
141
194
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
142
|
-
}, wrap(async ({ prompt, preference, style, aspect_ratio }) => corent("/v1/images/generate", {
|
|
195
|
+
}, wrap(async ({ prompt, tier, preference, style, aspect_ratio }) => corent("/v1/images/generate", {
|
|
143
196
|
method: "POST",
|
|
144
|
-
body: JSON.stringify({ prompt, preference, style, aspect_ratio }),
|
|
197
|
+
body: JSON.stringify({ prompt, tier, preference, style, aspect_ratio }),
|
|
145
198
|
})));
|
|
146
199
|
server.registerTool("generate_video", {
|
|
147
200
|
title: "Generate video",
|
|
201
|
+
_meta: WIDGET_TOOL_META,
|
|
148
202
|
description: "Start generating a video from a text prompt (optionally animating a source image). Asynchronous: returns a job id immediately; poll get_job until status is 'completed'. Video costs more than images (tens of cents to a few dollars depending on tier). Failed generations are never billed.",
|
|
149
203
|
inputSchema: {
|
|
150
204
|
prompt: z.string().describe("What to generate, in plain language"),
|
|
205
|
+
tier: z
|
|
206
|
+
.enum(["air", "lite", "premium", "pro", "max_pro"])
|
|
207
|
+
.optional()
|
|
208
|
+
.describe("Quality tier (preferred): air=cheapest/fastest, lite=everyday, premium=production, pro=advanced, max_pro=maximum fidelity (flagship models). Overrides preference."),
|
|
151
209
|
preference: z.enum(["fast", "cheap", "quality", "balanced"]).default("balanced"),
|
|
152
210
|
aspect_ratio: z.enum(["16:9", "9:16", "1:1"]).default("16:9"),
|
|
153
211
|
duration_s: z.number().int().min(1).max(30).optional().describe("Requested duration in seconds"),
|
|
@@ -155,12 +213,13 @@ export function createCorentServer(config = {}) {
|
|
|
155
213
|
},
|
|
156
214
|
outputSchema: jobOutput,
|
|
157
215
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
158
|
-
}, wrap(async ({ prompt, preference, aspect_ratio, duration_s, image_url }) => corent("/v1/videos/generate", {
|
|
216
|
+
}, wrap(async ({ prompt, tier, preference, aspect_ratio, duration_s, image_url }) => corent("/v1/videos/generate", {
|
|
159
217
|
method: "POST",
|
|
160
|
-
body: JSON.stringify({ prompt, preference, aspect_ratio, duration_s, image_url }),
|
|
218
|
+
body: JSON.stringify({ prompt, tier, preference, aspect_ratio, duration_s, image_url }),
|
|
161
219
|
})));
|
|
162
220
|
server.registerTool("get_job", {
|
|
163
221
|
title: "Check job status",
|
|
222
|
+
_meta: WIDGET_TOOL_META,
|
|
164
223
|
description: "Check the status of a generation job (mainly videos). When completed, the response includes the permanent media URL and the cost. Read-only and free.",
|
|
165
224
|
inputSchema: { job_id: z.string().describe("The job id returned by generate_video or generate_image") },
|
|
166
225
|
outputSchema: jobOutput,
|
|
@@ -199,6 +258,7 @@ export function createCorentServer(config = {}) {
|
|
|
199
258
|
}, wrap(async ({ intent }) => corent("/v1/intent", { method: "POST", body: JSON.stringify({ intent }) })));
|
|
200
259
|
server.registerTool("create", {
|
|
201
260
|
title: "Create (plan + generate, budget-capped)",
|
|
261
|
+
_meta: WIDGET_TOOL_META,
|
|
202
262
|
description: "Describe what you want in plain language and Corent plans AND generates it — choosing image vs video, tier, aspect ratio, and duration for you (the 'zero decisions' path). You MUST pass max_cost_cents as a spend ceiling; if the estimated cost exceeds it, nothing is generated and you're told the estimate so you can raise the ceiling. Image results come back with a URL; video results come back as a job id to poll with get_job. Failed generations are never billed.",
|
|
203
263
|
inputSchema: {
|
|
204
264
|
intent: z.string().describe("What you want, in natural language"),
|