@theronap/agnoclast-mcp 0.9.153 → 0.9.154
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/lib/server.mjs +16 -23
- package/package.json +1 -1
package/lib/server.mjs
CHANGED
|
@@ -1438,7 +1438,7 @@ function renderNudge(payload) {
|
|
|
1438
1438
|
{
|
|
1439
1439
|
title: 'Read a wiki page in full',
|
|
1440
1440
|
description:
|
|
1441
|
-
'READ the full authored wiki page for one node (project/person/org/you) by its canonical name — every section
|
|
1441
|
+
'READ the full authored wiki page for one node (project/person/org/you) by its canonical name — every section you can see. This is how you READ a node; `grep` only LOCATES pages (snippets + their [[links]]), it does not read them. Navigate like a researcher: read the page you need, then FOLLOW its inline [[links]] by calling read_page on each linked name — keep following while the linked pages stay relevant, stop when they do not. You decide how deep to go. Returns only what you are permitted to see.',
|
|
1442
1442
|
inputSchema: {
|
|
1443
1443
|
name: z.string().describe('the canonical node name exactly as written (e.g. "Agnoclast", "Ben", or a [[link]] target) — identifier links ([[repo:owner/name]]) resolve to their authored HOME + a visible-event count'),
|
|
1444
1444
|
kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('node kind — a PREFERENCE, not a filter. The lookup matches every kind by title and only prefers this one when several share a name, so omitting it never hides a person/org/user page. Pass it to disambiguate a name that exists as more than one kind.'),
|
|
@@ -1598,7 +1598,7 @@ function renderNudge(payload) {
|
|
|
1598
1598
|
// unrecoverable base_version guessing loop; say so explicitly instead.
|
|
1599
1599
|
const versionLine = t.version
|
|
1600
1600
|
? `\nversion: ${t.version}`
|
|
1601
|
-
: `\nversion: none (this
|
|
1601
|
+
: `\nversion: none (this page predates content-hash tracking — base_version writes will always fail here; ask an admin about the ADR-0018 backfill)`
|
|
1602
1602
|
const head = `[${t.tier}${day(t.updated_at) ? ` · authored ${day(t.updated_at)}` : ''}${t.validity && t.validity !== 'current' ? ` · ${t.validity}` : ''}${versionLine}]`
|
|
1603
1603
|
// ADR-0030 step 3b (2026-08-24): the summary header is NO LONGER RENDERED here either.
|
|
1604
1604
|
// ⚠ There were TWO of these — project_status and read_page — and the first patch fixed only
|
|
@@ -1824,19 +1824,18 @@ function renderNudge(payload) {
|
|
|
1824
1824
|
'rename_section',
|
|
1825
1825
|
{
|
|
1826
1826
|
title: 'Rename one section heading on a wiki page',
|
|
1827
|
-
description: 'Rename ONE section\'s heading on a wiki page, in place. Use this instead of re-authoring the page: `author` cannot express a rename, because a section\'s IDENTITY is its heading string — sending a new title reads as "dropped the old section, added a new one" and is rejected 409 would_drop_sections. This route changes the heading and NOTHING else: the section keeps its body, its position, and crucially its as-of date, so renaming does not reset the currency stamp on a claim nobody re-verified. It is also the ONLY way to fix a heading longer than the 80-char limit, which cannot be re-authored at all. Requires base_version (the `version` read_page prints) — that is also how it finds the right brain, so it can never rename on the wrong one. Refuses, rather than guessing, when the
|
|
1827
|
+
description: 'Rename ONE section\'s heading on a wiki page, in place. Use this instead of re-authoring the page: `author` cannot express a rename, because a section\'s IDENTITY is its heading string — sending a new title reads as "dropped the old section, added a new one" and is rejected 409 would_drop_sections. This route changes the heading and NOTHING else: the section keeps its body, its position, and crucially its as-of date, so renaming does not reset the currency stamp on a claim nobody re-verified. It is also the ONLY way to fix a heading longer than the 80-char limit, which cannot be re-authored at all. Requires base_version (the `version` read_page prints) — that is also how it finds the right brain, so it can never rename on the wrong one. Refuses, rather than guessing, when the heading repeats, or when the new heading is already taken.',
|
|
1828
1828
|
inputSchema: {
|
|
1829
1829
|
name: z.string().describe('the exact page name, as read_page shows it'),
|
|
1830
1830
|
from: z.string().describe('the CURRENT heading, exactly as stored (match is case- and whitespace-insensitive)'),
|
|
1831
1831
|
to: z.string().describe('the new heading. Max 80 chars. Must not already be used by another section on this page.'),
|
|
1832
1832
|
base_version: z.string().describe('the `version` read_page prints for this page (64-hex). REQUIRED — it is the concurrency check AND how the right brain is resolved. A per-SECTION hash is not valid here.'),
|
|
1833
|
-
tier: z.enum(['accessible', 'scoped', 'confidential']).optional().describe('only when the page exists at MORE THAN ONE tier — which one to rename in. A rename never moves content between tiers.'),
|
|
1834
1833
|
ordinal: z.number().optional().describe('only when the same heading appears more than once on the page — which occurrence to rename (the error lists the ordinals).'),
|
|
1835
1834
|
reason: z.string().optional().describe('why you are renaming it — recorded in page_history like any other edit'),
|
|
1836
1835
|
identity_claim_ack: z.boolean().optional().describe('ONLY after a 409 identity_claim refusal, and only if the answer is genuinely yes: this text is meant to publish an email address or phone number as page content at this tier — e.g. the subject is publishing their OWN contact detail on their own page. Leave unset otherwise; the alternatives the refusal names (a confidential section, or set_routing_identifier for a private claim) are the right answer in every other case.'),
|
|
1837
1836
|
},
|
|
1838
1837
|
},
|
|
1839
|
-
async ({ name, from, to, base_version,
|
|
1838
|
+
async ({ name, from, to, base_version, ordinal, reason, identity_claim_ack }) => {
|
|
1840
1839
|
let res
|
|
1841
1840
|
try {
|
|
1842
1841
|
res = await fetchCortex(`${BASE}/api/brain/rename-section`, {
|
|
@@ -1844,7 +1843,7 @@ function renderNudge(payload) {
|
|
|
1844
1843
|
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
1845
1844
|
body: JSON.stringify({
|
|
1846
1845
|
name, from, to, base_version,
|
|
1847
|
-
...(
|
|
1846
|
+
...(ordinal !== undefined ? { ordinal } : {}),
|
|
1848
1847
|
...(reason ? { reason } : {}), ...(identity_claim_ack ? { identity_claim_ack: true } : {}),
|
|
1849
1848
|
}),
|
|
1850
1849
|
})
|
|
@@ -1880,7 +1879,6 @@ function renderNudge(payload) {
|
|
|
1880
1879
|
into: z.string().optional().describe('the heading of the section that SURVIVES and absorbs the content. Omit to drop `from` outright without folding it anywhere.'),
|
|
1881
1880
|
new_body: z.string().optional().describe('the condensed body the surviving section should read AFTER the merge — you write it, this route does not summarize. Required with `into`; must be absent without it.'),
|
|
1882
1881
|
base_version: z.string().describe('the `version` read_page prints for this page (64-hex). REQUIRED — it is the concurrency check AND how the right brain is resolved. A per-SECTION hash is not valid here.'),
|
|
1883
|
-
tier: z.enum(['accessible', 'scoped', 'confidential']).optional().describe('only when the page exists at MORE THAN ONE tier — which one to condense in. A merge never moves content between tiers.'),
|
|
1884
1882
|
from_ordinal: z.number().optional().describe('only when `from`\'s heading appears more than once on the page — which occurrence (the error lists the ordinals).'),
|
|
1885
1883
|
into_ordinal: z.number().optional().describe('only when `into`\'s heading appears more than once on the page — which occurrence (the error lists the ordinals).'),
|
|
1886
1884
|
reason: z.string().optional().describe('WHY you are condensing, in one short phrase — recorded in page_history. Say what became redundant, not what you did.'),
|
|
@@ -1888,7 +1886,7 @@ function renderNudge(payload) {
|
|
|
1888
1886
|
identity_claim_ack: z.boolean().optional().describe('ONLY after a 409 identity_claim refusal, and only if the answer is genuinely yes: this text is meant to publish an email address or phone number as page content at this tier.'),
|
|
1889
1887
|
},
|
|
1890
1888
|
},
|
|
1891
|
-
async ({ name, from, into, new_body, base_version,
|
|
1889
|
+
async ({ name, from, into, new_body, base_version, from_ordinal, into_ordinal, reason, drop_links_ack, identity_claim_ack }) => {
|
|
1892
1890
|
let res
|
|
1893
1891
|
try {
|
|
1894
1892
|
res = await fetchCortex(`${BASE}/api/brain/merge-sections`, {
|
|
@@ -1897,7 +1895,6 @@ function renderNudge(payload) {
|
|
|
1897
1895
|
body: JSON.stringify({
|
|
1898
1896
|
name, from, base_version,
|
|
1899
1897
|
...(into ? { into } : {}), ...(new_body !== undefined ? { new_body } : {}),
|
|
1900
|
-
...(tier ? { tier } : {}),
|
|
1901
1898
|
...(from_ordinal !== undefined ? { from_ordinal } : {}),
|
|
1902
1899
|
...(into_ordinal !== undefined ? { into_ordinal } : {}),
|
|
1903
1900
|
...(reason ? { reason } : {}),
|
|
@@ -2754,11 +2751,10 @@ function renderNudge(payload) {
|
|
|
2754
2751
|
summary: z.string().describe('the new summary: one sentence saying what this is and where it stands. May contain [[links]]. Max 2,000 chars — detail belongs in a section.'),
|
|
2755
2752
|
base_version: z.string().describe('the `version` read_page prints for this page (64-hex). REQUIRED — it is the concurrency check AND how the right brain is resolved. A per-SECTION hash is not valid here.'),
|
|
2756
2753
|
reason: z.string().optional().describe('WHY the summary was wrong, in one short phrase — recorded in page_history. Say what changed ("blocker resolved 08-04; was still claiming BLOCKED"), not what you did.'),
|
|
2757
|
-
tier: z.enum(['accessible', 'scoped', 'confidential']).optional().describe('only when the page exists at MORE THAN ONE tier — which one to rewrite. This never moves content between tiers.'),
|
|
2758
2754
|
identity_claim_ack: z.boolean().optional().describe('ONLY after a 409 identity_claim refusal, and only if the answer is genuinely yes: this text is meant to publish an email address or phone number as page content at this tier — e.g. the subject is publishing their OWN contact detail on their own page. Leave unset otherwise; the alternatives the refusal names (a confidential section, or set_routing_identifier for a private claim) are the right answer in every other case.'),
|
|
2759
2755
|
},
|
|
2760
2756
|
},
|
|
2761
|
-
async ({ name, summary, base_version, reason,
|
|
2757
|
+
async ({ name, summary, base_version, reason, identity_claim_ack }) => {
|
|
2762
2758
|
let res
|
|
2763
2759
|
try {
|
|
2764
2760
|
res = await fetchCortex(`${BASE}/api/brain/set-summary`, {
|
|
@@ -2766,7 +2762,7 @@ function renderNudge(payload) {
|
|
|
2766
2762
|
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
2767
2763
|
body: JSON.stringify({
|
|
2768
2764
|
name, summary, base_version,
|
|
2769
|
-
...(
|
|
2765
|
+
...(reason ? { reason } : {}),
|
|
2770
2766
|
...(identity_claim_ack ? { identity_claim_ack: true } : {}),
|
|
2771
2767
|
}),
|
|
2772
2768
|
})
|
|
@@ -2803,12 +2799,11 @@ function renderNudge(payload) {
|
|
|
2803
2799
|
new_string: z.string().describe('what to replace it with. May be empty, which deletes the anchored text.'),
|
|
2804
2800
|
base_version: z.string().describe('the `version` read_page prints for this page (64-hex). REQUIRED — it is the concurrency check AND how the right brain is resolved.'),
|
|
2805
2801
|
reason: z.string().describe('WHY you are making this change, in one short phrase — recorded in page_history exactly like an author edit.'),
|
|
2806
|
-
tier: z.enum(['accessible', 'scoped', 'confidential']).optional().describe('only when the page exists at MORE THAN ONE tier — which one to edit. An edit never moves content between tiers.'),
|
|
2807
2802
|
ordinal: z.number().optional().describe('only when the same heading appears more than once on the page — which occurrence (the error lists the ordinals).'),
|
|
2808
2803
|
identity_claim_ack: z.boolean().optional().describe('ONLY after a 409 identity_claim refusal, and only if the answer is genuinely yes: this text is meant to publish an email address or phone number as page content at this tier — e.g. the subject is publishing their OWN contact detail on their own page. Leave unset otherwise; the alternatives the refusal names (a confidential section, or set_routing_identifier for a private claim) are the right answer in every other case.'),
|
|
2809
2804
|
},
|
|
2810
2805
|
},
|
|
2811
|
-
async ({ name, heading, old_string, new_string, base_version, reason,
|
|
2806
|
+
async ({ name, heading, old_string, new_string, base_version, reason, ordinal, identity_claim_ack }) => {
|
|
2812
2807
|
let res
|
|
2813
2808
|
try {
|
|
2814
2809
|
res = await fetchCortex(`${BASE}/api/brain/edit-page`, {
|
|
@@ -2816,7 +2811,7 @@ function renderNudge(payload) {
|
|
|
2816
2811
|
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
2817
2812
|
body: JSON.stringify({
|
|
2818
2813
|
name, heading, old_string, new_string, base_version, reason,
|
|
2819
|
-
...(
|
|
2814
|
+
...(ordinal !== undefined ? { ordinal } : {}),
|
|
2820
2815
|
...(identity_claim_ack ? { identity_claim_ack: true } : {}),
|
|
2821
2816
|
}),
|
|
2822
2817
|
})
|
|
@@ -3148,7 +3143,7 @@ function renderNudge(payload) {
|
|
|
3148
3143
|
org_id: z.string().describe('the org id of the brain to enumerate (from my_brains)'),
|
|
3149
3144
|
owner: z.string().optional().describe('only pages owned by this person — a user id or an EXACT display name. A name matching no member of the brain is an error, never a silently empty list. Owner applies to scoped/confidential pages; accessible pages have no owner.'),
|
|
3150
3145
|
updated_within_days: z.number().optional().describe('only pages touched in the last N days'),
|
|
3151
|
-
tier: z.enum(['accessible', 'scoped', 'confidential']).optional().describe('only
|
|
3146
|
+
tier: z.enum(['accessible', 'scoped', 'confidential']).optional().describe('only pages at this tier'),
|
|
3152
3147
|
kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('only nodes of this kind'),
|
|
3153
3148
|
validity: z.enum(['current', 'superseded', 'all']).optional().describe("default 'current'. Pass 'all' for the pre-2026-08 behavior, which mixed superseded pages in with nothing marking them."),
|
|
3154
3149
|
name_contains: z.string().optional().describe('case-insensitive substring match on the node name or page title'),
|
|
@@ -3960,16 +3955,15 @@ function renderNudge(payload) {
|
|
|
3960
3955
|
ref: z.string().optional().describe('the page\'s stable id, printed as `ref:` by read_page. Prefer this over name: unique across brains, so it addresses exactly one page.'),
|
|
3961
3956
|
grantee: z.string().describe('display name or email of a member OF THE PAGE\'S BRAIN (must resolve uniquely — use email if ambiguous)'),
|
|
3962
3957
|
action: z.enum(['grant', 'revoke']).describe('grant or revoke'),
|
|
3963
|
-
tier: z.enum(['scoped', 'confidential']).optional().describe('which variant (default: the most restrictive one)'),
|
|
3964
3958
|
},
|
|
3965
3959
|
},
|
|
3966
|
-
async ({ kind, name, ref, grantee, action
|
|
3960
|
+
async ({ kind, name, ref, grantee, action }) => {
|
|
3967
3961
|
let res
|
|
3968
3962
|
try {
|
|
3969
3963
|
res = await fetchCortex(`${BASE}/api/brain/page-grants`, {
|
|
3970
3964
|
method: 'POST',
|
|
3971
3965
|
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
|
|
3972
|
-
body: JSON.stringify({ kind, ...(name ? { name } : {}), ...(ref ? { ref } : {}), grantee, action
|
|
3966
|
+
body: JSON.stringify({ kind, ...(name ? { name } : {}), ...(ref ? { ref } : {}), grantee, action }),
|
|
3973
3967
|
})
|
|
3974
3968
|
} catch (e) {
|
|
3975
3969
|
return toolError(`Could not ${action}: ${e.message}`)
|
|
@@ -3985,7 +3979,7 @@ function renderNudge(payload) {
|
|
|
3985
3979
|
'list_page_grants',
|
|
3986
3980
|
{
|
|
3987
3981
|
title: 'List who has granted access to your page',
|
|
3988
|
-
description: 'Show every explicit access grant on YOUR
|
|
3982
|
+
description: 'Show every explicit access grant on YOUR pages (owner-only). Use after re-tiering a page — grants survive tier changes and keep granting until revoked.',
|
|
3989
3983
|
inputSchema: {
|
|
3990
3984
|
kind: z.enum(['project', 'person', 'org', 'user']).describe('the page kind'),
|
|
3991
3985
|
name: z.string().optional().describe('the exact page name (or pass `ref` instead — one of the two is required)'),
|
|
@@ -4005,7 +3999,7 @@ function renderNudge(payload) {
|
|
|
4005
3999
|
const out = await res.json().catch(() => null)
|
|
4006
4000
|
if (!res.ok) return toolError(`Could not list grants: ${out?.error ?? res.status}`)
|
|
4007
4001
|
if (!out.grants?.length) return { content: [{ type: 'text', text: 'No grants on this page.' }] }
|
|
4008
|
-
const lines = out.grants.map((g) => `- ${g.grantee_name ?? g.grantee_user_id} → ${g.tier}
|
|
4002
|
+
const lines = out.grants.map((g) => `- ${g.grantee_name ?? g.grantee_user_id} → ${g.tier} page (since ${String(g.created_at).slice(0, 10)})`)
|
|
4009
4003
|
return { content: [{ type: 'text', text: `Grants (${out.grants.length}):\n${lines.join('\n')}` }] }
|
|
4010
4004
|
},
|
|
4011
4005
|
)
|
|
@@ -4367,8 +4361,7 @@ function renderNudge(payload) {
|
|
|
4367
4361
|
// last chance to notice a page was fixed somewhere nobody reads.
|
|
4368
4362
|
const corrections = Array.isArray(out?.tierCorrections) && out.tierCorrections.length
|
|
4369
4363
|
? `\n⚠ TIER: this wrote to ${out.tierCorrections.map((c) => `${c.actualTier} (you asked for ${c.requestedTier})`).join('; ')}.` +
|
|
4370
|
-
`
|
|
4371
|
-
` a page can exist at several tiers and they drift apart independently.`
|
|
4364
|
+
` A page has one tier and author writes at it; to change who can see the page, use set_page_privacy.`
|
|
4372
4365
|
: ''
|
|
4373
4366
|
// KWA-26 — the advisory undated flag. The server has computed `undatedSections` since #558 and
|
|
4374
4367
|
// the route has returned it ever since; NOTHING PRINTED IT, so the one consumer the item names
|
package/package.json
CHANGED