feat(pulumi): @mcpctl/pulumi — generic Pulumi provider for mcpctl #79

Merged
michal merged 1 commits from feat/pulumi-provider into main 2026-07-17 21:02:45 +00:00
Owner

Adds a new workspace package @mcpctl/pulumi: a Pulumi dynamic provider that manages mcpctl resources declaratively from any TypeScript Pulumi program.

Why

Model drift: when the deployed vLLM/LiteLLM model changes (source of truth in the kubernetes-deployment Pulumi repo), mcpctl's llm target must follow or it 400s. A Pulumi resource makes the update automatic on pulumi up. Built generic so it's useful to any mcpctl user, not just one homelab.

Design

  • Generic core McpctlResource({ kind, name, spec, mcpd }) round-trips ANY resource kind; typed Llm wrapper for ergonomics.
  • Direct HTTP engine (ported from cli/api-client.ts) hitting the same REST endpoints the CLI uses, mirroring apply's name-keyed upsert (PUT strips immutable name/type) and get -o json's read field-stripping. No mcpctl binary needed → CI-friendly.
  • CRUD/diff/check with correct replace semantics (name/type/kind/mcpd.url → delete-before-replace), 429 retry, token secret in state.
  • Validation deferred to mcpd's Zod schemas — new kinds work with no provider release.
  • CommonJS build so the serialized dynamic provider's module refs are captured by Pulumi's closure serializer.

Auth

Long-lived PAT (mcpctl_pat_) with resource-wide view:llms+edit:llms bindings, passed as a Pulumi secret. (mcpd currently requires projectId on mint — flagged in the README as a future cleanup.)

Tests / gates

  • 18 Vitest tests: mocked mcpd (upsert immutability, 429 retry, diff/replace, read-when-gone) + Pulumi-mocks wrapper test.
  • pnpm --filter @mcpctl/pulumi build + root tsc --build clean; eslint clean.
  • README documents auth setup + the kubernetes-deployment consumer example.

Follow-up (not in this PR): publish the package + wire it into kubernetes-deployment; typed wrappers for more kinds.

🤖 Generated with Claude Code

Adds a new workspace package `@mcpctl/pulumi`: a Pulumi **dynamic provider** that manages mcpctl resources declaratively from any TypeScript Pulumi program. ## Why Model drift: when the deployed vLLM/LiteLLM model changes (source of truth in the kubernetes-deployment Pulumi repo), mcpctl's `llm` target must follow or it 400s. A Pulumi resource makes the update automatic on `pulumi up`. Built generic so it's useful to any mcpctl user, not just one homelab. ## Design - **Generic core** `McpctlResource({ kind, name, spec, mcpd })` round-trips ANY resource kind; typed `Llm` wrapper for ergonomics. - **Direct HTTP engine** (ported from `cli/api-client.ts`) hitting the same REST endpoints the CLI uses, mirroring `apply`'s name-keyed upsert (PUT strips immutable `name`/`type`) and `get -o json`'s read field-stripping. No `mcpctl` binary needed → CI-friendly. - **CRUD/diff/check** with correct replace semantics (`name`/`type`/`kind`/`mcpd.url` → delete-before-replace), 429 retry, token secret in state. - **Validation deferred** to mcpd's Zod schemas — new kinds work with no provider release. - **CommonJS** build so the serialized dynamic provider's module refs are captured by Pulumi's closure serializer. ## Auth Long-lived PAT (`mcpctl_pat_`) with resource-wide `view:llms`+`edit:llms` bindings, passed as a Pulumi secret. (mcpd currently requires `projectId` on mint — flagged in the README as a future cleanup.) ## Tests / gates - 18 Vitest tests: mocked mcpd (upsert immutability, 429 retry, diff/replace, read-when-gone) + Pulumi-mocks wrapper test. - `pnpm --filter @mcpctl/pulumi build` + root `tsc --build` clean; eslint clean. - README documents auth setup + the kubernetes-deployment consumer example. Follow-up (not in this PR): publish the package + wire it into kubernetes-deployment; typed wrappers for more kinds. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
michal added 1 commit 2026-07-17 21:02:37 +00:00
feat(pulumi): @mcpctl/pulumi — generic Pulumi provider for mcpctl
Some checks failed
CI/CD / lint (pull_request) Successful in 1m3s
CI/CD / typecheck (pull_request) Successful in 2m5s
CI/CD / test (pull_request) Successful in 1m19s
CI/CD / smoke (pull_request) Failing after 2m46s
CI/CD / build (pull_request) Successful in 2m25s
CI/CD / publish (pull_request) Has been skipped
f3d92235db
New workspace package: a Pulumi dynamic provider (TypeScript, in-process) that
manages mcpctl resources declaratively from any Pulumi program. Motivated by
model drift: when the deployed vLLM/LiteLLM model changes, mcpctl's llm target
must follow, and a Pulumi resource makes that automatic on `pulumi up`.

- Generic core `McpctlResource({ kind, name, spec, mcpd })` round-trips ANY
  mcpctl resource kind; typed `Llm` wrapper for ergonomics.
- Engine talks direct HTTP to mcpd's REST API (ported from cli/api-client.ts),
  mirroring apply's name-keyed upsert (PUT strips immutable name/type) and
  get -o json's read field-stripping. No mcpctl binary needed → CI-friendly.
- CRUD/diff/check with correct replace semantics (name/type/kind/mcpd.url force
  delete-before-replace), 429 retry, secret token in state.
- Validation deferred to mcpd's Zod schemas, so new resource kinds work with no
  provider release.
- CommonJS build so the serialized dynamic provider's module refs are captured.
- 18 Vitest tests (mocked mcpd + Pulumi mocks); build + lint clean; README with
  auth (PAT) setup and consumer example for kubernetes-deployment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
michal merged commit 8b6dbdd368 into main 2026-07-17 21:02:45 +00:00
michal deleted branch feat/pulumi-provider 2026-07-17 21:02:45 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: michal/mcpctl#79