web-comments 1.0.1 → 1.0.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 -0
- package/dist/chunks/{chunk-RRSB326L.js → chunk-HN5V6BS6.js} +42 -9
- package/dist/connectors/linear.js +367 -87
- package/dist/connectors/mcp.js +87 -4
- package/dist/index.js +19 -19
- package/dist/server.js +1 -1
- package/dist/types/connectors/linear/icon.d.ts +1 -0
- package/dist/types/connectors/linear/index.d.ts +7 -4
- package/dist/types/connectors/mcp/icon.d.ts +1 -0
- package/dist/types/connectors/mcp/oauth.d.ts +13 -0
- package/dist/types/server/connector.d.ts +19 -1
- package/dist/types/server/service.d.ts +2 -0
- package/dist/types/shared/types.d.ts +55 -0
- package/dist/types/widget/api.d.ts +10 -1
- package/dist/types/widget/connectors.d.ts +13 -0
- package/dist/widget.global.js +19 -19
- package/package.json +1 -1
package/dist/connectors/mcp.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CommentsError
|
|
3
|
-
} from "../chunks/chunk-
|
|
3
|
+
} from "../chunks/chunk-HN5V6BS6.js";
|
|
4
4
|
import "../chunks/chunk-WSFCRVEQ.js";
|
|
5
5
|
|
|
6
|
+
// src/connectors/mcp/icon.ts
|
|
7
|
+
var icon = "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIxLjIxIDAgMjEuNTggMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEzLjg1IDBhNC4xNiA0LjE2IDAgMCAwLTIuOTUgMS4yMTdMMS40NTYgMTAuNjZhLjgzNS44MzUgMCAwIDAgMCAxLjE4LjgzNS44MzUgMCAwIDAgMS4xOCAwbDkuNDQyLTkuNDQyYTIuNDkgMi40OSAwIDAgMSAzLjU0MSAwIDIuNDkgMi40OSAwIDAgMSAwIDMuNTQxTDguNTkgMTIuOTdsLS4xLjFhLjgzNS44MzUgMCAwIDAgMCAxLjE4LjgzNS44MzUgMCAwIDAgMS4xOCAwbC4xLS4wOTggNy4wMy03LjAzNGEyLjQ5IDIuNDkgMCAwIDEgMy41NDIgMGwuMDQ5LjA1YTIuNDkgMi40OSAwIDAgMSAwIDMuNTRsLTguNTQgOC41NGExLjk2IDEuOTYgMCAwIDAgMCAyLjc1NWwxLjc1MyAxLjc1M2EuODM1LjgzNSAwIDAgMCAxLjE4IDAgLjgzNS44MzUgMCAwIDAgMC0xLjE4bC0xLjc1My0xLjc1M2EuMjY2LjI2NiAwIDAgMSAwLS4zOTRsOC41NC04LjU0YTQuMTg1IDQuMTg1IDAgMCAwIDAtNS45bC0uMDUtLjA1YTQuMTYgNC4xNiAwIDAgMC0yLjk1LTEuMjE4Yy0uMiAwLS40MDEuMDItLjYuMDQ4YTQuMTcgNC4xNyAwIDAgMC0xLjE3LTMuNTUyQTQuMTYgNC4xNiAwIDAgMCAxMy44NSAwbTAgMy4zMzNhLjg0Ljg0IDAgMCAwLS41OS4yNDVMNi4yNzUgMTAuNTZhNC4xODYgNC4xODYgMCAwIDAgMCA1LjkwMiA0LjE4NiA0LjE4NiAwIDAgMCA1LjkwMiAwTDE5LjE2IDkuNDhhLjgzNS44MzUgMCAwIDAgMC0xLjE4LjgzNS44MzUgMCAwIDAtMS4xOCAwbC02Ljk4NSA2Ljk4NGEyLjQ5IDIuNDkgMCAwIDEtMy41NCAwIDIuNDkgMi40OSAwIDAgMSAwLTMuNTRsNi45ODMtNi45ODVhLjgzNS44MzUgMCAwIDAgMC0xLjE4Ljg0Ljg0IDAgMCAwLS41OS0uMjQ1Ii8+PC9zdmc+";
|
|
8
|
+
|
|
6
9
|
// src/connectors/mcp/pages.ts
|
|
7
10
|
function escapeHtml(value) {
|
|
8
11
|
return value.replace(
|
|
@@ -151,10 +154,35 @@ async function readJson(storage, key) {
|
|
|
151
154
|
async function grantOf(storage, token2) {
|
|
152
155
|
if (!token2.startsWith(TOKEN_PREFIX.access)) return null;
|
|
153
156
|
const record = await readJson(storage, `token:${await sha256(token2)}`);
|
|
154
|
-
if (record?.kind !== "access") return null;
|
|
157
|
+
if (record?.kind !== "access" || !await live(storage, record.grantId)) return null;
|
|
155
158
|
const { kind: _, ...grant } = record;
|
|
156
159
|
return grant;
|
|
157
160
|
}
|
|
161
|
+
async function live(storage, grantId) {
|
|
162
|
+
return await storage.get(`grant:${grantId}`) !== null;
|
|
163
|
+
}
|
|
164
|
+
async function grantIds(storage, identityId) {
|
|
165
|
+
return await readJson(storage, `grants:${identityId}`) ?? [];
|
|
166
|
+
}
|
|
167
|
+
async function addGrant(storage, identityId, grantId, record) {
|
|
168
|
+
await storage.set(`grant:${grantId}`, JSON.stringify(record));
|
|
169
|
+
const ids = await grantIds(storage, identityId);
|
|
170
|
+
await storage.set(`grants:${identityId}`, JSON.stringify([...ids, grantId]));
|
|
171
|
+
}
|
|
172
|
+
async function grantsOf(storage, identityId) {
|
|
173
|
+
const found = [];
|
|
174
|
+
for (const grantId of await grantIds(storage, identityId)) {
|
|
175
|
+
const record = await readJson(storage, `grant:${grantId}`);
|
|
176
|
+
if (record) found.push({ grantId, ...record });
|
|
177
|
+
}
|
|
178
|
+
return found.reverse();
|
|
179
|
+
}
|
|
180
|
+
async function revokeGrant(storage, identityId, grantId) {
|
|
181
|
+
const ids = await grantIds(storage, identityId);
|
|
182
|
+
if (!ids.includes(grantId)) return;
|
|
183
|
+
await storage.delete(`grant:${grantId}`);
|
|
184
|
+
await storage.set(`grants:${identityId}`, JSON.stringify(ids.filter((id) => id !== grantId)));
|
|
185
|
+
}
|
|
158
186
|
function looksLikeOurToken(token2) {
|
|
159
187
|
return token2.startsWith(TOKEN_PREFIX.access) || token2.startsWith(TOKEN_PREFIX.refresh);
|
|
160
188
|
}
|
|
@@ -357,6 +385,17 @@ async function authorize(request, context, options) {
|
|
|
357
385
|
if (resource !== at.resource) return fail("invalid_target", `the resource is ${at.resource}`);
|
|
358
386
|
const who = await context.identify(request);
|
|
359
387
|
if (!who) {
|
|
388
|
+
const someone = await context.signedIn(request);
|
|
389
|
+
if (someone) {
|
|
390
|
+
return page(
|
|
391
|
+
messagePage(
|
|
392
|
+
options.name,
|
|
393
|
+
"Not allowed",
|
|
394
|
+
`${someone.email ?? someone.name} may not use comments on ${url.host}.`
|
|
395
|
+
),
|
|
396
|
+
403
|
|
397
|
+
);
|
|
398
|
+
}
|
|
360
399
|
const login = context.loginUrl(request);
|
|
361
400
|
if (login) return Response.redirect(new URL(login, request.url).href, 302);
|
|
362
401
|
return page(
|
|
@@ -436,7 +475,14 @@ async function decide(request, context, options) {
|
|
|
436
475
|
);
|
|
437
476
|
}
|
|
438
477
|
const code = randomToken();
|
|
478
|
+
const grantId = randomToken(12);
|
|
479
|
+
await addGrant(context.storage, who.id, grantId, {
|
|
480
|
+
clientId: consent.clientId,
|
|
481
|
+
clientName: clientName(client),
|
|
482
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
483
|
+
});
|
|
439
484
|
const grant = {
|
|
485
|
+
grantId,
|
|
440
486
|
identity: who,
|
|
441
487
|
clientId: consent.clientId,
|
|
442
488
|
clientName: clientName(client),
|
|
@@ -496,7 +542,7 @@ async function token(request, context, options) {
|
|
|
496
542
|
const refresh = params.get("refresh_token") ?? "";
|
|
497
543
|
const key = `token:${await sha256(refresh)}`;
|
|
498
544
|
const record = refresh.startsWith(TOKEN_PREFIX.refresh) ? await readJson(storage, key) : null;
|
|
499
|
-
if (record?.kind !== "refresh" || record.clientId !== clientId || record.site !== context.site) {
|
|
545
|
+
if (record?.kind !== "refresh" || record.clientId !== clientId || record.site !== context.site || !await live(storage, record.grantId)) {
|
|
500
546
|
return oauthError("invalid_grant", "the refresh token is unknown, used or expired");
|
|
501
547
|
}
|
|
502
548
|
await storage.delete(key);
|
|
@@ -545,7 +591,7 @@ async function handleOAuth(request, path, context, options) {
|
|
|
545
591
|
}
|
|
546
592
|
|
|
547
593
|
// src/connectors/mcp/index.ts
|
|
548
|
-
var VERSION = "1.0.
|
|
594
|
+
var VERSION = "1.0.2";
|
|
549
595
|
var PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
|
|
550
596
|
function mcp(options = {}) {
|
|
551
597
|
const oauth = {
|
|
@@ -571,6 +617,42 @@ function mcp(options = {}) {
|
|
|
571
617
|
}
|
|
572
618
|
if (path === "/") return serve(request, context, oauth);
|
|
573
619
|
return await handleOAuth(request, path, context, oauth) ?? Response.json({ error: "not_found" }, { status: 404, headers: CORS });
|
|
620
|
+
},
|
|
621
|
+
// The toolbar's MCP panel: how to connect an agent, and the ones connected.
|
|
622
|
+
async panel(who, context) {
|
|
623
|
+
const url = `${context.origin}${context.basePath}/mcp`;
|
|
624
|
+
const grants = await grantsOf(context.storage, who.id);
|
|
625
|
+
return {
|
|
626
|
+
title: "MCP",
|
|
627
|
+
icon,
|
|
628
|
+
blocks: [
|
|
629
|
+
{
|
|
630
|
+
type: "text",
|
|
631
|
+
text: "Let an agent (Claude Code, Cursor, \u2026) read the comments here with their screenshots, reply, and resolve them, as you. You approve it in the browser the first time."
|
|
632
|
+
},
|
|
633
|
+
{ type: "code", label: "Server URL", value: url },
|
|
634
|
+
{
|
|
635
|
+
type: "code",
|
|
636
|
+
label: "Claude Code",
|
|
637
|
+
value: `claude mcp add --transport http comments ${url}`
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
type: "list",
|
|
641
|
+
label: "Your agents",
|
|
642
|
+
empty: "No agent connected yet.",
|
|
643
|
+
items: grants.map((grant) => ({
|
|
644
|
+
title: grant.clientName,
|
|
645
|
+
description: `Connected ${new Date(grant.createdAt).toLocaleDateString("en", { month: "short", day: "numeric", year: "numeric" })}`,
|
|
646
|
+
button: { label: "Disconnect", action: "revoke", input: { grant: grant.grantId } }
|
|
647
|
+
}))
|
|
648
|
+
}
|
|
649
|
+
]
|
|
650
|
+
};
|
|
651
|
+
},
|
|
652
|
+
async action(action, who, input, context) {
|
|
653
|
+
if (action === "revoke" && typeof input.grant === "string") {
|
|
654
|
+
await revokeGrant(context.storage, who.id, input.grant);
|
|
655
|
+
}
|
|
574
656
|
}
|
|
575
657
|
};
|
|
576
658
|
}
|
|
@@ -624,6 +706,7 @@ async function authenticate(request, context) {
|
|
|
624
706
|
if (!grant || grant.site !== context.site || grant.resource !== endpoints(context.url).resource) {
|
|
625
707
|
return null;
|
|
626
708
|
}
|
|
709
|
+
if (!await context.allowed(grant.identity, request)) return null;
|
|
627
710
|
return {
|
|
628
711
|
...grant.identity,
|
|
629
712
|
name: `${grant.identity.name} (${grant.clientName})`,
|