From 10570fb9bf93f1a04be4082f1641b47fde02572d Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 1 Sep 2026 23:17:42 -0700 Subject: [PATCH 1/3] fix(webmcp): publish human-readable skill catalogue --- src/webmcp/index.njk | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/webmcp/index.njk b/src/webmcp/index.njk index d864ad6..ff4daae 100644 --- a/src/webmcp/index.njk +++ b/src/webmcp/index.njk @@ -8,7 +8,7 @@ webmcp: true

WebMCP Challenge · live public surface

The Interdependency WebMCP

-

The page is the provider. Humans can operate the registry here; WebMCP-capable browsers receive the same five tools through document.modelContext.registerTool(...); remote MCP clients connect to the website-owned Render runtime.

+

The page is the provider. Humans can browse and operate the skill registry here; WebMCP-capable browsers receive the same five operations through document.modelContext.registerTool(...); remote MCP clients connect to the website-owned Render runtime.

Remote MCP: https://the-interdependency-mcp.onrender.com/mcp

Open remote MCP health check

@@ -18,12 +18,41 @@ webmcp: true

Loading the commit-pinned registry and checking browser WebMCP support…

Checking remote MCP health…

Registry source: resolving…

- + + + +
+

Human surface · {{ generated.skillRegistry.skills.length }} skills

+

Skill catalogue

+

These are the skills exposed by the same commit-pinned registry used by WebMCP and the remote MCP server. The description shown here is the registry's human-readable statement of when and why each skill is used.

+ +
+ + + {{ generated.skillRegistry.skills.length }} skills shown +
+ +
+ {% for skill in generated.skillRegistry.skills %} +
+

{{ skill.kind }}

+

{{ skill.name | replace("-", " ") }}

+

{{ skill.name }}

+

{{ skill.description }}

+ {% if skill.depends_on and skill.depends_on.length %} +

Depends on: {% for dependency in skill.depends_on %}{{ dependency }}{% if not loop.last %}, {% endif %}{% endfor %}

+ {% else %} +

Depends on: none declared

+ {% endif %} +

Read canonical SKILL.md

+
+ {% endfor %} +

Five registry operations

-

The controls below execute the same read-only registry logic exposed to agents.

+

These controls execute the same read-only registry logic exposed to browser agents and remote MCP clients. The raw output is intentionally shown here as the machine-facing view of the same catalogue above.

@@ -52,13 +81,14 @@ webmcp: true

-

One source, two agent surfaces

+

One source, three views

skill-lib/SKILL.md + skills.json
         ↓ commit-pinned projection
 website registry logic
+        ├── human-readable skill catalogue
         ├── browser WebMCP provider
         └── remote Streamable HTTP MCP server
-

The website owns both delivery surfaces. The-Interdependency/skill-lib remains authority for the skills themselves. All v0 operations are read-only.

+

The website owns the delivery surfaces. The-Interdependency/skill-lib remains authority for the skills themselves. All v0 operations are read-only.

