@reunionstudio/airlock-mcp 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/airlock-mcp/SKILL.md +17 -0
- package/.cortex-plugin/activation.md +6 -0
- package/.cortex-plugin/plugin.json +2 -2
- package/README.md +18 -8
- package/SECURITY.md +25 -2
- package/docs/architecture.md +13 -0
- package/docs/native-app-managed-mcp.md +67 -0
- package/docs/principal-workspace-interoperability.md +9 -4
- package/docs/workflows.md +14 -8
- package/package.json +2 -2
- package/patterns/okf-knowledge-bundle/README.md +42 -3
- package/patterns/okf-knowledge-bundle/sample.records.json +2 -2
- package/patterns/okf-knowledge-bundle/spec.config.json +1 -1
- package/setup.py +1 -1
- package/src/airlock_mcp/__init__.py +1 -1
- package/src/airlock_mcp/app_context.py +23 -0
- package/src/airlock_mcp/bootstrap.py +16 -0
- package/src/airlock_mcp/launcher.py +10 -0
- package/src/mcp.mjs +37 -11
- package/src/text.mjs +108 -12
- package/src/workbench.mjs +11 -8
|
@@ -83,6 +83,12 @@ deterministic checker for local spec drafts.
|
|
|
83
83
|
`airlock.admin.sync_okf_bundle_metadata(...)`, and exposes authoritative
|
|
84
84
|
accepted context through `AIRLOCK_DATA.ACTIVE.V_OKF_CONCEPT_METADATA`.
|
|
85
85
|
Draft and rejected bundles are not authoritative agent context.
|
|
86
|
+
Use OKF v0.2 `generated`, `sources`, `verified`, and lifecycle metadata;
|
|
87
|
+
legacy v0.1 bundles still work. See the pattern README for field shapes.
|
|
88
|
+
Verification and `status: stable` are producer claims, not Airlock approval
|
|
89
|
+
or authenticated identities. Neither `human:` nor other producer labels
|
|
90
|
+
grant authority. Never execute bundle computations or attesters as part of
|
|
91
|
+
validation/loading. Airlock retains its bounded Markdown-only profile.
|
|
86
92
|
6. Use `airlock-mcp import-spec <json-file> <name>` when starting from a
|
|
87
93
|
spec-library file, an exported `SPEC_CONFIG`, or an existing canonical config.
|
|
88
94
|
If the source is from `airlock-specs`, treat it as a reusable draft pattern.
|
|
@@ -229,6 +235,17 @@ Airlock:
|
|
|
229
235
|
- `airlock.admin.*` performs administrative changes and operational mutations.
|
|
230
236
|
- `airlock.agent.*` performs governed user or agent work in the actor's scope.
|
|
231
237
|
|
|
238
|
+
Installed Airlock may also expose separate opt-in Snowflake-managed MCP
|
|
239
|
+
endpoints for `app_agent` and `app_observer`. They are distinct from this local
|
|
240
|
+
spec-workbench MCP server. Administrators manage them with
|
|
241
|
+
`admin.enable_mcp_server`, `admin.disable_mcp_server`, and
|
|
242
|
+
`observe.mcp_servers`. Their fixed allowlists contain five agent tools
|
|
243
|
+
(`airlock_my_access_context`, work, safe spec description, spec states, and file
|
|
244
|
+
state) and four observer tools (specs, work, spec states, and file state). They
|
|
245
|
+
never expose mutation, governed payload rows, restricted-reference reads, or
|
|
246
|
+
attachment bytes. Each client must authenticate as its own Snowflake principal;
|
|
247
|
+
workspace identity is not Airlock authority.
|
|
248
|
+
|
|
232
249
|
Treat every mutating human or automated agent as an attributable Snowflake
|
|
233
250
|
principal. Direct work uses that principal's Airlock assignments. Work for
|
|
234
251
|
another principal requires explicit Airlock delegation and
|
|
@@ -10,5 +10,11 @@ work, `airlock.observe.*` for read-only observation, and `airlock.admin.*` only
|
|
|
10
10
|
for intentional administration. Use explicit Airlock delegation when the
|
|
11
11
|
connected principal acts for another principal.
|
|
12
12
|
|
|
13
|
+
Do not confuse this local spec-workbench MCP server with the optional
|
|
14
|
+
Snowflake-managed MCP endpoints exposed by an installed Airlock Native App.
|
|
15
|
+
Those role-separated agent and observer endpoints preserve Snowflake identity
|
|
16
|
+
and expose only fixed read-only tools; they do not expose mutation or governed
|
|
17
|
+
payload rows.
|
|
18
|
+
|
|
13
19
|
Never store credentials in this plugin or its workspace files. Configure the
|
|
14
20
|
Airlock MCP connection through the supported environment or connection setup.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "airlock-mcp",
|
|
3
3
|
"description": "Governed Airlock spec, observation, and commitment guidance for Snowflake principals.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.10",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Reunion Studio",
|
|
7
7
|
"url": "https://github.com/reunionstudio/airlock-mcp"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"command": "npx",
|
|
16
16
|
"args": [
|
|
17
17
|
"-y",
|
|
18
|
-
"@reunionstudio/airlock-mcp@0.1.
|
|
18
|
+
"@reunionstudio/airlock-mcp@0.1.10",
|
|
19
19
|
"server"
|
|
20
20
|
]
|
|
21
21
|
}
|
package/README.md
CHANGED
|
@@ -54,6 +54,13 @@ Current Airlock separates procedure intent:
|
|
|
54
54
|
specs, validating/loading data, workflow actions, attachments, delegations,
|
|
55
55
|
and references.
|
|
56
56
|
|
|
57
|
+
Installed Airlock can also expose a deliberately small read-only subset through
|
|
58
|
+
separate Snowflake-managed Native App MCP servers for `app_agent` and
|
|
59
|
+
`app_observer`. They are opt-in, preserve the authenticated Snowflake principal,
|
|
60
|
+
and do not replace the public procedure API or this package's local workbench.
|
|
61
|
+
See [Native App managed MCP](docs/native-app-managed-mcp.md) for the fixed tool
|
|
62
|
+
allowlists, setup calls, identity proof, and security boundary.
|
|
63
|
+
|
|
57
64
|
Every mutating human or automated agent should connect as its own attributable
|
|
58
65
|
Snowflake principal. When it acts for another principal, use explicit Airlock
|
|
59
66
|
delegation. Snowflake user type, chat identity, channel membership, workspace
|
|
@@ -78,18 +85,21 @@ current work, and
|
|
|
78
85
|
`airlock.observe.activity(...)` for historical events. The older split work
|
|
79
86
|
procedures are retired.
|
|
80
87
|
|
|
81
|
-
Polling agents should call `airlock.agent.spec_state(...)`
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
Polling agents should call `airlock.agent.spec_state(...)` for one known spec,
|
|
89
|
+
or `airlock.agent.spec_states(...)` for up to 100 known specs under one role
|
|
90
|
+
lens, before retrieving complete descriptors, file lists, or governed datasets.
|
|
91
|
+
Cache each authorization-scoped `STATE_TOKEN` and perform the heavier read only
|
|
92
|
+
when it changes. Poll `airlock.agent.list_my_work(...)` independently because
|
|
93
|
+
deadlines and expectation windows can change with time without rotating a spec
|
|
94
|
+
token.
|
|
86
95
|
After selecting a logical file, compare
|
|
87
96
|
`airlock.agent.file_state(...).FILE_STATE_ID`; that UUID rotates when its data,
|
|
88
97
|
workflow, attachments, exact source references, or retained version history
|
|
89
98
|
change. Observer clients use
|
|
90
|
-
the authorization-scoped, read-only `observe.spec_state(...)
|
|
91
|
-
`observe.file_state(...)` equivalents.
|
|
92
|
-
|
|
99
|
+
the authorization-scoped, read-only `observe.spec_state(...)`,
|
|
100
|
+
`observe.spec_states(...)`, and `observe.file_state(...)` equivalents. Missing
|
|
101
|
+
or inaccessible specs are omitted from a batched result without disclosing the
|
|
102
|
+
cause; that omitted row is the terminal polling signal. Authorized query
|
|
93
103
|
failures remain procedure errors.
|
|
94
104
|
|
|
95
105
|
### Observer Access And Seats
|
package/SECURITY.md
CHANGED
|
@@ -12,8 +12,31 @@ operation surface.
|
|
|
12
12
|
- Validates MCP server names before passing them to `codex mcp add`.
|
|
13
13
|
- Passes npm or GitHub package specs as argv entries, never through a shell.
|
|
14
14
|
- Rejects package specs with whitespace or control characters.
|
|
15
|
-
-
|
|
16
|
-
|
|
15
|
+
- Runs the bundled Python workbench in isolated mode from a trusted launcher;
|
|
16
|
+
project modules, user site-packages, and ambient `PYTHONPATH` do not enter its
|
|
17
|
+
import path. The selected Python interpreter and installed system packages
|
|
18
|
+
remain trusted. `cwd` controls workspace paths, not code discovery.
|
|
19
|
+
- Rejects malformed JSON-RPC envelopes without terminating the server.
|
|
20
|
+
Notifications never dispatch side-effecting workbench tools.
|
|
21
|
+
- Does not read local secrets, call Snowflake, or contact network services.
|
|
22
|
+
Workbench tools write only the project files explicitly requested by the
|
|
23
|
+
caller.
|
|
24
|
+
|
|
25
|
+
## Installed Native App MCP
|
|
26
|
+
|
|
27
|
+
Installed Airlock can separately expose opt-in Snowflake-managed MCP endpoints.
|
|
28
|
+
They are not hosted by this npm package and do not inherit local workspace
|
|
29
|
+
identity. Their fixed agent and observer allowlists expose only read-only access
|
|
30
|
+
context, work, safe spec discovery, and compact spec/file state. They exclude
|
|
31
|
+
administrative or governed mutation, governed payload rows, restricted-reference
|
|
32
|
+
reads, and attachment bytes.
|
|
33
|
+
|
|
34
|
+
Each client must authenticate as its own Snowflake principal with a role that
|
|
35
|
+
holds the matching Airlock application role and can use the configured
|
|
36
|
+
warehouse. The endpoint adapters call Airlock's existing handlers, so procedure
|
|
37
|
+
authorization, role lenses, observer scope, and license policy remain the
|
|
38
|
+
security boundary. Only fixed adapters in the role-accessible versioned `mcp`
|
|
39
|
+
schema are granted; Airlock's private `internal` schema remains ungranted.
|
|
17
40
|
|
|
18
41
|
## Production Rules
|
|
19
42
|
|
package/docs/architecture.md
CHANGED
|
@@ -36,10 +36,23 @@ product surface.
|
|
|
36
36
|
|
|
37
37
|
## MCP Surface
|
|
38
38
|
|
|
39
|
+
There are two complementary MCP surfaces:
|
|
40
|
+
|
|
41
|
+
- This package is a local bootstrap and spec-workbench server. It does not call
|
|
42
|
+
Snowflake or become an Airlock authorization layer.
|
|
43
|
+
- Installed Airlock may expose optional Snowflake-managed Native App MCP
|
|
44
|
+
servers over nine fixed read-only adapters. Those endpoints execute inside
|
|
45
|
+
Snowflake as the authenticated principal and retain the existing `agent.*`
|
|
46
|
+
and `observe.*` policy checks.
|
|
47
|
+
|
|
48
|
+
See [Native App managed MCP](native-app-managed-mcp.md) for the installed
|
|
49
|
+
surface. The remainder of this section describes this package's local server.
|
|
50
|
+
|
|
39
51
|
The bootstrap server exposes:
|
|
40
52
|
|
|
41
53
|
- prompt: `airlock-start`
|
|
42
54
|
- resource: `airlock://getting-started`
|
|
55
|
+
- resource: `airlock://native-app-managed-mcp`
|
|
43
56
|
- orientation tool: `airlock_start`
|
|
44
57
|
- workbench tools: `airlock_doctor`, `airlock_init_repo`,
|
|
45
58
|
`airlock_init_app_context`, `airlock_list_patterns`,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Native App Managed MCP
|
|
2
|
+
|
|
3
|
+
Installed Airlock can optionally expose separate Snowflake-managed MCP servers
|
|
4
|
+
for agent-scoped and observer-scoped reads. The stored procedures remain the
|
|
5
|
+
authoritative API; MCP is a small transport adapter over existing policy checks.
|
|
6
|
+
|
|
7
|
+
This is distinct from the local `airlock-mcp` package. The package helps agents
|
|
8
|
+
design specs and build Airlock-aware software. It does not automatically query
|
|
9
|
+
Snowflake. The Native App endpoints run in the consumer account and require an
|
|
10
|
+
independently authenticated Snowflake principal.
|
|
11
|
+
|
|
12
|
+
## Enablement
|
|
13
|
+
|
|
14
|
+
Airlock does not create MCP objects during installation. An app administrator
|
|
15
|
+
must opt in and name a usable warehouse:
|
|
16
|
+
|
|
17
|
+
```sql
|
|
18
|
+
CALL airlock.admin.enable_mcp_server('agent', 'AGENT_WH');
|
|
19
|
+
CALL airlock.admin.enable_mcp_server('observer', 'OBSERVER_WH');
|
|
20
|
+
CALL airlock.observe.mcp_servers();
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Use `validate_only => TRUE` to check the fixed plan without DDL. Disable an
|
|
24
|
+
endpoint with `airlock.admin.disable_mcp_server('agent')` or `'observer'`.
|
|
25
|
+
Disable and re-enable to change its warehouse or refresh its specification
|
|
26
|
+
after an Airlock upgrade.
|
|
27
|
+
|
|
28
|
+
The authenticated Snowflake role needs warehouse access and the matching
|
|
29
|
+
Airlock application role. Each mutating human or automated agent must retain
|
|
30
|
+
its own Snowflake principal; never use a shared MCP credential as a
|
|
31
|
+
meta-permission layer.
|
|
32
|
+
|
|
33
|
+
## Fixed Read-Only Tools
|
|
34
|
+
|
|
35
|
+
The agent endpoint exposes:
|
|
36
|
+
|
|
37
|
+
- `airlock_my_access_context`
|
|
38
|
+
- `airlock_list_my_work`
|
|
39
|
+
- `airlock_describe_spec`
|
|
40
|
+
- `airlock_spec_states`
|
|
41
|
+
- `airlock_file_state`
|
|
42
|
+
|
|
43
|
+
The observer endpoint exposes:
|
|
44
|
+
|
|
45
|
+
- `airlock_observe_specs`
|
|
46
|
+
- `airlock_observe_work`
|
|
47
|
+
- `airlock_observe_spec_states`
|
|
48
|
+
- `airlock_observe_file_state`
|
|
49
|
+
|
|
50
|
+
Neither endpoint exposes mutation, `admin.*`, governed row reads,
|
|
51
|
+
restricted-reference reads, or attachment bytes. Agent calls retain ordinary
|
|
52
|
+
assignment, role-lens, and named-license checks. Observer calls retain
|
|
53
|
+
assignment-scoped observer access and do not claim a named license.
|
|
54
|
+
|
|
55
|
+
Snowflake's managed SQL transport requires scalar `VARIANT` results, so the
|
|
56
|
+
servers target fixed adapters in Airlock's versioned `mcp` schema. Those
|
|
57
|
+
adapters invoke the same handlers as the public read procedures. Only the fixed
|
|
58
|
+
adapters are granted; Airlock's private `internal` schema remains ungranted.
|
|
59
|
+
|
|
60
|
+
## Principal Proof
|
|
61
|
+
|
|
62
|
+
Before relying on a new PAT-based client, run Airlock's
|
|
63
|
+
`scripts/run_batch62_mcp_identity_probe.py` with two independently issued,
|
|
64
|
+
role-restricted Snowflake programmatic access tokens. The proof must return two
|
|
65
|
+
distinct matching `USER_ID` values from `airlock_my_access_context`. External
|
|
66
|
+
workspace, channel, bot, or public-key identifiers are provenance only and
|
|
67
|
+
never confer Airlock authority.
|
|
@@ -26,12 +26,12 @@ or chat approvals as Snowflake authority.
|
|
|
26
26
|
|
|
27
27
|
Use state tokens before larger reads:
|
|
28
28
|
|
|
29
|
-
- `agent.spec_state(...).STATE_TOKEN` for
|
|
30
|
-
|
|
29
|
+
- `agent.spec_state(...).STATE_TOKEN` for one actor-visible spec, or
|
|
30
|
+
`agent.spec_states(...)` for up to 100 known specs under one role lens;
|
|
31
31
|
- `agent.file_state(...).FILE_STATE_ID` for one visible file's data, workflow,
|
|
32
32
|
attachment, reference, and retained-version state;
|
|
33
|
-
- `observe.spec_state` and `observe.file_state` for a
|
|
34
|
-
and
|
|
33
|
+
- `observe.spec_state`, `observe.spec_states`, and `observe.file_state` for a
|
|
34
|
+
scoped observer principal; and
|
|
35
35
|
- `agent.list_my_work` or `observe.work` on a separate cadence for deadlines
|
|
36
36
|
and current work.
|
|
37
37
|
|
|
@@ -39,6 +39,11 @@ A missing state row is the terminal result for that cursor. Return to discovery
|
|
|
39
39
|
instead of inferring whether the object was deleted, retired, or became
|
|
40
40
|
inaccessible.
|
|
41
41
|
|
|
42
|
+
Plural spec polling preserves first-request order after case-insensitive
|
|
43
|
+
deduplication and omits missing or inaccessible targets without identifying the
|
|
44
|
+
cause. Use it for one logical multi-spec polling cycle; keep the singular call
|
|
45
|
+
for one target.
|
|
46
|
+
|
|
42
47
|
## Commitment receipts
|
|
43
48
|
|
|
44
49
|
After every supported `agent.*` or intentional `admin.*` mutation, store a
|
package/docs/workflows.md
CHANGED
|
@@ -209,17 +209,18 @@ confirm the principal no longer needs `agent.*` or Streamlit before calling
|
|
|
209
209
|
`admin.unassign_license(...)`. Never share one Snowflake username across
|
|
210
210
|
channels merely to simplify setup; that collapses attribution and revocation.
|
|
211
211
|
|
|
212
|
-
Watcher loops should call `agent.spec_state`
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
Watcher loops should call `agent.spec_state` for one known spec or
|
|
213
|
+
`agent.spec_states` for up to 100 known specs under one role lens before larger
|
|
214
|
+
reads. Cache each authorization-scoped `STATE_TOKEN` and retrieve descriptors,
|
|
215
|
+
file lists, or governed data only when it changes. Poll `agent.list_my_work`
|
|
216
|
+
independently because deadlines and expectation windows can change without
|
|
217
|
+
rotating a spec token. Once the app tracks one logical file,
|
|
217
218
|
compare `agent.file_state(...).FILE_STATE_ID` to detect data replacement,
|
|
218
219
|
workflow movement, attachment changes, exact-reference changes, or retained
|
|
219
220
|
version-history changes. Observer principals use the authorization-scoped,
|
|
220
|
-
read-only `observe.spec_state` and `observe.file_state`
|
|
221
|
-
|
|
222
|
-
authorized query failures remain procedure errors.
|
|
221
|
+
read-only `observe.spec_state`, `observe.spec_states`, and `observe.file_state`
|
|
222
|
+
equivalents. Missing or inaccessible specs are omitted from a plural result as
|
|
223
|
+
the terminal polling signal; authorized query failures remain procedure errors.
|
|
223
224
|
|
|
224
225
|
Every mutating human or automated agent should connect as an attributable
|
|
225
226
|
Snowflake principal. When it acts for another principal, use explicit Airlock
|
|
@@ -424,6 +425,11 @@ Do not treat draft or rejected Markdown bundles as authoritative agent context.
|
|
|
424
425
|
The MCP local checker validates the spec workspace; bundle validation belongs
|
|
425
426
|
to Airlock's OKF validator and installed procedures.
|
|
426
427
|
|
|
428
|
+
The [pattern README](../patterns/okf-knowledge-bundle/README.md) describes the
|
|
429
|
+
OKF v0.2 profile and backward compatibility. Preserve provenance, verification,
|
|
430
|
+
and lifecycle claims as metadata. Never interpret them as Airlock permission or
|
|
431
|
+
approval, and never run a computation or attester during bundle loading.
|
|
432
|
+
|
|
427
433
|
## Start From A Known Process
|
|
428
434
|
|
|
429
435
|
Use this when the process is known but the data shape is not.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reunionstudio/airlock-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Single-install MCP interface for Airlock agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"airlock-mcp": "bin/airlock-mcp.mjs"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
|
-
"test": "node test/smoke.mjs"
|
|
25
|
+
"test": "node test/smoke.mjs && node test/mcp-boundaries.mjs"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"bin",
|
|
@@ -4,7 +4,7 @@ Use `okf-knowledge-bundle` when the team wants governed Markdown knowledge for
|
|
|
4
4
|
people and agents to use as accepted business context.
|
|
5
5
|
|
|
6
6
|
This pattern is for Airlock's `okf_knowledge_bundle` payload adapter. It is
|
|
7
|
-
|
|
7
|
+
aligned with Google's Open Knowledge Format v0.2, but Airlock applies a narrower
|
|
8
8
|
profile so accepted bundles can become governed context instead of loose wiki
|
|
9
9
|
content.
|
|
10
10
|
|
|
@@ -25,9 +25,44 @@ content.
|
|
|
25
25
|
- Every non-reserved Markdown file is a concept document with YAML frontmatter.
|
|
26
26
|
- Concept documents require `type`.
|
|
27
27
|
- Recommended concept fields are `title`, `description`, `resource`, `tags`,
|
|
28
|
-
`
|
|
28
|
+
`generated`, and `sources`. Legacy v0.1 `timestamp` and `source_links` work too.
|
|
29
|
+
- New root indexes should declare `okf_version: "0.2"`.
|
|
29
30
|
- `index.md` and `log.md` are reserved at every hierarchy level.
|
|
30
31
|
|
|
32
|
+
## V0.2 Example
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
---
|
|
36
|
+
type: Metric
|
|
37
|
+
title: Net Revenue
|
|
38
|
+
generated: {by: process:finance-context, at: "2026-09-01T00:00:00Z"}
|
|
39
|
+
verified: {by: process:finance-check, at: "2026-09-02T00:00:00Z"}
|
|
40
|
+
status: stable
|
|
41
|
+
stale_after: "2026-10-01T00:00:00Z"
|
|
42
|
+
sources:
|
|
43
|
+
- id: revenue-policy
|
|
44
|
+
resource: https://example.com/revenue-policy
|
|
45
|
+
---
|
|
46
|
+
Revenue less refunds and adjustments.[^revenue-policy]
|
|
47
|
+
|
|
48
|
+
[^revenue-policy]: Finance revenue policy
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Airlock normalizes a single `verified` object to an array and preserves source
|
|
52
|
+
IDs, producer identities, freshness, and extension fields in each parsed row's
|
|
53
|
+
`metadata`. All v0.2 datetimes need an explicit UTC offset. Optional families
|
|
54
|
+
may be omitted. External body links can be declared by `sources[].resource`
|
|
55
|
+
or legacy `source_links`. The existing timestamp projection uses `generated.at`
|
|
56
|
+
when `generated` exists, otherwise legacy `timestamp`.
|
|
57
|
+
|
|
58
|
+
`status: stable` and `verified` are producer claims, not Airlock approval or
|
|
59
|
+
authenticated identities. A `human:` verifier has no extra authority. Airlock
|
|
60
|
+
workflow, PDP, and separately configured Expectations remain authoritative;
|
|
61
|
+
`stale_after` does not create an Expectation. `Attested Computation` descriptors
|
|
62
|
+
can be stored, but Airlock does not execute computations or attesters, fetch
|
|
63
|
+
their resources, or verify their results. Bundles remain Markdown-only with
|
|
64
|
+
bounded size and strict paths/body links, not universal OKF v0.2 packages.
|
|
65
|
+
|
|
31
66
|
## Airlock Contract
|
|
32
67
|
|
|
33
68
|
The spec must declare:
|
|
@@ -54,7 +89,11 @@ authoritative only from:
|
|
|
54
89
|
AIRLOCK_DATA.ACTIVE.V_OKF_CONCEPT_METADATA
|
|
55
90
|
```
|
|
56
91
|
|
|
57
|
-
Draft and rejected bundles are not authoritative agent context.
|
|
92
|
+
Draft and rejected bundles are not authoritative agent context. In the view,
|
|
93
|
+
normalized frontmatter is under `METADATA:metadata`, not top-level columns
|
|
94
|
+
named after every new OKF field. Verify that the installed Airlock release
|
|
95
|
+
includes the v0.2 metadata update before relying on its normalization; updating
|
|
96
|
+
this local MCP package does not deploy the Native App.
|
|
58
97
|
|
|
59
98
|
## Local Workbench Notes
|
|
60
99
|
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
{
|
|
6
6
|
"concept_path": "runbooks/monthly-close",
|
|
7
7
|
"concept_type": "Runbook",
|
|
8
|
-
"bundle_version": "2026.
|
|
8
|
+
"bundle_version": "2026.09"
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"concept_path": "metrics/net-revenue",
|
|
12
12
|
"concept_type": "Metric",
|
|
13
|
-
"bundle_version": "2026.
|
|
13
|
+
"bundle_version": "2026.09"
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
}
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"sample_data": {
|
|
56
56
|
"sample_data_acknowledged": true,
|
|
57
57
|
"sample_rows": [],
|
|
58
|
-
"edited_sample_data": "concept_path,concept_type,bundle_version\nrunbooks/monthly-close,Runbook,2026.
|
|
58
|
+
"edited_sample_data": "concept_path,concept_type,bundle_version\nrunbooks/monthly-close,Runbook,2026.09\nmetrics/net-revenue,Metric,2026.09\n"
|
|
59
59
|
},
|
|
60
60
|
"attachment_policy": {
|
|
61
61
|
"attachments_enabled": true,
|
package/setup.py
CHANGED
|
@@ -20,6 +20,14 @@ The canonical specs live in the specs repo or installed Airlock. Files here are
|
|
|
20
20
|
snapshots, samples, generated helpers, and planning context for app development.
|
|
21
21
|
Refresh them when the canonical spec changes.
|
|
22
22
|
|
|
23
|
+
For governed knowledge, the `okf-knowledge-bundle` pattern uses Airlock's bounded
|
|
24
|
+
Markdown-only OKF v0.2 profile with legacy v0.1 support. Producer `verified`,
|
|
25
|
+
`generated`, lifecycle, and computation metadata do not grant Airlock authority
|
|
26
|
+
or approval. Loading never executes computations or attesters. Consume current
|
|
27
|
+
accepted context through `AIRLOCK_DATA.ACTIVE.V_OKF_CONCEPT_METADATA`; normalized
|
|
28
|
+
frontmatter is under `METADATA:metadata`. Verify the installed Airlock release
|
|
29
|
+
supports v0.2 normalization before relying on it.
|
|
30
|
+
|
|
23
31
|
Airlock's built-in Streamlit Native App is a generic operating and fallback
|
|
24
32
|
surface. A purpose-built app is the preferred place for domain-specific
|
|
25
33
|
summaries, calculations, evidence layout, terminology, and controls when those
|
|
@@ -42,6 +50,14 @@ Installed Airlock separates procedure intent:
|
|
|
42
50
|
- `agent.*`: governed agent work in the actor's scope.
|
|
43
51
|
- `admin.*`: administrative mutation and operational changes.
|
|
44
52
|
|
|
53
|
+
Installed Airlock may separately expose opt-in Snowflake-managed MCP endpoints
|
|
54
|
+
for `app_agent` and `app_observer`. They use fixed read-only tool allowlists,
|
|
55
|
+
preserve each client's authenticated Snowflake principal, and never expose
|
|
56
|
+
mutation, governed payload rows, restricted-reference reads, or attachment
|
|
57
|
+
bytes. They are distinct from the local Airlock MCP spec workbench. Manage them
|
|
58
|
+
with `admin.enable_mcp_server`, `admin.disable_mcp_server`, and
|
|
59
|
+
`observe.mcp_servers`.
|
|
60
|
+
|
|
45
61
|
Use `agent.list_my_work` for the actor's unified current-work inbox,
|
|
46
62
|
`observe.work` for account-wide current work, and `observe.activity` for
|
|
47
63
|
historical events. The older split workflow/expectation work calls are retired.
|
|
@@ -139,6 +155,12 @@ Installed Airlock procedure grammar:
|
|
|
139
155
|
- `admin.*` is administrative mutation. Do not use retired admin read wrappers;
|
|
140
156
|
use observe list/detail procedures instead.
|
|
141
157
|
|
|
158
|
+
An installed Airlock app may also provide separate opt-in Snowflake-managed MCP
|
|
159
|
+
endpoints for `app_agent` and `app_observer`. Their fixed tools are read-only,
|
|
160
|
+
retain the authenticated Snowflake principal, and exclude mutation and governed
|
|
161
|
+
payload reads. Do not confuse those endpoints with the local Airlock MCP spec
|
|
162
|
+
workbench or treat workspace identity as Airlock authority.
|
|
163
|
+
|
|
142
164
|
Use `agent.list_my_work` for the current actor's unified work inbox. Use
|
|
143
165
|
`observe.work` for account-wide current work and `observe.activity` for event
|
|
144
166
|
history.
|
|
@@ -224,6 +246,7 @@ def _manifest(mode: str, entries: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
224
246
|
"required_source_references": "load Draft, list eligible sources, pin exact manifest rows, then advance; SOURCE_REFERENCE_REQUIRED blocks missing evidence",
|
|
225
247
|
"spec_migration": "immutable two-version lifecycle; list with observe.spec_migrations, run bounded admin migration batches, inspect observe.spec_migration evidence, cancel only before activation, and retire only after the source is drained",
|
|
226
248
|
"admin": "administrative mutation and operational changes",
|
|
249
|
+
"native_mcp": "optional role-separated Snowflake-managed read-only endpoints; fixed agent and observer tools preserve Snowflake principal identity and never expose mutation or governed payload rows",
|
|
227
250
|
"restricted_reference": "one-record reference lookup through agent.get_reference_record; do not enumerate protected reference paths",
|
|
228
251
|
"attachment_preview": "governed Streamlit preview emits metadata-only ATTACHMENT_PREVIEW; MCP clients do not get direct stage access",
|
|
229
252
|
"application_surface": "built-in Streamlit is a generic operating/fallback surface; purpose-built apps own domain UI and use Airlock as the governed backend",
|
|
@@ -86,6 +86,13 @@ plan for more. Do not create the first workspace until the user chooses a path.
|
|
|
86
86
|
Create `posts` only when the user wants a shared feedback loop or explicitly
|
|
87
87
|
asks for the posts pattern.
|
|
88
88
|
|
|
89
|
+
For governed Markdown knowledge, use `okf-knowledge-bundle`. The Airlock profile
|
|
90
|
+
supports OKF v0.2 provenance, verification, and lifecycle metadata with legacy
|
|
91
|
+
v0.1 support. Read the pattern README for examples. `verified` and
|
|
92
|
+
`status: stable` are producer claims, never Airlock permissions or acceptance.
|
|
93
|
+
Bundle loading does not execute computations or attesters. Use the bounded
|
|
94
|
+
Markdown-only profile and verify the installed Airlock release has the update.
|
|
95
|
+
|
|
89
96
|
## Building Apps With Existing Specs
|
|
90
97
|
|
|
91
98
|
Use this path when the user wants to build an app, dashboard, approval queue,
|
|
@@ -126,6 +133,15 @@ Installed Airlock separates procedure intent:
|
|
|
126
133
|
- `agent.*`: governed agent work in the actor's scope.
|
|
127
134
|
- `admin.*`: administrative mutation and operational changes.
|
|
128
135
|
|
|
136
|
+
Installed Airlock may also expose separate opt-in Snowflake-managed MCP
|
|
137
|
+
endpoints for `app_agent` and `app_observer`. They are distinct from this local
|
|
138
|
+
spec-workbench MCP server and contain only fixed read-only tools for access
|
|
139
|
+
context, work, safe spec discovery, and compact spec/file state. They preserve
|
|
140
|
+
the authenticated Snowflake principal and never expose mutation, governed
|
|
141
|
+
payload rows, restricted-reference reads, or attachment bytes. Administrators
|
|
142
|
+
manage them through `admin.enable_mcp_server`, `admin.disable_mcp_server`, and
|
|
143
|
+
`observe.mcp_servers`.
|
|
144
|
+
|
|
129
145
|
Use `agent.list_my_work` for the actor's unified current-work inbox,
|
|
130
146
|
`observe.work` for account-wide current work, and `observe.activity` for
|
|
131
147
|
historical events. The older split workflow/expectation work calls are retired.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Start the bundled workbench under Python isolated mode (including Python 3.9)."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import runpy
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if __name__ == "__main__":
|
|
9
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
10
|
+
runpy.run_module("airlock_mcp", run_name="__main__")
|
package/src/mcp.mjs
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PROTOCOL_VERSION,
|
|
5
|
+
airlockPrompt,
|
|
6
|
+
gettingStartedText,
|
|
7
|
+
nativeAppManagedMcpText,
|
|
8
|
+
} from "./text.mjs";
|
|
4
9
|
import { WORKBENCH_TOOLS, callWorkbenchTool } from "./workbench.mjs";
|
|
5
10
|
|
|
6
11
|
const GETTING_STARTED_URI = "airlock://getting-started";
|
|
12
|
+
const NATIVE_APP_MANAGED_MCP_URI = "airlock://native-app-managed-mcp";
|
|
7
13
|
|
|
8
14
|
const START_TOOL = {
|
|
9
15
|
name: "airlock_start",
|
|
@@ -32,6 +38,21 @@ export function makeError(id, code, message) {
|
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
export function handleMcpRequest(message) {
|
|
41
|
+
const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
42
|
+
const validId = typeof message?.id === "string" || (typeof message?.id === "number" && Number.isFinite(message.id));
|
|
43
|
+
if (
|
|
44
|
+
!isObject(message) ||
|
|
45
|
+
message.jsonrpc !== "2.0" ||
|
|
46
|
+
typeof message.method !== "string" ||
|
|
47
|
+
!message.method ||
|
|
48
|
+
(Object.hasOwn(message, "id") && !validId) ||
|
|
49
|
+
(Object.hasOwn(message, "params") && !isObject(message.params))
|
|
50
|
+
) {
|
|
51
|
+
return makeError(validId ? message.id : null, -32600, "invalid request");
|
|
52
|
+
}
|
|
53
|
+
// Notifications have no response and must not dispatch side-effecting tools.
|
|
54
|
+
if (!Object.hasOwn(message, "id")) return undefined;
|
|
55
|
+
|
|
35
56
|
const { id, method, params } = message;
|
|
36
57
|
|
|
37
58
|
if (method === "initialize") {
|
|
@@ -44,10 +65,10 @@ export function handleMcpRequest(message) {
|
|
|
44
65
|
},
|
|
45
66
|
serverInfo: {
|
|
46
67
|
name: "airlock",
|
|
47
|
-
version: "0.1.
|
|
68
|
+
version: "0.1.10",
|
|
48
69
|
},
|
|
49
70
|
instructions:
|
|
50
|
-
"Airlock MCP
|
|
71
|
+
"Airlock MCP is the local spec workbench and app-context bootstrap server. Use airlock_start for orientation or the airlock_* tools to bootstrap, draft, check, summarize, export, and render specs. Installed Airlock can separately expose opt-in Snowflake-managed read-only MCP endpoints; read airlock://native-app-managed-mcp for their fixed tools and security boundary.",
|
|
51
72
|
});
|
|
52
73
|
}
|
|
53
74
|
|
|
@@ -122,20 +143,30 @@ export function handleMcpRequest(message) {
|
|
|
122
143
|
description: "How to start building and using Airlock specs with Codex.",
|
|
123
144
|
mimeType: "text/markdown",
|
|
124
145
|
},
|
|
146
|
+
{
|
|
147
|
+
uri: NATIVE_APP_MANAGED_MCP_URI,
|
|
148
|
+
name: "Installed Airlock Native App MCP",
|
|
149
|
+
description: "Opt-in Snowflake-managed read-only endpoints exposed by an installed Airlock Native App.",
|
|
150
|
+
mimeType: "text/markdown",
|
|
151
|
+
},
|
|
125
152
|
],
|
|
126
153
|
});
|
|
127
154
|
}
|
|
128
155
|
|
|
129
156
|
if (method === "resources/read") {
|
|
130
|
-
if (params?.uri !== GETTING_STARTED_URI) {
|
|
157
|
+
if (params?.uri !== GETTING_STARTED_URI && params?.uri !== NATIVE_APP_MANAGED_MCP_URI) {
|
|
131
158
|
return makeError(id, -32602, `unknown resource: ${params?.uri || ""}`);
|
|
132
159
|
}
|
|
160
|
+
const text =
|
|
161
|
+
params.uri === NATIVE_APP_MANAGED_MCP_URI
|
|
162
|
+
? nativeAppManagedMcpText()
|
|
163
|
+
: gettingStartedText("Home");
|
|
133
164
|
return makeResponse(id, {
|
|
134
165
|
contents: [
|
|
135
166
|
{
|
|
136
167
|
uri: params.uri,
|
|
137
168
|
mimeType: "text/markdown",
|
|
138
|
-
text
|
|
169
|
+
text,
|
|
139
170
|
},
|
|
140
171
|
],
|
|
141
172
|
});
|
|
@@ -167,18 +198,13 @@ export async function runServer({ input = process.stdin, output = process.stdout
|
|
|
167
198
|
continue;
|
|
168
199
|
}
|
|
169
200
|
|
|
170
|
-
if (message.id === undefined) {
|
|
171
|
-
handleMcpRequest(message);
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
201
|
try {
|
|
176
202
|
const response = handleMcpRequest(message);
|
|
177
203
|
if (response) {
|
|
178
204
|
output.write(encodeMessage(response));
|
|
179
205
|
}
|
|
180
206
|
} catch (error) {
|
|
181
|
-
output.write(encodeMessage(makeError(message
|
|
207
|
+
output.write(encodeMessage(makeError(message?.id ?? null, -32603, error.message)));
|
|
182
208
|
}
|
|
183
209
|
}
|
|
184
210
|
}
|
package/src/text.mjs
CHANGED
|
@@ -56,20 +56,28 @@ access, which specs are read sources, which spec records decisions or actions,
|
|
|
56
56
|
and where the app should run. Teach the installed Airlock procedure split:
|
|
57
57
|
\`airlock.observe.*\` is read-only governance observation,
|
|
58
58
|
\`airlock.agent.*\` is governed agent work, and \`airlock.admin.*\` is
|
|
59
|
-
administrative mutation.
|
|
59
|
+
administrative mutation. Installed Airlock may also expose separate opt-in
|
|
60
|
+
Snowflake-managed MCP endpoints for the agent and observer application roles.
|
|
61
|
+
Those endpoints have fixed read-only tool allowlists and preserve the
|
|
62
|
+
authenticated Snowflake principal; they are not this local spec-workbench MCP
|
|
63
|
+
server. They never expose mutation, governed payload rows, restricted-reference
|
|
64
|
+
reads, or attachment bytes. Prefer observe payloads such as
|
|
60
65
|
\`observe.procedures\`, \`observe.specs\`, \`observe.spec\`,
|
|
61
66
|
\`observe.governance_map\`, \`observe.explain_access\`, \`observe.health\`,
|
|
62
67
|
\`observe.activity\`, \`observe.admin_activity\`, \`observe.spec_admin_activity\`,
|
|
63
68
|
and \`observe.billing_events\` before inventing custom read paths. For \`alter_spec\`
|
|
64
69
|
activity, use \`CHANGED_SECTIONS\` and \`CHANGED_FIELDS\` to triage what changed
|
|
65
|
-
before fetching version snapshots. For polling loops,
|
|
66
|
-
\`agent.spec_state(...).STATE_TOKEN\`
|
|
70
|
+
before fetching version snapshots. For polling loops, use
|
|
71
|
+
\`agent.spec_state(...).STATE_TOKEN\` for one known spec or
|
|
72
|
+
\`agent.spec_states(...)\` for up to 100 known specs under one role lens, and
|
|
73
|
+
do larger reads only when a token changes.
|
|
67
74
|
After selecting one logical file, compare
|
|
68
75
|
\`agent.file_state(...).FILE_STATE_ID\` to detect data, workflow, attachment,
|
|
69
76
|
exact-reference, or retained version-history changes. Observer principals may use
|
|
70
|
-
the authorization-scoped \`observe.spec_state
|
|
71
|
-
equivalents.
|
|
72
|
-
|
|
77
|
+
the authorization-scoped \`observe.spec_state\`, \`observe.spec_states\`, and
|
|
78
|
+
\`observe.file_state\` equivalents. Missing or inaccessible specs are omitted
|
|
79
|
+
from a plural result without disclosing the cause; that omitted row is the
|
|
80
|
+
terminal polling signal. Authorized query failures remain errors. Holding
|
|
73
81
|
\`app_observer\` opens only the read-only procedure family; Airlock
|
|
74
82
|
assignments plus \`observer_access.observer_roles\` determine spec visibility,
|
|
75
83
|
and account-wide observation requires \`app_admin\` or the locked Airlock role
|
|
@@ -164,6 +172,7 @@ Airlock MCP will offer:
|
|
|
164
172
|
- spec design with the bundled workbench
|
|
165
173
|
- Airlock operating patterns for OODA loops and separation of duties
|
|
166
174
|
- read-only observe procedures for governance maps, health, access explanation, activity, billing events, and context packets
|
|
175
|
+
- the optional role-separated Snowflake-managed MCP endpoints exposed by installed Airlock
|
|
167
176
|
- scoped spec and file state tokens for efficient polling
|
|
168
177
|
- app context seeding with spec snapshots and manifests
|
|
169
178
|
- app and workflow coding against existing Airlock specs
|
|
@@ -175,6 +184,78 @@ Airlock MCP will offer:
|
|
|
175
184
|
- airlock-specs library patterns as starting points, checked against current artifacts`;
|
|
176
185
|
}
|
|
177
186
|
|
|
187
|
+
export function nativeAppManagedMcpText() {
|
|
188
|
+
return `# Installed Airlock Native App MCP
|
|
189
|
+
|
|
190
|
+
This npm package runs a local spec workbench and app-context bootstrap MCP
|
|
191
|
+
server. An installed Airlock Native App can separately expose two opt-in,
|
|
192
|
+
Snowflake-managed MCP endpoints for governed operational discovery. Enabling
|
|
193
|
+
those endpoints does not turn this local server into a Snowflake client.
|
|
194
|
+
|
|
195
|
+
## Enable And Inspect
|
|
196
|
+
|
|
197
|
+
An Airlock administrator selects a warehouse and enables each interface
|
|
198
|
+
explicitly. Replace \`airlock\` with the installed application object name when
|
|
199
|
+
it differs:
|
|
200
|
+
|
|
201
|
+
\`\`\`sql
|
|
202
|
+
CALL airlock.admin.enable_mcp_server('agent', 'AGENT_WH', TRUE);
|
|
203
|
+
CALL airlock.admin.enable_mcp_server('agent', 'AGENT_WH');
|
|
204
|
+
CALL airlock.admin.enable_mcp_server('observer', 'OBSERVER_WH');
|
|
205
|
+
CALL airlock.observe.mcp_servers();
|
|
206
|
+
\`\`\`
|
|
207
|
+
|
|
208
|
+
The first call is validate-only. Use
|
|
209
|
+
\`airlock.admin.disable_mcp_server('agent')\` or \`'observer'\` to remove an
|
|
210
|
+
endpoint. Disable and re-enable an endpoint to change its warehouse or refresh
|
|
211
|
+
its fixed tool specification after an Airlock upgrade. The client principal's
|
|
212
|
+
Snowflake role must be able to use the selected warehouse and hold the matching
|
|
213
|
+
Airlock application role.
|
|
214
|
+
|
|
215
|
+
## Fixed Tool Allowlists
|
|
216
|
+
|
|
217
|
+
The agent endpoint, \`core.airlock_agent_mcp\`, exposes only:
|
|
218
|
+
|
|
219
|
+
- \`airlock_my_access_context\`
|
|
220
|
+
- \`airlock_list_my_work\`
|
|
221
|
+
- \`airlock_describe_spec\`
|
|
222
|
+
- \`airlock_spec_states\`
|
|
223
|
+
- \`airlock_file_state\`
|
|
224
|
+
|
|
225
|
+
The observer endpoint, \`core.airlock_observer_mcp\`, exposes only:
|
|
226
|
+
|
|
227
|
+
- \`airlock_observe_specs\`
|
|
228
|
+
- \`airlock_observe_work\`
|
|
229
|
+
- \`airlock_observe_spec_states\`
|
|
230
|
+
- \`airlock_observe_file_state\`
|
|
231
|
+
|
|
232
|
+
Neither endpoint exposes \`admin.*\`, governed or administrative mutation,
|
|
233
|
+
governed payload rows, restricted-reference reads, or attachment bytes. Existing
|
|
234
|
+
Airlock procedure authorization remains the policy boundary. Except for the
|
|
235
|
+
non-claiming \`airlock_my_access_context\` tool, agent tools retain ordinary
|
|
236
|
+
named-license enforcement and may auto-assign a license when consumer policy
|
|
237
|
+
allows it. Observer tools neither require nor assign a named license.
|
|
238
|
+
|
|
239
|
+
## Identity And Transport Boundary
|
|
240
|
+
|
|
241
|
+
Every MCP client should authenticate as its own Snowflake principal. A shared
|
|
242
|
+
token, workspace role, channel identity, or external agent id does not create
|
|
243
|
+
Airlock authority. Treat external identity metadata as provenance only; use
|
|
244
|
+
Airlock delegation when one authenticated principal acts for another.
|
|
245
|
+
|
|
246
|
+
The endpoint specifications target scalar \`VARIANT\` adapters in Airlock's
|
|
247
|
+
role-accessible, versioned \`mcp\` transport schema because Snowflake's managed
|
|
248
|
+
MCP SQL API consumes JSONV2 rather than Arrow table results. The adapters call
|
|
249
|
+
the same read-only handlers and retain the same authorization, role-lens,
|
|
250
|
+
observer-scope, and license checks as the public procedures. Only the fixed
|
|
251
|
+
adapters are granted from \`mcp\`; Airlock's private \`internal\` schema remains
|
|
252
|
+
ungranted.
|
|
253
|
+
|
|
254
|
+
Use \`airlock_my_access_context\` first when validating a new agent connection,
|
|
255
|
+
and require that independently issued credentials return their exact distinct
|
|
256
|
+
\`USER_ID\` values.`;
|
|
257
|
+
}
|
|
258
|
+
|
|
178
259
|
export function gettingStartedText(project) {
|
|
179
260
|
return `# Airlock MCP
|
|
180
261
|
|
|
@@ -198,6 +279,13 @@ Airlock MCP gives agents four kinds of Airlock help:
|
|
|
198
279
|
context packets, and governance maps before deciding what an app or agent
|
|
199
280
|
should do.
|
|
200
281
|
|
|
282
|
+
Installed Airlock can also expose separate opt-in, role-separated,
|
|
283
|
+
Snowflake-managed read-only MCP endpoints. They preserve the authenticated
|
|
284
|
+
Snowflake principal and use fixed agent and observer tool allowlists. They are
|
|
285
|
+
distinct from this local spec-workbench server. Read
|
|
286
|
+
\`airlock://native-app-managed-mcp\` for the exact tools, lifecycle procedures,
|
|
287
|
+
licensing behavior, and security boundary.
|
|
288
|
+
|
|
201
289
|
Start in a Git-backed specs repo such as ${specsRepoName(project)}. GitHub is
|
|
202
290
|
the recommended default when the user has it set up, but any normal repository
|
|
203
291
|
works. If Codex is creating the repo, ask where the directory should live before
|
|
@@ -244,6 +332,11 @@ creates a spec with \`core_config.payload_adapter\` set to
|
|
|
244
332
|
\`airlock.admin.load_okf_bundle(...)\`, can sync parsed metadata with
|
|
245
333
|
\`airlock.admin.sync_okf_bundle_metadata(...)\`, and exposes authoritative
|
|
246
334
|
accepted context through \`AIRLOCK_DATA.ACTIVE.V_OKF_CONCEPT_METADATA\`.
|
|
335
|
+
The profile supports OKF v0.2 provenance, verification, and lifecycle metadata,
|
|
336
|
+
with legacy v0.1 support. Read the pattern README for examples. Verification
|
|
337
|
+
claims and \`status: stable\` do not grant Airlock approval or permissions.
|
|
338
|
+
Bundles remain bounded Markdown-only packages; loading never executes a
|
|
339
|
+
computation or attester. This MCP workbench does not deploy that Native App update.
|
|
247
340
|
|
|
248
341
|
Give Codex the messy version of the process. Airlock MCP should help turn it
|
|
249
342
|
into a small first Airlock spec and a plan for more.
|
|
@@ -258,13 +351,16 @@ read-side discovery with observe payloads such as \`observe.procedures\`,
|
|
|
258
351
|
\`observe.explain_access\`, \`observe.health\`, \`observe.activity\`,
|
|
259
352
|
\`observe.admin_activity\`, \`observe.spec_admin_activity\`, and \`observe.billing_events\`;
|
|
260
353
|
for \`alter_spec\` activity, use \`CHANGED_SECTIONS\` and \`CHANGED_FIELDS\` to
|
|
261
|
-
triage what changed before fetching version snapshots. For polling loops,
|
|
262
|
-
\`agent.spec_state\`
|
|
354
|
+
triage what changed before fetching version snapshots. For polling loops, use
|
|
355
|
+
\`agent.spec_state\` for one known spec or \`agent.spec_states\` for up to 100
|
|
356
|
+
known specs under one role lens, and compare scoped \`STATE_TOKEN\` values before
|
|
357
|
+
larger reads;
|
|
263
358
|
compare \`agent.file_state(...).FILE_STATE_ID\` for one selected file. Observer
|
|
264
|
-
principals use \`observe.spec_state
|
|
265
|
-
authorization-scoped read-only state. File-state
|
|
266
|
-
version-history changes.
|
|
267
|
-
the terminal polling signal; authorized
|
|
359
|
+
principals use \`observe.spec_state\`, \`observe.spec_states\`, and
|
|
360
|
+
\`observe.file_state\` for authorization-scoped read-only state. File-state
|
|
361
|
+
UUIDs include retained version-history changes. Missing or inaccessible specs
|
|
362
|
+
are omitted from a plural result as the terminal polling signal; authorized
|
|
363
|
+
query failures remain errors.
|
|
268
364
|
\`app_observer\` grants access to the
|
|
269
365
|
read-only procedure family; Airlock assignments and
|
|
270
366
|
\`observer_access.observer_roles\` grant spec visibility. Observe-only calls do
|
package/src/workbench.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, statSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -24,6 +24,9 @@ const workspaceProperty = {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
const appModeValues = ["spec-first", "app-first", "co-development"];
|
|
27
|
+
const patternValues = JSON.parse(
|
|
28
|
+
readFileSync(new URL("../patterns/manifest.json", import.meta.url), "utf8"),
|
|
29
|
+
).patterns.map((pattern) => pattern.name);
|
|
27
30
|
|
|
28
31
|
export const WORKBENCH_TOOLS = [
|
|
29
32
|
{
|
|
@@ -82,7 +85,7 @@ export const WORKBENCH_TOOLS = [
|
|
|
82
85
|
{
|
|
83
86
|
pattern: {
|
|
84
87
|
type: "string",
|
|
85
|
-
enum:
|
|
88
|
+
enum: patternValues,
|
|
86
89
|
description: "Pattern to inspect.",
|
|
87
90
|
},
|
|
88
91
|
files: {
|
|
@@ -106,7 +109,7 @@ export const WORKBENCH_TOOLS = [
|
|
|
106
109
|
},
|
|
107
110
|
pattern: {
|
|
108
111
|
type: "string",
|
|
109
|
-
enum:
|
|
112
|
+
enum: patternValues,
|
|
110
113
|
description: "Starting pattern. Defaults to blank.",
|
|
111
114
|
},
|
|
112
115
|
output: {
|
|
@@ -330,12 +333,12 @@ function cliArgsForTool(name, args = {}) {
|
|
|
330
333
|
case "airlock_list_patterns":
|
|
331
334
|
return { cwd, cliArgs: ["list-patterns"] };
|
|
332
335
|
case "airlock_show_pattern": {
|
|
333
|
-
const cliArgs = ["show-pattern", requiredEnum(args, "pattern",
|
|
336
|
+
const cliArgs = ["show-pattern", requiredEnum(args, "pattern", patternValues)];
|
|
334
337
|
if (optionalBoolean(args, "files")) cliArgs.push("--files");
|
|
335
338
|
return { cwd, cliArgs };
|
|
336
339
|
}
|
|
337
340
|
case "airlock_init_workspace": {
|
|
338
|
-
const cliArgs = ["init", requiredString(args, "name"), "--pattern", optionalEnum(args, "pattern",
|
|
341
|
+
const cliArgs = ["init", requiredString(args, "name"), "--pattern", optionalEnum(args, "pattern", patternValues, "blank")];
|
|
339
342
|
maybePush(cliArgs, "--output", optionalString(args, "output", undefined));
|
|
340
343
|
if (force) cliArgs.push("--force");
|
|
341
344
|
return { cwd, cliArgs };
|
|
@@ -374,15 +377,15 @@ function cliArgsForTool(name, args = {}) {
|
|
|
374
377
|
export function runWorkbenchCli(cliArgs, { cwd, env = process.env, spawn = spawnSync } = {}) {
|
|
375
378
|
const root = packageRoot();
|
|
376
379
|
const python = env.AIRLOCK_MCP_PYTHON || env.PYTHON || "python3";
|
|
377
|
-
|
|
378
|
-
const
|
|
380
|
+
// Keep cwd for workspace paths, but never import Python code from that project.
|
|
381
|
+
const launcher = path.join(root, "src", "airlock_mcp", "launcher.py");
|
|
382
|
+
const result = spawn(python, ["-I", launcher, ...cliArgs], {
|
|
379
383
|
cwd,
|
|
380
384
|
encoding: "utf8",
|
|
381
385
|
maxBuffer: OUTPUT_MAX_BUFFER,
|
|
382
386
|
env: {
|
|
383
387
|
...env,
|
|
384
388
|
AIRLOCK_MCP_HOME: root,
|
|
385
|
-
PYTHONPATH: pythonPath,
|
|
386
389
|
},
|
|
387
390
|
});
|
|
388
391
|
|