corent-mcp 0.3.1 → 0.4.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 +11 -2
- package/dist/brand.js +10 -0
- package/dist/http.js +77 -0
- package/dist/oauth.js +678 -0
- package/dist/server.js +64 -7
- package/dist/widget-html.js +2 -0
- package/package.json +8 -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;
|
|
@@ -62,7 +80,7 @@ export function createCorentServer(config = {}) {
|
|
|
62
80
|
// can advertise its tools to catalogs/agents without a key configured.
|
|
63
81
|
if (!apiKey) {
|
|
64
82
|
throw new CorentApiError(401, {
|
|
65
|
-
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",
|
|
66
84
|
});
|
|
67
85
|
}
|
|
68
86
|
const res = await fetch(`${apiUrl}${path}`, {
|
|
@@ -106,7 +124,30 @@ export function createCorentServer(config = {}) {
|
|
|
106
124
|
}
|
|
107
125
|
};
|
|
108
126
|
}
|
|
109
|
-
|
|
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
|
+
}));
|
|
110
151
|
// Loose output shapes: every field optional so an additive API change can
|
|
111
152
|
// never break a client's schema validation.
|
|
112
153
|
// model is null while a video job is still processing (no provider chosen
|
|
@@ -131,9 +172,14 @@ export function createCorentServer(config = {}) {
|
|
|
131
172
|
};
|
|
132
173
|
server.registerTool("generate_image", {
|
|
133
174
|
title: "Generate image",
|
|
175
|
+
_meta: WIDGET_TOOL_META,
|
|
134
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.",
|
|
135
177
|
inputSchema: {
|
|
136
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."),
|
|
137
183
|
preference: z
|
|
138
184
|
.enum(["fast", "cheap", "quality", "balanced"])
|
|
139
185
|
.default("balanced")
|
|
@@ -146,28 +192,38 @@ export function createCorentServer(config = {}) {
|
|
|
146
192
|
},
|
|
147
193
|
outputSchema: imageOutput,
|
|
148
194
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
149
|
-
}, wrap(async ({ prompt, preference, style, aspect_ratio }) => corent("/v1/images/generate", {
|
|
195
|
+
}, wrap(async ({ prompt, tier, preference, style, aspect_ratio }) => corent("/v1/images/generate", {
|
|
150
196
|
method: "POST",
|
|
151
|
-
body: JSON.stringify({ prompt, preference, style, aspect_ratio }),
|
|
197
|
+
body: JSON.stringify({ prompt, tier, preference, style, aspect_ratio }),
|
|
152
198
|
})));
|
|
153
199
|
server.registerTool("generate_video", {
|
|
154
200
|
title: "Generate video",
|
|
201
|
+
_meta: WIDGET_TOOL_META,
|
|
155
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.",
|
|
156
203
|
inputSchema: {
|
|
157
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."),
|
|
158
209
|
preference: z.enum(["fast", "cheap", "quality", "balanced"]).default("balanced"),
|
|
159
210
|
aspect_ratio: z.enum(["16:9", "9:16", "1:1"]).default("16:9"),
|
|
160
211
|
duration_s: z.number().int().min(1).max(30).optional().describe("Requested duration in seconds"),
|
|
212
|
+
resolution: z
|
|
213
|
+
.enum(["720p", "1080p", "4k"])
|
|
214
|
+
.optional()
|
|
215
|
+
.describe("Pixel resolution (default 720p). Tier-capped: premium/pro allow 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."),
|
|
161
216
|
image_url: z.string().url().optional().describe("If set, animates this image instead of pure text-to-video"),
|
|
162
217
|
},
|
|
163
218
|
outputSchema: jobOutput,
|
|
164
219
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
165
|
-
}, wrap(async ({ prompt, preference, aspect_ratio, duration_s, image_url }) => corent("/v1/videos/generate", {
|
|
220
|
+
}, wrap(async ({ prompt, tier, preference, aspect_ratio, duration_s, resolution, image_url }) => corent("/v1/videos/generate", {
|
|
166
221
|
method: "POST",
|
|
167
|
-
body: JSON.stringify({ prompt, preference, aspect_ratio, duration_s, image_url }),
|
|
222
|
+
body: JSON.stringify({ prompt, tier, preference, aspect_ratio, duration_s, resolution, image_url }),
|
|
168
223
|
})));
|
|
169
224
|
server.registerTool("get_job", {
|
|
170
225
|
title: "Check job status",
|
|
226
|
+
_meta: WIDGET_TOOL_META,
|
|
171
227
|
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.",
|
|
172
228
|
inputSchema: { job_id: z.string().describe("The job id returned by generate_video or generate_image") },
|
|
173
229
|
outputSchema: jobOutput,
|
|
@@ -206,6 +262,7 @@ export function createCorentServer(config = {}) {
|
|
|
206
262
|
}, wrap(async ({ intent }) => corent("/v1/intent", { method: "POST", body: JSON.stringify({ intent }) })));
|
|
207
263
|
server.registerTool("create", {
|
|
208
264
|
title: "Create (plan + generate, budget-capped)",
|
|
265
|
+
_meta: WIDGET_TOOL_META,
|
|
209
266
|
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.",
|
|
210
267
|
inputSchema: {
|
|
211
268
|
intent: z.string().describe("What you want, in natural language"),
|