hmmmWrite-capable operations remain intentionally absent. Installing, propagating, or changing repository state requires a separate authenticated boundary rather than silently enlarging this public surface.
From c123136e6717484b795a091020bf1c8e5b4e7c25 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 1 Sep 2026 23:18:13 -0700 Subject: [PATCH 2/3] feat(webmcp): filter human skill catalogue --- src/assets/js/webmcp.js | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/assets/js/webmcp.js b/src/assets/js/webmcp.js index 42e4ec3..2000a53 100644 --- a/src/assets/js/webmcp.js +++ b/src/assets/js/webmcp.js @@ -2,7 +2,7 @@ import { createSkillRegistry } from './webmcp-registry.js'; // === MODULE_BUILD === // id: interdependency_webmcp_surface -// purpose: Register the website-owned, read-only WebMCP tool surface and provide the same registry operations to the human-facing demo page. +// purpose: Register the website-owned, read-only WebMCP tool surface, provide the same registry operations to the human-facing demo page, and filter the build-time human skill catalogue. // entrypoint: /webmcp/ // tests: tests/webmcp.test.mjs // === END MODULE_BUILD === @@ -19,8 +19,13 @@ import { createSkillRegistry } from './webmcp-registry.js'; // given: a human control or browser agent invokes any v0 operation // then: execution reads the generated registry projection and returns structured results without mutating the site, GitHub, or skill-lib // class: safety +// +// id: webmcp_human_catalogue_remains_source_bound +// given: a visitor browses or filters the human-readable skill catalogue +// then: filtering only hides or reveals build-time cards derived from the same generated registry and never creates a second skill definition +// class: correctness // === END CONTRACTS === -// Usage: open `/webmcp/` in any browser to exercise the five registry operations directly. In a WebMCP-capable browser the same operations register through `document.modelContext.registerTool(...)`. The remote MCP endpoint is independently health-checked and remains read-only. +// Usage: open `/webmcp/` in any browser to browse the human-readable skill catalogue and exercise the five registry operations directly. In a WebMCP-capable browser the same operations register through `document.modelContext.registerTool(...)`. The remote MCP endpoint is independently health-checked and remains read-only. const REGISTRY_URL = '/assets/data/skill-registry.json'; const REMOTE_MCP_BASE = 'https://the-interdependency-mcp.onrender.com'; @@ -81,6 +86,30 @@ async function checkRemoteMcp() { } } +function bindHumanCatalogue() { + const form = document.querySelector('[data-human-skill-filter-form]'); + const input = document.querySelector('[data-human-skill-filter]'); + const count = document.querySelector('[data-human-skill-count]'); + const cards = [...document.querySelectorAll('[data-human-skill]')]; + + form?.addEventListener('submit', event => event.preventDefault()); + if (!input || cards.length === 0) return; + + const applyFilter = () => { + const query = String(input.value || '').trim().toLowerCase(); + let visible = 0; + for (const card of cards) { + const show = !query || card.textContent.toLowerCase().includes(query); + card.hidden = !show; + if (show) visible += 1; + } + if (count) count.textContent = `${visible} of ${cards.length} skills shown`; + }; + + input.addEventListener('input', applyFilter); + applyFilter(); +} + function bindHumanControls(registry) { document.querySelector('[data-webmcp-action="status"]')?.addEventListener('click', () => { showResult('STATUS', registry.getRegistryStatus()); @@ -128,11 +157,12 @@ export async function registerInterdependencyWebMCP() { const registry = createSkillRegistry(data); const status = registry.getRegistryStatus(); updateSource(status); + bindHumanCatalogue(); bindHumanControls(registry); void checkRemoteMcp(); if (!modelContext()?.registerTool) { - setStatus(`Registry live for ${status.skill_count} skills. Browser WebMCP registration requires a WebMCP-capable browser; the human controls and remote MCP server remain usable.`, 'hmmm'); + setStatus(`Registry live for ${status.skill_count} skills. Browser WebMCP registration requires a WebMCP-capable browser; the human catalogue, controls, and remote MCP server remain usable.`, 'hmmm'); return { registered: false, reason: 'webmcp-unavailable', registry: status }; } if (globalThis.__interdependencyWebMcpRegistered) { From 89ad27fe15852a33a950b7cf0c1fa4d6d8aa16c9 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 1 Sep 2026 23:18:39 -0700 Subject: [PATCH 3/3] test(webmcp): require human-readable skill catalogue --- tests/webmcp.test.mjs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/webmcp.test.mjs b/tests/webmcp.test.mjs index a1f239a..e17e612 100644 --- a/tests/webmcp.test.mjs +++ b/tests/webmcp.test.mjs @@ -4,7 +4,7 @@ import { readFile } from 'node:fs/promises'; import { normalizeRegistry, readFallback } from '../scripts/fetch-skill-registry.mjs'; import { createSkillRegistry } from '../src/assets/js/webmcp-registry.js'; -// Usage: run with `npm test`; these checks verify registry provenance, dependency closure, clean-checkout fallback identity, current WebMCP Document API usage, live human controls, and the visible remote MCP endpoint without requiring a WebMCP-capable test browser. +// Usage: run with `npm test`; these checks verify registry provenance, dependency closure, clean-checkout fallback identity, current WebMCP Document API usage, the build-time human-readable skill catalogue, live human controls, and the visible remote MCP endpoint without requiring a WebMCP-capable test browser. const BOOTSTRAP_SNAPSHOT_COMMIT = '260671303733a45c8f8d5563e41d8854e09856e6'; const SNAPSHOT_PATH = 'src/_data/snapshots/skill-registry.last-known-good.json'; @@ -90,18 +90,20 @@ test('WebMCP provider registers only the five declared read-only registry tools assert.doesNotMatch(source, /unregisterTool|install_skill|propagate_skill|update_file|create_file/); }); -test('registry provenance and human controls resolve before unsupported-browser WebMCP return', async () => { +test('human catalogue and controls resolve before unsupported-browser WebMCP return', async () => { const source = await readFile('src/assets/js/webmcp.js', 'utf8'); const loadIndex = source.indexOf('const data = await loadRegistry();'); const sourceIndex = source.indexOf('updateSource(status);'); + const catalogueIndex = source.indexOf('bindHumanCatalogue();'); const controlsIndex = source.indexOf('bindHumanControls(registry);'); const unsupportedIndex = source.indexOf("if (!modelContext()?.registerTool)"); assert.ok(loadIndex >= 0 && sourceIndex > loadIndex); - assert.ok(controlsIndex > sourceIndex); + assert.ok(catalogueIndex > sourceIndex); + assert.ok(controlsIndex > catalogueIndex); assert.ok(unsupportedIndex > controlsIndex); }); -test('dedicated WebMCP route is the visible browser and remote MCP front door', async () => { +test('dedicated WebMCP route is a human-readable skill browser and the agent front door', async () => { const [page, layout, packageJson] = await Promise.all([ readFile('src/webmcp/index.njk', 'utf8'), readFile('src/_includes/layouts/base.njk', 'utf8'), @@ -114,6 +116,15 @@ test('dedicated WebMCP route is the visible browser and remote MCP front door', assert.match(page, /document\.modelContext\.registerTool/); assert.match(page, /https:\/\/the-interdependency-mcp\.onrender\.com\/mcp/); assert.match(page, /data-remote-mcp-status/); + + assert.match(page, /id="skill-catalog-title">Skill catalogue/); + assert.match(page, /generated\.skillRegistry\.skills/); + assert.match(page, /data-human-skill-filter/); + assert.match(page, /data-human-skill-catalog/); + assert.match(page, /data-human-skill/); + assert.match(page, /skill\.description/); + assert.match(page, /Read canonical SKILL\.md/); + assert.match(page, /data-webmcp-action="status"/); assert.match(page, /data-webmcp-find/); assert.match(page, /data-webmcp-inspect/);