@sendora/mcp 1.4.1 → 1.5.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.
@@ -0,0 +1,662 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="color-scheme" content="light dark">
7
+ <title>DNS records</title>
8
+ <style>
9
+ /*
10
+ * The screens' look (docs/design/oauth.md §9). Each colour, font, size and
11
+ * radius is the host's style variable where the host gives one, and the
12
+ * value Claude documents for it otherwise, in light and in dark. The host
13
+ * draws the border and the background, so the page paints neither. Status
14
+ * tints sit behind the primary text, which passes WCAG AA on each of them.
15
+ */
16
+
17
+ :root {
18
+ --own-text-primary: #141413;
19
+ --own-text-secondary: #3d3d3a;
20
+ --own-text-inverse: #ffffff;
21
+ --own-background-secondary: #f5f4ed;
22
+ --own-background-inverse: #141413;
23
+ --own-background-info: #d6e4f6;
24
+ --own-background-success: #e9f1dc;
25
+ --own-background-warning: #f6eedf;
26
+ --own-background-danger: #f7ecec;
27
+ --own-border-primary: rgb(31 30 29 / 40%);
28
+ --own-border-tertiary: rgb(31 30 29 / 15%);
29
+ --own-ring-primary: rgb(20 20 19 / 70%);
30
+ }
31
+
32
+ :root[data-theme='dark'] {
33
+ --own-text-primary: #faf9f5;
34
+ --own-text-secondary: #c2c0b6;
35
+ --own-text-inverse: #141413;
36
+ --own-background-secondary: #262624;
37
+ --own-background-inverse: #faf9f5;
38
+ --own-background-info: #253e5f;
39
+ --own-background-success: #1b4614;
40
+ --own-background-warning: #483a0f;
41
+ --own-background-danger: #602a28;
42
+ --own-border-primary: rgb(222 220 209 / 40%);
43
+ --own-border-tertiary: rgb(222 220 209 / 15%);
44
+ --own-ring-primary: rgb(250 249 245 / 70%);
45
+ }
46
+
47
+ html,
48
+ body {
49
+ margin: 0;
50
+ background: transparent;
51
+ }
52
+
53
+ body {
54
+ padding: calc(12px + var(--inset-top, 0px)) calc(12px + var(--inset-right, 0px))
55
+ calc(12px + var(--inset-bottom, 0px)) calc(12px + var(--inset-left, 0px));
56
+ color: var(--color-text-primary, var(--own-text-primary));
57
+ font-family: var(--font-sans, system-ui, sans-serif);
58
+ font-size: var(--font-text-sm-size, 14px);
59
+ line-height: var(--font-text-sm-line-height, 1.4);
60
+ }
61
+
62
+ h1 {
63
+ margin: 0 0 8px;
64
+ font-size: var(--font-heading-md-size, 16px);
65
+ line-height: var(--font-heading-md-line-height, 1.4);
66
+ font-weight: var(--font-weight-semibold, 600);
67
+ }
68
+
69
+ h2 {
70
+ margin: 0;
71
+ font-size: var(--font-heading-sm-size, 14px);
72
+ line-height: var(--font-heading-sm-line-height, 1.4);
73
+ font-weight: var(--font-weight-semibold, 600);
74
+ }
75
+
76
+ p {
77
+ margin: 0 0 8px;
78
+ }
79
+
80
+ ul,
81
+ ol {
82
+ margin: 0 0 12px;
83
+ padding: 0;
84
+ list-style: none;
85
+ }
86
+
87
+ .aside,
88
+ dt,
89
+ .reason,
90
+ time {
91
+ color: var(--color-text-secondary, var(--own-text-secondary));
92
+ }
93
+
94
+ .aside,
95
+ .reason {
96
+ font-size: var(--font-text-xs-size, 12px);
97
+ line-height: var(--font-text-xs-line-height, 1.4);
98
+ }
99
+
100
+ .badge {
101
+ display: inline-block;
102
+ padding: 1px 8px;
103
+ border-radius: var(--border-radius-full, 9999px);
104
+ font-size: var(--font-text-xs-size, 12px);
105
+ line-height: var(--font-text-xs-line-height, 1.4);
106
+ font-weight: var(--font-weight-medium, 500);
107
+ color: var(--color-text-primary, var(--own-text-primary));
108
+ background: var(--color-background-secondary, var(--own-background-secondary));
109
+ white-space: nowrap;
110
+ }
111
+
112
+ .badge[data-tone='info'],
113
+ .note {
114
+ background: var(--color-background-info, var(--own-background-info));
115
+ }
116
+
117
+ .badge[data-tone='success'] {
118
+ background: var(--color-background-success, var(--own-background-success));
119
+ }
120
+
121
+ .badge[data-tone='warning'] {
122
+ background: var(--color-background-warning, var(--own-background-warning));
123
+ }
124
+
125
+ .badge[data-tone='danger'],
126
+ .refusal {
127
+ background: var(--color-background-danger, var(--own-background-danger));
128
+ }
129
+
130
+ .note,
131
+ .refusal {
132
+ padding: 8px 12px;
133
+ border-radius: var(--border-radius-md, 8px);
134
+ }
135
+
136
+ .address {
137
+ overflow-wrap: anywhere;
138
+ }
139
+
140
+ .value {
141
+ font-family: var(--font-mono, ui-monospace, monospace);
142
+ font-size: var(--font-text-xs-size, 12px);
143
+ overflow-wrap: anywhere;
144
+ -webkit-user-select: all;
145
+ user-select: all;
146
+ }
147
+
148
+ .facts {
149
+ display: grid;
150
+ grid-template-columns: max-content minmax(0, 1fr);
151
+ gap: 4px 12px;
152
+ margin: 0 0 12px;
153
+ }
154
+
155
+ .facts dd {
156
+ margin: 0;
157
+ min-width: 0;
158
+ }
159
+
160
+ .recipients li {
161
+ margin: 0 0 4px;
162
+ }
163
+
164
+ .events li {
165
+ padding: 8px 0;
166
+ border-top: 1px solid var(--color-border-tertiary, var(--own-border-tertiary));
167
+ }
168
+
169
+ .events li time {
170
+ display: block;
171
+ font-size: var(--font-text-xs-size, 12px);
172
+ }
173
+
174
+ .events p {
175
+ margin: 0;
176
+ }
177
+
178
+ .events:focus {
179
+ outline: none;
180
+ }
181
+
182
+ .standing .badge,
183
+ .recipients .badge {
184
+ margin-right: 4px;
185
+ }
186
+
187
+ .record {
188
+ margin: 0 0 12px;
189
+ padding: 12px 0 0;
190
+ border-top: 1px solid var(--color-border-tertiary, var(--own-border-tertiary));
191
+ }
192
+
193
+ .record-head {
194
+ display: flex;
195
+ flex-wrap: wrap;
196
+ gap: 8px;
197
+ align-items: center;
198
+ margin: 0 0 8px;
199
+ }
200
+
201
+ .actions {
202
+ margin: 4px 0 8px;
203
+ }
204
+
205
+ button {
206
+ min-height: 44px;
207
+ padding: 0 16px;
208
+ border-radius: var(--border-radius-md, 8px);
209
+ font: inherit;
210
+ font-weight: var(--font-weight-semibold, 600);
211
+ cursor: pointer;
212
+ }
213
+
214
+ button.primary {
215
+ border: 0;
216
+ color: var(--color-text-inverse, var(--own-text-inverse));
217
+ background: var(--color-background-inverse, var(--own-background-inverse));
218
+ }
219
+
220
+ button.secondary {
221
+ border: 1px solid var(--color-border-primary, var(--own-border-primary));
222
+ color: var(--color-text-primary, var(--own-text-primary));
223
+ background: var(--color-background-secondary, var(--own-background-secondary));
224
+ }
225
+
226
+ button[aria-disabled='true'] {
227
+ cursor: progress;
228
+ }
229
+
230
+ button:focus-visible {
231
+ outline: 2px solid var(--color-ring-primary, var(--own-ring-primary));
232
+ outline-offset: 2px;
233
+ }
234
+
235
+ .said {
236
+ margin: 0;
237
+ font-size: var(--font-text-xs-size, 12px);
238
+ color: var(--color-text-secondary, var(--own-text-secondary));
239
+ }
240
+
241
+ .skeleton span {
242
+ display: block;
243
+ height: 12px;
244
+ margin: 0 0 10px;
245
+ border-radius: var(--border-radius-xs, 4px);
246
+ background: var(--color-background-secondary, var(--own-background-secondary));
247
+ }
248
+
249
+ .skeleton span:nth-child(1) {
250
+ width: 40%;
251
+ }
252
+
253
+ .skeleton span:nth-child(2) {
254
+ width: 75%;
255
+ }
256
+
257
+ .skeleton span:nth-child(3) {
258
+ width: 60%;
259
+ }
260
+
261
+ .visually-hidden {
262
+ position: absolute;
263
+ width: 1px;
264
+ height: 1px;
265
+ margin: -1px;
266
+ padding: 0;
267
+ overflow: hidden;
268
+ clip-path: inset(50%);
269
+ white-space: nowrap;
270
+ border: 0;
271
+ }
272
+ </style>
273
+ </head>
274
+ <body>
275
+ <main>
276
+ <div id="screen" aria-busy="true"></div>
277
+ <p id="said" class="said" role="status"></p>
278
+ </main>
279
+ <script>
280
+ "use strict";
281
+ (() => {
282
+ // screens/dom.ts
283
+ function element(tag, className, ...children) {
284
+ const made = document.createElement(tag);
285
+ if (className !== "") {
286
+ made.className = className;
287
+ }
288
+ made.append(...children);
289
+ return made;
290
+ }
291
+ function badge(words, tone) {
292
+ const pill = element("span", "badge", words);
293
+ pill.dataset.tone = tone;
294
+ return pill;
295
+ }
296
+ function value(text) {
297
+ return element("code", "value", text);
298
+ }
299
+ function knownZone(zone) {
300
+ if (typeof zone !== "string") {
301
+ return void 0;
302
+ }
303
+ try {
304
+ new Intl.DateTimeFormat("sv-SE", { timeZone: zone });
305
+ } catch (error) {
306
+ if (error instanceof RangeError) {
307
+ return void 0;
308
+ }
309
+ throw error;
310
+ }
311
+ return zone;
312
+ }
313
+ function timeOf(iso, zone) {
314
+ const at = new Date(iso);
315
+ const clock = new Intl.DateTimeFormat("sv-SE", {
316
+ dateStyle: "short",
317
+ timeStyle: "medium",
318
+ ...zone === void 0 ? {} : { timeZone: zone }
319
+ });
320
+ const time = element("time", "", Number.isNaN(at.getTime()) ? iso : clock.format(at));
321
+ time.dateTime = iso;
322
+ return time;
323
+ }
324
+ function part(id) {
325
+ const found = document.getElementById(id);
326
+ if (found === null) {
327
+ throw new Error(`the document has no #${id}`);
328
+ }
329
+ return found;
330
+ }
331
+ function say(words) {
332
+ part("said").textContent = words;
333
+ }
334
+ function waiting() {
335
+ const lines = element(
336
+ "div",
337
+ "skeleton",
338
+ element("span", ""),
339
+ element("span", ""),
340
+ element("span", "")
341
+ );
342
+ lines.setAttribute("aria-hidden", "true");
343
+ const screen = part("screen");
344
+ screen.setAttribute("aria-busy", "true");
345
+ screen.replaceChildren(element("p", "visually-hidden", "Waiting for the answer."), lines);
346
+ }
347
+ function show(...children) {
348
+ const screen = part("screen");
349
+ screen.setAttribute("aria-busy", "false");
350
+ screen.replaceChildren(...children);
351
+ }
352
+ function failed(text) {
353
+ const said = element("p", "refusal", text === "" ? "The tool gave no reason." : text);
354
+ said.setAttribute("role", "alert");
355
+ return [element("h1", "", "The call failed"), said];
356
+ }
357
+
358
+ // src/version.ts
359
+ var MCP_VERSION = "1.5.1";
360
+
361
+ // screens/host.ts
362
+ var PROTOCOL_VERSION = "2026-01-26";
363
+ function isFields(value2) {
364
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
365
+ }
366
+ function itemsOf(value2) {
367
+ return Array.isArray(value2) ? value2 : [];
368
+ }
369
+ function toolResultOf(value2) {
370
+ const result = isFields(value2) ? value2 : {};
371
+ const text = itemsOf(result.content).map(
372
+ (block) => isFields(block) && block.type === "text" && typeof block.text === "string" ? block.text : ""
373
+ ).join("");
374
+ return { text, structuredContent: result.structuredContent, isError: result.isError === true };
375
+ }
376
+ var root = document.documentElement;
377
+ function post(message) {
378
+ window.parent.postMessage({ jsonrpc: "2.0", ...message }, "*");
379
+ }
380
+ var pending = /* @__PURE__ */ new Map();
381
+ var nextId = 1;
382
+ function request(method, params) {
383
+ const id = nextId;
384
+ nextId += 1;
385
+ const answered = new Promise((resolve, reject) => {
386
+ pending.set(id, { resolve, reject });
387
+ });
388
+ post({ id, method, params });
389
+ return answered;
390
+ }
391
+ function settle(message) {
392
+ if (typeof message.id !== "number") {
393
+ return;
394
+ }
395
+ const waiting2 = pending.get(message.id);
396
+ if (waiting2 === void 0) {
397
+ return;
398
+ }
399
+ pending.delete(message.id);
400
+ if (isFields(message.error)) {
401
+ const said = message.error.message;
402
+ waiting2.reject(new Error(typeof said === "string" ? said : "The host refused the request."));
403
+ } else {
404
+ waiting2.resolve(message.result);
405
+ }
406
+ }
407
+ function answer(id, method) {
408
+ if (method === "ping" || method === "ui/resource-teardown") {
409
+ post({ id, result: {} });
410
+ } else {
411
+ post({ id, error: { code: -32601, message: "Method not found" } });
412
+ }
413
+ }
414
+ function theme(chosen) {
415
+ root.dataset.theme = chosen;
416
+ root.style.colorScheme = chosen;
417
+ }
418
+ function follow(context, host) {
419
+ if (context.theme === "light" || context.theme === "dark") {
420
+ theme(context.theme);
421
+ }
422
+ const variables = isFields(context.styles) ? context.styles.variables : void 0;
423
+ if (isFields(variables)) {
424
+ for (const [name, setting] of Object.entries(variables)) {
425
+ if (name.startsWith("--") && typeof setting === "string") {
426
+ root.style.setProperty(name, setting);
427
+ }
428
+ }
429
+ }
430
+ const insets = context.safeAreaInsets;
431
+ if (isFields(insets)) {
432
+ for (const side of ["top", "right", "bottom", "left"]) {
433
+ const inset = insets[side];
434
+ if (typeof inset === "number") {
435
+ root.style.setProperty(`--inset-${side}`, `${String(inset)}px`);
436
+ }
437
+ }
438
+ }
439
+ if ("timeZone" in context) {
440
+ host.zone = knownZone(context.timeZone);
441
+ }
442
+ }
443
+ function reportSize() {
444
+ let last = "";
445
+ const report = () => {
446
+ const width = Math.ceil(window.innerWidth);
447
+ const height = Math.ceil(root.getBoundingClientRect().height);
448
+ const size = `${String(width)} ${String(height)}`;
449
+ if (size !== last) {
450
+ last = size;
451
+ post({ method: "ui/notifications/size-changed", params: { width, height } });
452
+ }
453
+ };
454
+ new ResizeObserver(report).observe(root);
455
+ report();
456
+ }
457
+ async function initialize(view, host) {
458
+ const answered = await request("ui/initialize", {
459
+ appInfo: { name: view.name, version: MCP_VERSION },
460
+ appCapabilities: { availableDisplayModes: ["inline"] },
461
+ protocolVersion: PROTOCOL_VERSION
462
+ });
463
+ const result = isFields(answered) ? answered : {};
464
+ host.callsTools = isFields(result.hostCapabilities) && isFields(result.hostCapabilities.serverTools);
465
+ follow(isFields(result.hostContext) ? result.hostContext : {}, host);
466
+ post({ method: "ui/notifications/initialized", params: {} });
467
+ reportSize();
468
+ }
469
+ function connect(view) {
470
+ const host = {
471
+ callsTools: false,
472
+ zone: void 0,
473
+ callTool: async (name, args) => toolResultOf(await request("tools/call", { name, arguments: args }))
474
+ };
475
+ const notifications = /* @__PURE__ */ new Map([
476
+ [
477
+ "ui/notifications/tool-result",
478
+ (params) => {
479
+ view.render(toolResultOf(params), host);
480
+ }
481
+ ],
482
+ [
483
+ "ui/notifications/tool-cancelled",
484
+ () => {
485
+ view.stopped("The call was cancelled.");
486
+ }
487
+ ],
488
+ [
489
+ "ui/notifications/host-context-changed",
490
+ (params) => {
491
+ follow(params, host);
492
+ }
493
+ ]
494
+ ]);
495
+ window.addEventListener("message", (event) => {
496
+ const message = event.data;
497
+ if (event.source !== window.parent || !isFields(message) || message.jsonrpc !== "2.0") {
498
+ return;
499
+ }
500
+ if (typeof message.method !== "string") {
501
+ settle(message);
502
+ } else if ("id" in message) {
503
+ answer(message.id, message.method);
504
+ } else {
505
+ notifications.get(message.method)?.(isFields(message.params) ? message.params : {});
506
+ }
507
+ });
508
+ theme(matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
509
+ initialize(view, host).catch((error) => {
510
+ const reason = error instanceof Error ? `: ${error.message}` : "";
511
+ view.stopped(`The host did not connect this screen${reason}.`);
512
+ });
513
+ }
514
+
515
+ // screens/domain-records.ts
516
+ var checkWords = {
517
+ ok: "Found",
518
+ missing: "Missing",
519
+ mismatch: "Wrong value",
520
+ dns_error: "Lookup failed"
521
+ };
522
+ var checkTones = {
523
+ ok: "success",
524
+ missing: "danger",
525
+ mismatch: "danger",
526
+ dns_error: "warning"
527
+ };
528
+ function isDomain(content) {
529
+ return isFields(content) && typeof content.domainId === "string" && isFields(content.returnPath) && isFields(content.dkim);
530
+ }
531
+ function recordBadge(record, check) {
532
+ if (check !== void 0) {
533
+ return badge(checkWords[check], checkTones[check]);
534
+ }
535
+ return record.verified ? badge("Found", "success") : badge("Not found yet", "neutral");
536
+ }
537
+ function standing(domain, host) {
538
+ if (!domain.verified) {
539
+ return element(
540
+ "p",
541
+ "standing",
542
+ badge("Not verified", "neutral"),
543
+ " Add both records to the domain's DNS, then check them."
544
+ );
545
+ }
546
+ if (domain.failingSince !== null) {
547
+ return element(
548
+ "p",
549
+ "standing",
550
+ badge("Records failing", "warning"),
551
+ " Since ",
552
+ timeOf(domain.failingSince, host.zone),
553
+ ". Mail stops if a record is still wrong 24 hours after that."
554
+ );
555
+ }
556
+ return element("p", "standing", badge("Verified", "success"), " Mail goes out from this domain.");
557
+ }
558
+ function recordSection(name, record, check) {
559
+ return element(
560
+ "section",
561
+ "record",
562
+ element(
563
+ "div",
564
+ "record-head",
565
+ element("h2", "", `${name}, ${record.type}`),
566
+ recordBadge(record, check)
567
+ ),
568
+ element(
569
+ "dl",
570
+ "facts",
571
+ element("dt", "", "Name"),
572
+ element("dd", "", value(record.host)),
573
+ element("dt", "", "Value"),
574
+ element("dd", "", value(record.value))
575
+ )
576
+ );
577
+ }
578
+ function outcome(domain) {
579
+ const { returnPath, dkim } = domain.check;
580
+ if (returnPath === "ok" && dkim === "ok") {
581
+ return "Both records were found.";
582
+ }
583
+ return `The CNAME record: ${checkWords[returnPath].toLowerCase()}. The TXT record: ${checkWords[dkim].toLowerCase()}.`;
584
+ }
585
+ async function checkAgain(domain, host, button) {
586
+ if (button.getAttribute("aria-disabled") === "true") {
587
+ return;
588
+ }
589
+ const label = button.textContent;
590
+ button.setAttribute("aria-disabled", "true");
591
+ button.textContent = "Checking\u2026";
592
+ say("Checking the records.");
593
+ let result;
594
+ try {
595
+ result = await host.callTool("check_domain", { domainId: domain.domainId });
596
+ } catch (error) {
597
+ result = {
598
+ text: error instanceof Error ? error.message : "",
599
+ structuredContent: void 0,
600
+ isError: true
601
+ };
602
+ }
603
+ const content = result.structuredContent;
604
+ if (result.isError || !isDomain(content) || !("check" in content)) {
605
+ button.removeAttribute("aria-disabled");
606
+ button.textContent = label;
607
+ say(result.text === "" ? "The check failed." : `The check failed. ${result.text}`);
608
+ return;
609
+ }
610
+ const focused = document.activeElement === button;
611
+ show(...domainScreen(content, host));
612
+ say(outcome(content));
613
+ if (focused) {
614
+ document.querySelector(".actions button")?.focus();
615
+ }
616
+ }
617
+ function domainScreen(domain, host) {
618
+ const check = "check" in domain ? domain.check : void 0;
619
+ const checked = domain.lastCheckedAt === null ? element("p", "aside", "Not checked yet.") : element("p", "aside", "Last checked ", timeOf(domain.lastCheckedAt, host.zone), ".");
620
+ const parts = [
621
+ element("h1", "", domain.domain),
622
+ standing(domain, host),
623
+ recordSection("Return path", domain.returnPath, check?.returnPath),
624
+ recordSection("DKIM key", domain.dkim, check?.dkim),
625
+ checked
626
+ ];
627
+ if (host.callsTools) {
628
+ const button = element(
629
+ "button",
630
+ "primary",
631
+ domain.lastCheckedAt === null ? "Check now" : "Check again"
632
+ );
633
+ button.type = "button";
634
+ button.addEventListener("click", () => {
635
+ void checkAgain(domain, host, button);
636
+ });
637
+ parts.push(element("div", "actions", button));
638
+ }
639
+ return parts;
640
+ }
641
+ function render(result, host) {
642
+ const content = result.structuredContent;
643
+ if (result.isError) {
644
+ show(...failed(result.text));
645
+ } else if (isDomain(content)) {
646
+ show(...domainScreen(content, host));
647
+ } else {
648
+ show(element("p", "", "This answer cannot be shown here. The conversation has it as text."));
649
+ }
650
+ }
651
+ waiting();
652
+ connect({
653
+ name: "sendora-domain-records",
654
+ render,
655
+ stopped: (words) => {
656
+ show(element("p", "", words));
657
+ }
658
+ });
659
+ })();
660
+ </script>
661
+ </body>
662
+ </html>