From 1f6389c29e879adcac43fe2002891bcbc5d122c9 Mon Sep 17 00:00:00 2001 From: Simon Mak Date: Sat, 3 Oct 2026 01:46:19 +0800 Subject: [PATCH 1/2] feat(vdd): TDQS lint gate + official registry manifest + listing runbook (I-006/I-007) - vision: add I-006 (TDQS quality) and I-007 (top-5 registry distribution) - tactics: A-008..A-011 AMEND block - spec: vdd/specs/mcp-quality-and-registry/spec.md (clarified: 5/5=TDQS, 5 registries enumerated) + tdqs-baseline.md - disambiguate all tool descriptions vs bootstrap_host (TDQS coherence) - server.json for the Official MCP Registry + CI drift guard - docs/publishing/registry-listing.md runbook - verified: 49/49 tests, build ok, mcp-tdqs lint 0 errors --- .github/workflows/validate-inventory.yml | 14 ++ docs/publishing/registry-listing.md | 80 +++++++++++ mcp-server/src/tools.ts | 10 +- server.json | 24 ++++ vdd/specs/mcp-quality-and-registry/spec.md | 135 ++++++++++++++++++ .../mcp-quality-and-registry/tdqs-baseline.md | 39 +++++ vdd/tactics.md | 14 ++ vdd/vision.md | 2 + 8 files changed, 313 insertions(+), 5 deletions(-) create mode 100644 docs/publishing/registry-listing.md create mode 100644 server.json create mode 100644 vdd/specs/mcp-quality-and-registry/spec.md create mode 100644 vdd/specs/mcp-quality-and-registry/tdqs-baseline.md diff --git a/.github/workflows/validate-inventory.yml b/.github/workflows/validate-inventory.yml index 18311c0..f7a729a 100644 --- a/.github/workflows/validate-inventory.yml +++ b/.github/workflows/validate-inventory.yml @@ -66,6 +66,20 @@ jobs: print(f'Docs match config: {total} configured, {enabled} enabled') PYEOF + - name: Assert server.json matches package.json + run: | + python3 << 'PYEOF' + import json, sys + pkg = json.load(open('package.json')) + srv = json.load(open('server.json')) + vp, vs = pkg.get('version'), srv.get('version') + ident = (srv.get('packages') or [{}])[0].get('identifier') + print(f"package.json: {pkg.get('name')}@{vp}; server.json: {ident}@{vs}") + if vs != vp or ident != pkg.get('name'): + print('DRIFT: server.json must match package.json name/version') + sys.exit(1) + PYEOF + - name: Check skills count run: | count=$(find .opencode/skills -name "SKILL.md" 2>/dev/null | wc -l) diff --git a/docs/publishing/registry-listing.md b/docs/publishing/registry-listing.md new file mode 100644 index 0000000..50b4adb --- /dev/null +++ b/docs/publishing/registry-listing.md @@ -0,0 +1,80 @@ +# Registry listing runbook + +> Goal: make `opencode-workbench` discoverable in the five free MCP registries +> (Vision I-007 / spec `mcp-quality-and-registry`). Never commit API keys. + +Status as of 2026-10-03: + +| Registry | Mechanism | Status | Credential | +|----------|-----------|--------|------------| +| Official MCP Registry | `server.json` + `mcp-publisher` | manifest ready | GitHub OIDC | +| Glama | crawler + claim file | **live** (`/.well-known/glama.json`) | claim token | +| Smithery | `@smithery/cli publish` / API | prepared | `SMITHERY_API_KEY` | +| PulseMCP | submission form / API | prepared | account | +| mcp.so | auto-crawl GitHub + claim | prepared | claim (optional) | + +## 0. Prerequisites + +```bash +# build + TDQS lint baseline (free, deterministic) +npx mcp-tdqs lint --command 'node mcp-server/dist/index.js' --server-name opencode-workbench --fail-on error +``` + +## 1. Official MCP Registry (canonical — do this first) + +```bash +# prebuilt binary +curl -L "https://github.com/modelcontextprotocol/registry/releases/download/latest/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" \ + | tar xz mcp-publisher && sudo mv mcp-publisher /usr/local/bin/ + +mcp-publisher login github # GitHub OIDC/device flow +mcp-publisher publish # reads ./server.json +``` + +Verify: `curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=opencode-workbench"`. + +> The `name` in `server.json` (`io.github.simonmak-ascent/opencode-workbench`) is +> namespaced to the GitHub org that owns the repo; `login github` asserts that. + +## 2. Glama + +Already indexed (Dockerfile + `api/glama.js` + `glama.json`). The live claim file: + +``` +https://opencode-workbench.simonmak.com/.well-known/glama.json +``` + +Claim the listing on glama.ai to unlock scores/analytics. Glama re-publishes the +official registry, so it will also pick up §1 automatically. + +## 3. Smithery + +Needs an API key (user-held). Once `SMITHERY_API_KEY` is in the environment: + +```bash +npx -y @smithery/cli@latest publish --name opencode-workbench +# or via the registry API using SMITHERY_API_KEY +``` + +Store the key in `~/.env.workbench` (never commit it). + +## 4. PulseMCP + +Editorial/curated submission — submit the official-registry name +(`io.github.simonmak-ascent/opencode-workbench`) and repo URL through the PulseMCP +site; no API key needed beyond an account. + +## 5. mcp.so + +Auto-crawls public GitHub servers. Claim the generated listing (GitHub sign-in) to +add the install command and remote URL. + +## Drift guard + +`server.json` `version` and package `identifier`/`version` must match +`package.json`. Keep them in lockstep on every release (bump + tag `vX.Y.Z`). + +## What the agent can automate vs not + +- **Automated:** build, TDQS lint, `server.json` creation, Glama claim file, this runbook. +- **Credential/user:** TDQS full score (AC-3), Smithery publish (AC-6), PulseMCP submission (AC-7). diff --git a/mcp-server/src/tools.ts b/mcp-server/src/tools.ts index 96a2915..8d60a22 100644 --- a/mcp-server/src/tools.ts +++ b/mcp-server/src/tools.ts @@ -230,7 +230,7 @@ export function registerTools(server: McpServer): void { { title: "Inspect a Linux target", description: - "Probe a target machine — this host or a remote one over SSH — and report its OS, architecture, package manager, Node/npm, OpenCode, Docker and per-tool detection flags. Read-only: runs a single shell probe and changes nothing. Requires SSH access for `mode: ssh`. Use it before plan_clone to understand what a clone would touch.", + "Probe a target machine — this host or a remote one over SSH — and report its OS, architecture, package manager, Node/npm, OpenCode, Docker and per-tool detection flags. Read-only: runs a single shell probe and changes nothing. Requires SSH access for `mode: ssh`. Use it before plan_clone to understand what a clone would touch. Report-only: it does not provision — for a one-call provision use bootstrap_host.", inputSchema: { target: targetShape }, outputSchema: inspectOutput, annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }, @@ -249,7 +249,7 @@ export function registerTools(server: McpServer): void { { title: "Plan a Workbench clone", description: - "Compare a target against the portable Workbench profile and return each component as 'install', 'present', or 'manual', plus the list to install. Read-only: it installs nothing; requires SSH access for `mode: ssh`. Restrict the plan with `components`. Use this before apply_clone; use verify_clone after to confirm the result.", + "Compare a target against the portable Workbench profile and return each component as 'install', 'present', or 'manual', plus the list to install. Read-only: it installs nothing; requires SSH access for `mode: ssh`. Restrict the plan with `components`. Use this before apply_clone; use verify_clone after to confirm the result. For granular control of an existing profile use this; for a first-time end-to-end provision use bootstrap_host instead.", inputSchema: { target: targetShape, ...optionsShape }, outputSchema: planOutput, annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }, @@ -268,7 +268,7 @@ export function registerTools(server: McpServer): void { { title: "Apply a Workbench clone", description: - "Clone the Workbench profile onto a target and install missing components: repo, OpenCode CLI, Node/pnpm, npm MCPs, vendored MCPs, research MCPs, skills, plugins, and optionally Docker. Idempotent — already-present components are skipped. Requires SSH access and write permission on the target; installs can take several minutes. Consent-gated: without `confirm:true` it returns a plan (components + privileged-command preview) and changes nothing. Writes a rendered opencode.json and an empty ~/.env.workbench template (mode 600), never secret values. Restrict with `components`, preview with `dryRun:true`, then confirm with verify_clone.", + "Clone the Workbench profile onto a target and install missing components: repo, OpenCode CLI, Node/pnpm, npm MCPs, vendored MCPs, research MCPs, skills, plugins, and optionally Docker. Idempotent — already-present components are skipped. Requires SSH access and write permission on the target; installs can take several minutes. Consent-gated: without `confirm:true` it returns a plan (components + privileged-command preview) and changes nothing. Writes a rendered opencode.json and an empty ~/.env.workbench template (mode 600), never secret values. Restrict with `components`, preview with `dryRun:true`, then confirm with verify_clone. Prefer bootstrap_host for a first-time provision; use apply_clone for a specific component subset or per-component control.", inputSchema: { target: targetShape, confirm: z.boolean().optional().describe("Set true to actually install. When absent, the call returns a plan and makes no changes."), @@ -304,7 +304,7 @@ export function registerTools(server: McpServer): void { { title: "Verify a Workbench clone", description: - "Re-check a target after cloning: presence of opencode.json and ~/.env.workbench plus per-component detection, returning a missing list. Read-only; requires SSH access for `mode: ssh`. Restrict the check with `components`. Use this after apply_clone; for a pre-clone preview use plan_clone.", + "Re-check a target after cloning: presence of opencode.json and ~/.env.workbench plus per-component detection, returning a missing list. Read-only; requires SSH access for `mode: ssh`. Restrict the check with `components`. Use this after apply_clone; for a pre-clone preview use plan_clone. bootstrap_host runs this automatically, so call verify_clone directly only for a targeted re-check.", inputSchema: { target: targetShape, ...optionsShape }, outputSchema: verifyOutput, annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }, @@ -511,7 +511,7 @@ export function registerTools(server: McpServer): void { { title: "Bootstrap a bare Linux host", description: - "Provision a bare Linux target into a VDD-configured OpenCode workstation in one call: scan the platform from the kernel up, return a dry-run upgrade plan, install the latest stable OpenCode and record its version, apply the VDD profile config, and verify parity. Pass help:true for full parameter documentation without contacting the target. Consent-gated: without confirm:true it returns a plan (platform + upgrade commands + components) and changes nothing. Set upgrade:true (requires root/sudo) to execute the platform upgrade; default is plan-only. Never reads or transmits secret values.", + "Provision a bare Linux target into a VDD-configured OpenCode workstation in one call: scan the platform from the kernel up, return a dry-run upgrade plan, install the latest stable OpenCode and record its version, apply the VDD profile config, and verify parity. Pass help:true for full parameter documentation without contacting the target. Consent-gated: without confirm:true it returns a plan (platform + upgrade commands + components) and changes nothing. Set upgrade:true (requires root/sudo) to execute the platform upgrade; default is plan-only. Never reads or transmits secret values. Use bootstrap_host for a first-time, end-to-end provision of a bare host; for granular control of an already-provisioned profile call inspect_target, plan_clone, apply_clone or verify_clone individually instead — bootstrap_host composes them, so do not call both for the same change.", inputSchema: { target: targetShape.optional(), help: z.boolean().optional().describe("Return parameter documentation and skip all target access."), diff --git a/server.json b/server.json new file mode 100644 index 0000000..c816c11 --- /dev/null +++ b/server.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", + "name": "io.github.simonmak-ascent/opencode-workbench", + "description": "Provision and clone an OpenCode workbench onto a Linux machine (local or over SSH): kernel-up platform scan, latest-stable OpenCode, the MCP stack, agent skills and the VDD profile — with a one-call bootstrap_host and a value-blind, consent-gated clone.", + "version": "1.1.0", + "repository": { + "url": "https://github.com/simonmak-ascent/opencode-workbench", + "source": "github" + }, + "packages": [ + { + "registryType": "npm", + "identifier": "@simonmak-ascent/opencode-workbench", + "version": "1.1.0", + "transport": { "type": "stdio" } + } + ], + "remotes": [ + { + "type": "streamable-http", + "url": "https://opencode-workbench.simonmak.com/mcp" + } + ] +} diff --git a/vdd/specs/mcp-quality-and-registry/spec.md b/vdd/specs/mcp-quality-and-registry/spec.md new file mode 100644 index 0000000..ee00aa9 --- /dev/null +++ b/vdd/specs/mcp-quality-and-registry/spec.md @@ -0,0 +1,135 @@ +# mcp-quality-and-registry + +Status: Clarified +Version: 1.0 +Last updated: 2026-10-03 + +> Impact Chain: V-001 → S-002 → T-003 → SP-005 (amends SP-004) + +## Tactical Origin + +Implements: `vdd/tactics.md` → A-008 … A-011 (AMEND block), serving vision impacts +I-006 (TDQS quality) and I-007 (registry distribution). + +## Clarified Requirement + +> "ensure the mcp achieve 5/5, register the mcp in the top 5 free mcp registry" + +**Resolved ambiguities** (via `vdd_clarify` + research): + +- **"5/5" = TDQS 5.0** (Tool Definition Quality Score, per-tool 1–5, six weighted + dimensions). The practical gate is **tier A (≥ 3.5)**; 5.0 is the aspirational + ceiling. Deterministic `mcp-tdqs lint` needs no key; the full `score` needs an + OpenAI-compatible endpoint or the TDQS hosted API key. +- **"top 5 free MCP registries"** = **Official MCP Registry**, **Glama**, + **Smithery**, **PulseMCP**, **mcp.so**. + +## Overview + +Raise the server's tool-definition quality and publish it to the five free +registries so agents can discover and install it. Serves I-006 and I-007. + +## User Stories + +### Primary + +As an agent-consumer, I want every `opencode-workbench` tool to declare clearly +what it does and when to use it (vs its siblings), and I want the server listed in +the registries my client reads, so I can discover and invoke it correctly. + +## Boundaries + +**Always do:** +- Keep every tool's `purpose`, `usage`, `behavior` and parameter semantics explicit. +- Keep `outputSchema` + `annotations` on every tool (lint requires them). +- Publish the manifest to the official registry before the aggregators re-ingest it. + +**Ask first:** +- Submitting to any registry that requires an account or API key (Smithery, + PulseMCP) or that publishes under the org identity. +- Changing tool names (breaks existing clients). + +**Never do:** +- Invent quality scores; only report measured values. +- Commit registry API keys. + +## Acceptance Criteria + +### AC-1: TDQS lint clean [MUST] +Given the built server +When `npx mcp-tdqs lint --command 'node mcp-server/dist/index.js'` runs in CI +Then it reports **0 errors** (warnings tracked, not gating). + +### AC-2: Tool descriptions disambiguate siblings [MUST] +Given every registered tool +When its description is read +Then it states its purpose **and** when to use it versus the neighbouring tools +(addresses TDQS dimensions Purpose Clarity + Usage Guidelines + Disambiguation). + +### AC-3: TDQS score recorded [SHOULD] +Given a scorer credential (TDQS hosted `--hosted` or an OpenAI-compatible +`--base-url/--api-key/--model`) +When `mcp-tdqs score` runs against the built server +Then the tier and score are captured in the repo, with a floor of **tier A**. +*(Blocked: no scorer key held by the agent — see blockers.)* + +### AC-4: Official registry manifest [MUST] +Given the repository root +Then `server.json` exists, validates against the official schema, and matches the +published npm name/version and the remote `https://opencode-workbench.simonmak.com/mcp`. + +### AC-E4: Manifest drift [MUST] +Given `server.json` version ≠ `package.json` version +Then a check fails (drift guard). + +### AC-5: Glama listing [MUST] +Given the live deployment +Then `/.well-known/glama.json` serves a claim and the connector is indexed by Glama. + +### AC-6: smithery listing [SHOULD] +Given a `SMITHERY_API_KEY` +When `npx @smithery/cli publish` (or the registry API) runs +Then the server appears at smithery.ai. *(Blocked on key.)* + +### AC-7: PulseMCP + mcp.so listing [SHOULD] +Given the server is in the official registry +Then it is submitted/claimed at PulseMCP and mcp.so per the runbook. +*(mcp.so auto-crawls GitHub; PulseMCP has a submission form.)* + +## Out of Scope + +- Paid registries/hosting. +- Rewriting tool behaviour purely to game a score. +- Non-MCP distribution (it is already on npm). + +## Non-Functional Requirements + +- Registry steps must be reproducible from `docs/publishing/registry-listing.md`. +- No secret values in `server.json` or the runbook. + +## Impact Verification + +- AC-1, AC-2, AC-3 → I-006. +- AC-4, AC-5, AC-6, AC-7 → I-007. + +## Blockers (human-decision / credential) + +| Blocker | Needed for | Owner | +|---------|-----------|-------| +| TDQS hosted API key **or** OpenAI-compatible key | AC-3 full score | user | +| `SMITHERY_API_KEY` | AC-6 | user | +| PulseMCP submission account | AC-7 | user | + +## S&T Assumptions (Specs → Plan) + +**Necessity:** the requirement adds a quality gate and a distribution channel, not +new code paths; the plan is mostly metadata + one description pass. + +**Achievability:** lint runs free today (0 errors baseline captured); the manifest +and runbook are pure artifacts; scoring/listing need external credentials. + +**Sufficiency:** AC-1/2/4/5 are fully deliverable now; AC-3/6/7 are prepared and +credential-gated. + +**Warnings:** do not gate CI on the paid scorer without a key; registry ingestion +lags the manifest by hours-to-days. diff --git a/vdd/specs/mcp-quality-and-registry/tdqs-baseline.md b/vdd/specs/mcp-quality-and-registry/tdqs-baseline.md new file mode 100644 index 0000000..1e25bf6 --- /dev/null +++ b/vdd/specs/mcp-quality-and-registry/tdqs-baseline.md @@ -0,0 +1,39 @@ +# TDQS baseline — opencode-workbench + +> Evidence for spec `mcp-quality-and-registry` AC-1. Command run on the compute +> box (`wcag-workforce`) against the built server. + +``` +npx -y mcp-tdqs lint --command 'node mcp-server/dist/index.js' \ + --server-name opencode-workbench --format text +``` + +**Result (2026-10-03): 0 errors, 7 warnings, 0 notes · 9 tools · specification 1.3** + +| Tool | Params | Coverage | Annotations | Output schema | Cost | Warning | +|------|--------|----------|-------------|---------------|------|---------| +| workbench_info | 0 | 100% | yes | yes | 0 | — | +| inspect_target | 1 | 100% | yes | yes | 4 | shadow-candidate (vs bootstrap_host) | +| plan_clone | 6 | 100% | yes | yes | 4 | shadow-candidate | +| apply_clone | 7 | 100% | yes | yes | 4 | shadow-candidate | +| verify_clone | 6 | 100% | yes | yes | 4 | shadow-candidate | +| install_component | 4 | 100% | yes | yes | 5 | shadow-candidate | +| list_required_credentials | 1 | 100% | yes | yes | 4 | shadow-candidate | +| run_auth_flow | 2 | 100% | yes | yes | 5 | shadow-candidate | +| bootstrap_host | 11 | 100% | yes | yes | 0 | — | + +All warnings are the deterministic **shadow-candidate prefilter**: `bootstrap_host` +(cost 0) may shadow the granular tools. Only the full coherence evaluation (needs a +scorer key) can confirm or clear them; disambiguation text was added to all five +descriptions to address the underlying risk (AC-2). + +## Full score (AC-3) + +Blocked: `tdqs score` requires an OpenAI-compatible endpoint +(`TDQS_BASE_URL`/`TDQS_API_KEY`/`TDQS_MODEL`) or `--hosted` with a TDQS site key. +No scorer credential is held by the agent. Once one is provided: + +``` +TDQS_BASE_URL= TDQS_API_KEY= TDQS_MODEL= \ + npx -y mcp-tdqs score --command 'node mcp-server/dist/index.js' --fail-under A +``` diff --git a/vdd/tactics.md b/vdd/tactics.md index 1335b99..3b982ca 100644 --- a/vdd/tactics.md +++ b/vdd/tactics.md @@ -68,6 +68,20 @@ Derived from: `vdd/strategy.md`. Scope: Infrastructure only. | A-006 | CI assertion: docs counts match `opencode.json` | SHOULD | Governance | S | None | | A-007 | Tests: platform parsing/plan, help, idempotency | MUST | P1, P2 | M | A-001, A-002 | +### [AMEND 2026-10-03] Quality + registry distribution (V-001 → I-006/I-007) + +| ID | Action Item | Priority | Pillar | Size | Deps | +|----|------------|----------|--------|------|------| +| A-008 | Resolve TDQS lint warnings (shadow-candidates) and keep lint at 0 errors | MUST | P1 | S | A-002 | +| A-009 | Add `server.json` manifest for the Official MCP Registry | MUST | Distribution | S | None | +| A-010 | Registry-listing runbook + claim/verify each of the 5 registries; automate what the API allows | MUST | Distribution | M | A-009 | +| A-011 | Capture a full `mcp-tdqs score` (tier A floor) and wire a CI gate when a scorer key is available | SHOULD | P1 | M | A-008 | + +**AMEND dependency map:** `A-002 → A-008 → A-011`; `A-009 → A-010`. +**AMEND blocker:** A-011 (full score) and Smithery/PulseMCP listing require credentials the +agent does not hold (TDQS hosted API key, Smithery API key, PulseMCP submission); Glama is +already live/claimable and the Official Registry supports GitHub OIDC. + ## Dependency Map ``` diff --git a/vdd/vision.md b/vdd/vision.md index c12a047..a6b2dec 100644 --- a/vdd/vision.md +++ b/vdd/vision.md @@ -43,6 +43,8 @@ configuration, on localhost or a remote SSH target. | I-003 | Latest stable OpenCode installed (version recorded) | Target host | Version freshness at run + captured pin record | | I-004 | VDD MCP/skill/`AGENTS.md`/config applied on local **and** remote | Operator | Config-parity/drift check clean after apply | | I-005 | Self-describing interface (`-help`) | Operator | Success reached without reading source | +| I-006 | Every MCP tool passes TDQS with no defects (target 5/5; floor tier A) | Operator / agent consumers | `mcp-tdqs lint` 0 errors; `mcp-tdqs score` tier + score | +| I-007 | Server discoverable in the top free MCP registries | Operator | Listed/live in Official MCP Registry + Glama + Smithery + PulseMCP + mcp.so | ## Stakeholder Map From 80b9cc8cb380fc7ed848df48ae72a9121f92016a Mon Sep 17 00:00:00 2001 From: Simon Mak Date: Sat, 3 Oct 2026 01:47:20 +0800 Subject: [PATCH 2/2] chore(release): v1.1.1 (TDQS gate + registry manifest) --- package.json | 2 +- server.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8313cca..19c657e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@simonmak-ascent/opencode-workbench", - "version": "1.1.0", + "version": "1.1.1", "description": "MCP server and connector that clones the OpenCode workbench configuration onto Linux machines (locally or over SSH).", "license": "MIT", "type": "module", diff --git a/server.json b/server.json index c816c11..c910551 100644 --- a/server.json +++ b/server.json @@ -2,7 +2,7 @@ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json", "name": "io.github.simonmak-ascent/opencode-workbench", "description": "Provision and clone an OpenCode workbench onto a Linux machine (local or over SSH): kernel-up platform scan, latest-stable OpenCode, the MCP stack, agent skills and the VDD profile — with a one-call bootstrap_host and a value-blind, consent-gated clone.", - "version": "1.1.0", + "version": "1.1.1", "repository": { "url": "https://github.com/simonmak-ascent/opencode-workbench", "source": "github" @@ -11,7 +11,7 @@ { "registryType": "npm", "identifier": "@simonmak-ascent/opencode-workbench", - "version": "1.1.0", + "version": "1.1.1", "transport": { "type": "stdio" } } ],