@prefix sioc: <http://rdfs.org/sioc/ns#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix content: <http://purl.org/rss/1.0/modules/content/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<https://lhero.org/portfolio/lh-mcp-general-abilities/>
  a sioc:Post ;
  dc:title "Notes" ;
  dcterms:identifier 148354 ;
  dc:modified "2026-10-01T00:06:43Z"^^xsd:dateTime ;
  dc:created "2026-09-24T19:25:24Z"^^xsd:dateTime ;
  sioc:link <https://lhero.org/portfolio/lh-mcp-general-abilities/notes/> ;
  sioc:has_creator <https://lhero.org/author/1/#account> ;
  sioc:has_container <https://lhero.org/#posts> ;
  content:encoded "<ul class=\"lh_portfolio-meta\"><li><strong>Part of:</strong> <a href=\"https://lhero.org/portfolio/lh-mcp-general-abilities/\">LH MCP General Abilities</a></li></ul><p class=\"wp-block-paragraph\">Rationale and cross-ability notes behind the <a href=\"https://lhero.org/portfolio/lh-mcp-general-abilities/abilities/\">generated ability reference</a>.</p><h2 class=\"wp-block-heading\">Permission gates</h2><p class=\"wp-block-paragraph\">Most abilities are gated to <code>edit_posts</code> by a single plugin-wide permission check. Two differ: <code>search-users</code> requires <code>list_users</code>, because it returns email addresses, and <code>get-site-info</code> requires nothing at all. That gate is deliberately coarse: it establishes that the caller edits content on this site and nothing more, so any ability touching a specific object does its own per-object check on top of it (<code>read_post</code> in <code>get-post</code>, <code>edit_post</code> in <code>update-post</code>, the target resource&#8217;s own permission callback in <code>read-mcp-resource</code>).</p><h2 class=\"wp-block-heading\">get-site-info is open on purpose</h2><p class=\"wp-block-paragraph\">It is a trivial ability anyone can use as a connection test. Everything it can return already appears in the markup of every front-end page. What keeps that safe is a fixed, deliberately non-filterable allowlist: requested fields are intersected against it before anything reaches <code>get_bloginfo()</code>, which would otherwise hand back <code>admin_email</code>, the WordPress version and the template paths. audit-abilities flags its <code>__return_true</code> permission callback; that warning is accepted.</p><h2 class=\"wp-block-heading\">Reading content</h2><p class=\"wp-block-paragraph\"><code>get-post</code> is constrained to the readable post-type allowlist (<code>post</code> and <code>page</code> by default). <code>content_raw</code> is always complete; <code>content_markdown</code> is the part that can be absent. <code>markdown_source</code> takes one of five values. <code>lh-content-converter</code> means the conversion ran (before 1.11.0 this value was <code>lh-mcp-block-transformer</code>). <code>empty</code> means the post has no content, so <code>content_markdown</code> is an empty string. <code>classic</code> means the post has no block markup to convert (classic-editor content, a bare shortcode or embed URL); it is expected rather than an error, and <code>content_raw</code> should be read and edited in whatever form it was saved. <code>unavailable</code> means LH Content Converter is not active, and <code>failed</code> means it ran and reported an error; both are genuine problems, and <code>notice</code> carries what to tell the user.</p><p class=\"wp-block-paragraph\"><code>list-posts</code> is deliberately not narrowed to the readable allowlist, since it returns titles, slugs, links and dates rather than content. Whether it should be narrowed is a real question, and an open one.</p><h2 class=\"wp-block-heading\">Authoring content</h2><p class=\"wp-block-paragraph\">Both write abilities are draft-only by construction rather than by default: there is no status parameter in either input schema (inputs are closed, so passing one is refused), and the status is written as a literal. No call through either can publish, schedule, or set a member-visibility status. Publishing stays a human act.</p><p class=\"wp-block-paragraph\"><code>create-post</code> is restricted to the writable post-type allowlist (<code>post</code> and <code>page</code> by default, filterable, and deliberately separate from the readable one) and checks the type&#8217;s own <code>create_posts</code> capability. Everything is validated before the insert runs, so a failure on meta or terms leaves no empty draft behind. It is annotated non-idempotent: repeating a call creates a second draft.</p><p class=\"wp-block-paragraph\"><code>update-post</code> has patch semantics: a field is written only if it was supplied, so the natural way to retitle a post is to send a title alone. Which posts may be touched is decided by an allowlist of current statuses (draft, pending, auto-draft) rather than by excluding publish, so a status nobody anticipated is refused by default. Anything already live is refused with a pointer to <code>lh-agora/propose-changes</code>. It is annotated destructive, because supplied fields replace what was there and the post&#8217;s revisions are the only way back, and idempotent, because repeating the same call leaves the post in the same state.</p><h2 class=\"wp-block-heading\">Users</h2><p class=\"wp-block-paragraph\"><code>search-users</code> ranks across nine priorities: exact beats partial, and last name beats first name. Multi-word input runs an AND pass first and falls back to OR if that yields nothing. Each result reports which field and match type determined its rank.</p><h2 class=\"wp-block-heading\">MCP resources</h2><p class=\"wp-block-paragraph\"><code>list-mcp-resources</code> and <code>read-mcp-resource</code> let a caller discover and read abilities registered as MCP resources rather than tools, instead of a human attaching them by hand. <code>read-mcp-resource</code> invokes the target exactly as the MCP resources/read handler does, and its <code>content</code> is whatever the target returns, typed as any JSON value. audit-abilities asks for an <code>items</code> schema on it because the type list includes array; that warning is accepted.</p>"^^rdf:XMLLiteral ;
  sioc:content "Part of: LH MCP General AbilitiesRationale and cross-ability notes behind the generated ability reference.Permission gatesMost abilities are gated to edit_posts by a single plugin-wide permission check. Two differ: search-users requires list_users, because it returns email addresses, and get-site-info requires nothing at all. That gate is deliberately coarse: it establishes that the caller edits content on this site and nothing more, so any ability touching a specific object does its own per-object check on top of it (read_post in get-post, edit_post in update-post, the target resource&#8217;s own permission callback in read-mcp-resource).get-site-info is open on purposeIt is a trivial ability anyone can use as a connection test. Everything it can return already appears in the markup of every front-end page. What keeps that safe is a fixed, deliberately non-filterable allowlist: requested fields are intersected against it before anything reaches get_bloginfo(), which would otherwise hand back admin_email, the WordPress version and the template paths. audit-abilities flags its __return_true permission callback; that warning is accepted.Reading contentget-post is constrained to the readable post-type allowlist (post and page by default). content_raw is always complete; content_markdown is the part that can be absent. markdown_source takes one of five values. lh-content-converter means the conversion ran (before 1.11.0 this value was lh-mcp-block-transformer). empty means the post has no content, so content_markdown is an empty string. classic means the post has no block markup to convert (classic-editor content, a bare shortcode or embed URL); it is expected rather than an error, and content_raw should be read and edited in whatever form it was saved. unavailable means LH Content Converter is not active, and failed means it ran and reported an error; both are genuine problems, and notice carries what to tell the user.list-posts is deliberately not narrowed to the readable allowlist, since it returns titles, slugs, links and dates rather than content. Whether it should be narrowed is a real question, and an open one.Authoring contentBoth write abilities are draft-only by construction rather than by default: there is no status parameter in either input schema (inputs are closed, so passing one is refused), and the status is written as a literal. No call through either can publish, schedule, or set a member-visibility status. Publishing stays a human act.create-post is restricted to the writable post-type allowlist (post and page by default, filterable, and deliberately separate from the readable one) and checks the type&#8217;s own create_posts capability. Everything is validated before the insert runs, so a failure on meta or terms leaves no empty draft behind. It is annotated non-idempotent: repeating a call creates a second draft.update-post has patch semantics: a field is written only if it was supplied, so the natural way to retitle a post is to send a title alone. Which posts may be touched is decided by an allowlist of current statuses (draft, pending, auto-draft) rather than by excluding publish, so a status nobody anticipated is refused by default. Anything already live is refused with a pointer to lh-agora/propose-changes. It is annotated destructive, because supplied fields replace what was there and the post&#8217;s revisions are the only way back, and idempotent, because repeating the same call leaves the post in the same state.Userssearch-users ranks across nine priorities: exact beats partial, and last name beats first name. Multi-word input runs an AND pass first and falls back to OR if that yields nothing. Each result reports which field and match type determined its rank.MCP resourceslist-mcp-resources and read-mcp-resource let a caller discover and read abilities registered as MCP resources rather than tools, instead of a human attaching them by hand. read-mcp-resource invokes the target exactly as the MCP resources/read handler does, and its content is whatever the target returns, typed as any JSON value. audit-abilities asks for an items schema on it because the type list includes array; that warning is accepted." ;
  sioc:topic <https://lhero.org/?taxonomy=author&term=cap-1> .

<https://lhero.org/author/1/#account> rdfs:seeAlso <https://lhero.org/author/1/?feed=lhrdf&format=turtle> .
<https://lhero.org/?taxonomy=author&term=cap-1> rdfs:seeAlso <https://lhero.org/?taxonomy=author&term=cap-1&feed=lhrdf&format=turtle> .
