Skip to content

Commit fa79b6a

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/objectql-protocol-layering-s2sn67
# Conflicts: # packages/qa/dogfood/test/showcase-declarative-mcp.dogfood.test.ts # pnpm-lock.yaml
2 parents 8e81f62 + 330ceba commit fa79b6a

13 files changed

Lines changed: 383 additions & 93 deletions

File tree

.changeset/console-23d65c396b8c.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"@objectstack/console": patch
3+
---
4+
5+
Console (objectui) refreshed to `23d65c396b8c`. Frontend changes in this range:
6+
7+
- fix(i18n): drop try/catch-around-hook in createSafeTranslation / useSafeTranslate (#2605)
8+
- fix(app-shell): Studio Access matrix — history opens in-place sheet, breadcrumb stops escaping the pillar (#2599)
9+
- fix(data-objectstack): emit mutation events from batchTransaction/bulk so related lists refresh after master-detail saves (#2607)
10+
- fix(metadata-admin): follow the live app locale, not just navigator.language (#2602)
11+
- feat(detail+fields+components+app-shell): record inline-edit polish (#2572) (#2604)
12+
- fix(app-shell+kanban+list): row-predicate CEL authoring advertises runtime-bound roots; kanban binds host scope (#2571 follow-up) (#2603)
13+
- fix(plugin-list): spec bare-string sort form crashed ListView (#2578 shape-mismatch audit) (#2601)
14+
- fix(app-shell): lock the Access pillar permission matrix in read-only packages (#2570)
15+
- fix(fields): localize relative-date humanize via Intl.RelativeTimeFormat (framework#3040) (#2593)
16+
- fix(components): pin sticky leading cells at measured header widths (#2592)
17+
- fix(app-shell,core): keep error-envelope objects out of toast.error — React #31 page crash (#2579) (#2580)
18+
- feat(flow-designer): pick the target node per branch in the Decision Branches editor (#1942) (#2568)
19+
- fix(core+data-objectstack+app-shell): canonicalize reference/reference_to at the schema chokepoints (#2407) (#2598)
20+
- fix(dashboard-filters): spec-form filter options crashed the dashboard; add guide screenshots (#2578) (#2597)
21+
- fix(fields): PeoplePicker cursor resets only on real result changes (de-flakes keyboard test) (#2594)
22+
- fix(studio): stop force-opening the new-object dialog on empty packages (#2569)
23+
- feat(studio): CEL editor with validate + autocomplete for field conditional rules (#1582) (#2571)
24+
- feat(kanban): default lane field honours the ADR-0085 stageField role (#2596)
25+
- fix(fields+detail): resolve pre-existing rules-of-hooks violations in cell renderers (#2595)
26+
27+
objectui range: `fb35e4828fdb...23d65c396b8c`

.changeset/console-b8967495be73.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/console": minor
3+
---
4+
5+
Console (objectui) refreshed to `b8967495be73`. Frontend changes in this range:
6+
7+
- fix(app-shell): guard unsaved OWD overview rows in the Access rail and Studio header nav (#2600 follow-up) (#2610)
8+
- fix(actions): defuse the three action-visibility traps (#2358) (#2611)
9+
- fix(studio): confirm before header SPA nav discards unsaved pillar edits (#2600) (#2606)
10+
- feat(studio): CEL formula editor with inferred result type; structured summary roll-up editor (#1582) (#2609)
11+
12+
objectui range: `23d65c396b8c...b8967495be73`

.objectui-sha

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
fb35e4828fdb3b6ecc23761a82e9ec4a13890727
1+
b8967495be738780b3bf2e42b0eaeeb8fd127fa8

content/docs/automation/flows.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ Each node performs a specific action in the flow.
117117
| `map` | Sequential multi-instance — run a per-item subflow for each element, one at a time (each may pause); batch approval (ADR-0039) |
118118
| `connector_action` | Execute an external connector action |
119119

120+
`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.
121+
120122
### Node Structure
121123

122124
```typescript

examples/app-showcase/objectstack.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { defineStack } from '@objectstack/spec';
4+
import { ConnectorMcpPlugin } from '@objectstack/connector-mcp';
45
import { ConnectorOpenApiPlugin } from '@objectstack/connector-openapi';
56
import { ConnectorRestPlugin } from '@objectstack/connector-rest';
67
import { ConnectorSlackPlugin } from '@objectstack/connector-slack';
@@ -112,8 +113,19 @@ export default defineStack({
112113
// (ADR-0097), which materializes the StatusOpenApiConnector
113114
// declarative instance below — its OpenAPI document is a
114115
// package-relative FILE PATH read at boot (#3016).
116+
// • mcp → contributes the `mcp` provider factory, which materializes
117+
// the DevToolsMcpConnector declarative instance from the
118+
// in-repo stdio fixture (scripts/mcp-fixture.mjs). The
119+
// `declarativeStdio` allowlist is the #3055 security opt-in:
120+
// declarative stdio transports spawn a local process from
121+
// METADATA, so they are denied by default; this host
122+
// deliberately trusts `node` to run the in-repo fixture.
123+
// (A coarse boundary by design — trusting `node` trusts what
124+
// it is asked to run; real deployments should list specific
125+
// server binaries.)
115126
plugins: [
116127
new ConnectorOpenApiPlugin(),
128+
new ConnectorMcpPlugin({ declarativeStdio: ['node'] }),
117129
new ConnectorRestPlugin({
118130
name: 'rest',
119131
baseUrl: process.env.SHOWCASE_SELF_URL ?? 'http://127.0.0.1:3000',

examples/app-showcase/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
"test:smoke": "playwright test --config=playwright.config.ts"
2323
},
2424
"dependencies": {
25+
"@modelcontextprotocol/sdk": "^1.29.0",
2526
"@objectstack/cloud-connection": "workspace:*",
27+
"@objectstack/connector-mcp": "workspace:*",
2628
"@objectstack/connector-openapi": "workspace:*",
2729
"@objectstack/connector-rest": "workspace:*",
2830
"@objectstack/connector-slack": "workspace:*",
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// Tiny in-repo MCP server (stdio) — the deterministic target for the
4+
// showcase's declarative `provider: 'mcp'` connector instance (#3056,
5+
// completing the live demo deferred from #3017 / ADR-0097 §6).
6+
//
7+
// Why a fixture instead of a real server: the demo must materialize during
8+
// BOOT in CI (the Dogfood Regression Gate), so the target has to exist with
9+
// no network, no ports, and no boot-ordering coupling. A stdio child process
10+
// spawned at materialization is exactly that. The spawn is allowlisted by the
11+
// host via `new ConnectorMcpPlugin({ declarativeStdio: ['node'] })` in
12+
// objectstack.config.ts — dogfooding the #3055 opt-in.
13+
//
14+
// One deliberately boring, deterministic tool: `echo_upper` upper-cases the
15+
// input text, so the flow run's captured output proves the whole chain
16+
// (metadata entry → mcp provider factory → tools/list → connector_action
17+
// dispatch → tools/call) with zero flakiness.
18+
19+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
20+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
21+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
22+
23+
const server = new Server(
24+
{ name: 'showcase-mcp-fixture', version: '1.0.0' },
25+
{ capabilities: { tools: {} } },
26+
);
27+
28+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
29+
tools: [
30+
{
31+
name: 'echo_upper',
32+
description: 'Upper-case the input text (deterministic showcase demo tool).',
33+
inputSchema: {
34+
type: 'object',
35+
properties: { text: { type: 'string', description: 'Text to upper-case' } },
36+
required: ['text'],
37+
},
38+
},
39+
],
40+
}));
41+
42+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
43+
if (req.params.name !== 'echo_upper') {
44+
return { content: [{ type: 'text', text: `unknown tool: ${req.params.name}` }], isError: true };
45+
}
46+
const text = String(req.params.arguments?.text ?? '');
47+
const upper = text.toUpperCase();
48+
return {
49+
content: [{ type: 'text', text: upper }],
50+
structuredContent: { upper },
51+
};
52+
});
53+
54+
await server.connect(new StdioServerTransport());

examples/app-showcase/src/automation/flows/index.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,55 @@ export const ShowcaseDeclarativeConnectorPingFlow = defineFlow({
445445
],
446446
});
447447

448+
/**
449+
* MCP Connector Echo (ADR-0097 / #3056) — dispatches through the DECLARATIVE
450+
* MCP instance `showcase_mcp_tools` (src/system/connectors/index.ts), which the
451+
* `mcp` provider materialized at boot from the in-repo stdio fixture server:
452+
* its `tools/list` became the action list, and this `connector_action` invokes
453+
* the `echo_upper` tool via `tools/call`. The run's captured output
454+
* (`structuredContent.upper === 'OBJECTSTACK'`) proves the full chain —
455+
* metadata entry → provider factory → MCP handshake → flow dispatch — with no
456+
* external dependency. Completes the `provider: 'mcp'` acceptance demo from
457+
* ADR-0097 §6, deferred at #3017.
458+
*/
459+
export const ShowcaseMcpConnectorEchoFlow = defineFlow({
460+
name: 'showcase_mcp_connector_echo',
461+
label: 'MCP Connector Echo (ADR-0097)',
462+
description:
463+
'Dispatches the echo_upper tool of showcase_mcp_tools — a declarative MCP connector instance materialized ' +
464+
'at boot from the in-repo stdio fixture (scripts/mcp-fixture.mjs).',
465+
type: 'autolaunched',
466+
// Surface the tool result on the run output, so the flow run view (and the
467+
// dogfood proof) can assert the MCP round-trip observably.
468+
variables: [{ name: 'echo.structuredContent', type: 'json', isOutput: true }],
469+
nodes: [
470+
{
471+
id: 'start',
472+
type: 'start',
473+
label: 'On Task Created',
474+
config: {
475+
objectName: 'showcase_task',
476+
triggerType: 'record-after-create',
477+
},
478+
},
479+
{
480+
id: 'echo',
481+
type: 'connector_action',
482+
label: 'echo_upper via MCP (declarative)',
483+
connectorConfig: {
484+
connectorId: 'showcase_mcp_tools',
485+
actionId: 'echo_upper',
486+
input: { text: 'objectstack' },
487+
},
488+
},
489+
{ id: 'end', type: 'end', label: 'End' },
490+
],
491+
edges: [
492+
{ id: 'e1', source: 'start', target: 'echo' },
493+
{ id: 'e2', source: 'echo', target: 'end' },
494+
],
495+
});
496+
448497
/**
449498
* Task Follow-up Reminder — the worked `wait` (durable timer) example.
450499
*
@@ -1327,6 +1376,7 @@ export const allFlows = [
13271376
ScheduledDigestFlow,
13281377
TaskCompletedRestPingFlow,
13291378
ShowcaseDeclarativeConnectorPingFlow,
1379+
ShowcaseMcpConnectorEchoFlow,
13301380
TaskFollowUpFlow,
13311381
NotifyOwnerSubflow,
13321382
TaskDoneNotifyOwnerFlow,

examples/app-showcase/src/coverage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export const STACK_COLLECTION_COVERAGE: Record<string, KindCoverage> = {
184184
status: 'demonstrated',
185185
files: ['src/system/connectors/index.ts', 'src/automation/flows/index.ts'],
186186
notes:
187-
'Both connector kinds are demonstrated. (1) Provider-bound INSTANCES (ADR-0097 / #2977): StatusApiConnector declares `provider: rest` (inline config) and StatusOpenApiConnector declares `provider: openapi` with its OpenAPI document referenced as a package-relative FILE PATH (#3016, read at boot and confined to the package root) — both are materialized into live, dispatchable connectors at boot; ShowcaseDeclarativeConnectorPingFlow calls the rest instance via connector_action and both appear in GET /connectors. (2) Catalog DESCRIPTOR (#2612): ErpCatalogConnector has no provider, so it stays inert metadata; enabled:false marks the deliberate catalog entry and silences the boot audit. Plugin-registered connectors (ConnectorRestPlugin/ConnectorSlackPlugin in objectstack.config.ts) are also exercised by the connector flows.',
187+
'Both connector kinds are demonstrated. (1) Provider-bound INSTANCES (ADR-0097 / #2977) cover all three generic executors: StatusApiConnector declares `provider: rest` (inline config), StatusOpenApiConnector declares `provider: openapi` with its OpenAPI document referenced as a package-relative FILE PATH (#3016, read at boot and confined to the package root), and DevToolsMcpConnector declares `provider: mcp` against the in-repo stdio fixture (scripts/mcp-fixture.mjs, #3056) — spawned under the host declarativeStdio allowlist (#3055) and mapped tools/list → actions. All three are materialized into live, dispatchable connectors at boot; ShowcaseDeclarativeConnectorPingFlow calls the rest instance and ShowcaseMcpConnectorEchoFlow calls the mcp instance via connector_action, and all appear in GET /connectors. (2) Catalog DESCRIPTOR (#2612): ErpCatalogConnector has no provider, so it stays inert metadata; enabled:false marks the deliberate catalog entry and silences the boot audit. Plugin-registered connectors (ConnectorRestPlugin/ConnectorSlackPlugin in objectstack.config.ts) are also exercised by the connector flows.',
188188
},
189189
};
190190

examples/app-showcase/src/system/connectors/index.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,45 @@ export const StatusOpenApiConnector = defineConnector({
9191
auth: { type: 'none' },
9292
});
9393

94+
/**
95+
* ADR-0097 provider-bound instance, **mcp** form (#3056, completing the live
96+
* demo deferred from #3017): the entry points at the tiny in-repo MCP fixture
97+
* server (`scripts/mcp-fixture.mjs`) over a **stdio** transport. At boot the
98+
* `mcp` provider factory (ConnectorMcpPlugin in objectstack.config.ts) spawns
99+
* the fixture, calls `tools/list`, and maps its one deterministic tool
100+
* (`echo_upper`) to a connector action — no network, no ports, no boot-ordering
101+
* coupling, so the demo is CI-deterministic.
102+
*
103+
* Two platform behaviors are dogfooded here:
104+
* - **#3055 stdio policy**: a declarative stdio transport spawns a local
105+
* process from metadata, so it is denied by default; the host opts in via
106+
* `new ConnectorMcpPlugin({ declarativeStdio: ['node'] })` in
107+
* objectstack.config.ts. Remove that option and boot fails loudly with the
108+
* opt-in instructions — try it.
109+
* - **#3049 degrade + retry**: if the fixture were unreachable (e.g. the
110+
* script path wrong), boot would NOT crash — the instance registers
111+
* `state: 'degraded'` on GET /connectors and the platform retries with
112+
* backoff until it heals.
113+
*/
114+
export const DevToolsMcpConnector = defineConnector({
115+
name: 'showcase_mcp_tools',
116+
label: 'Dev Tools (Declarative MCP Instance)',
117+
type: 'api',
118+
description:
119+
'Provider-bound declarative connector instance (ADR-0097) backed by a Model Context Protocol server: the ' +
120+
'in-repo stdio fixture (scripts/mcp-fixture.mjs). Its tools/list becomes the action list — echo_upper is ' +
121+
'dispatched end-to-end by ShowcaseMcpConnectorEchoFlow.',
122+
provider: 'mcp',
123+
providerConfig: {
124+
// Spawned at materialization; the path is relative to the app's working
125+
// directory (`os dev`/`serve` run from the app root). The command must be
126+
// allowlisted by the host's declarativeStdio policy (#3055) — see
127+
// objectstack.config.ts.
128+
transport: { kind: 'stdio', command: 'node', args: ['./scripts/mcp-fixture.mjs'] },
129+
},
130+
auth: { type: 'none' },
131+
});
132+
94133
export const ErpCatalogConnector = defineConnector({
95134
name: 'showcase_erp_catalog',
96135
label: 'ERP Integration (Catalog Descriptor)',
@@ -143,4 +182,9 @@ export const ErpCatalogConnector = defineConnector({
143182
enabled: false,
144183
});
145184

146-
export const allConnectors: Connector[] = [StatusApiConnector, StatusOpenApiConnector, ErpCatalogConnector];
185+
export const allConnectors: Connector[] = [
186+
StatusApiConnector,
187+
StatusOpenApiConnector,
188+
DevToolsMcpConnector,
189+
ErpCatalogConnector,
190+
];

0 commit comments

Comments
 (0)