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
28 changes: 28 additions & 0 deletions .changeset/scaffold-default-connector-executors.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 8 additions & 7 deletions .github/workflows/scaffold-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ on:
pull_request:
paths:
- 'packages/create-objectstack/**'
- 'examples/docker/**'
- 'docker/**'
- '.github/workflows/scaffold-e2e.yml'
schedule:
Expand Down Expand Up @@ -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).
Expand All @@ -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)" \
Expand Down
2 changes: 1 addition & 1 deletion content/docs/automation/flows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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: ['<trusted-command>'] })`; `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: ['<trusted-command>'] })`; `http` transports need no opt-in.

### Node Structure

Expand Down
19 changes: 18 additions & 1 deletion content/docs/getting-started/your-first-project.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions packages/create-objectstack/src/templates/blank/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,32 @@ curl -b cookies.txt "http://localhost:3000/api/v1/data/<your_object>"
- `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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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: ['<trusted-command>'] })`.
plugins: [
new ConnectorRestPlugin(),
new ConnectorOpenApiPlugin(),
new ConnectorMcpPlugin(),
],

objects: Object.values(objects),
});
5 changes: 4 additions & 1 deletion packages/create-objectstack/src/templates/blank/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down