Knowledge Islands MCP standards
You are helping audit, conform, or scaffold a workspace MCP server — one of the stdio MCP servers in the knowledgeislands/ workspace (mcp-git-audit, mcp-ki-repo-kb-fs, mcp-gsuite, mcp-m365, mcp-housekeeping-claude, mcp-ki-repo-kb-notion-mirror). They all share one canonical shape, so a new one should be scaffolded to it and an existing one should be auditable against it. This skill carries that standard and the audit procedure.
This skill audits declared server source shape — src/ layout, config injection, tool declarations, and tooling. A structural result is not evidence that a server was registered, loaded, secured, or executed. A repo's GitHub configuration and standard files, and a SKILL.md's prose, are out of scope (other skills own those). How the skills divide the work is documented once in the ki-agentic-harness README.md.
The full, quotable standard lives in Workspace MCP Standard; the line-by-line pass/fail items live in Audit Rubric. The hosted rubric contract is scripts/rubric/items/index.ts, enforced by ki repo audit --skill ki-repo-mcp. Read those when you need detail; this file is the operating procedure.
The canonical shape at a glance
src/
├── config/index.ts # loadConfig(env?) → Config. NO module-level singleton; nothing reads env at import time.
├── mcp-server/index.ts# stdio entry: loadConfig() once, makeAccessGatedRegister, registerXxxTools(server, config). Coverage-excluded.
├── tools/<group>/index.ts # thin: zod-validate args → call main/, map to MCP envelope (jsonResult/errorResult). Coverage-excluded. NO logic.
├── main/<concern>/ # the real implementation, usable from a script. Each entry takes its config slice as the FIRST arg.
├── cli/ # OPTIONAL human-runnable bin: cli.ts does ALL stdout printing; index.ts re-exports main. Mirrors the tool surface. cli.ts coverage-excluded.
└── utils/ # cross-MCP helpers kept in sync across siblings: access-level.ts, annotations.ts, audit-log.ts, paths/results, …
Three rules define the layer boundaries — most audit findings are a violation of one:
- Config is injected, never ambient.
loadConfig()is called exactly once (inmcp-server/index.ts, or by acli.ts/ a script). No other module readsprocess.envat import time; everymain/andutils/function takes the config (or the specific slice it needs) as its first argument. - Layers have one job each.
tools/validates + envelopes and nothing more;main/holds the logic and is runnable without the MCP server;cli/only prints (the library it calls never writes to stdout);mcp-server/only wires. - The access-level gate is annotation-driven, not name-driven. A tool registers only if the level derived from its
annotationsis ≤config.accessLevel. See the gate rules below.
Surface-area model: main vs tools vs cli
Decide where code lives by who needs to call it, not by what it does:
main/— the implementation, callable from anywhere. May contain: all real logic, FS / network / git, returns plain data. Must not: print to stdout/stderr or read env directly.tools/— exposesmain/over MCP. May contain: zod schema, arg validation,jsonResult/errorResult. Must not: hold logic or be the only caller ofmain/.cli/— exposesmain/to a human at a terminal. May contain: arg parsing and ALL human-readable printing. Must not: hold logic or be the only caller ofmain/.
main/ is the single source of truth; tools/ and cli/ are two thin shells over the same functions. If logic exists only inside a tool handler or only inside the CLI, that is a finding — push it down into main/. Group main/ by concern, mirroring the tool groups (main/repo-audit/, main/notes/, …), each with an index.ts re-export, and surface the reusable ones through the package exports map so the code is consumable as a library.
Tool naming
<app>_<resource>_<action>, snake_case. <app> is fixed per repo (git, kb, gsuite, m365, claude_code/claude_desktop/vscode, voicenotes, notion_mirror). Plural resource for collection ops (git_repos_scan, gsuite_email_messages_search); singular for single-item ops (kb_note_read, git_repo_commit). Metadata/lifecycle tools may drop the resource segment (gsuite_auth_start, m365_about). The CLI verb surface mirrors these names.
Access-level gate
makeAccessGatedRegister(server, accessLevel, audit) in utils/access-level.ts derives a level from each tool's annotations and registers it only when that level ≤ config.accessLevel (env MCP_<APP>_ACCESS_LEVEL, default read; levels nest read ⊂ write ⊂ destructive):
readOnlyHint: true→ readdestructiveHint: true→ destructive- explicit
readOnlyHint: falseANDdestructiveHint: false→ write - anything else (unannotated / partial) → destructive (fail-safe)
Every tool MUST set annotations to a preset from utils/annotations.ts (READ_ONLY, WRITE, WRITE_IDEMPOTENT, DESTRUCTIVE, DESTRUCTIVE_ONESHOT, and the _REMOTE variants). DESTRUCTIVE_ONESHOT is for tools whose end state depends on current FS/index state (running twice ≠ same result). Never bypass the register proxy. The default read gate hides every mutation until the operator opts in.
Operating modes
Every governance skill carries the universal four AUDIT · CONFORM · EDUCATE · REFRESH; EDUCATE here scaffolds a new server. Invoked as help / -h / ?, it explains itself and stops — the generated HELP block (name, purpose, invocation, modes, off-ramps), taking no action. With no mode it does the same, then, in an interactive session only, offers the mode choice via AskUserQuestion, prompting for any argument-hint target the chosen mode shows.
Mode AUDIT
→ Read references/mode-audit.md
Mode CONFORM
→ Read references/mode-conform.md
Mode EDUCATE
→ Read references/mode-educate.md
Mode REFRESH
→ Read references/mode-refresh.md
Bun vs Node — the common layer
The Bun-install / Node-run split, the bun test trap, and the process.loadEnvFile() parity call are the common engineering standard—ki-engineering owns and checks them through ki repo audit --skill ki-engineering. The one MCP-relevant consequence to keep in mind: NODE_ENV=development is set only by the ki:server:mcp:dev / :inspect scripts, so in production .env.* is ignored and config must come from the MCP client's env block.
Runtime binding — MCP operating surfaces
Server-code governance and surface operation are separate. Two standards remain here because they capture MCP-specific runtime semantics that the server standard must route to, while ki-binding owns actual cross-surface agreement and safe file-backed binding:
- Cross-surface MCP enablement defines the canonical source, per-surface control boundary, Cowork sandbox constraint, and binding sequence.
- claude.ai connector control defines the manual account, organization, and managed Claude Code policy layers for connectors with no local conform surface.
Both are report-only from ki-repo-mcp. Client registration, web/admin changes, remote deployment, generated clients, and repository-defined commands are never hidden conform operations.
Notes
- This skill targets the standard documented in the sibling repos' own
CLAUDE.mdfiles; when they disagree, the majority shape is the standard and the outlier is a finding (unless the outlier is a deliberate, documented exception). When unsure whether a divergence is intentional, ask rather than "fix" it. - Keep the shared
utils/helpers (access-level.ts,annotations.ts,audit-log.ts) in sync across repos — a fix to one usually applies to all. - The standard sits on top of a moving spec. When citing a requirement, know whether it is spec-driven (traces to the official MCP spec in the source list) or house style — never present a workspace preference as a protocol "MUST". Run Mode REFRESH when in doubt.
- Full detail: Workspace MCP Standard, Audit Rubric, and the tracked source list.