skills/ARCHIVE/archive-2026-07-16-zen/KERNEL-mcp-builder/SKILL.md
Canonical MCP server-builder doctrine — naming laws, capability declaration discipline, _meta envelope contract, lifecycle notification rules, error envelope contract, and the 28 bindings every MCP server in arifOS federation must honour. Load before designing, scaffolding, or auditing ANY MCP server surface.
npx skillsauth add ariffazil/openclaw-workspace KERNEL-mcp-builderInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The LLM discovers and invokes tools/resources/prompts via the model + the host. Your server's
nameanddescriptionfields are not documentation. They are the interface contract to agentic intelligence. Bad naming causes silent misfires — the model picks the wrong tool, fills wrong arguments, skips a tool entirely. No error is thrown. The system just produces wrong outputs.
DITEMPA BUKAN DIBERI — Naming is the first act of creation.
| Primitive | Decided by | Surfaced via | GEOX example |
|---|---|---|---|
| Tools | Model (LLM) | tools/call | geox_basin(mode='macrostrat') |
| Resources | Host (Cursor/app) | resources/read after host injects | geox://literature/sabah/madon-2021 |
| Prompts | User | /slash_name | /analyse-well-log |
Critical split: Resources are application-driven — server exposes, host decides context inclusion, server only hints via annotations. DO NOT rely on resources being model-callable. Use TOOLS for that.
| # | Law | Consequence of violation |
|---|---|---|
| 1 | name is a machine contract — never rename after publish | every agent, cached plan, every reference breaks |
| 2 | description is the model's only briefing | vague = misfire, hallucinated args, tool skipped silently. Must be 50-300 chars. Must include "Use when..." sentence. |
| 3 | title and name must diverge deliberately | human readable + machine stable |
| 4 | Argument names are semantic, not syntactic | latitude: float, longitude: float — never p1, p2 |
| 5 | Prompt names are user vocabulary | /analyse-well-log matches how a geologist thinks |
{
"name": "query_macrostrat_columns", // machine, snake_case, stable
"title": "Macrostrat Geological Columns", // human, readable, can change
"description": "Query Macrostrat for columnar stratigraphy at lat/lon — returns columns JSON with units, lithology, age. Use when the user asks about stratigraphic columns at a location."
}
| Field | Role in agentic intelligence |
|---|---|
| name | Unique identifier — the model calls this exactly |
| title | Human display only — model ignores |
| description | The model's decision surface — it reads this to decide whether + how to call |
| inputSchema.properties | Per-parameter intent — model fills arguments from description |
| Field | Role |
|---|---|
| name | Machine identifier — used in URI resolution |
| title | Host UI display |
| description | Host uses this to decide whether to inject into context |
| uri | The address — must be unambiguous, no collisions |
| annotations.audience | ["assistant"] for papers; ["user"] for index/README |
| annotations.priority | 0.7-0.9 papers, 1.0 LAS, 0.5 index |
| annotations.lastModified | ISO 8601 — clients sort by recency |
| mimeType | text/plain preferred for agents; application/pdf only for small blobs |
| size | Include byte count — clients budget context |
| Field | Role |
|---|---|
| name | Unique identifier — user types /this-exact-name |
| title | Slash command menu label |
| description | Tells user what the prompt does before trigger |
| arguments[].description | Drives autocomplete via completion API |
/mcp initialize response declares everything the client may use:
{
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": false, "listChanged": true },
"prompts": { "listChanged": true },
"logging": {},
"completions": {}
}
}
Hard rules:
subscribe, omit it — don't declare false and silently fail.completions, declare {} to signal API presence — clients may try.| Error type | Mechanism | When to use |
|---|---|---|
| Protocol error | JSON-RPC error field | unknown tool, invalid args, server crash |
| Execution error | isError: true in result | API timeout, bad response, business logic |
Live API failures (isError: true) NEVER throw protocol errors. The Macrostrat tool returns isError: true for API down — not JSON-RPC error.
| Decision | Use |
|---|---|
| Live API call (Macrostrat, USGS, etc.) | Tool with strict inputSchema |
| Cached snapshot of API response | Resource, mimeType application/json |
| Schema/citation metadata only | Resource (index) |
| Tool returns URIs + content | Tool result with embedded type: "resource" |
Hybrid pattern: tool returns content: [{type: "resource", resource: {uri, ...}}] to bridge cache + freshness.
_meta Envelope — Shape A (per MCP 2025-11-25)The _meta extension lives on the contents object, not the response envelope. Carries seal_id, evidence_class, authority, sha256 — survives client-side caching.
{
"contents": [{
"uri": "geox://literature/sabah/madon-2021",
"mimeType": "text/markdown",
"text": "...",
"_meta": {
"seal_id": "VAULT999-2026-0731-A7",
"evidence_class": "DERIVED",
"authority": "EVIDENCE",
"sha256": "c01031...",
"contract_version": "geox.resource.v2"
}
}]
}
audit_class enum (F2 TRUTH): OBSERVED / DERIVED / INTERPRETED / SPECULATED.
supportsListTemplates are pure URI shapes for resources/read. Discovery = resources/list (with cursor pagination). Completion = completion/complete for {param} enum.
# Templates are URI SHAPES:
geox://literature/{basin}/{paper_id} # shape for read
geox://wells/{basin}/{well_id}/logs # shape for read
# Discovery always via:
resources/list → cursor-paginated list
resources/templates/list → paginated list of shapes
# Completion for parameter hints:
completion/complete {ref: "geox://literature/{basin}", argument: {name: "basin"}}
→ returns ["sabah", "malay-basin", "sarawak", ...] (cap 100)
New tool → notifications/tools/list_changed (no payload)
New resource → notifications/resources/list_changed (no payload)
New prompt → notifications/prompts/list_changed (no payload)
Resource update → notifications/resources/updated { uri } (per-URI; only if subscribe: true)
Discipline: Never fire for undeclared capabilities. Client MUST send notifications/initialized before server sends anything except pings/logs. Timeouts on every request — use cancellation notifications; never hang.
| Type | audience | priority |
|---|---|---|
| Published papers | ["assistant"] | 0.7–0.9 |
| LAS well data | ["assistant"] | 1.0 |
| README/index | ["user"] | 0.5 |
| Internal analyses | ["user","assistant"] | 0.8 |
| Operator-private wells (F13) | ["assistant"] | 0.95 |
Always include lastModified for recency sort.
| Surface | Mechanism | Authority |
|---|---|---|
| Client fetches directly | https:// URI allowed | Client + server both see bytes |
| Server proxies | geox:// (custom scheme) + URL field | Server-stamped sha256 |
| File-backed (real or virtual) | file:// (no real FS required per spec) | Per organ |
| Version control | git:// built-in | For VCS integration |
For paywalled/internal papers: NEVER fake https:// you'll proxy. Use geox:// + server-side sha256.
cursor = base64url(json.dumps({
"p": page_idx, # 0-indexed
"f": filter_hash, # sha256 of filter args
"s": page_size,
"v": 1 # version
}))
Clients MUST NOT persist cursors across sessions. Server chooses page size dynamically under load; clients handle silently.
{ "code": -32002, "message": "Resource not found", "data": { "uri": "geox://..." } }
JSON-RPC codes for MCP:
-32002 not found-32003 forbidden (F13 gate)-32602 invalid params-32603 internal-32601 method not foundname is machine contract — never change after publishdescription is model's only briefing — vague = silent misfire. Must be 50-300 chars, include "Use when..." sentencetitle and name must diverge deliberately/mcp initialize)subscribe: false declared ONLY if not implemented; omit otherwiseisError: true for execution vs JSON-RPC error for protocol_meta Shape A on contents object (not envelope)supportsListlist_changed notifications have no payloadresources/updated per-URI fires only if subscribe: true negotiatedhttps:// only when client fetches directly — never fake for proxyfile:// no real FS requiredgit:// built-in for VCS integrationdata: {uri})messages[] can embed type: "resource" content blocks — pre-loads context before model reasoning (MCP 2025-06-18 spec)PromptArgument[]; no-default = required, has-default = optionalArgs: section becomes argument description — drives completion API autocompleteError Envelope Contract — every tool MUST return structured errors, never raw tracebacks. Minimum shape:
{
"error_code": "EXTERNAL_API_TIMEOUT",
"message": "Macrostrat API did not respond within 5s",
"retry_allowed": true,
"suggested_action": "Retry with shorter timeout or check Macrostrat status page"
}
Rules:
error_code = UPPER_SNAKE_CASE, domain-prefixed (e.g., GEOX_*, WEALTH_*, FORGE_*)message = human-readable, one sentence, no stack tracesretry_allowed = boolean — model uses this to decide whether to retrysuggested_action = what the model should tell the user OR try nextisError: true in MCP result envelope (per binding 10) wraps this payloaderror field (binding 10)External MCP Ingress — connecting to third-party MCP servers (e.g., DeepWiki, public MCP endpoints) MUST go through an ingress boundary:
forge_fetch SSRF rules (no internal IPs, no metadata endpoints)forge_work/ receiptsideEffects and authorityRequired annotations — the model needs to know if a tool is read-only or destructive. Governance gates need the authority band to fire.authorityRequired >= OPERATOR must call verify_federation_sct() before executing.Full reference: references/dual-surface-web.md
□ name field present + snake_case + stable across versions?
□ description field rich enough to brief an LLM in isolation? (50-300 chars, includes "Use when..." sentence)
□ title distinct from name (human ≠ machine)?
□ inputSchema properties each described (intent, not just type)?
□ capability declared in /mcp initialize response?
□ subscribe omitted if not implemented (NOT false)?
□ external live API — using TOOL not RESOURCE?
□ _meta lives on contents object (not envelope)?
□ template is URI shape only (no supportsList)?
□ annotations cover audience + priority + lastModified?
□ error codes include data.uri (per JSON-RPC convention)?
□ tool execution errors use structured envelope (error_code, message, retry_allowed, suggested_action)?
□ external MCP connections go through ingress boundary (SSRF + F12 + trust state)?
□ ISO 8601 timestamps (lastModified, read_at_iso, etc.)?
□ F2 evidence label (OBS/DERIVED/INT/SPEC) in every output?
□ F11 audit trail maintained (read ledger append, sha256, actor)?
□ F13 sensitive resource requires actor_signature?
□ cursor opaque + non-traversable + session-scoped?
□ [dual-surface] HTML and MCP share one domain service (no duplicated logic)?
□ [dual-surface] tool annotations include sideEffects + authorityRequired?
□ [dual-surface] public health endpoint works without auth?
□ [dual-surface] mutation tools validate SCT via verify_federation_sct()?
□ [dual-surface] humanEquivalent annotation set on mutation tools?
□ [dual-surface] WebMCP manifest at /.well-known/webmcp.json?
□ [dual-surface] agent card at /.well-known/agent.json?
| Floor | MCP binding |
|---|---|
| F1 AMANAH | reversible-first; backups before edits; irrefutable ops explicit |
| F2 TRUTH | evidence labels in _meta; capability declaration honest |
| F3 WITNESS | tri-witness on SEAL-grade claims; _meta.actor_signature slot |
| F4 CLARITY | single-URI-namespace; templates fixed param order |
| F5 PEACE² | de-escalate in description; never weaponize "must" |
| F6 MARUAH | pii_redacted field; dignity in error messages; error envelope never exposes internals |
| F7 HUMILITY | confidence cap 0.90 on claims; declare unknowns in description |
| F8 GENIUS | simplest correct path: description first, code second |
| F9 ANTI-HANTU | no soul claims; bundled cross-checks; tier caps on blobs |
| F10 ONTOLOGY | categorical scope: AI-only, substrate ≠ being |
| F11 AUDIT | every read appended to ledger; sha256 verified |
| F12 INJECTION | sanitize inputs; external ≠ authority |
| F13 SOVEREIGN | operator-private resources gated by actor_signature |
from fastmcp import FastMCP
from mcp.types import EmbeddedResource, TextResourceContents
from fastmcp.prompts.base import Message
from pydantic import AnyUrl
mcp = FastMCP("organ-name", version="v2026.07.x")
# Tool
@mcp.tool(name="query_macrostrat_columns",
annotations={"readOnlyHint": True, "openWorldHint": True})
async def query_macrostrat_columns(latitude: float, longitude: float) -> str:
"""Query Macrostrat for columnar stratigraphy at lat/lon — returns columns JSON ..."""
...
# Resource
@mcp.resource("geox://literature/{basin}/{paper_id}",
name="geox-literature-paper",
title="Geological Literature Paper",
mime_type="text/markdown",
annotations={"audience": ["assistant"], "priority": 0.85})
async def read_paper(basin: str, paper_id: str) -> str:
...
# Prompt — SIMPLE (string return, no args)
@mcp.prompt(name="geox_guard",
description="CONSTRAIN: F10 ontology enforcement.")
async def guard() -> str:
return GUARD_PROMPT_TEXT
# Prompt — SPEC-ALIGNED (messages[] with embedded resources + arguments)
# FastMCP infers PromptArgument[] from function signature automatically.
# Parameters with defaults = optional. Parameters without = required.
@mcp.prompt(name="analyse-well-log",
description="ANALYSE: single-well petrophysics + interpretation...")
async def analyse_well_log(las_path: str, well_name: str = "") -> list[Message]:
"""Analyse a single well log end-to-end.
Args:
las_path: Path to the LAS file
well_name: Well identifier for URI resolution
"""
return [
Message(f"Analyse this well log:\n\n{PROMPT_TEXT}", role="user"),
Message(
EmbeddedResource(
type="resource",
resource=TextResourceContents(
uri=AnyUrl(f"las://{well_name or 'unknown'}"),
mimeType="text/plain",
text=f"LAS path: {las_path}\nWell: {well_name}",
),
),
role="user",
),
]
import json
import httpx
# Tool with structured error envelope
@mcp.tool(name="fetch_external_data",
annotations={"readOnlyHint": True, "openWorldHint": True})
async def fetch_external_data(url: str) -> str:
"""Fetch data from an external API with governed error handling.
Use when the user requests data from a specific external source.
Returns JSON on success, structured error envelope on failure.
"""
try:
result = await httpx.get(url, timeout=5)
result.raise_for_status()
return result.text
except httpx.TimeoutException:
return json.dumps({
"error_code": "EXTERNAL_API_TIMEOUT",
"message": f"Request to {url} timed out after 5s",
"retry_allowed": True,
"suggested_action": "Retry with shorter timeout or check if the URL is reachable"
})
except httpx.HTTPStatusError as e:
return json.dumps({
"error_code": f"EXTERNAL_API_{e.response.status_code}",
"message": f"External API returned {e.response.status_code}",
"retry_allowed": e.response.status_code >= 500,
"suggested_action": "Check URL validity or try again later" if e.response.status_code >= 500 else "Do not retry — client error"
})
Why this matters: The FreeCodeCamp pattern returns raw text/errors. The model sees isError: true with a Python traceback and has to guess whether to retry. Our envelope tells the model: "here's what went wrong, here's whether you can retry, here's what to do next." The model makes better decisions. Fewer hallucinated retry loops.
Per MCP 2025-06-18 spec: prompts/get response messages[] supports type: "resource" content blocks. This lets prompts pre-load context before the model touches any tool.
When to embed resources in prompts:
When NOT to embed:
FastMCP argument inference:
async def fn(las_path: str) → PromptArgument(name="las_path", required=True)async def fn(name: str = "") → PromptArgument(name="name", required=False)Args: section → description field on each argumentname containing spaces, hyphens, or capitalsdescription < 50 chars (model's only briefing — must be 50-300 chars)description missing "Use when..." sentence (model needs trigger guidance)description > 300 chars (model skims, not reads — long descriptions get truncated mentally)uri mapping to https:// you'll proxysubscribe: true declared when handler not implemented_meta on envelope instead of contentstitle: matching name (must diverge)p1, p2, x, y, data (use semantic names)authorityRequired annotation (binding 32)humanEquivalent: null on mutation tools (binding 35 — no audit trail)| Field | Value | |---|---| | Name | unchanged since stable | | Description | brief on its own | | Capability declaration | honest | | Evidence class | OBS / DER / INT / SPEC | | Floor active | F1, F2, F11 (always); F13 if sensitive | | Audit trail | append-only ledger entry per read |
forge_work/YYYY-MM-DD/MCP-AUDIT-<organ>.md is the canonical receipt.
For every MCP-related change:
forge_work/YYYY-MM-DD/MCP-AUDIT-<organ>.md (per organ audit receipt)forge_work/YYYY-MM-DD/MCP-CONSOLIDATED.md (cross-organ summary)F1 AMANAH → /mcp read = non-mutating
F2 TRUTH → _meta.evidence_class present
F3 WITNESS → _meta.actor_signature on sealed claims
F4 CLARITY → no double registrations, no duplicate URIs
F5 PEACE² → de-escalate in tool descriptions
F6 MARUAH → pii_redacted + dignity-first error messages
F7 HUMILITY → confidence cap in description (never claim certainty)
F8 GENIUS → simplest correct path: description-first
F9 ANTI-HANTU → never claim consciousness via tool output
F10 ONTOLOGY → AI-vs-human categories preserved
F11 AUDIT → read_ledger.append + sha256 verified
F12 INJECTION → input sanitized; external ≠ authority
F13 SOVEREIGN → actor_signature on operator-private URIs
These bindings are derived from MCP 2025-06-18 spec text + 2025-11-25 server-card extensions + docs-agent 2026-07-10 consensus. Every binding cites its source primitive.
Naming is the first act of creation. Every server registers a contract with the model. Honour it.
When an organ serves both humans (HTML) and agents (MCP), the implementation must be one service with two faces — not two services pretending to be one.
Human Surface (HTML) ─┐
├─ Shared Domain Service ── one source of truth
Agent Surface (MCP) ─┘
| # | Binding | Consequence of violation |
|---|---|---|
| 29 | HTML and MCP must NOT contain separate business logic | Divergent behaviour — humans see one answer, agents see another |
| 30 | Both surfaces use the same canonical service layer | Bug fixes in one surface don't propagate to the other |
| 31 | Tool manifests generated from runtime registration | Stale manifests → model calls tools that don't exist |
| 32 | Agent tools declare sideEffects and authorityRequired annotations | Model cannot distinguish read from mutate; governance gates can't fire |
| 33 | Public health endpoints must NOT require sovereign sessions | Health probes are observational (T1); auth blocks monitoring |
| 34 | Protected tool routes validate signed session capability tokens | Unprotected mutation tools → authorization bypass |
| 35 | Browser-facing actions have machine-readable equivalents | Agents can't replicate what humans do; no audit trail linking agent action to human-visible effect |
| Annotation | Type | Meaning |
|---|---|---|
| readOnlyHint | bool | true = observation only |
| sideEffects | string | "none" / "state_change" / "external_action" / "irreversible" |
| authorityRequired | string | Minimum band: OBSERVE_ONLY / OPERATOR / LIMITED_MUTATE / FULL / SOVEREIGN |
| destructiveHint | bool | true = cannot be undone |
| humanEquivalent | string | Route on the human surface that does the same thing |
references/dual-surface-web.md — architecture pattern, shared service design, manifest generation, authority enforcement, WebMCP/agent card requirements, navigation consistency, design system contract, testing requirements, anti-patterns.
Load this reference when:
development
Federation-wide gold (XAUUSD) trading capability. Python stack, OANDA broker, backtesting, macro signals, RSI strategy. Every organ has a role.
development
Capital claim state management — tracks claim lifecycle across WEALTH organ.
development
Archived constitutional warga placeholder retained only for audit provenance. Do not use for active work; use the live arifOS governance and constitutional skills instead.
testing
Warga (citizen) agent skills for AAA federation members. See subdirectories for specialized warga skills.