@skyelight/mcp 0.5.3 → 0.7.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.
- package/CHANGELOG.md +31 -1
- package/package.json +1 -1
- package/src/brand.js +1 -0
- package/src/client.js +1 -0
- package/src/tools.d.ts +2 -0
- package/src/tools.js +127 -0
- package/src/version.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,7 +11,37 @@ keeps working. A **patch** is a fix or a wording change. A **major** would be
|
|
|
11
11
|
removing a tool or an argument, or changing what an existing field means, and
|
|
12
12
|
there has not been one.
|
|
13
13
|
|
|
14
|
-
## 0.
|
|
14
|
+
## 0.7.0 — unreleased
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **`publish_prototype` is now `publish_review_link`, and takes `code` or
|
|
19
|
+
`url`.** With `code`, as before: Skyelight hosts one file at its own link.
|
|
20
|
+
With `url`, a deployed app (Lovable, v0, Bolt, Vercel) goes on the
|
|
21
|
+
project's Review Links, and the answer carries the script tag to add to
|
|
22
|
+
the app's `<head>`. Code that is part of a larger app is no longer hosted;
|
|
23
|
+
the answer says to send the app's `url`. The server still answers the old
|
|
24
|
+
name, so 0.6.0 keeps working.
|
|
25
|
+
- **`source`:** where a hosted prototype was made (`claude`, `openai`, `v0`,
|
|
26
|
+
`lovable` and others), shown as its logo on the project's Review Links.
|
|
27
|
+
|
|
28
|
+
- **`list_workspaces` says what an agent can't do in a workspace:** a
|
|
29
|
+
reviewer seat, a plan without agent tools, or Free, whose MCP publishes
|
|
30
|
+
prototypes.
|
|
31
|
+
|
|
32
|
+
## 0.6.0 — 2026-10-05
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- **`publish_prototype`.** Hosts a prototype on Skyelight: pass the complete
|
|
37
|
+
code of one file (an HTML page, or one React component with an export
|
|
38
|
+
default) and Skyelight serves it at its own link with the review badge on
|
|
39
|
+
it, in a project of your choosing or a new one. Returns the share link and
|
|
40
|
+
the project link. Publishing again with the same `prototypeId`, or the same
|
|
41
|
+
name in the same project, makes a new version at the same link. Owners,
|
|
42
|
+
admins and collaborators; on a person's own connection.
|
|
43
|
+
|
|
44
|
+
## 0.5.3 — 2026-10-02
|
|
15
45
|
|
|
16
46
|
### Changed
|
|
17
47
|
|
package/package.json
CHANGED
package/src/brand.js
CHANGED
|
@@ -61,4 +61,5 @@ export const STATUS = {
|
|
|
61
61
|
merge_items: ["Merging duplicates", "Merged the duplicates"],
|
|
62
62
|
decision_log: ["Reading the decisions", "Read the decisions"],
|
|
63
63
|
save_rule: ["Saving the rule", "Saved the rule"],
|
|
64
|
+
publish_review_link: ["Publishing to Skyelight", "Published to Skyelight"],
|
|
64
65
|
};
|
package/src/client.js
CHANGED
|
@@ -110,5 +110,6 @@ export function createClient({ apiUrl, token, fetchImpl = fetch }) {
|
|
|
110
110
|
mergeItems: (body) => post("/api/v1/items/merge", body),
|
|
111
111
|
decisionLog: (params) => request("/api/v1/decisions", params),
|
|
112
112
|
saveRule: (body) => post("/api/v1/rules", body),
|
|
113
|
+
publishReviewLink: (body) => post("/api/v1/review-links", body),
|
|
113
114
|
};
|
|
114
115
|
}
|
package/src/tools.d.ts
CHANGED
|
@@ -41,6 +41,8 @@ export declare function renderProjects(result: unknown): string;
|
|
|
41
41
|
export declare function renderMembers(result: unknown): string;
|
|
42
42
|
export declare function renderAssigned(result: unknown): string;
|
|
43
43
|
|
|
44
|
+
export declare function renderPublished(result: unknown): string;
|
|
45
|
+
export declare function renderReviewLink(result: unknown): string;
|
|
44
46
|
export declare function renderPosted(
|
|
45
47
|
result: unknown,
|
|
46
48
|
opts: { kind: "update" | "item" },
|
package/src/tools.js
CHANGED
|
@@ -203,6 +203,67 @@ export function toolDefinitions(opts = {}) {
|
|
|
203
203
|
required: ["url", "body"],
|
|
204
204
|
},
|
|
205
205
|
},
|
|
206
|
+
{
|
|
207
|
+
name: "publish_review_link",
|
|
208
|
+
description:
|
|
209
|
+
"Share something with Skyelight for review, and get a link people can pin feedback on. " +
|
|
210
|
+
"Two ways:\n" +
|
|
211
|
+
"- code: one file Skyelight hosts at its own link with the review badge on it. A whole HTML page, " +
|
|
212
|
+
"or one React component with an export default (Tailwind classes and npm imports work; local " +
|
|
213
|
+
"imports such as @/components do not). For Claude prototypes, v0 snippets and single pages. " +
|
|
214
|
+
"Publishing again with the same prototypeId, or the same name in the same project, makes a new " +
|
|
215
|
+
"version at the same link.\n" +
|
|
216
|
+
"- url: an app already deployed (a Lovable, v0, Bolt or Vercel project: anything with more than " +
|
|
217
|
+
"one file). Adds its address to the project's Review Links and returns a script tag to add to the " +
|
|
218
|
+
"app's <head> (index.html, or the root layout); add it and redeploy. Owners and admins.\n" +
|
|
219
|
+
"Returns the share link and the project link; give both to the person. A hosted link is public " +
|
|
220
|
+
"to anyone who has it.",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
code: {
|
|
225
|
+
type: "string",
|
|
226
|
+
description:
|
|
227
|
+
"For Skyelight to host: the complete file, never a summary or an excerpt. Or give url.",
|
|
228
|
+
},
|
|
229
|
+
url: {
|
|
230
|
+
type: "string",
|
|
231
|
+
description:
|
|
232
|
+
"Where the app is deployed, e.g. https://my-app.lovable.app. Or give code.",
|
|
233
|
+
},
|
|
234
|
+
name: {
|
|
235
|
+
type: "string",
|
|
236
|
+
description:
|
|
237
|
+
"What it is, in a few words. Names the hosted link and, when no project is given, a new project.",
|
|
238
|
+
},
|
|
239
|
+
kind: {
|
|
240
|
+
type: "string",
|
|
241
|
+
enum: ["html", "react"],
|
|
242
|
+
description: "With code; worked out from it when left out.",
|
|
243
|
+
},
|
|
244
|
+
projectId: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description:
|
|
247
|
+
"Optional: the project to put it in. Left out, a new project is made for it.",
|
|
248
|
+
},
|
|
249
|
+
prototypeId: {
|
|
250
|
+
type: "string",
|
|
251
|
+
description:
|
|
252
|
+
"With code: to publish a new version of a prototype published before.",
|
|
253
|
+
},
|
|
254
|
+
workspaceId: {
|
|
255
|
+
type: "string",
|
|
256
|
+
description:
|
|
257
|
+
"When you are in more than one workspace and give no project, from list_workspaces.",
|
|
258
|
+
},
|
|
259
|
+
source: {
|
|
260
|
+
type: "string",
|
|
261
|
+
description:
|
|
262
|
+
'With code: where it was made, shown as its logo in Skyelight: "claude", "claude-code", "openai" (ChatGPT), "cursor", "windsurf", "v0", "lovable" or "other". Name yourself.',
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
206
267
|
{
|
|
207
268
|
name: "list_members",
|
|
208
269
|
description:
|
|
@@ -340,6 +401,9 @@ const TOOL_ANNOTATIONS = {
|
|
|
340
401
|
save_rule: { ...WRITE, idempotentHint: true },
|
|
341
402
|
// Hides items from every list (reversible in the app, but not here).
|
|
342
403
|
merge_items: { ...WRITE, destructiveHint: true },
|
|
404
|
+
// Puts code at a public link, or lets reviewers sign in on another site:
|
|
405
|
+
// either way, something the person should agree to.
|
|
406
|
+
publish_review_link: { ...WRITE, openWorldHint: true },
|
|
343
407
|
};
|
|
344
408
|
|
|
345
409
|
function plural(n, one, many) {
|
|
@@ -385,6 +449,9 @@ export function renderWorkspaces(result) {
|
|
|
385
449
|
const projects = plural(w.projectCount, "project", "projects");
|
|
386
450
|
lines.push(` ${w.name} — ${w.role}, ${projects}`);
|
|
387
451
|
lines.push(` ${w.id}`);
|
|
452
|
+
// What an agent cannot do here, and why: a seat to ask for, a plan to
|
|
453
|
+
// upgrade, or Free's MCP, which publishes prototypes.
|
|
454
|
+
if (w.unavailableReason) lines.push(` ${w.unavailableReason}`);
|
|
388
455
|
}
|
|
389
456
|
return lines.join("\n");
|
|
390
457
|
}
|
|
@@ -749,6 +816,32 @@ export function renderPosted(result, { kind }) {
|
|
|
749
816
|
);
|
|
750
817
|
}
|
|
751
818
|
|
|
819
|
+
export function renderPublished(result) {
|
|
820
|
+
const lines = [
|
|
821
|
+
result.newPrototype
|
|
822
|
+
? `Published "${result.name}" in ${result.projectName}.`
|
|
823
|
+
: `Published version ${result.version}. The link is the same, and shows this version now.`,
|
|
824
|
+
`Share link: ${result.shareUrl}`,
|
|
825
|
+
`Project: ${result.projectUrl}${result.createdProject ? " (new project)" : ""}`,
|
|
826
|
+
`prototypeId ${result.prototypeId}: pass it to publish the next version at the same link.`,
|
|
827
|
+
"Anyone with the share link can open it; people sign in to Skyelight to leave pins.",
|
|
828
|
+
];
|
|
829
|
+
return lines.join("\n");
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
export function renderReviewLink(result) {
|
|
833
|
+
return [
|
|
834
|
+
result.alreadyAdded
|
|
835
|
+
? `${result.origin} was already on ${result.projectName}'s Review Links.`
|
|
836
|
+
: `Added ${result.origin} to ${result.projectName}'s Review Links${result.createdProject ? " (new project)" : ""}.`,
|
|
837
|
+
"Add this to the app's <head> (index.html, or the root layout), then redeploy:",
|
|
838
|
+
result.scriptTag,
|
|
839
|
+
`Share link: ${result.shareUrl}`,
|
|
840
|
+
`Project: ${result.projectUrl}`,
|
|
841
|
+
"Reviewers sign in to Skyelight from the badge to leave pins.",
|
|
842
|
+
].join("\n");
|
|
843
|
+
}
|
|
844
|
+
|
|
752
845
|
export function renderMembers(result) {
|
|
753
846
|
const members = result?.members ?? [];
|
|
754
847
|
const where = result?.workspace?.name ? ` in ${result.workspace.name}` : "";
|
|
@@ -836,6 +929,40 @@ export async function callTool(name, args, { client, config }) {
|
|
|
836
929
|
return { text: renderPosted(result, { kind: "item" }), data: result };
|
|
837
930
|
}
|
|
838
931
|
|
|
932
|
+
if (name === "publish_review_link" || name === "publish_prototype") {
|
|
933
|
+
const hasCode = !!args.code?.trim();
|
|
934
|
+
const hasUrl = !!args.url?.trim();
|
|
935
|
+
if (hasCode === hasUrl) {
|
|
936
|
+
throw new Error(
|
|
937
|
+
"publish_review_link takes code (one file for Skyelight to host) or url (where the app is deployed)",
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
if (hasCode && !args.prototypeId && !args.name?.trim()) {
|
|
941
|
+
throw new Error("publish_review_link needs a name");
|
|
942
|
+
}
|
|
943
|
+
const pick = (keys) =>
|
|
944
|
+
Object.fromEntries(keys.filter((k) => args[k]).map((k) => [k, args[k]]));
|
|
945
|
+
const result = await client.publishReviewLink(
|
|
946
|
+
hasUrl
|
|
947
|
+
? pick(["url", "projectId", "name", "workspaceId"])
|
|
948
|
+
: {
|
|
949
|
+
name: args.name ?? "",
|
|
950
|
+
...pick([
|
|
951
|
+
"code",
|
|
952
|
+
"kind",
|
|
953
|
+
"projectId",
|
|
954
|
+
"prototypeId",
|
|
955
|
+
"workspaceId",
|
|
956
|
+
"source",
|
|
957
|
+
]),
|
|
958
|
+
},
|
|
959
|
+
);
|
|
960
|
+
return {
|
|
961
|
+
text: result?.linked ? renderReviewLink(result) : renderPublished(result),
|
|
962
|
+
data: result,
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
839
966
|
if (name === "list_members") {
|
|
840
967
|
const result = await client.listMembers({
|
|
841
968
|
projectId,
|
package/src/version.js
CHANGED