From c71585412d5c28367e5db3ad746610aee0a995d0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 08:53:30 +0000 Subject: [PATCH 1/2] feat(create-objectstack): default scaffold ships the three generic connector executors (#3056) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the last authoring gap in the ADR-0097 promise that integrations are expressible **and executable** as pure metadata. `npm create objectstack` now generates an `objectstack.config.ts` whose `plugins:` wires the `rest`, `openapi`, and `mcp` connector executors (zero-arg = contribute the provider factory only), so an author (human or AI) can add a declarative `connectors:` entry naming `provider: 'rest' | 'openapi' | 'mcp'` and have it materialize into a live, dispatchable connector at boot — no host-code edit. Why `requires: ['automation']` is added alongside the plugins: the connector plugins declare a hard `dependencies = ['com.objectstack.service-automation']`, and the automation service is what actually materializes declarative `connectors:` entries (ADR-0097). Without it, the kernel throws `Dependency 'com.objectstack.service-automation' not found` at boot — a broken scaffold. Verified end-to-end against the published registry: connectors-only crashes boot; connectors + `requires: ['automation']` boots healthy and a declarative `provider: 'rest'` instance materializes (a declarative connector whose factory is absent fails boot loudly). Automation ships transitively via `@objectstack/cli`, so no new runtime dependency is needed. Scope note: the showcase demo half (in-repo stdio MCP fixture, DevToolsMcpConnector, CI-pinned dogfood) already landed in #3062; this is the template-preset half that was blocked on the 15.1.0 publish (now 15.1.1 is `latest`, and the template's `^15.0.0` pins resolve to it). Changes: - templates/blank/objectstack.config.ts — `requires: ['automation']` + `plugins:` with the three zero-arg executors; brand connectors stay marketplace/opt-in; stdio-MCP opt-in (#3055) documented inline. - templates/blank/package.json — add `@objectstack/connector-{rest,openapi,mcp}` at `^15.0.0` (in lockstep via scripts/sync-template-versions.mjs). - templates/blank/README.md — "Connectors (default providers)" section. - docs/getting-started/your-first-project.mdx — config snippet + packages table match the new scaffold output. - docs/automation/flows.mdx — restore the "scaffolded apps ship the three executors" statement. - changeset (create-objectstack, minor). Refs #3056, #3062 (showcase half), #3055 / #3059 (stdio gate), ADR-0097. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GjAgyTXaxSMVDWtGYxAXL7 --- .../scaffold-default-connector-executors.md | 28 +++++++++++++++++++ content/docs/automation/flows.mdx | 2 +- .../getting-started/your-first-project.mdx | 19 ++++++++++++- .../src/templates/blank/README.md | 26 +++++++++++++++++ .../src/templates/blank/objectstack.config.ts | 24 ++++++++++++++++ .../src/templates/blank/package.json | 5 +++- 6 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 .changeset/scaffold-default-connector-executors.md diff --git a/.changeset/scaffold-default-connector-executors.md b/.changeset/scaffold-default-connector-executors.md new file mode 100644 index 0000000000..ca3707fd43 --- /dev/null +++ b/.changeset/scaffold-default-connector-executors.md @@ -0,0 +1,28 @@ +--- +"create-objectstack": minor +--- + +feat(create-objectstack): the blank scaffold ships the three generic connector executors by default + +`npm create objectstack` now generates an `objectstack.config.ts` that wires the +`rest`, `openapi`, and `mcp` connector executor plugins (ADR-0022/0023/0024 + +ADR-0097) into `plugins:`, alongside `requires: ['automation']`. This closes the +last authoring gap in the ADR-0097 promise that integrations are expressible +**and executable** as pure metadata: an author (human or AI) can now add a +declarative `connectors:` entry naming `provider: 'rest' | 'openapi' | 'mcp'` +and have it materialize into a live, dispatchable connector at boot — with no +host-code edit. + +- `plugins:` — `new ConnectorRestPlugin()`, `new ConnectorOpenApiPlugin()`, + `new ConnectorMcpPlugin()` (zero-arg = contribute the provider factory only). +- `requires: ['automation']` — the automation service performs the + materialization and owns the registry the executors register into. It is also + a hard dependency of the connector plugins, so a scaffold that lists them in + `plugins:` without it fails boot; automation ships transitively via + `@objectstack/cli`. +- deps — `@objectstack/connector-rest`, `@objectstack/connector-openapi`, + `@objectstack/connector-mcp`. +- Security (#3055): declarative `mcp` stdio transports stay denied by default — + opt in per host with `new ConnectorMcpPlugin({ declarativeStdio: ['node'] })`. + +Brand connectors (Slack, …) remain marketplace/opt-in. diff --git a/content/docs/automation/flows.mdx b/content/docs/automation/flows.mdx index d8e5d986cf..8be4fcf147 100644 --- a/content/docs/automation/flows.mdx +++ b/content/docs/automation/flows.mdx @@ -117,7 +117,7 @@ Each node performs a specific action in the flow. | `map` | Sequential multi-instance — run a per-item subflow for each element, one at a time (each may pause); batch approval (ADR-0039) | | `connector_action` | Execute an external connector action | -`connector_action` dispatches against the runtime **connector registry**, which connector plugins populate. The three **generic executors** — `rest`, `openapi`, and `mcp` — double as provider factories: with them in your app's `plugins:`, a declarative `connectors:` entry that names a `provider` is materialized into a live, dispatchable connector at boot with no plugin code (ADR-0097; the showcase example wires all three). Brand connectors (Slack, …) are installed separately. One security note: a declarative `mcp` instance using a **stdio** transport spawns a local process from metadata and is therefore denied by default — the host opts in with `new ConnectorMcpPlugin({ declarativeStdio: [''] })`; `http` transports need no opt-in. +`connector_action` dispatches against the runtime **connector registry**, which connector plugins populate. The three **generic executors** — `rest`, `openapi`, and `mcp` — double as provider factories: with them in your app's `plugins:`, a declarative `connectors:` entry that names a `provider` is materialized into a live, dispatchable connector at boot with no plugin code (ADR-0097). Scaffolded projects (`npm create objectstack`) ship all three executors by default — paired with the `automation` capability that performs the materialization — and the showcase wires them too. Brand connectors (Slack, …) are installed separately. One security note: a declarative `mcp` instance using a **stdio** transport spawns a local process from metadata and is therefore denied by default — the host opts in with `new ConnectorMcpPlugin({ declarativeStdio: [''] })`; `http` transports need no opt-in. ### Node Structure diff --git a/content/docs/getting-started/your-first-project.mdx b/content/docs/getting-started/your-first-project.mdx index 9acac917e2..e5d469c5a8 100644 --- a/content/docs/getting-started/your-first-project.mdx +++ b/content/docs/getting-started/your-first-project.mdx @@ -101,6 +101,9 @@ filename-suffix magic — metadata exists in the app only if it is imported here ```typescript title="objectstack.config.ts" import { defineStack } from '@objectstack/spec'; +import { ConnectorRestPlugin } from '@objectstack/connector-rest'; +import { ConnectorOpenApiPlugin } from '@objectstack/connector-openapi'; +import { ConnectorMcpPlugin } from '@objectstack/connector-mcp'; import * as objects from './src/objects/index.js'; export default defineStack({ @@ -111,12 +114,25 @@ export default defineStack({ type: 'app', name: 'My App', }, + // `automation` runs flows and, per ADR-0097, materializes declarative + // `connectors:` entries at boot. The three generic executors below register + // their `rest` / `openapi` / `mcp` provider factories with it. + requires: ['automation'], + plugins: [ + new ConnectorRestPlugin(), + new ConnectorOpenApiPlugin(), + new ConnectorMcpPlugin(), + ], objects: Object.values(objects), }); ``` The REST API needs no configuration — it is on by default for every object -with `apiEnabled`. +with `apiEnabled`. The `plugins:` array ships the three **generic connector +executors**, so you can integrate an external REST / OpenAPI / MCP service as +pure metadata: add a `connectors:` entry naming a `provider` and the +`automation` capability materializes it into a live connector at boot — see +[Automation → Flows](/docs/automation/flows). **`src/objects/note.object.ts`** is a complete data model — schema, validation, API surface, and searchability in one typed definition: @@ -151,6 +167,7 @@ framework surface you install: | `@objectstack/runtime` | The kernel that interprets metadata at runtime. | | `@objectstack/driver-memory` | In-memory database driver for development. Swap for SQLite / Postgres / MongoDB in production — [no code changes](/docs/data-modeling/drivers). | | `@objectstack/plugin-hono-server` | The HTTP server that mounts the generated REST API. | +| `@objectstack/connector-rest` · `-openapi` · `-mcp` | The three generic connector executors — register the `rest` / `openapi` / `mcp` provider factories so declarative `connectors:` entries materialize ([ADR-0097](/docs/automation/flows)). | | `@objectstack/cli` *(dev)* | The `os` / `objectstack` CLI: `dev`, `validate`, `build`, `start`. | ## 3. Run it diff --git a/packages/create-objectstack/src/templates/blank/README.md b/packages/create-objectstack/src/templates/blank/README.md index 0b7746a6d1..06ba2d7613 100644 --- a/packages/create-objectstack/src/templates/blank/README.md +++ b/packages/create-objectstack/src/templates/blank/README.md @@ -26,6 +26,32 @@ curl -b cookies.txt "http://localhost:3000/api/v1/data/" - `objectstack.config.ts` — environment manifest (objects, API, plugins) - `src/objects/` — object definitions (one file per object) +## Connectors (default providers) + +`objectstack.config.ts` wires the three **generic connector executors**, so you +can call an external system from a flow as pure metadata — no host code: + +| Provider | Package | Use for | +|:---|:---|:---| +| `rest` | `@objectstack/connector-rest` | Any JSON/HTTP REST API | +| `openapi` | `@objectstack/connector-openapi` | An API described by an OpenAPI document | +| `mcp` | `@objectstack/connector-mcp` | A Model Context Protocol server | + +Add a `connectors:` entry that names one of these `provider`s and the +`automation` capability materializes it into a live, dispatchable connector at +boot (ADR-0097); a flow's `connector_action` node then calls it. To add a brand +connector (e.g. Slack), install its package and add `new ConnectorSlackPlugin()` +to `plugins:`; to drop a provider, remove its plugin. + +> **Security — declarative MCP over stdio.** An `mcp` connector whose transport +> spawns a local process (`stdio`) is denied by default, because the command +> comes from metadata. Opt in per host with +> `new ConnectorMcpPlugin({ declarativeStdio: ['node'] })`; `http` transports +> need no opt-in. + +See [Automation → Flows](https://docs.objectstack.ai/docs/automation/flows) for +the full connector and `connector_action` guide. + ## Verify your changes After editing any metadata, run: diff --git a/packages/create-objectstack/src/templates/blank/objectstack.config.ts b/packages/create-objectstack/src/templates/blank/objectstack.config.ts index 2927273a32..9576fea072 100644 --- a/packages/create-objectstack/src/templates/blank/objectstack.config.ts +++ b/packages/create-objectstack/src/templates/blank/objectstack.config.ts @@ -1,4 +1,7 @@ import { defineStack } from '@objectstack/spec'; +import { ConnectorRestPlugin } from '@objectstack/connector-rest'; +import { ConnectorOpenApiPlugin } from '@objectstack/connector-openapi'; +import { ConnectorMcpPlugin } from '@objectstack/connector-mcp'; import * as objects from './src/objects/index.js'; export default defineStack({ @@ -15,5 +18,26 @@ export default defineStack({ // scripts/sync-template-versions.mjs. engines: { protocol: '^15' }, }, + + // `automation` backs flow execution and, per ADR-0097, materializes any + // declarative `connectors:` entry into a live, dispatchable connector at boot. + // The connector executors below register their provider factories with it — + // without `automation` loaded they have nowhere to register and boot fails, so + // keep this capability whenever `plugins:` lists a connector. + requires: ['automation'], + + // Generic connector executors (ADR-0022/0023/0024 + ADR-0097), default-present + // so you can add a `connectors:` entry naming `provider: 'rest' | 'openapi' | + // 'mcp'` and have it materialize with zero host code. Zero-arg = contribute the + // provider factory only. Brand connectors (Slack, …) stay marketplace/opt-in. + // Security (#3055): a declarative `mcp` stdio transport spawns a local process + // from metadata and is denied by default — opt in per host with + // `new ConnectorMcpPlugin({ declarativeStdio: [''] })`. + plugins: [ + new ConnectorRestPlugin(), + new ConnectorOpenApiPlugin(), + new ConnectorMcpPlugin(), + ], + objects: Object.values(objects), }); diff --git a/packages/create-objectstack/src/templates/blank/package.json b/packages/create-objectstack/src/templates/blank/package.json index 564b9efae0..5a1faddbd3 100644 --- a/packages/create-objectstack/src/templates/blank/package.json +++ b/packages/create-objectstack/src/templates/blank/package.json @@ -14,7 +14,10 @@ "@objectstack/spec": "^15.0.0", "@objectstack/runtime": "^15.0.0", "@objectstack/driver-memory": "^15.0.0", - "@objectstack/plugin-hono-server": "^15.0.0" + "@objectstack/plugin-hono-server": "^15.0.0", + "@objectstack/connector-rest": "^15.0.0", + "@objectstack/connector-openapi": "^15.0.0", + "@objectstack/connector-mcp": "^15.0.0" }, "devDependencies": { "@objectstack/cli": "^15.0.0", From 871209f04c61fbc29e67d02e3dea027930dcc466 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 09:08:19 +0000 Subject: [PATCH 2/2] ci(scaffold-e2e): build the scaffolded app's own Dockerfile (examples/docker was removed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Docker build and run" step `cp`'d Dockerfile / docker-compose.yml / .dockerignore from `examples/docker/`, which was deleted in 6c28bac without updating this workflow — so the job failed at `cp: cannot stat '.../examples/docker/Dockerfile'` on every PR touching `packages/create-objectstack/**` (a pre-existing break, not from #3056). The blank template already ships its own Dockerfile (multi-stage: `npm ci` + `os build`, runtime `FROM ghcr.io/objectstack-ai/objectstack`), so build the generated project as-is with `--pull=false` against the base image built in the prior step. This fixes the failure and strictly improves coverage — the e2e now exercises the exact Docker path a real `npm create objectstack` user ships, instead of a separate example copy. Also drops the now-dead `examples/docker/**` path trigger. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01GjAgyTXaxSMVDWtGYxAXL7 --- .github/workflows/scaffold-e2e.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/scaffold-e2e.yml b/.github/workflows/scaffold-e2e.yml index 1b476310e0..2e034da56f 100644 --- a/.github/workflows/scaffold-e2e.yml +++ b/.github/workflows/scaffold-e2e.yml @@ -19,7 +19,6 @@ on: pull_request: paths: - 'packages/create-objectstack/**' - - 'examples/docker/**' - 'docker/**' - '.github/workflows/scaffold-e2e.yml' schedule: @@ -116,8 +115,9 @@ jobs: kill "$SERVER_PID" - name: Build official runtime image from this checkout - # examples/docker builds FROM ghcr.io/objectstack-ai/objectstack. - # Build that base HERE from docker/Dockerfile instead of pulling, so + # The scaffolded app's Dockerfile builds FROM + # ghcr.io/objectstack-ai/objectstack. Build that base HERE from + # docker/Dockerfile instead of pulling, so # (a) PRs exercise the official runtime Dockerfile itself, and # (b) the e2e stays hermetic — no dependency on a prior release # having published the tag (chicken-and-egg on the very first one). @@ -126,12 +126,13 @@ jobs: --build-arg OS_CLI_VERSION=latest \ "$GITHUB_WORKSPACE/docker" - - name: Docker build and run (examples/docker) + - name: Docker build and run (scaffolded Dockerfile) + # The blank template ships its own Dockerfile / docker-compose.yml / + # .dockerignore, so build the generated project as-is — this exercises + # the exact Docker path a real `npm create objectstack` user ships (the + # standalone examples/docker copy was removed in 6c28bac). run: | cd "$RUNNER_TEMP/e2e-app" - cp "$GITHUB_WORKSPACE"/examples/docker/Dockerfile \ - "$GITHUB_WORKSPACE"/examples/docker/docker-compose.yml \ - "$GITHUB_WORKSPACE"/examples/docker/.dockerignore . docker build --pull=false -t e2e-app . docker run -d --name e2e -p 18080:8080 \ -e OS_SECRET_KEY="$(openssl rand -hex 32)" \