@snagset/mcp 0.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.
@@ -0,0 +1,256 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // GENERATED FILE — DO NOT EDIT BY HAND.
3
+ // Source of truth: packages/types/registry.json
4
+ // Regenerate: pnpm gen:names (CI runs `pnpm gen:names:check`)
5
+ //
6
+ // The Name Registry (specs/00-foundations.md §2) made executable. Every token
7
+ // below is DERIVED from registry.json's `brand` block — the product name lives
8
+ // in exactly one place. test/rename.test.ts proves it: flip the brand, and no
9
+ // token of the old one survives. SCHEMA_VERSION is the one deliberate literal.
10
+ // ─────────────────────────────────────────────────────────────────────────────
11
+ /** The one place the product name exists. Everything else derives from this. */
12
+ export const BRAND = {
13
+ $comment: "The ONLY place the product name appears. `long` = display/global name. `short` = the terse prefix that goes on attributes, classes, params, storage keys and artefact filenames (a client's DOM should not carry a long word). `pkg` = the npm/lowercase form used for env vars, headers, metrics, DOM events and symbols.",
14
+ long: "Snagset",
15
+ short: "snag",
16
+ pkg: "snagset",
17
+ };
18
+ export const PRODUCT = "Snagset";
19
+ export const NPM_PACKAGE = "snagset";
20
+ export const NPM_SCOPE = "@snagset";
21
+ /** Frozen once v1 stores real data — stamped into exports, issue bodies and the MCP contract, so it must survive a rename (registry.frozen). */
22
+ export const SCHEMA_VERSION = "snagset.v1";
23
+ export const CSS_VAR_PREFIX = "--snag-";
24
+ export const HTTP_HEADER_PREFIX = "x-snagset-";
25
+ export const ENV_PREFIX = "SNAGSET_";
26
+ export const PG_TYPE_PREFIX = "snag_";
27
+ export const METRIC_PREFIX = "snagset_";
28
+ export const DOM_EVENT_PREFIX = "snagset:";
29
+ export const SYMBOL_PREFIX = "snagset.";
30
+ export const MENTION_SCHEME = "snag:user/";
31
+ export const SKIP_HEADER = "x-snagset-skip";
32
+ export const THREAD_REF_PREFIX = "SN-";
33
+ export const TERM_SET = "Snagset";
34
+ export const TERM_SET_PLURAL = "Snagsets";
35
+ export const TERM_ITEM = "Snag";
36
+ export const TERM_ITEM_PLURAL = "Snags";
37
+ export const RESERVED_CUSTOM_PREFIX = "cr.";
38
+ /** @returns e.g. `x-snagset-idempotency-key` */
39
+ export const header = (name) => HTTP_HEADER_PREFIX + name;
40
+ /** @returns e.g. `SNAGSET_SESSION_TTL_S` */
41
+ export const env = (name) => ENV_PREFIX + name;
42
+ /** @returns e.g. `snag_attachment_kind` */
43
+ export const pgType = (name) => PG_TYPE_PREFIX + name;
44
+ /** @returns e.g. `snagset_http_requests_total` */
45
+ export const metric = (name) => METRIC_PREFIX + name;
46
+ /** window.* names (§2.2). Dunders are internal/test affordances, not public API. */
47
+ export const GLOBALS = {
48
+ api: "Snagset",
49
+ config: "SnagsetConfig",
50
+ disabled: "__SNAGSET_DISABLED__",
51
+ assetBase: "__SNAGSET_ASSET_BASE__",
52
+ collisionFallback: "__snagset__",
53
+ };
54
+ const scriptAttrToken = {
55
+ site: "data-snag-site",
56
+ host: "data-snag-host",
57
+ disabled: "data-snag-disabled",
58
+ env: "data-snag-env",
59
+ mode: "data-snag-mode",
60
+ badge: "data-snag-badge",
61
+ lang: "data-snag-lang",
62
+ theme: "data-snag-theme",
63
+ position: "data-snag-position",
64
+ offset: "data-snag-offset",
65
+ assetBase: "data-snag-asset-base",
66
+ branch: "data-snag-branch",
67
+ build: "data-snag-build",
68
+ storage: "data-snag-storage",
69
+ hotkey: "data-snag-hotkey",
70
+ allowAutomation: "data-snag-allow-automation",
71
+ screenshots: "data-snag-screenshots",
72
+ maskSelectors: "data-snag-mask-selectors",
73
+ nonce: "data-snag-nonce",
74
+ autoMount: "data-snag-auto-mount",
75
+ };
76
+ export const scriptAttr = (name) => scriptAttrToken[name];
77
+ const widgetAttrToken = {
78
+ root: "data-snag-root",
79
+ };
80
+ export const widgetAttr = (name) => widgetAttrToken[name];
81
+ const hostAttrToken = {
82
+ anchor: "data-snag-anchor",
83
+ ignore: "data-snag-ignore",
84
+ mask: "data-snag-mask",
85
+ nomask: "data-snag-nomask",
86
+ src: "data-snag-src",
87
+ component: "data-snag-component",
88
+ };
89
+ export const hostAttr = (name) => hostAttrToken[name];
90
+ const cssClassToken = {
91
+ mask: "snag-mask",
92
+ block: "snag-block",
93
+ };
94
+ export const cssClass = (name) => cssClassToken[name];
95
+ /** Custom properties (§2.1's prefix, enumerated). 06 §14 makes every one of these public SemVer surface, so they are registered rather than minted at the point of use. Values are NOT here — the registry governs names, never design. */
96
+ export const CSS_VAR = {
97
+ font: "--snag-font",
98
+ accent: "--snag-accent",
99
+ onAccent: "--snag-onAccent",
100
+ bg: "--snag-bg",
101
+ fg: "--snag-fg",
102
+ muted: "--snag-muted",
103
+ border: "--snag-border",
104
+ radius: "--snag-radius",
105
+ shadow: "--snag-shadow",
106
+ z: "--snag-z",
107
+ motion: "--snag-motion",
108
+ surface: "--snag-surface",
109
+ sunken: "--snag-sunken",
110
+ hover: "--snag-hover",
111
+ focus: "--snag-focus",
112
+ disabled: "--snag-disabled",
113
+ statusOpen: "--snag-status-open",
114
+ statusProgress: "--snag-status-progress",
115
+ statusReview: "--snag-status-review",
116
+ statusResolved: "--snag-status-resolved",
117
+ anchorOrphan: "--snag-anchor-orphan",
118
+ tbH: "--snag-tb-h",
119
+ dockW: "--snag-dock-w",
120
+ kb: "--snag-kb",
121
+ thumbX: "--snag-thumb-x",
122
+ thumbW: "--snag-thumb-w",
123
+ };
124
+ /** Which tokens belong to which role — the dark block must override theme members only. */
125
+ export const CSS_VAR_GROUPS = {
126
+ theme: ["font", "accent", "onAccent", "bg", "fg", "muted", "border", "radius", "shadow", "z", "motion"],
127
+ derived: ["surface", "sunken", "hover", "focus", "disabled"],
128
+ status: ["statusOpen", "statusProgress", "statusReview", "statusResolved"],
129
+ anchor: ["anchorOrphan"],
130
+ geometry: ["tbH", "dockW", "kb", "thumbX", "thumbW"],
131
+ };
132
+ /** @returns e.g. `--snag-bg` */
133
+ export const cssVar = (name) => CSS_VAR[name];
134
+ /** @returns e.g. `var(--snag-bg)` — what stylesheets actually interpolate. */
135
+ export const cssRef = (name) => `var(${CSS_VAR[name]})`;
136
+ /** @returns e.g. `snag_t`. The review-link secret is a FRAGMENT param (§2.5). */
137
+ export const param = (name) => "snag_" + name;
138
+ export const RESERVED_URL_PARAMS = {
139
+ query: ["snag_t", "snag_off", "snag_debug", "snag_theme"],
140
+ fragment: ["snag_k"],
141
+ };
142
+ /** Point consts for hot paths: a chunk needing ONE key imports it and
143
+ * drops the whole table — object properties cannot tree-shake, and the
144
+ * panels chunk was carrying ten keys to read two (M2-13). */
145
+ export const DISCLOSURE_KEY = "snag:disclosure:v1";
146
+ export const IDENTITY_KEY = "snag:identity";
147
+ export const THEME_ATTR = "data-snag-theme";
148
+ /** Web-storage keys (§2.6). Parameterised keys are functions. */
149
+ export const storageKey = {
150
+ session: (uid) => `snag:session:${uid}`,
151
+ identity: IDENTITY_KEY,
152
+ threads: (uid, env) => `snag:threads:${uid}:${env}`,
153
+ seen: (uid, env) => `snag:seen:${uid}:${env}`,
154
+ counts: (origin) => `snag:counts:${origin}`,
155
+ draft: (route, draftId) => `snag:draft:${route}:${draftId}`,
156
+ outbox: (uid) => `snag:outbox:${uid}`,
157
+ mode: "snag:mode",
158
+ handoff: "snag:handoff",
159
+ seenCoach: "snag:seen-coach",
160
+ disclosure: DISCLOSURE_KEY,
161
+ };
162
+ /** The v0.1 IndexedDB blob store (§2.6, 05 §10.1) — one database, one
163
+ * object store, shared by screenshots, reviewer images and the v2 outbox. */
164
+ export const IDB = {
165
+ database: "snag:blobs",
166
+ store: "blobs",
167
+ };
168
+ /** Anchored regex sources matching every registered key — AC-22's storage diff. */
169
+ export const STORAGE_KEY_PATTERNS = [
170
+ "^snag:session:.+$",
171
+ "^snag:identity$",
172
+ "^snag:threads:.+:.+$",
173
+ "^snag:seen:.+:.+$",
174
+ "^snag:counts:.+$",
175
+ "^snag:draft:.+:.+$",
176
+ "^snag:outbox:.+$",
177
+ "^snag:mode$",
178
+ "^snag:handoff$",
179
+ "^snag:seen-coach$",
180
+ "^snag:disclosure:v1$",
181
+ ];
182
+ /** The WIDGET's two (§2.7). Neither carries a credential, and no cookie authenticates the widget API — it runs on somebody else's origin. */
183
+ export const COOKIE = {
184
+ off: "snag_off",
185
+ on: "snag_on",
186
+ };
187
+ /** The dashboard's session + CSRF pair (05 §4.7) — first-party, on the server's own origin, owner audience. */
188
+ export const DASHBOARD_COOKIE = {
189
+ session: "snag_session",
190
+ csrf: "snag_csrf",
191
+ };
192
+ /** 05 §4.7 names these two constants directly; both spellings resolve to the same string. */
193
+ export const DASHBOARD_SESSION_COOKIE = DASHBOARD_COOKIE.session;
194
+ export const DASHBOARD_CSRF_COOKIE = DASHBOARD_COOKIE.csrf;
195
+ /** Built widget chunks (§2.8). Preact lives only in `panels`. */
196
+ export const ARTIFACT = {
197
+ stub: "snag.js",
198
+ runtime: "snag.runtime.js",
199
+ panels: "snag.panels.js",
200
+ crumbs: "snag.crumbs.js",
201
+ capture: "snag.capture.js",
202
+ screenshot: "snag.screenshot.js",
203
+ anchorExt: "snag.anchor-ext.js",
204
+ anchorFuzzy: "snag.anchor-fuzzy.js",
205
+ dock: "snag.dock.js",
206
+ text: "snag.text.js",
207
+ };
208
+ /** Wire-form entity id prefixes (§2.10). Not brand-derived. */
209
+ export const ID_PREFIX = {
210
+ project: "prj_",
211
+ thread: "thr_",
212
+ comment: "cmt_",
213
+ reviewer: "rev_",
214
+ attachment: "att_",
215
+ link: "lnk_",
216
+ round: "rnd_",
217
+ };
218
+ /** Credential literals (07 §8.2). Brand-DERIVED, and deliberately not the three-letter row shape: the literal is what gets registered with secret scanning, so a token pasted into a public repo is reported. */
219
+ export const CREDENTIAL_PREFIX = {
220
+ mcp: "snagmcp_",
221
+ };
222
+ /** CustomEvents dispatched on document (§2.11). */
223
+ export const DOM_EVENT = {
224
+ ready: "snagset:ready",
225
+ activate: "snagset:activate",
226
+ deactivate: "snagset:deactivate",
227
+ threadCreated: "snagset:thread-created",
228
+ threadResolved: "snagset:thread-resolved",
229
+ };
230
+ /** Request/response header names inside §2.1's prefix (05 §2.3). */
231
+ export const HTTP_HEADER = {
232
+ client: "x-snagset-client",
233
+ schema: "x-snagset-schema",
234
+ idempotencyKey: "x-snagset-idempotency-key",
235
+ identity: "x-snagset-identity",
236
+ csrf: "x-snagset-csrf",
237
+ attachmentKind: "x-snagset-attachment-kind",
238
+ attachmentWidth: "x-snagset-attachment-width",
239
+ attachmentHeight: "x-snagset-attachment-height",
240
+ server: "x-snagset-server",
241
+ minClient: "x-snagset-min-client",
242
+ time: "x-snagset-time",
243
+ configVersion: "x-snagset-config-version",
244
+ idempotentReplay: "x-snagset-idempotent-replay",
245
+ requestId: "x-snagset-request-id",
246
+ };
247
+ /** Host-authored `<meta name>` keys we READ and never mint (§2.11). 03 §5.3: `route` is the only one. */
248
+ export const META_NAME = {
249
+ route: "snagset:route",
250
+ };
251
+ /** Keys for Symbol.for() patch bookkeeping (§2.11). */
252
+ export const SYMBOL_KEY = {
253
+ original: "snagset.original",
254
+ wrapped: "snagset.wrapped",
255
+ instance: "snagset.instance",
256
+ };
package/dist/view.js ADDED
@@ -0,0 +1,174 @@
1
+ import { fenced, DATA_BOUNDARY } from "./vendor/adapters/outbound/fence.js";
2
+ import { sanitise, trusted } from "./vendor/adapters/outbound/sanitise.js";
3
+ /**
4
+ * The view an agent sees — 07 §8.3, and the four §8.6 mitigations that live in
5
+ * a projection rather than in a policy.
6
+ *
7
+ * `ThreadSummary` and `ThreadDetail` are projections of
8
+ * `OutboundThreadView<'plain'>`, *"not of `Thread`. That is deliberate and load
9
+ * bearing: the MCP server gets the same sanitiser, the same clamps and the same
10
+ * fixture corpus as every provider."* So every string that a reviewer could
11
+ * have typed goes through `@snagset/adapters` on the way to a model, and this
12
+ * package holds no scrubbing logic of its own.
13
+ *
14
+ * ── WHAT IS DELIBERATELY MISSING ──
15
+ *
16
+ * §8.3, on orphans, states the general rule this file follows everywhere:
17
+ * *"a payload that promises fields the release does not have is worse than a
18
+ * short one."* `OutboundThreadView` lists `branch`, `labels`, `assigneeName`,
19
+ * `viewport`, `browser`, `platform` and `prefersColorScheme`; the L1 API does
20
+ * not serve most of them yet. They are absent here rather than emitted empty,
21
+ * because an agent that reads `labels: []` concludes the thread has no labels.
22
+ *
23
+ * ── THE FENCE ──
24
+ *
25
+ * Every Class R and Class P string is WRAPPED, not merely escaped:
26
+ * `<untrusted-content kind="…">…</untrusted-content>` (§8.6 mitigation 3).
27
+ * `author` is fenced too — the review found it emitted bare while §8.6 already
28
+ * listed `Reviewer.name` among the untrusted strings reaching the model, *"the
29
+ * kind of contradiction that survives because the two facts sat four pages
30
+ * apart."*
31
+ */
32
+ /** 00 §5. The third place the token becomes irreversible (§8.3). */
33
+ export const SCHEMA_VERSION = "snagset.v1";
34
+ /** §8.3's clamp: comments capped at 50, oldest first. */
35
+ export const MAX_COMMENTS = 50;
36
+ /** §8.3: `limit` ≤ 50 on `list_threads`. */
37
+ export const MAX_THREADS = 50;
38
+ /**
39
+ * Clamps on the fenced strings themselves.
40
+ *
41
+ * A comment body is capped at 10 000 characters server-side, and fifty of them
42
+ * is half a megabyte of a model's context spent on one tool call. 4 000 is the
43
+ * length at which a snag's text stops being a snag and starts being a document
44
+ * somebody pasted *(calibration, like every number in §8)*.
45
+ */
46
+ const CLAMP = { body: 4_000, name: 200, label: 400, selector: 600 };
47
+ /**
48
+ * The anchor's human-readable half, fenced.
49
+ *
50
+ * `sourceRef` is NOT read here and that is §2.6.3: source locations never leave
51
+ * the machine. It is the one field the local MCP server *may* read — but only
52
+ * in `map_thread_to_source`, where it never leaves either.
53
+ */
54
+ function element(anchor) {
55
+ if (!anchor || typeof anchor !== "object")
56
+ return {};
57
+ const out = {};
58
+ const el = (anchor["element"] ?? anchor);
59
+ const put = (key, kind, max) => {
60
+ const v = el[key];
61
+ if (typeof v === "string" && v)
62
+ out[key] = fenced(kind, v, max);
63
+ };
64
+ put("role", "label", CLAMP.label);
65
+ put("accessibleName", "label", CLAMP.label);
66
+ put("testId", "label", CLAMP.label);
67
+ put("selector", "selector", CLAMP.selector);
68
+ return out;
69
+ }
70
+ /** `Thread` → what `list_threads` returns per row. */
71
+ export function summary(t) {
72
+ return {
73
+ id: t.id,
74
+ key: t.key,
75
+ status: t.status,
76
+ anchorState: t.anchorState ?? "unknown",
77
+ // Class S — ids, enums and timestamps we minted. `trusted()` is greppable
78
+ // on purpose: the review question is "is this genuinely ours?" rather than
79
+ // "what does this do?".
80
+ createdAt: trusted(t.createdAt),
81
+ updatedAt: trusted(t.updatedAt),
82
+ ...(t.environment ? { environment: t.environment } : {}),
83
+ ...(t.round !== undefined ? { round: t.round } : {}),
84
+ ...(t.route ? { route: fenced("label", t.route, CLAMP.label) } : {}),
85
+ commentCount: t.commentCount ?? 0,
86
+ ...(t.createdBy?.name ? { author: fenced("name", t.createdBy.name, CLAMP.name) } : {}),
87
+ // The first comment IS the snag (05 §6.2 writes them in one transaction),
88
+ // so a summary without it is a list of keys. Clamped harder than the
89
+ // detail's: this one appears fifty times in a single result.
90
+ ...(t.comments?.[0] ? { firstComment: fenced("body", t.comments[0].body, 500) } : {}),
91
+ element: element(t.anchor),
92
+ };
93
+ }
94
+ /** `get_thread` — the same row plus its conversation. */
95
+ export function detail(t) {
96
+ const comments = (t.comments ?? []).slice(0, MAX_COMMENTS).map((c) => ({
97
+ id: c.id,
98
+ createdAt: trusted(c.createdAt),
99
+ author: fenced("name", c.author?.name ?? "Someone", CLAMP.name),
100
+ authorKind: c.author?.kind ?? "guest",
101
+ body: fenced("body", c.body ?? "", CLAMP.body),
102
+ }));
103
+ return {
104
+ ...summary(t),
105
+ // Replaced rather than merged: the summary's excerpt would be a second,
106
+ // shorter copy of `comments[0]` sitting beside the real thing.
107
+ firstComment: undefined,
108
+ comments,
109
+ ...(t.comments && t.comments.length > MAX_COMMENTS
110
+ ? {
111
+ // Said out loud. §8's own rule about silent caps: an agent that reads
112
+ // 50 of 200 comments and is not told will answer as if it read them
113
+ // all.
114
+ commentsTruncated: `showing the oldest ${MAX_COMMENTS} of ${t.comments.length} comments`,
115
+ }
116
+ : {}),
117
+ ...(t.attachments?.length
118
+ ? {
119
+ attachments: t.attachments.map((a) => ({
120
+ id: a.id,
121
+ kind: a.kind,
122
+ // A path on the instance, never a bucket URL. There is no tool that
123
+ // fetches it: §8.6 mitigation 4 — no arbitrary-fetch tool.
124
+ url: a.url,
125
+ ...(a.width ? { width: a.width, height: a.height } : {}),
126
+ })),
127
+ }
128
+ : {}),
129
+ };
130
+ }
131
+ /**
132
+ * `get_thread_context` — `context:read`, the personal-data-bearing half.
133
+ *
134
+ * §8.3: `CaptureContext` minus `sourceRef`, plus breadcrumbs and
135
+ * `capture.reasons[]` / `capture.blindSpots[]`. Breadcrumb messages are Class R
136
+ * — a console log on somebody else's site is text an attacker can write — so
137
+ * every one is fenced.
138
+ */
139
+ export function context(t) {
140
+ const ctx = (t.context ?? {});
141
+ const crumbs = Array.isArray(ctx["breadcrumbs"]) ? ctx["breadcrumbs"] : [];
142
+ const { sourceRef: _dropped, ...rest } = ctx;
143
+ return {
144
+ // Everything except the breadcrumbs, which are re-emitted fenced below.
145
+ ...Object.fromEntries(Object.entries(rest).filter(([k]) => k !== "breadcrumbs")),
146
+ breadcrumbs: crumbs.slice(-50).map((b) => ({
147
+ t: b["t"],
148
+ type: b["type"],
149
+ ...(b["level"] ? { level: b["level"] } : {}),
150
+ message: fenced("breadcrumb", String(b["message"] ?? ""), CLAMP.body),
151
+ })),
152
+ };
153
+ }
154
+ /**
155
+ * Wrap a result for the model — mitigation 1's preamble, then the data.
156
+ *
157
+ * Text and not a JSON content block, deliberately: the preamble has to be the
158
+ * FIRST thing read, and a structured result puts the boundary statement
159
+ * somewhere inside an object where it reads as one more field.
160
+ */
161
+ export function envelope(payload) {
162
+ return `${DATA_BOUNDARY}\n\n${JSON.stringify({ schemaVersion: SCHEMA_VERSION, ...payload }, null, 2)}`;
163
+ }
164
+ /**
165
+ * A tool's failure, in words an agent can relay.
166
+ *
167
+ * Sanitised, because the string may carry a server message that quoted the
168
+ * caller's own input back — an error path is exactly where an unfenced string
169
+ * gets emitted by accident.
170
+ */
171
+ export function failure(message) {
172
+ return `${DATA_BOUNDARY}\n\n${sanitise(message, "plain", { max: 1_000 })}`;
173
+ }
174
+ //# sourceMappingURL=view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,qCAAqC,CAAC;AAExE;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,YAAY,CAAC;AAE3C,yDAAyD;AACzD,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,CAAC;AAC/B,4CAA4C;AAC5C,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAE9B;;;;;;;GAOG;AACH,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAW,CAAC;AA6B7E;;;;;;GAMG;AACH,SAAS,OAAO,CAAC,MAAkD;IACjE,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACrD,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,MAAM,CAA4B,CAAC;IACpE,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,IAA0B,EAAE,GAAW,EAAE,EAAE;QACnE,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QAClB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IAClE,CAAC,CAAC;IACF,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAClC,GAAG,CAAC,gBAAgB,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5C,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACpC,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC5C,OAAO,GAAG,CAAC;AACb,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,OAAO,CAAC,CAAa;IACnC,OAAO;QACL,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,WAAW,EAAE,CAAC,CAAC,WAAW,IAAI,SAAS;QACvC,0EAA0E;QAC1E,2EAA2E;QAC3E,wBAAwB;QACxB,SAAS,EAAE,OAAO,CAAU,CAAC,CAAC,SAAS,CAAC;QACxC,SAAS,EAAE,OAAO,CAAU,CAAC,CAAC,SAAS,CAAC;QACxC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,CAAC;QACjC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtF,0EAA0E;QAC1E,qEAAqE;QACrE,6DAA6D;QAC7D,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrF,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;KAC3B,CAAC;AACJ,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,MAAM,CAAC,CAAa;IAClC,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrE,EAAE,EAAE,CAAC,CAAC,EAAE;QACR,SAAS,EAAE,OAAO,CAAU,CAAC,CAAC,SAAS,CAAC;QACxC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,IAAI,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC;QAC/D,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,IAAI,OAAO;QACrC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC;KAC/C,CAAC,CAAC,CAAC;IACJ,OAAO;QACL,GAAG,OAAO,CAAC,CAAC,CAAC;QACb,wEAAwE;QACxE,+DAA+D;QAC/D,YAAY,EAAE,SAAS;QACvB,QAAQ;QACR,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,YAAY;YAChD,CAAC,CAAC;gBACE,sEAAsE;gBACtE,oEAAoE;gBACpE,OAAO;gBACP,iBAAiB,EAAE,sBAAsB,YAAY,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,WAAW;aACzF;YACH,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,MAAM;YACvB,CAAC,CAAC;gBACE,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBACrC,EAAE,EAAE,CAAC,CAAC,EAAE;oBACR,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,oEAAoE;oBACpE,2DAA2D;oBAC3D,GAAG,EAAE,CAAC,CAAC,GAAG;oBACV,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACzD,CAAC,CAAC;aACJ;YACH,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAC,CAAa;IACnC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAA4B,CAAC;IACzD,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,aAAa,CAA+B,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1G,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,GAA8B,CAAC;IACxE,OAAO;QACL,wEAAwE;QACxE,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC;QAChF,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC;YACT,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;YACf,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5C,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC;SACtE,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAgB;IACvC,OAAO,GAAG,aAAa,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,cAAc,EAAE,GAAI,OAAkB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;AACrH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,OAAe;IACrC,OAAO,GAAG,aAAa,OAAO,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAC7E,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@snagset/mcp",
3
+ "version": "0.0.2",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Read your Snagset review threads from a coding agent \u2014 the pin, the element, the route and the console with each one.",
7
+ "license": "MIT",
8
+ "bin": {
9
+ "snagset-mcp": "./dist/bin.js"
10
+ },
11
+ "scripts": {
12
+ "typecheck": "tsc -p tsconfig.json --noEmit",
13
+ "test": "node --experimental-strip-types --test test/*.test.ts",
14
+ "build": "node scripts/build.mjs",
15
+ "prepack": "node scripts/build.mjs"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "1.30.0",
19
+ "zod": "4.4.3"
20
+ },
21
+ "devDependencies": {
22
+ "@snagset/adapters": "workspace:*",
23
+ "@snagset/tsconfig": "workspace:*",
24
+ "@snagset/types": "workspace:*",
25
+ "@types/node": "26.2.0",
26
+ "typescript": "^5.7.3"
27
+ },
28
+ "exports": {
29
+ ".": "./dist/server.js",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "engines": {
38
+ "node": ">=22"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }