Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/validate-inventory.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
80 changes: 80 additions & 0 deletions docs/publishing/registry-listing.md
Original file line number Diff line number Diff line change
@@ -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).
10 changes: 5 additions & 5 deletions mcp-server/src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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 },
Expand All @@ -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."),
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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."),
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
24 changes: 24 additions & 0 deletions server.json
Original file line number Diff line number Diff line change
@@ -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.1",
"repository": {
"url": "https://github.com/simonmak-ascent/opencode-workbench",
"source": "github"
},
"packages": [
{
"registryType": "npm",
"identifier": "@simonmak-ascent/opencode-workbench",
"version": "1.1.1",
"transport": { "type": "stdio" }
}
],
"remotes": [
{
"type": "streamable-http",
"url": "https://opencode-workbench.simonmak.com/mcp"
}
]
}
135 changes: 135 additions & 0 deletions vdd/specs/mcp-quality-and-registry/spec.md
Original file line number Diff line number Diff line change
@@ -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.
39 changes: 39 additions & 0 deletions vdd/specs/mcp-quality-and-registry/tdqs-baseline.md
Original file line number Diff line number Diff line change
@@ -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=<openai-compat> TDQS_API_KEY=<key> TDQS_MODEL=<id> \
npx -y mcp-tdqs score --command 'node mcp-server/dist/index.js' --fail-under A
```
14 changes: 14 additions & 0 deletions vdd/tactics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down
Loading
Loading