RFC: OpenAPI spec + generated clients (kill MCP/CLI/SDK hand-written HTTP wrapper drift) #1706

Open
opened 2026-05-23 06:33:19 +00:00 by hongming · 0 comments
Owner

Summary

The platform HTTP API in workspace-server/internal/handlers/*.go is the single source of truth for business logic. But four hand-written clients reimplement the HTTP wrapper layer with drifted auth + retry + error mapping.

Client HTTP wrapper Auth
canvas/src/lib/api.ts api.get/patch/post/del Bearer + X-Molecule-Org-Slug
molecule-mcp-server/src/api.ts apiCall(method, path) none (drifted; CLAUDE.md says MOLECULE_API_KEY required, code didn't read it)
molecule-cli/internal/client/platform.go getInto/postInto/delete none
molecule-sdk-python/molecule_agent/client.py httpx Bearer + X-Molecule-Org-Id

Problems

  1. 3 different auth shapes across 4 clients
  2. MCP server MOLECULE_API_KEY documented-but-not-read — silent auth bypass against prod
  3. No OpenAPI spec anywhere in the org — every client hand-typed against Go handler signatures
  4. 4 retry/error implementations — no consistency
  5. Adding an endpoint requires 4 hand edits; easy to miss one

Proposed shape

  1. OpenAPI 3.1 spec generated from Go handlers (swaggo or huma). Emit workspace-server/openapi.yaml at build.
  2. Generate clients from spec:
    • TS (canvas + MCP): openapi-typescript-codegen
    • Python SDK: openapi-python-client
    • Go CLI: oapi-codegen
  3. Hand-write only an AuthProvider shim per client (~20 lines)
  4. CI gate: PR fails if regenerated client doesn't match committed

Phases

  • Phase 1: Add swaggo annotations to one handler (schedules.go) + make openapi-spec target + commit workspace-server/openapi.yaml — prove pipeline
  • Phase 2: Annotate remaining handlers (workspaces/agents/secrets/files/memory/channels/plugins)
  • Phase 3: Generate TS client; replace MCP api.ts hand wrapper (keep auth shim)
  • Phase 4: Generate Go client; replace CLI platform.go
  • Phase 5: Generate Python client; replace SDK HTTP layer
  • Phase 6: canvas — types-only regen (keep their fetch wrapper for browser session-cookie path)
  • Phase 7: CI gate

Non-goals

  • No backend handler rewrites
  • Don't unify auth in this RFC — follow-up after shape is shared
  • canvas keeps custom browser fetch wrapper (session-cookie path is special)

Refs

  • canvas: molecule-core/canvas/src/lib/api.ts
  • MCP: molecule-mcp-server/src/api.ts
  • CLI: molecule-cli/internal/client/platform.go
  • Python SDK: molecule-sdk-python/molecule_agent/client.py
  • Backend handlers SSOT: molecule-core/workspace-server/internal/handlers/*.go
  • Backend router: molecule-core/workspace-server/internal/router/router.go:376-388
## Summary The platform HTTP API in `workspace-server/internal/handlers/*.go` is the single source of truth for business logic. But **four hand-written clients** reimplement the HTTP wrapper layer with drifted auth + retry + error mapping. | Client | HTTP wrapper | Auth | |---|---|---| | `canvas/src/lib/api.ts` | `api.get/patch/post/del` | Bearer + `X-Molecule-Org-Slug` | | `molecule-mcp-server/src/api.ts` | `apiCall(method, path)` | **none** (drifted; CLAUDE.md says `MOLECULE_API_KEY` required, code didn't read it) | | `molecule-cli/internal/client/platform.go` | `getInto/postInto/delete` | **none** | | `molecule-sdk-python/molecule_agent/client.py` | httpx | Bearer + `X-Molecule-Org-Id` | ## Problems 1. 3 different auth shapes across 4 clients 2. MCP server `MOLECULE_API_KEY` documented-but-not-read — silent auth bypass against prod 3. No OpenAPI spec anywhere in the org — every client hand-typed against Go handler signatures 4. 4 retry/error implementations — no consistency 5. Adding an endpoint requires 4 hand edits; easy to miss one ## Proposed shape 1. OpenAPI 3.1 spec generated from Go handlers (swaggo or huma). Emit `workspace-server/openapi.yaml` at build. 2. Generate clients from spec: - TS (canvas + MCP): `openapi-typescript-codegen` - Python SDK: `openapi-python-client` - Go CLI: `oapi-codegen` 3. Hand-write only an `AuthProvider` shim per client (~20 lines) 4. CI gate: PR fails if regenerated client doesn't match committed ## Phases - Phase 1: Add swaggo annotations to one handler (`schedules.go`) + `make openapi-spec` target + commit `workspace-server/openapi.yaml` — prove pipeline - Phase 2: Annotate remaining handlers (workspaces/agents/secrets/files/memory/channels/plugins) - Phase 3: Generate TS client; replace MCP `api.ts` hand wrapper (keep auth shim) - Phase 4: Generate Go client; replace CLI `platform.go` - Phase 5: Generate Python client; replace SDK HTTP layer - Phase 6: canvas — types-only regen (keep their fetch wrapper for browser session-cookie path) - Phase 7: CI gate ## Non-goals - No backend handler rewrites - Don't unify auth in this RFC — follow-up after shape is shared - canvas keeps custom browser fetch wrapper (session-cookie path is special) ## Refs - canvas: `molecule-core/canvas/src/lib/api.ts` - MCP: `molecule-mcp-server/src/api.ts` - CLI: `molecule-cli/internal/client/platform.go` - Python SDK: `molecule-sdk-python/molecule_agent/client.py` - Backend handlers SSOT: `molecule-core/workspace-server/internal/handlers/*.go` - Backend router: `molecule-core/workspace-server/internal/router/router.go:376-388`
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#1706