@dnsdoctor/mcp 1.2.1 → 1.4.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/README.md +18 -9
- package/dist/api.js +9 -1
- package/dist/api.js.map +1 -1
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/instructions.txt +1 -1
- package/package.json +2 -1
- package/tools.json +167 -2
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ claude-plugin/
|
|
|
18
18
|
├── .mcp.json # MCP server: https://dnsdoctor.dev/mcp (HTTP)
|
|
19
19
|
├── skills/dns-doctor/SKILL.md # the scan → diagnose → fix workflow
|
|
20
20
|
├── src/ # @dnsdoctor/mcp — the local stdio MCP server
|
|
21
|
-
├── tools.json # the
|
|
21
|
+
├── tools.json # the 15 tool definitions (generated, never hand-edited)
|
|
22
22
|
├── instructions.txt # the server's own `initialize` guidance (generated)
|
|
23
23
|
├── tests/ # vitest suite for the stdio server
|
|
24
24
|
├── package.json tsconfig.json # npm package + build
|
|
@@ -40,12 +40,19 @@ claude-plugin/
|
|
|
40
40
|
| `parse_dmarc_report` | Parse an aggregate (RUA) report file into rows. |
|
|
41
41
|
| `check_record` | Read any DNS record type for a name. |
|
|
42
42
|
| `check_reverse_dns` | PTR / forward-confirmed reverse DNS for an IP. |
|
|
43
|
+
| `audit_spf_includes` | The SPF include/redirect tree — who can transitively send as the domain, with typed findings (broken include, confirmed-unregistered include, expiring registration, nested `+all`). Analysis only; no SPF fix record. |
|
|
44
|
+
| `build_parked_domain_records` | The Null MX + `v=spf1 -all` + `p=reject; np=reject` hardening pack for a domain that sends no mail. The server re-checks DNS itself and refuses when it finds evidence of mail. |
|
|
43
45
|
| `start_monitoring_signup` | A sign-up link to hand to the human who owns the domain. Sends no email and creates nothing — they open it, sign in on our page themselves (a social provider or an emailed link, whichever that deployment offers), and the domain is carried over to their dashboard already filled in; monitoring starts once they verify it with a TXT record. |
|
|
46
|
+
| `get_alerts` | **Token required.** The account's monitoring alert log, newest first. Read-only — no acknowledge, no delete. Page down with `before` until `next_before` is `null` before advancing `since`. |
|
|
47
|
+
| `get_readiness` | **Token required.** Whether one monitored domain's aggregate-report evidence justifies a stronger DMARC policy yet: `ready`, the `blockers`, and `next_record` (validated, or `null` while blocked — which is an answer, not a gap). |
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
refused
|
|
48
|
-
|
|
49
|
+
The two monitoring reads are **listed for everyone and callable with a token**:
|
|
50
|
+
they appear in the tool list on both transports, and without a valid token the
|
|
51
|
+
call is refused with the page the account owner mints one on. Over the hosted
|
|
52
|
+
HTTP transport the `dnsdoctor://domains` resource (your monitored domains) is
|
|
53
|
+
likewise always listed and refused without a token; the local stdio server
|
|
54
|
+
registers the tools only — no resource. Anonymous access covers all thirteen
|
|
55
|
+
diagnosis tools, which is enough for a one-off diagnosis either way.
|
|
49
56
|
|
|
50
57
|
## Install
|
|
51
58
|
|
|
@@ -77,8 +84,9 @@ Add a custom connector with:
|
|
|
77
84
|
## Optional: API token for monitored domains
|
|
78
85
|
|
|
79
86
|
Anonymous access covers scanning and fixes. A per-account API token unlocks the
|
|
80
|
-
`
|
|
81
|
-
|
|
87
|
+
account's own monitoring data: the `get_alerts` and `get_readiness` tools, and
|
|
88
|
+
the `dnsdoctor://domains` resource (your continuously-monitored domains and
|
|
89
|
+
their latest per-check statuses).
|
|
82
90
|
|
|
83
91
|
1. Sign in at <https://dnsdoctor.dev> → **Settings → API tokens** → create a token.
|
|
84
92
|
The plaintext (`dnsd_…`) is shown once; copy it.
|
|
@@ -101,7 +109,7 @@ latest per-check statuses).
|
|
|
101
109
|
|
|
102
110
|
### Transport
|
|
103
111
|
|
|
104
|
-
Two supported public transports, same
|
|
112
|
+
Two supported public transports, same 15 tools:
|
|
105
113
|
|
|
106
114
|
- **Hosted streamable HTTP** — `https://dnsdoctor.dev/mcp`, wired in this
|
|
107
115
|
plugin's `.mcp.json`. No install, no keys.
|
|
@@ -121,7 +129,8 @@ Two supported public transports, same 11 tools:
|
|
|
121
129
|
}
|
|
122
130
|
```
|
|
123
131
|
|
|
124
|
-
`DNSDOCTOR_API_TOKEN` is optional (anonymous access covers scanning and fixes
|
|
132
|
+
`DNSDOCTOR_API_TOKEN` is optional (anonymous access covers scanning and fixes;
|
|
133
|
+
the two monitoring reads need it).
|
|
125
134
|
`DNSDOCTOR_API_BASE` overrides the origin — it must be an origin that serves the
|
|
126
135
|
`/api/…` paths, i.e. the public site rather than a bare backend port.
|
|
127
136
|
|
package/dist/api.js
CHANGED
|
@@ -37,7 +37,15 @@ export function apiBase() {
|
|
|
37
37
|
const base = raw ? raw : DEFAULT_API_BASE;
|
|
38
38
|
return base.replace(/\/+$/, "");
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* The bearer token, when the environment carries one — sent on every request,
|
|
42
|
+
* GET included.
|
|
43
|
+
*
|
|
44
|
+
* Most tools work anonymously and a token only raises the budget, but the two
|
|
45
|
+
* monitoring reads (`get_alerts`, `get_readiness`) REQUIRE one: without it the
|
|
46
|
+
* API answers 401 and its `detail` is the guidance (mint one on the dashboard),
|
|
47
|
+
* which `toApiError` relays verbatim. Never prompt a human for the token here.
|
|
48
|
+
*/
|
|
41
49
|
function authHeaders() {
|
|
42
50
|
const token = process.env.DNSDOCTOR_API_TOKEN?.trim();
|
|
43
51
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAExD,iFAAiF;AACjF,MAAM,CAAC,MAAM,gBAAgB,GAAG,uCAAuC,CAAC;AAExE,MAAM,CAAC,MAAM,oBAAoB,GAAG,oCAAoC,CAAC;AAEzE,gEAAgE;AAChE,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,MAAM,CAAgB;IACtB,SAAS,CAAU;IAE5B,YAAY,OAAe,EAAE,MAAqB,EAAE,SAAkB;QACpE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAED,sFAAsF;AACtF,MAAM,UAAU,OAAO;IACrB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,CAAC;IACnD,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,13 @@ const ROUTES = {
|
|
|
34
34
|
},
|
|
35
35
|
check_record: { kind: "json", path: "/api/tools/check-record" },
|
|
36
36
|
check_reverse_dns: { kind: "json", path: "/api/tools/reverse-dns-check" },
|
|
37
|
+
audit_spf_includes: { kind: "json", path: "/api/tools/spf-audit" },
|
|
38
|
+
build_parked_domain_records: {
|
|
39
|
+
kind: "json",
|
|
40
|
+
path: "/api/tools/parked-domain-records",
|
|
41
|
+
},
|
|
42
|
+
get_alerts: { kind: "query", path: "/api/v1/alerts" },
|
|
43
|
+
get_readiness: { kind: "query", path: "/api/v1/readiness" },
|
|
37
44
|
};
|
|
38
45
|
/** Default upload name when the caller supplies none — the API only reads bytes. */
|
|
39
46
|
const DEFAULT_REPORT_FILENAME = "report.xml";
|
|
@@ -44,6 +51,24 @@ function requireString(args, key) {
|
|
|
44
51
|
}
|
|
45
52
|
return value;
|
|
46
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The tool's own arguments as a query string — no defaults, no renames.
|
|
56
|
+
*
|
|
57
|
+
* `undefined`/`null` are dropped rather than sent as empty strings: the two
|
|
58
|
+
* monitoring reads treat an absent filter and a blank one differently, and the
|
|
59
|
+
* server owns every default (page size, window). Everything else is stringified
|
|
60
|
+
* verbatim, so an opaque cursor goes back exactly as it came out.
|
|
61
|
+
*/
|
|
62
|
+
function queryString(args) {
|
|
63
|
+
const params = new URLSearchParams();
|
|
64
|
+
for (const [key, value] of Object.entries(args)) {
|
|
65
|
+
if (value === undefined || value === null)
|
|
66
|
+
continue;
|
|
67
|
+
params.append(key, typeof value === "string" ? value : String(value));
|
|
68
|
+
}
|
|
69
|
+
const encoded = params.toString();
|
|
70
|
+
return encoded === "" ? "" : `?${encoded}`;
|
|
71
|
+
}
|
|
47
72
|
/** Run one tool and return the API's parsed body, untouched. */
|
|
48
73
|
export async function callTool(name, args = {}) {
|
|
49
74
|
const route = ROUTES[name];
|
|
@@ -54,6 +79,9 @@ export async function callTool(name, args = {}) {
|
|
|
54
79
|
const domain = requireString(args, "domain");
|
|
55
80
|
return getJson(`/api/v1/report/${encodeURIComponent(domain)}`);
|
|
56
81
|
}
|
|
82
|
+
if (route.kind === "query") {
|
|
83
|
+
return getJson(`${route.path}${queryString(args)}`);
|
|
84
|
+
}
|
|
57
85
|
if (route.kind === "upload") {
|
|
58
86
|
const content = requireString(args, "content_base64");
|
|
59
87
|
const filename = args["filename"];
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GAEvB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/instructions.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
DNS Doctor scans a domain's email-authentication posture (SPF, DMARC, DKIM, MX, DNS, blacklist, expiry) and returns deterministic verdicts plus copy-paste fix records generated by a validating engine — never a guessed record. Workflow: call scan_domain (or get_report), read the verdicts failing-first (a 'temperror' status is transient, NOT a failure), then call build_dmarc_upgrade for a DMARC enforcement record. ONE EXCEPTION TO READING FAILING-FIRST: when a report carries not_registered: true the domain has no DNS records at all, so no check ran and every status is an 'info' placeholder — zero failing checks there does NOT mean the domain is healthy. Report that the domain does not resolve (usually a typo), propose no records for it, and do not offer monitoring until it resolves. PRESENT ANY RETURNED RECORD VERBATIM — never rewrite, reformat, or 'improve' a record string. build_dmarc_upgrade may return record: null (the domain does not exist; the DMARC lookup itself hit NXDOMAIN while the existence probe did not resolve; the DMARC lookup temp-failed; or the domain already applies a policy at least as strong as the one this scan justifies) — relay its 'rationale' as the answer and NEVER compose a record yourself to fill the gap. 'policy' describes the returned record and is null whenever record is; the domain's observed policy is in 'current_policy'. A human must approve every DNS change; nothing is applied automatically. SPF is diagnose-only: relay the report's SPF findings, but never propose SPF record edits of your own (e.g. tightening ~all to -all) — an SPF change can silently de-authorize a real sender, which is why the engine emits no SPF fix record. Scan responses end with a next_steps block: relay it — safe DMARC enforcement needs ~30 days of aggregate-report (RUA) evidence that no chat session can watch, so when a domain lacks reporting, call start_monitoring_signup and give the human the signup_url it returns. That tool sends no email and creates nothing: the human opens the link, signs in themselves, and adds the domain themselves. Never ask a human for their email address to pass to us, and never invent one — hand over the link and let them sign in on our page. Also share the report_url and monitor_url links from next_steps.
|
|
1
|
+
DNS Doctor scans a domain's email-authentication posture (SPF, DMARC, DKIM, MX, DNS, blacklist, expiry) and returns deterministic verdicts plus copy-paste fix records generated by a validating engine — never a guessed record. Workflow: call scan_domain (or get_report), read the verdicts failing-first (a 'temperror' status is transient, NOT a failure), then call build_dmarc_upgrade for a DMARC enforcement record. ONE EXCEPTION TO READING FAILING-FIRST: when a report carries not_registered: true the domain has no DNS records at all, so no check ran and every status is an 'info' placeholder — zero failing checks there does NOT mean the domain is healthy. Report that the domain does not resolve (usually a typo), propose no records for it, and do not offer monitoring until it resolves. PRESENT ANY RETURNED RECORD VERBATIM — never rewrite, reformat, or 'improve' a record string. build_dmarc_upgrade may return record: null (the domain does not exist; the DMARC lookup itself hit NXDOMAIN while the existence probe did not resolve; the DMARC lookup temp-failed; or the domain already applies a policy at least as strong as the one this scan justifies) — relay its 'rationale' as the answer and NEVER compose a record yourself to fill the gap. 'policy' describes the returned record and is null whenever record is; the domain's observed policy is in 'current_policy'. A human must approve every DNS change; nothing is applied automatically. SPF is diagnose-only: relay the report's SPF findings, but never propose SPF record edits of your own (e.g. tightening ~all to -all) — an SPF change can silently de-authorize a real sender, which is why the engine emits no SPF fix record. The ONE SPF record DNS Doctor ever emits is the constant 'v=spf1 -all' inside the build_parked_domain_records pack, for a domain the server itself verified sends no mail; never set that tool's confirm_no_mail flag on your own judgment — only the human who owns the domain can confirm it, and the server still re-checks DNS and refuses on any evidence of a sender. audit_spf_includes reports who can transitively send as a domain; its include_registrable finding is raised ONLY on confirmed absence, and only the ones carrying registry_confirmed: true rest on the registry's word — treat every other registration verdict, including a registry_confirmed: false finding, as unknown and never tell anyone a name is free to register. Scan responses end with a next_steps block: relay it — safe DMARC enforcement needs ~30 days of aggregate-report (RUA) evidence that no chat session can watch, so when a domain lacks reporting, call start_monitoring_signup and give the human the signup_url it returns. That tool sends no email and creates nothing: the human opens the link, signs in themselves, and adds the domain themselves. Never ask a human for their email address to pass to us, and never invent one — hand over the link and let them sign in on our page. Also share the report_url and monitor_url links from next_steps. Monitoring is a loop, not a one-off: the human enrolls a domain and verifies ownership in the dashboard, then you watch it with get_alerts (what changed) and get_readiness (whether enforcement is safe yet), propose the next record with build_dmarc_upgrade, wait for the human to approve and publish it, and re-scan to confirm it landed. Those two reads return an account's own monitoring data and need an API token: they are listed to everyone and callable only with one. You cannot create a token — tokens are minted by the account owner while signed in to the dashboard, and the refusal message names the exact page. Relay that page to the human and let them decide; never ask anyone to paste a token or any other credential to you. Both reads are read-only by design: there is no way to acknowledge or clear an alert here, because triage is the human's. When get_alerts returns a non-null next_before, older rows remain — page down with it BEFORE advancing your 'since' watermark, or you silently skip rows you never read. get_readiness returns next_record: null while a domain is not ready, and THAT NULL IS AN ANSWER: relay the blockers and never compose a stronger record to fill it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dnsdoctor/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"mcpName": "dev.dnsdoctor/dns-doctor",
|
|
4
5
|
"description": "Thin stdio MCP server for DNS Doctor \u2014 diagnoses SPF, DMARC, DKIM, MX, DNS, blacklist and expiry over the public DNS Doctor REST API.",
|
|
5
6
|
"license": "Apache-2.0",
|
|
6
7
|
"author": {
|
package/tools.json
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"annotations": {
|
|
4
|
+
"destructiveHint": false,
|
|
5
|
+
"idempotentHint": true,
|
|
6
|
+
"openWorldHint": true,
|
|
7
|
+
"readOnlyHint": true
|
|
8
|
+
},
|
|
9
|
+
"description": "Audit a domain's SPF supply chain: walks every include and redirect it delegates to, and reports who can transitively send as it. Returns the resolved tree, per-node lookup attribution, the total authorized IPv4 address count, and typed findings \u2014 include_broken (a target that no longer publishes SPF, a PermError today), include_registrable (a delegated-to domain that does not exist, so a stranger who registers it becomes an authorized sender), include_expiring (registration lapsing within 30 days), pass_all_nested (a +all deep in the chain) and spf_record_unusable (the audited domain's OWN record is missing or does not parse, so there is no chain to walk). A domain we could not verify is reported as unverified and NEVER as available \u2014 never tell anyone a name is free on this tool's say-so unless the finding is include_registrable AND carries registry_confirmed: true. A registry_confirmed: false finding rests on DNS alone, which cannot tell an unsold name from one in redemption or on clientHold: report the mechanism as broken and the takeover risk as possible, but never as an available domain. Findings are risk analysis, not instructions: no SPF fix record exists here or anywhere else in DNS Doctor, because dropping a mechanism can silently de-authorize a real sender \u2014 relay the findings and let the domain's owner decide. Use count_spf_lookups instead when the question is only the 10-lookup limit.",
|
|
10
|
+
"inputSchema": {
|
|
11
|
+
"properties": {
|
|
12
|
+
"domain": {
|
|
13
|
+
"description": "The domain to check, e.g. example.com. Bare registrable names and subdomains both work; scheme, path or port do not belong here. Unicode names are accepted and normalized to punycode.",
|
|
14
|
+
"title": "Domain",
|
|
15
|
+
"type": "string"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"required": [
|
|
19
|
+
"domain"
|
|
20
|
+
],
|
|
21
|
+
"title": "audit_spf_includesArguments",
|
|
22
|
+
"type": "object"
|
|
23
|
+
},
|
|
24
|
+
"name": "audit_spf_includes"
|
|
25
|
+
},
|
|
2
26
|
{
|
|
3
27
|
"annotations": {
|
|
4
28
|
"destructiveHint": false,
|
|
@@ -6,7 +30,7 @@
|
|
|
6
30
|
"openWorldHint": true,
|
|
7
31
|
"readOnlyHint": false
|
|
8
32
|
},
|
|
9
|
-
"description": "Return a validated DMARC enforcement-upgrade record for a domain. The alignment gate (whether p=reject is safe) is derived server-side from a scan \u2014 a caller can never assert alignment. `record` is null when there is no honest upgrade to offer (the domain does not exist; the DMARC lookup itself hit NXDOMAIN while the existence probe did not resolve; the DMARC lookup temp-failed; or the domain already applies a policy at least as strong as this scan justifies): relay `rationale` and never compose a record to fill the gap. Present a returned record verbatim; a human must approve before publishing.",
|
|
33
|
+
"description": "Return a validated DMARC enforcement-upgrade record for a domain. The alignment gate (whether p=reject is safe) is derived server-side from a scan \u2014 a caller can never assert alignment. `record` is null when there is no honest upgrade to offer (the domain does not exist; the DMARC lookup itself hit NXDOMAIN while the existence probe did not resolve; the DMARC lookup temp-failed; or the domain already applies a policy at least as strong as this scan justifies): relay `rationale` and never compose a record to fill the gap. A returned record also carries np=reject (the DMARCbis tag covering non-existent subdomains, which can have no legitimate aligned mail) unless the domain already publishes an np tag, which is preserved as-is. Present a returned record verbatim; a human must approve before publishing.",
|
|
10
34
|
"inputSchema": {
|
|
11
35
|
"properties": {
|
|
12
36
|
"domain": {
|
|
@@ -23,6 +47,49 @@
|
|
|
23
47
|
},
|
|
24
48
|
"name": "build_dmarc_upgrade"
|
|
25
49
|
},
|
|
50
|
+
{
|
|
51
|
+
"annotations": {
|
|
52
|
+
"destructiveHint": false,
|
|
53
|
+
"idempotentHint": true,
|
|
54
|
+
"openWorldHint": true,
|
|
55
|
+
"readOnlyHint": true
|
|
56
|
+
},
|
|
57
|
+
"description": "Build the three-record hardening pack that makes a NON-SENDING domain unusable for spoofing: a Null MX, a hard-fail SPF record, and a p=reject; np=reject DMARC record. For parked, redirect and brand-defensive domains only \u2014 NEVER for a domain that sends any mail, including transactional or one legacy system. Do NOT set confirm_no_mail on your own judgment or because a scan looked quiet: only the human who owns the domain can confirm it sends nothing, so ask them first. That flag unlocks the question, not the answer \u2014 the server re-checks DNS itself (existence, MX, SPF, DKIM selectors) and returns records: null with a rationale when it finds evidence of mail; relay that rationale rather than retrying. A lookup failure is reported as a failure, never as a pack. Publishing is the human's decision: present the records verbatim, in the order given, and let them approve each one.",
|
|
58
|
+
"inputSchema": {
|
|
59
|
+
"properties": {
|
|
60
|
+
"confirm_no_mail": {
|
|
61
|
+
"description": "Must be true, and only the HUMAN who owns the domain may decide it: it records their confirmation that this domain sends no email at all. Never set it on your own judgment or because a scan looked quiet \u2014 ask them. It unlocks the question only; the server independently re-checks DNS for evidence of mail and refuses when it finds any.",
|
|
62
|
+
"title": "Confirm No Mail",
|
|
63
|
+
"type": "boolean"
|
|
64
|
+
},
|
|
65
|
+
"domain": {
|
|
66
|
+
"description": "The domain to check, e.g. example.com. Bare registrable names and subdomains both work; scheme, path or port do not belong here. Unicode names are accepted and normalized to punycode.",
|
|
67
|
+
"title": "Domain",
|
|
68
|
+
"type": "string"
|
|
69
|
+
},
|
|
70
|
+
"rua_email": {
|
|
71
|
+
"anyOf": [
|
|
72
|
+
{
|
|
73
|
+
"type": "string"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"type": "null"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"default": null,
|
|
80
|
+
"description": "Mailbox to receive DMARC aggregate (RUA) reports, as a plain address like dmarc@example.com. Strongly recommended: without it nobody can see who sends as the domain.",
|
|
81
|
+
"title": "Rua Email"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"required": [
|
|
85
|
+
"domain",
|
|
86
|
+
"confirm_no_mail"
|
|
87
|
+
],
|
|
88
|
+
"title": "build_parked_domain_recordsArguments",
|
|
89
|
+
"type": "object"
|
|
90
|
+
},
|
|
91
|
+
"name": "build_parked_domain_records"
|
|
92
|
+
},
|
|
26
93
|
{
|
|
27
94
|
"annotations": {
|
|
28
95
|
"destructiveHint": false,
|
|
@@ -178,7 +245,7 @@
|
|
|
178
245
|
"openWorldHint": false,
|
|
179
246
|
"readOnlyHint": true
|
|
180
247
|
},
|
|
181
|
-
"description": "Build a DMARC record from scratch for a domain that has none, using a validating engine \u2014 never compose one yourself. `policy` is none|quarantine|reject; optional `rua_email` (aggregate-report destination), `subdomain_policy`, and `strict_alignment`. The generated record is re-validated before it is returned. Present it verbatim; a human must approve before publishing.",
|
|
248
|
+
"description": "Build a DMARC record from scratch for a domain that has none, using a validating engine \u2014 never compose one yourself. `policy` is none|quarantine|reject; optional `rua_email` (aggregate-report destination), `subdomain_policy`, and `strict_alignment`. Every generated record carries np=reject \u2014 the DMARCbis tag for non-existent subdomains, which can have no legitimate aligned mail \u2014 independently of the p= you choose. The generated record is re-validated before it is returned. Present it verbatim; a human must approve before publishing.",
|
|
182
249
|
"inputSchema": {
|
|
183
250
|
"properties": {
|
|
184
251
|
"policy": {
|
|
@@ -237,6 +304,104 @@
|
|
|
237
304
|
},
|
|
238
305
|
"name": "generate_dmarc_record"
|
|
239
306
|
},
|
|
307
|
+
{
|
|
308
|
+
"annotations": {
|
|
309
|
+
"destructiveHint": false,
|
|
310
|
+
"idempotentHint": true,
|
|
311
|
+
"openWorldHint": false,
|
|
312
|
+
"readOnlyHint": true
|
|
313
|
+
},
|
|
314
|
+
"description": "Read the monitoring alert log for the domains the caller's account monitors, newest first. Requires an API token. Each row carries id, domain, type, check, summary, a deterministic detail map, created_at, email_sent_at, acknowledged_at and delivery_class \u2014 a 'dashboard_only' row was deliberately kept out of the digest mail, so an agent watching only the inbox would never see it; this log is the complete picture. PAGE DOWN BEFORE ADVANCING `since`: next_before is non-null exactly when older rows remain, and a caller that ignores it, takes a full page and moves its watermark to the newest row it saw drops every row it did not receive. `since` is an INCLUSIVE floor, so rows repeat rather than go missing \u2014 de-duplicate on id. READ-ONLY by decision: there is no ack and no delete here, because acknowledging an alert is the human's own triage on their dashboard and an agent that acks on their behalf silences a row they have never seen. Report what the log says and let them clear it.",
|
|
315
|
+
"inputSchema": {
|
|
316
|
+
"properties": {
|
|
317
|
+
"before": {
|
|
318
|
+
"anyOf": [
|
|
319
|
+
{
|
|
320
|
+
"type": "string"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"type": "null"
|
|
324
|
+
}
|
|
325
|
+
],
|
|
326
|
+
"default": null,
|
|
327
|
+
"description": "The opaque cursor from a previous page's next_before, relayed verbatim to fetch the next older page. Never construct or edit one.",
|
|
328
|
+
"title": "Before"
|
|
329
|
+
},
|
|
330
|
+
"domain": {
|
|
331
|
+
"anyOf": [
|
|
332
|
+
{
|
|
333
|
+
"type": "string"
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
"type": "null"
|
|
337
|
+
}
|
|
338
|
+
],
|
|
339
|
+
"default": null,
|
|
340
|
+
"description": "Optional filter to ONE of the account's verified monitored domains. Omit it for every domain the account monitors; an unowned or unknown name is refused as not found.",
|
|
341
|
+
"title": "Domain"
|
|
342
|
+
},
|
|
343
|
+
"limit": {
|
|
344
|
+
"default": 50,
|
|
345
|
+
"description": "Page size, 1..100 (default 50). Page down with `before` before you advance `since`, or you will skip every row you did not receive.",
|
|
346
|
+
"title": "Limit",
|
|
347
|
+
"type": "integer"
|
|
348
|
+
},
|
|
349
|
+
"since": {
|
|
350
|
+
"anyOf": [
|
|
351
|
+
{
|
|
352
|
+
"type": "string"
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"type": "null"
|
|
356
|
+
}
|
|
357
|
+
],
|
|
358
|
+
"default": null,
|
|
359
|
+
"description": "Optional ISO-8601 timestamp: return alerts created at or after it (INCLUSIVE). Poll by storing the newest created_at you have seen and passing it back \u2014 rows repeat rather than go missing, so de-duplicate on id.",
|
|
360
|
+
"title": "Since"
|
|
361
|
+
},
|
|
362
|
+
"type": {
|
|
363
|
+
"anyOf": [
|
|
364
|
+
{
|
|
365
|
+
"type": "string"
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
"type": "null"
|
|
369
|
+
}
|
|
370
|
+
],
|
|
371
|
+
"default": null,
|
|
372
|
+
"description": "Optional alert-type filter, e.g. 'record_changed'. An unknown value is rejected rather than silently returning an empty page \u2014 omit it unless you know the exact type.",
|
|
373
|
+
"title": "Type"
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
"title": "get_alertsArguments",
|
|
377
|
+
"type": "object"
|
|
378
|
+
},
|
|
379
|
+
"name": "get_alerts"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"annotations": {
|
|
383
|
+
"destructiveHint": false,
|
|
384
|
+
"idempotentHint": true,
|
|
385
|
+
"openWorldHint": true,
|
|
386
|
+
"readOnlyHint": true
|
|
387
|
+
},
|
|
388
|
+
"description": "Read the DMARC enforcement-readiness verdict for ONE domain the caller's account monitors, computed from its aggregate (RUA) report window. Requires an API token. Returns whether the domain is ready to step its policy up, the blockers that say why it is not, the window the verdict rests on, and next_record \u2014 the validated record for the next step, generated by the engine and null while blocked. THAT NULL IS AN ANSWER: relay the blockers and never compose a stronger record to fill the gap. Present a returned record verbatim; a human must approve it before it is published. Use this before proposing enforcement \u2014 a scan can show a domain's current policy, but only this evidence window can say whether tightening it would start rejecting real mail.",
|
|
389
|
+
"inputSchema": {
|
|
390
|
+
"properties": {
|
|
391
|
+
"domain": {
|
|
392
|
+
"description": "One of the token account's VERIFIED monitored domains, e.g. example.com. Any other name \u2014 another account's, or one nobody monitors \u2014 is refused as not found; ownership is never disclosed.",
|
|
393
|
+
"title": "Domain",
|
|
394
|
+
"type": "string"
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
"required": [
|
|
398
|
+
"domain"
|
|
399
|
+
],
|
|
400
|
+
"title": "get_readinessArguments",
|
|
401
|
+
"type": "object"
|
|
402
|
+
},
|
|
403
|
+
"name": "get_readiness"
|
|
404
|
+
},
|
|
240
405
|
{
|
|
241
406
|
"annotations": {
|
|
242
407
|
"destructiveHint": false,
|