@reunionstudio/airlock-mcp 0.1.7 → 0.1.9
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 +47 -0
- package/.cortex-plugin/activation.md +20 -0
- package/.cortex-plugin/plugin.json +23 -0
- package/README.md +113 -15
- package/SECURITY.md +19 -2
- package/docs/architecture.md +13 -0
- package/docs/native-app-managed-mcp.md +67 -0
- package/docs/principal-workspace-interoperability.md +106 -0
- package/docs/workflows.md +69 -12
- package/package.json +2 -1
- package/setup.py +1 -1
- package/src/airlock_mcp/__init__.py +1 -1
- package/src/airlock_mcp/app_context.py +15 -0
- package/src/airlock_mcp/bootstrap.py +9 -0
- package/src/mcp.mjs +21 -5
- package/src/text.mjs +161 -10
|
@@ -229,6 +229,38 @@ Airlock:
|
|
|
229
229
|
- `airlock.admin.*` performs administrative changes and operational mutations.
|
|
230
230
|
- `airlock.agent.*` performs governed user or agent work in the actor's scope.
|
|
231
231
|
|
|
232
|
+
Installed Airlock may also expose separate opt-in Snowflake-managed MCP
|
|
233
|
+
endpoints for `app_agent` and `app_observer`. They are distinct from this local
|
|
234
|
+
spec-workbench MCP server. Administrators manage them with
|
|
235
|
+
`admin.enable_mcp_server`, `admin.disable_mcp_server`, and
|
|
236
|
+
`observe.mcp_servers`. Their fixed allowlists contain five agent tools
|
|
237
|
+
(`airlock_my_access_context`, work, safe spec description, spec states, and file
|
|
238
|
+
state) and four observer tools (specs, work, spec states, and file state). They
|
|
239
|
+
never expose mutation, governed payload rows, restricted-reference reads, or
|
|
240
|
+
attachment bytes. Each client must authenticate as its own Snowflake principal;
|
|
241
|
+
workspace identity is not Airlock authority.
|
|
242
|
+
|
|
243
|
+
Treat every mutating human or automated agent as an attributable Snowflake
|
|
244
|
+
principal. Direct work uses that principal's Airlock assignments. Work for
|
|
245
|
+
another principal requires explicit Airlock delegation and
|
|
246
|
+
`on_behalf_of_user`; pass `delegation_id` only when Airlock reports more than
|
|
247
|
+
one matching active grant. Snowflake user type and agent-session metadata are
|
|
248
|
+
informational, not authority. Never map workspace roles, channel membership,
|
|
249
|
+
reactions, or chat approval into Airlock permissions.
|
|
250
|
+
A `DELEGATED_ACTION` event records the authorized attempt and actor/principal
|
|
251
|
+
identity; the mutation result and normal file, attachment, or workflow events
|
|
252
|
+
provide commitment evidence.
|
|
253
|
+
|
|
254
|
+
Stored-procedure events may expose
|
|
255
|
+
`EVENT_CONTEXT.snowflake_agent_active` as informational provenance. Never use
|
|
256
|
+
that marker to grant access, infer delegation, classify billing, or replace the
|
|
257
|
+
authenticated Snowflake principal.
|
|
258
|
+
|
|
259
|
+
A read-only channel may use a dedicated observer principal with
|
|
260
|
+
`app_observer`, a distinct Airlock role/assignment, and explicit per-spec
|
|
261
|
+
`observer_access`. Do not reuse that observer principal for mutation, and do
|
|
262
|
+
not share one mutating principal across human or automated actors.
|
|
263
|
+
|
|
232
264
|
Use `agent.list_my_work` as the actor's single operational inbox for workflow,
|
|
233
265
|
watcher, deadline, overdue, and exception work. Use `observe.work` for the
|
|
234
266
|
read-only account-wide current-work projection and `observe.activity` for event
|
|
@@ -243,6 +275,19 @@ workflow, attachment, and exact-reference changes. Use `observe.spec_state` and
|
|
|
243
275
|
`observe.file_state` only with observer/admin authority for account-wide state.
|
|
244
276
|
Do not substitute global observer tokens for an agent's scoped token.
|
|
245
277
|
|
|
278
|
+
After every supported `agent.*` or intentional `admin.*` mutation, preserve a
|
|
279
|
+
bounded `airlock.commitment-receipt/v1` in the external workspace history.
|
|
280
|
+
Record the procedure and authenticated Snowflake principal; derive any
|
|
281
|
+
represented principal and delegation id from the actual procedure arguments.
|
|
282
|
+
Retain the target spec/path/filename, returned status/code/message and issues,
|
|
283
|
+
and timestamp. After a successful direct file mutation, call `agent.file_state`
|
|
284
|
+
with the returned target and retain its `UPLOAD_ID`, `FILE_STATE_ID`, workflow
|
|
285
|
+
state, attachment count, and reference count. Do not make that direct actor
|
|
286
|
+
state read after delegated work; leave `committed_state` null unless the actor
|
|
287
|
+
performs a separate independently authorized observation. Keep a denied
|
|
288
|
+
mutation as a receipt with no committed state. Never use a chat display name as
|
|
289
|
+
the Snowflake actor or describe workspace approval as an Airlock commitment.
|
|
290
|
+
|
|
246
291
|
Required source references are exact governed evidence, not filename fields.
|
|
247
292
|
When an active downstream-to-source link has `min_count > 0`, load the
|
|
248
293
|
downstream file into Draft, discover eligible sources with
|
|
@@ -394,6 +439,8 @@ human decisions remain.
|
|
|
394
439
|
|
|
395
440
|
- Local checks are not Airlock authority.
|
|
396
441
|
- Installed Airlock procedures remain the execution contract.
|
|
442
|
+
- Every mutating actor keeps an attributable Snowflake principal; shared
|
|
443
|
+
integration identities and workspace roles are not authorization shortcuts.
|
|
397
444
|
- Do not write directly to Airlock-owned tables, stages, generated views, or
|
|
398
445
|
generated tables.
|
|
399
446
|
- Do not put Airlock workflow state, reviewer comments, approval status, or
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Airlock MCP activation
|
|
2
|
+
|
|
3
|
+
Use the bundled Airlock skill when a task concerns Airlock specs, governed
|
|
4
|
+
Snowflake work, read-only governance observation, delegation, expectations,
|
|
5
|
+
attachments, workflow, state polling, or commitment receipts.
|
|
6
|
+
|
|
7
|
+
Before acting, preserve the authenticated Snowflake principal. Workspace roles
|
|
8
|
+
and chat identity are not Airlock authority. Use `airlock.agent.*` for governed
|
|
9
|
+
work, `airlock.observe.*` for read-only observation, and `airlock.admin.*` only
|
|
10
|
+
for intentional administration. Use explicit Airlock delegation when the
|
|
11
|
+
connected principal acts for another principal.
|
|
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
|
+
|
|
19
|
+
Never store credentials in this plugin or its workspace files. Configure the
|
|
20
|
+
Airlock MCP connection through the supported environment or connection setup.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "airlock-mcp",
|
|
3
|
+
"description": "Governed Airlock spec, observation, and commitment guidance for Snowflake principals.",
|
|
4
|
+
"version": "0.1.9",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Reunion Studio",
|
|
7
|
+
"url": "https://github.com/reunionstudio/airlock-mcp"
|
|
8
|
+
},
|
|
9
|
+
"skills": [
|
|
10
|
+
"./.agents/skills"
|
|
11
|
+
],
|
|
12
|
+
"mcpServers": {
|
|
13
|
+
"airlock": {
|
|
14
|
+
"type": "stdio",
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": [
|
|
17
|
+
"-y",
|
|
18
|
+
"@reunionstudio/airlock-mcp@0.1.9",
|
|
19
|
+
"server"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
package/README.md
CHANGED
|
@@ -28,15 +28,25 @@ Airlock MCP gives agents four kinds of Airlock help:
|
|
|
28
28
|
procedures to inspect setup, access, activity, billing events, health,
|
|
29
29
|
context packets, and governance maps before deciding what an app or agent
|
|
30
30
|
should do.
|
|
31
|
+
5. Workspace interoperability: preserve principal identity, poll scoped state,
|
|
32
|
+
commit only through installed procedures, and retain portable Airlock
|
|
33
|
+
receipts in external workspace history.
|
|
31
34
|
|
|
32
35
|
## Installed Airlock Contract
|
|
33
36
|
|
|
34
37
|
Current Airlock separates procedure intent:
|
|
35
38
|
|
|
36
39
|
- `airlock.observe.*` is the read-only governance observation surface. It is
|
|
37
|
-
available to `app_admin` and `app_observer
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
available at the Snowflake object layer to `app_admin` and `app_observer`.
|
|
41
|
+
Airlock assignments and per-spec `observer_access` rules scope ordinary
|
|
42
|
+
observers; account-wide surfaces require `app_admin` or the locked Airlock
|
|
43
|
+
role `global_observer`. That reserved role is observation-only: it cannot own
|
|
44
|
+
specs, receive guest/reviewer grants, or manage child roles.
|
|
45
|
+
Account-wide auditors should use `airlock.observe.observers(...)` for a flat
|
|
46
|
+
user, assignment, observer-role, spec/path/workflow, and license inventory.
|
|
47
|
+
`active_seat_observer_only` means the identity is observer-only now but still
|
|
48
|
+
has a billable seat that must be explicitly unassigned; role changes never
|
|
49
|
+
silently rewrite billing history.
|
|
40
50
|
- `airlock.admin.*` is for admin changes and operational actions such as
|
|
41
51
|
creating specs, changing roles, loading OKF bundles, rerunning setup, or
|
|
42
52
|
deleting purge candidates.
|
|
@@ -44,22 +54,93 @@ Current Airlock separates procedure intent:
|
|
|
44
54
|
specs, validating/loading data, workflow actions, attachments, delegations,
|
|
45
55
|
and references.
|
|
46
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
|
+
|
|
64
|
+
Every mutating human or automated agent should connect as its own attributable
|
|
65
|
+
Snowflake principal. When it acts for another principal, use explicit Airlock
|
|
66
|
+
delegation. Snowflake user type, chat identity, channel membership, workspace
|
|
67
|
+
roles, and reactions do not create Airlock authority. A read-only channel may
|
|
68
|
+
use a distinct scoped observer principal, but that principal must not be reused
|
|
69
|
+
for mutation.
|
|
70
|
+
|
|
71
|
+
Stored-procedure activity includes the informational
|
|
72
|
+
`EVENT_CONTEXT.snowflake_agent_active` marker when Snowflake exposes it. The
|
|
73
|
+
marker identifies execution context for observation; it never changes Airlock
|
|
74
|
+
access, workflow, expectations, billing, or receipt identity.
|
|
75
|
+
A `DELEGATED_ACTION` event records an authorized attempt, not proof that its
|
|
76
|
+
mutation committed. Commitment receipts derive delegation identity from the
|
|
77
|
+
actual call arguments, and only successful direct file mutations are hydrated
|
|
78
|
+
through `agent.file_state`; delegated work leaves `committed_state` null unless
|
|
79
|
+
the actor performs a separate independently authorized observation.
|
|
80
|
+
|
|
47
81
|
Use `airlock.agent.list_my_work(...)` for the current actor's unified workflow,
|
|
48
|
-
|
|
49
|
-
for the read-only
|
|
82
|
+
read-only observation, deadline, overdue, and exception inbox. Use
|
|
83
|
+
`airlock.observe.work(...)` for the read-only assignment-scoped projection of
|
|
84
|
+
current work, and
|
|
50
85
|
`airlock.observe.activity(...)` for historical events. The older split work
|
|
51
86
|
procedures are retired.
|
|
52
87
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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.
|
|
58
95
|
After selecting a logical file, compare
|
|
59
96
|
`airlock.agent.file_state(...).FILE_STATE_ID`; that UUID rotates when its data,
|
|
60
|
-
workflow, attachments,
|
|
61
|
-
|
|
62
|
-
`observe.
|
|
97
|
+
workflow, attachments, exact source references, or retained version history
|
|
98
|
+
change. Observer clients use
|
|
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
|
|
103
|
+
failures remain procedure errors.
|
|
104
|
+
|
|
105
|
+
### Observer Access And Seats
|
|
106
|
+
|
|
107
|
+
An observe-only integration does not claim or consume a named Airlock seat.
|
|
108
|
+
`airlock.agent.*` and the built-in Streamlit app remain seat-gated. Moving an
|
|
109
|
+
existing identity to observe-only does not silently remove a seat that was
|
|
110
|
+
previously assigned or claimed; an administrator should explicitly call
|
|
111
|
+
`airlock.admin.unassign_license(...)` after confirming the identity no longer
|
|
112
|
+
uses those surfaces.
|
|
113
|
+
|
|
114
|
+
Specs grant read-only visibility with canonical `observer_access` config:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"observer_access": {
|
|
119
|
+
"enabled": true,
|
|
120
|
+
"observer_roles": [
|
|
121
|
+
{
|
|
122
|
+
"role_name": "finance_observer",
|
|
123
|
+
"path_filter": "finance",
|
|
124
|
+
"workflow_states": ["Approved"]
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Grant the integration Snowflake application role `app_observer`, create or
|
|
132
|
+
reuse the Airlock observer role, and assign the integration's Snowflake
|
|
133
|
+
username to it. Use a distinct observer principal per channel so attribution,
|
|
134
|
+
scope, and revocation remain independent. Observe procedures return governance
|
|
135
|
+
metadata only, never governed payload rows or attachment bytes.
|
|
136
|
+
|
|
137
|
+
Set up an observer in four explicit steps: grant the observer principal's Snowflake role
|
|
138
|
+
`<APP_NAME>.app_observer`, create and assign a distinct Airlock role, publish
|
|
139
|
+
that role in each intended spec's `observer_access`, then verify the result with
|
|
140
|
+
`observe.observers(...)`. Audit assignment creation, changes, and removal with
|
|
141
|
+
`observe.admin_activity(..., action_family => 'assignments')`; its structured
|
|
142
|
+
event context includes before/after state. See [docs/workflows.md](docs/workflows.md)
|
|
143
|
+
for the operating checklist and billing cleanup boundary.
|
|
63
144
|
|
|
64
145
|
When building an app or workflow, prefer `observe.*` for read-only setup and
|
|
65
146
|
monitoring questions, `agent.*` for governed submissions in the actor's scope,
|
|
@@ -115,6 +196,19 @@ Install from npm:
|
|
|
115
196
|
npx @reunionstudio/airlock-mcp install
|
|
116
197
|
```
|
|
117
198
|
|
|
199
|
+
Install the bundled Cortex Code plugin from GitHub:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
cortex plugin install reunionstudio/airlock-mcp
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The `.cortex-plugin/plugin.json` manifest loads the same Airlock skill and pins
|
|
206
|
+
the stdio MCP server to this package version. Keep Snowflake and MCP credentials
|
|
207
|
+
outside the manifest. When the Cortex Code CLI is available locally, validate a
|
|
208
|
+
checkout with `cortex plugin validate` before distributing it. Account-local
|
|
209
|
+
Cortex Extension sharing remains subject to Snowflake's current preview and
|
|
210
|
+
RBAC requirements.
|
|
211
|
+
|
|
118
212
|
Today this package is a small installer and MCP launcher. For Codex, install
|
|
119
213
|
registers a local stdio server with:
|
|
120
214
|
|
|
@@ -133,6 +227,10 @@ person through process discovery, and entering the bundled spec-building
|
|
|
133
227
|
workbench when a first spec is ready to draft. It also guides agents building
|
|
134
228
|
apps or workflows that use specs the user already has access to.
|
|
135
229
|
|
|
230
|
+
See [principal and workspace interoperability](docs/principal-workspace-interoperability.md)
|
|
231
|
+
for the identity matrix, state-polling contract, and portable commitment
|
|
232
|
+
receipt.
|
|
233
|
+
|
|
136
234
|
Workspace summaries are structured spec cards. They present the current spec
|
|
137
235
|
core, file rules, attachment policy, guest access, column rules, sample record
|
|
138
236
|
shape, note-file status, and local check status so Codex can reflect the draft
|
|
@@ -273,8 +371,8 @@ before designing direct SQL helpers. These payloads are intended to be useful
|
|
|
273
371
|
to agents as well as humans.
|
|
274
372
|
|
|
275
373
|
For operational queues, call `agent.list_my_work` in the actor's scope. An
|
|
276
|
-
observer may call `observe.work` for
|
|
277
|
-
belong to `observe.activity`.
|
|
374
|
+
observer may call `observe.work` for current work in its assigned scope;
|
|
375
|
+
historical questions belong to `observe.activity`.
|
|
278
376
|
|
|
279
377
|
Required source references are a governed submission contract. When a
|
|
280
378
|
downstream spec has an active source link with `min_count > 0`, load the file
|
package/SECURITY.md
CHANGED
|
@@ -12,8 +12,25 @@ 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
|
-
files
|
|
15
|
+
- Does not read local secrets, call Snowflake, or contact network services.
|
|
16
|
+
Workbench tools write only the project files explicitly requested by the
|
|
17
|
+
caller.
|
|
18
|
+
|
|
19
|
+
## Installed Native App MCP
|
|
20
|
+
|
|
21
|
+
Installed Airlock can separately expose opt-in Snowflake-managed MCP endpoints.
|
|
22
|
+
They are not hosted by this npm package and do not inherit local workspace
|
|
23
|
+
identity. Their fixed agent and observer allowlists expose only read-only access
|
|
24
|
+
context, work, safe spec discovery, and compact spec/file state. They exclude
|
|
25
|
+
administrative or governed mutation, governed payload rows, restricted-reference
|
|
26
|
+
reads, and attachment bytes.
|
|
27
|
+
|
|
28
|
+
Each client must authenticate as its own Snowflake principal with a role that
|
|
29
|
+
holds the matching Airlock application role and can use the configured
|
|
30
|
+
warehouse. The endpoint adapters call Airlock's existing handlers, so procedure
|
|
31
|
+
authorization, role lenses, observer scope, and license policy remain the
|
|
32
|
+
security boundary. Only fixed adapters in the role-accessible versioned `mcp`
|
|
33
|
+
schema are granted; Airlock's private `internal` schema remains ungranted.
|
|
17
34
|
|
|
18
35
|
## Production Rules
|
|
19
36
|
|
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.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Principal and workspace interoperability
|
|
2
|
+
|
|
3
|
+
Airlock treats a workspace as a place for discussion, drafts, monitoring, and
|
|
4
|
+
automation. It does not treat workspace roles, channel membership, reactions,
|
|
5
|
+
or chat approvals as Snowflake authority.
|
|
6
|
+
|
|
7
|
+
## Required identity model
|
|
8
|
+
|
|
9
|
+
- Every mutating human or automated agent connects as its own attributable
|
|
10
|
+
Snowflake principal.
|
|
11
|
+
- Direct work uses that principal's Airlock assignments.
|
|
12
|
+
- Work for another principal uses an explicit Airlock delegation and
|
|
13
|
+
`on_behalf_of_user`; pass `delegation_id` only when Airlock reports multiple
|
|
14
|
+
matching active grants.
|
|
15
|
+
- Snowflake user type and agent-session metadata are informational. They do not
|
|
16
|
+
create Airlock authority or change policy, workflow, expectations, or
|
|
17
|
+
billing.
|
|
18
|
+
- Stored-procedure activity exposes `event_context.snowflake_agent_active` when
|
|
19
|
+
Snowflake supplies the execution-context marker. Use it for observation only;
|
|
20
|
+
the authenticated principal remains the authoritative actor.
|
|
21
|
+
- A read-only channel may use a dedicated observer principal with
|
|
22
|
+
`<APP_NAME>.app_observer` and explicit per-spec `observer_access`. Never reuse
|
|
23
|
+
that principal for mutation.
|
|
24
|
+
|
|
25
|
+
## Polling
|
|
26
|
+
|
|
27
|
+
Use state tokens before larger reads:
|
|
28
|
+
|
|
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
|
+
- `agent.file_state(...).FILE_STATE_ID` for one visible file's data, workflow,
|
|
32
|
+
attachment, reference, and retained-version state;
|
|
33
|
+
- `observe.spec_state`, `observe.spec_states`, and `observe.file_state` for a
|
|
34
|
+
scoped observer principal; and
|
|
35
|
+
- `agent.list_my_work` or `observe.work` on a separate cadence for deadlines
|
|
36
|
+
and current work.
|
|
37
|
+
|
|
38
|
+
A missing state row is the terminal result for that cursor. Return to discovery
|
|
39
|
+
instead of inferring whether the object was deleted, retired, or became
|
|
40
|
+
inaccessible.
|
|
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
|
+
|
|
47
|
+
## Commitment receipts
|
|
48
|
+
|
|
49
|
+
After every supported `agent.*` or intentional `admin.*` mutation, store a
|
|
50
|
+
bounded receipt in the workspace event history:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"schema": "airlock.commitment-receipt/v1",
|
|
55
|
+
"procedure": "airlock.agent.load_data",
|
|
56
|
+
"actor": {
|
|
57
|
+
"snowflake_principal": "CSMITH",
|
|
58
|
+
"on_behalf_of": "ASMITH",
|
|
59
|
+
"delegation_id": null
|
|
60
|
+
},
|
|
61
|
+
"target": {
|
|
62
|
+
"spec_name": "reimbursements",
|
|
63
|
+
"path": "asmith",
|
|
64
|
+
"filename": "expense_2026_08_08"
|
|
65
|
+
},
|
|
66
|
+
"outcome": {
|
|
67
|
+
"status": "ok",
|
|
68
|
+
"code": "LOADED",
|
|
69
|
+
"message": null,
|
|
70
|
+
"issues": []
|
|
71
|
+
},
|
|
72
|
+
"committed_state": null,
|
|
73
|
+
"operation_identity": {},
|
|
74
|
+
"recorded_at": "2026-08-08T12:00:00Z"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Bind `actor.snowflake_principal` to the authenticated connection identity, not
|
|
79
|
+
a chat display name. Preserve the procedure's stable status, code, message,
|
|
80
|
+
and issues. Derive `on_behalf_of` and `delegation_id` from the actual procedure
|
|
81
|
+
arguments and reject conflicting receipt metadata.
|
|
82
|
+
|
|
83
|
+
After a successful direct file mutation, call `agent.file_state` with the
|
|
84
|
+
returned spec, path, and filename to populate `committed_state`. Do not make
|
|
85
|
+
that direct actor state read after delegated work because `agent.file_state`
|
|
86
|
+
has no delegated call shape. Leave `committed_state` null unless the actor
|
|
87
|
+
performs a separate independently authorized observation. For attachment
|
|
88
|
+
mutations, retain the returned `ATTACHMENT_ID` in `operation_identity`.
|
|
89
|
+
|
|
90
|
+
A `DELEGATED_ACTION` event records the authorized delegated attempt and its
|
|
91
|
+
actor/principal identity. It does not by itself prove that the requested
|
|
92
|
+
mutation committed; use the procedure outcome and normal mutation events as
|
|
93
|
+
commitment evidence.
|
|
94
|
+
|
|
95
|
+
Denied mutations are receipts too. Preserve the denial code and issues, omit
|
|
96
|
+
committed state, and never describe workspace approval as an Airlock
|
|
97
|
+
commitment.
|
|
98
|
+
|
|
99
|
+
## Cortex Code plugin
|
|
100
|
+
|
|
101
|
+
This repository includes `.cortex-plugin/plugin.json`, which loads the bundled
|
|
102
|
+
Airlock skill and starts the pinned Airlock MCP package over stdio. Install from
|
|
103
|
+
the Git repository with Cortex Code's plugin command, inspect the version, and
|
|
104
|
+
keep credentials outside the plugin manifest. Account-local Cortex Extension
|
|
105
|
+
sharing may be used where enabled; follow Snowflake's current preview and RBAC
|
|
106
|
+
requirements.
|
package/docs/workflows.md
CHANGED
|
@@ -175,19 +175,76 @@ Use the installed Airlock procedure grammar:
|
|
|
175
175
|
- `airlock.admin.*` is for administrative mutation and operational changes.
|
|
176
176
|
|
|
177
177
|
For work discovery, use `agent.list_my_work` as the current actor's unified
|
|
178
|
-
inbox. Observers use `observe.work` for
|
|
179
|
-
`observe.activity` for historical events.
|
|
180
|
-
and
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
178
|
+
inbox. Observers use `observe.work` for current work within their assignment and
|
|
179
|
+
spec scope, and `observe.activity` for scoped historical events. `app_admin`
|
|
180
|
+
and the locked Airlock role `global_observer` receive the account-wide forms.
|
|
181
|
+
Do not recreate separate workflow and expectation inbox calls in app code.
|
|
182
|
+
|
|
183
|
+
For an account-wide observer audit, call `observe.observers(...)`. It flattens
|
|
184
|
+
the current user assignment, effective observer role, spec/path/workflow scope,
|
|
185
|
+
and named-license posture. Treat `active_seat_observer_only` as an explicit
|
|
186
|
+
administrative cleanup state: observer calls are free, but a previously claimed
|
|
187
|
+
seat remains billable until `admin.unassign_license(...)` is called.
|
|
188
|
+
|
|
189
|
+
When asked to set up an observer principal for a channel, keep these controls
|
|
190
|
+
separate and apply them in order:
|
|
191
|
+
|
|
192
|
+
1. Grant a dedicated Snowflake account role the installed app's
|
|
193
|
+
`<APP_NAME>.app_observer` application role.
|
|
194
|
+
2. Create a distinct Airlock role with `admin.create_roles(...)`, then assign
|
|
195
|
+
the observer principal's Snowflake username with
|
|
196
|
+
`admin.create_assignments(...)`.
|
|
197
|
+
3. Add that role to each intended spec's `observer_access.observer_roles`, using
|
|
198
|
+
`path_filter` and `workflow_states` when narrower visibility is required.
|
|
199
|
+
4. As `app_admin` or `global_observer`, verify the result with
|
|
200
|
+
`observe.observers(...)` and audit the setup through
|
|
201
|
+
`observe.admin_activity(procedure_name => 'create_assignments', action_family
|
|
202
|
+
=> 'assignments', ...)`.
|
|
203
|
+
|
|
204
|
+
Assignment create, alter, and drop activity includes structured
|
|
205
|
+
`EVENT_CONTEXT.before`, `EVENT_CONTEXT.after`, and
|
|
206
|
+
`EVENT_CONTEXT.changed_fields`. Expect `free_observer_only` when the principal
|
|
207
|
+
has no agent access or active seat. If it reports `active_seat_observer_only`,
|
|
208
|
+
confirm the principal no longer needs `agent.*` or Streamlit before calling
|
|
209
|
+
`admin.unassign_license(...)`. Never share one Snowflake username across
|
|
210
|
+
channels merely to simplify setup; that collapses attribution and revocation.
|
|
211
|
+
|
|
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,
|
|
187
218
|
compare `agent.file_state(...).FILE_STATE_ID` to detect data replacement,
|
|
188
|
-
workflow movement, attachment changes,
|
|
189
|
-
|
|
190
|
-
`observe.
|
|
219
|
+
workflow movement, attachment changes, exact-reference changes, or retained
|
|
220
|
+
version-history changes. Observer principals use the authorization-scoped,
|
|
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.
|
|
224
|
+
|
|
225
|
+
Every mutating human or automated agent should connect as an attributable
|
|
226
|
+
Snowflake principal. When it acts for another principal, use explicit Airlock
|
|
227
|
+
delegation and `on_behalf_of_user`; pass `delegation_id` only when multiple
|
|
228
|
+
active grants match. Workspace roles, channel membership, reactions, and chat
|
|
229
|
+
approvals remain collaboration context, not Airlock authority.
|
|
230
|
+
A `DELEGATED_ACTION` event records the authorized attempt and actor/principal
|
|
231
|
+
identity; it does not by itself prove that the requested mutation committed.
|
|
232
|
+
|
|
233
|
+
When activity includes `EVENT_CONTEXT.snowflake_agent_active`, treat it as
|
|
234
|
+
informational execution provenance. It does not grant authority, prove
|
|
235
|
+
delegation, or change policy and billing.
|
|
236
|
+
|
|
237
|
+
After each supported `agent.*` or intentional `admin.*` mutation, store an
|
|
238
|
+
`airlock.commitment-receipt/v1` in workspace history. Preserve the authenticated
|
|
239
|
+
Snowflake actor and derive any represented principal and delegation id from the
|
|
240
|
+
actual call arguments. Retain the target, stable status/code/message/issues,
|
|
241
|
+
and timestamp. For a successful direct file mutation, follow with
|
|
242
|
+
`agent.file_state` and retain `UPLOAD_ID`, `FILE_STATE_ID`, workflow state,
|
|
243
|
+
attachment count, and reference count. Do not make that direct actor state read
|
|
244
|
+
after delegated work; leave `committed_state` null unless the actor performs a
|
|
245
|
+
separate independently authorized observation. A denied call is still a
|
|
246
|
+
receipt, but it has no committed state. See
|
|
247
|
+
`docs/principal-workspace-interoperability.md` for the complete shape.
|
|
191
248
|
|
|
192
249
|
For downstream work that requires governed source evidence, use the installed
|
|
193
250
|
source-reference sequence: load the downstream Draft, call
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reunionstudio/airlock-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Single-install MCP interface for Airlock agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"src/*.mjs",
|
|
31
31
|
"src/airlock_mcp/*.py",
|
|
32
32
|
".agents",
|
|
33
|
+
".cortex-plugin",
|
|
33
34
|
"patterns",
|
|
34
35
|
"schemas",
|
|
35
36
|
"workspaces",
|
package/setup.py
CHANGED
|
@@ -42,6 +42,14 @@ Installed Airlock separates procedure intent:
|
|
|
42
42
|
- `agent.*`: governed agent work in the actor's scope.
|
|
43
43
|
- `admin.*`: administrative mutation and operational changes.
|
|
44
44
|
|
|
45
|
+
Installed Airlock may separately expose opt-in Snowflake-managed MCP endpoints
|
|
46
|
+
for `app_agent` and `app_observer`. They use fixed read-only tool allowlists,
|
|
47
|
+
preserve each client's authenticated Snowflake principal, and never expose
|
|
48
|
+
mutation, governed payload rows, restricted-reference reads, or attachment
|
|
49
|
+
bytes. They are distinct from the local Airlock MCP spec workbench. Manage them
|
|
50
|
+
with `admin.enable_mcp_server`, `admin.disable_mcp_server`, and
|
|
51
|
+
`observe.mcp_servers`.
|
|
52
|
+
|
|
45
53
|
Use `agent.list_my_work` for the actor's unified current-work inbox,
|
|
46
54
|
`observe.work` for account-wide current work, and `observe.activity` for
|
|
47
55
|
historical events. The older split workflow/expectation work calls are retired.
|
|
@@ -139,6 +147,12 @@ Installed Airlock procedure grammar:
|
|
|
139
147
|
- `admin.*` is administrative mutation. Do not use retired admin read wrappers;
|
|
140
148
|
use observe list/detail procedures instead.
|
|
141
149
|
|
|
150
|
+
An installed Airlock app may also provide separate opt-in Snowflake-managed MCP
|
|
151
|
+
endpoints for `app_agent` and `app_observer`. Their fixed tools are read-only,
|
|
152
|
+
retain the authenticated Snowflake principal, and exclude mutation and governed
|
|
153
|
+
payload reads. Do not confuse those endpoints with the local Airlock MCP spec
|
|
154
|
+
workbench or treat workspace identity as Airlock authority.
|
|
155
|
+
|
|
142
156
|
Use `agent.list_my_work` for the current actor's unified work inbox. Use
|
|
143
157
|
`observe.work` for account-wide current work and `observe.activity` for event
|
|
144
158
|
history.
|
|
@@ -224,6 +238,7 @@ def _manifest(mode: str, entries: list[dict[str, Any]]) -> dict[str, Any]:
|
|
|
224
238
|
"required_source_references": "load Draft, list eligible sources, pin exact manifest rows, then advance; SOURCE_REFERENCE_REQUIRED blocks missing evidence",
|
|
225
239
|
"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
240
|
"admin": "administrative mutation and operational changes",
|
|
241
|
+
"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
242
|
"restricted_reference": "one-record reference lookup through agent.get_reference_record; do not enumerate protected reference paths",
|
|
228
243
|
"attachment_preview": "governed Streamlit preview emits metadata-only ATTACHMENT_PREVIEW; MCP clients do not get direct stage access",
|
|
229
244
|
"application_surface": "built-in Streamlit is a generic operating/fallback surface; purpose-built apps own domain UI and use Airlock as the governed backend",
|
|
@@ -126,6 +126,15 @@ Installed Airlock separates procedure intent:
|
|
|
126
126
|
- `agent.*`: governed agent work in the actor's scope.
|
|
127
127
|
- `admin.*`: administrative mutation and operational changes.
|
|
128
128
|
|
|
129
|
+
Installed Airlock may also expose separate opt-in Snowflake-managed MCP
|
|
130
|
+
endpoints for `app_agent` and `app_observer`. They are distinct from this local
|
|
131
|
+
spec-workbench MCP server and contain only fixed read-only tools for access
|
|
132
|
+
context, work, safe spec discovery, and compact spec/file state. They preserve
|
|
133
|
+
the authenticated Snowflake principal and never expose mutation, governed
|
|
134
|
+
payload rows, restricted-reference reads, or attachment bytes. Administrators
|
|
135
|
+
manage them through `admin.enable_mcp_server`, `admin.disable_mcp_server`, and
|
|
136
|
+
`observe.mcp_servers`.
|
|
137
|
+
|
|
129
138
|
Use `agent.list_my_work` for the actor's unified current-work inbox,
|
|
130
139
|
`observe.work` for account-wide current work, and `observe.activity` for
|
|
131
140
|
historical events. The older split workflow/expectation work calls are retired.
|
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",
|
|
@@ -44,10 +50,10 @@ export function handleMcpRequest(message) {
|
|
|
44
50
|
},
|
|
45
51
|
serverInfo: {
|
|
46
52
|
name: "airlock",
|
|
47
|
-
version: "0.1.
|
|
53
|
+
version: "0.1.9",
|
|
48
54
|
},
|
|
49
55
|
instructions:
|
|
50
|
-
"Airlock MCP
|
|
56
|
+
"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
57
|
});
|
|
52
58
|
}
|
|
53
59
|
|
|
@@ -122,20 +128,30 @@ export function handleMcpRequest(message) {
|
|
|
122
128
|
description: "How to start building and using Airlock specs with Codex.",
|
|
123
129
|
mimeType: "text/markdown",
|
|
124
130
|
},
|
|
131
|
+
{
|
|
132
|
+
uri: NATIVE_APP_MANAGED_MCP_URI,
|
|
133
|
+
name: "Installed Airlock Native App MCP",
|
|
134
|
+
description: "Opt-in Snowflake-managed read-only endpoints exposed by an installed Airlock Native App.",
|
|
135
|
+
mimeType: "text/markdown",
|
|
136
|
+
},
|
|
125
137
|
],
|
|
126
138
|
});
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
if (method === "resources/read") {
|
|
130
|
-
if (params?.uri !== GETTING_STARTED_URI) {
|
|
142
|
+
if (params?.uri !== GETTING_STARTED_URI && params?.uri !== NATIVE_APP_MANAGED_MCP_URI) {
|
|
131
143
|
return makeError(id, -32602, `unknown resource: ${params?.uri || ""}`);
|
|
132
144
|
}
|
|
145
|
+
const text =
|
|
146
|
+
params.uri === NATIVE_APP_MANAGED_MCP_URI
|
|
147
|
+
? nativeAppManagedMcpText()
|
|
148
|
+
: gettingStartedText("Home");
|
|
133
149
|
return makeResponse(id, {
|
|
134
150
|
contents: [
|
|
135
151
|
{
|
|
136
152
|
uri: params.uri,
|
|
137
153
|
mimeType: "text/markdown",
|
|
138
|
-
text
|
|
154
|
+
text,
|
|
139
155
|
},
|
|
140
156
|
],
|
|
141
157
|
});
|
package/src/text.mjs
CHANGED
|
@@ -56,20 +56,61 @@ 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
|
|
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
|
-
|
|
70
|
-
\`observe.spec_state
|
|
76
|
+
exact-reference, or retained version-history changes. Observer principals may use
|
|
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
|
|
81
|
+
\`app_observer\` opens only the read-only procedure family; Airlock
|
|
82
|
+
assignments plus \`observer_access.observer_roles\` determine spec visibility,
|
|
83
|
+
and account-wide observation requires \`app_admin\` or the locked Airlock role
|
|
84
|
+
\`global_observer\`. That reserved role cannot own specs, receive guest/reviewer
|
|
85
|
+
grants, or manage child roles. Observe-only calls do not consume a named seat, while
|
|
86
|
+
\`agent.*\` and Streamlit remain seat-gated. Use a distinct Snowflake username
|
|
87
|
+
per observer channel principal. For account-wide audits, call
|
|
88
|
+
\`observe.observers(...)\` to join observer assignment/scope with agent-access
|
|
89
|
+
and license posture. \`active_seat_observer_only\` means the identity remains
|
|
90
|
+
billable until an administrator explicitly calls \`admin.unassign_license\`;
|
|
91
|
+
never infer billing from role names alone. When asked to configure an observer
|
|
92
|
+
principal, apply four explicit controls: grant its dedicated Snowflake role
|
|
93
|
+
\`<APP_NAME>.app_observer\`, create and assign a distinct Airlock role, publish
|
|
94
|
+
that role through each spec's \`observer_access\`, then verify scope and billing
|
|
95
|
+
with \`observe.observers(...)\`. Audit assignment create, alter, and drop
|
|
96
|
+
through \`observe.admin_activity(..., action_family => 'assignments')\`; use
|
|
97
|
+
the structured \`EVENT_CONTEXT.before\`, \`after\`, and \`changed_fields\`
|
|
98
|
+
instead of parsing descriptions. Offer to run \`airlock-mcp init-app-context\`
|
|
71
99
|
in the app repo to seed \`airlock/specs.manifest.json\`, spec snapshots, sample records, and
|
|
72
100
|
generated helper folders. Help code the app using approved Airlock/Snowflake access paths.
|
|
101
|
+
Treat every mutating human or automated agent as an attributable Snowflake
|
|
102
|
+
principal. Direct work uses that principal's assignments; work for another
|
|
103
|
+
principal uses explicit Airlock delegation and \`on_behalf_of_user\`. Workspace
|
|
104
|
+
roles, channel membership, reactions, and chat identity do not create Airlock
|
|
105
|
+
authority. Treat \`EVENT_CONTEXT.snowflake_agent_active\` as informational
|
|
106
|
+
provenance only. A \`DELEGATED_ACTION\` event records an authorized attempt, not
|
|
107
|
+
proof that its mutation committed. After each supported mutation, retain an
|
|
108
|
+
\`airlock.commitment-receipt/v1\` with the authenticated actor, delegation
|
|
109
|
+
identity derived from the actual call arguments, target, stable result, and
|
|
110
|
+
timestamp. Hydrate successful direct file mutations with \`agent.file_state\` so
|
|
111
|
+
the receipt includes \`UPLOAD_ID\`, \`FILE_STATE_ID\`, and current workflow state.
|
|
112
|
+
Leave delegated \`committed_state\` null unless the actor performs a separate
|
|
113
|
+
independently authorized observation.
|
|
73
114
|
Treat Airlock's built-in Streamlit app as a generic operating and fallback
|
|
74
115
|
surface, not a universal domain app. When a repeated, high-value decision needs
|
|
75
116
|
domain-specific summaries, calculations, evidence layout, terminology, or
|
|
@@ -131,7 +172,8 @@ Airlock MCP will offer:
|
|
|
131
172
|
- spec design with the bundled workbench
|
|
132
173
|
- Airlock operating patterns for OODA loops and separation of duties
|
|
133
174
|
- read-only observe procedures for governance maps, health, access explanation, activity, billing events, and context packets
|
|
134
|
-
-
|
|
175
|
+
- the optional role-separated Snowflake-managed MCP endpoints exposed by installed Airlock
|
|
176
|
+
- scoped spec and file state tokens for efficient polling
|
|
135
177
|
- app context seeding with spec snapshots and manifests
|
|
136
178
|
- app and workflow coding against existing Airlock specs
|
|
137
179
|
- observe specs for controlled interface ingestion
|
|
@@ -142,6 +184,78 @@ Airlock MCP will offer:
|
|
|
142
184
|
- airlock-specs library patterns as starting points, checked against current artifacts`;
|
|
143
185
|
}
|
|
144
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
|
+
|
|
145
259
|
export function gettingStartedText(project) {
|
|
146
260
|
return `# Airlock MCP
|
|
147
261
|
|
|
@@ -165,6 +279,13 @@ Airlock MCP gives agents four kinds of Airlock help:
|
|
|
165
279
|
context packets, and governance maps before deciding what an app or agent
|
|
166
280
|
should do.
|
|
167
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
|
+
|
|
168
289
|
Start in a Git-backed specs repo such as ${specsRepoName(project)}. GitHub is
|
|
169
290
|
the recommended default when the user has it set up, but any normal repository
|
|
170
291
|
works. If Codex is creating the repo, ask where the directory should live before
|
|
@@ -225,11 +346,41 @@ read-side discovery with observe payloads such as \`observe.procedures\`,
|
|
|
225
346
|
\`observe.explain_access\`, \`observe.health\`, \`observe.activity\`,
|
|
226
347
|
\`observe.admin_activity\`, \`observe.spec_admin_activity\`, and \`observe.billing_events\`;
|
|
227
348
|
for \`alter_spec\` activity, use \`CHANGED_SECTIONS\` and \`CHANGED_FIELDS\` to
|
|
228
|
-
triage what changed before fetching version snapshots. For
|
|
229
|
-
\`agent.spec_state\`
|
|
349
|
+
triage what changed before fetching version snapshots. For polling loops, use
|
|
350
|
+
\`agent.spec_state\` for one known spec or \`agent.spec_states\` for up to 100
|
|
351
|
+
known specs under one role lens, and compare scoped \`STATE_TOKEN\` values before
|
|
352
|
+
larger reads;
|
|
230
353
|
compare \`agent.file_state(...).FILE_STATE_ID\` for one selected file. Observer
|
|
231
|
-
|
|
232
|
-
read-only state.
|
|
354
|
+
principals use \`observe.spec_state\`, \`observe.spec_states\`, and
|
|
355
|
+
\`observe.file_state\` for authorization-scoped read-only state. File-state
|
|
356
|
+
UUIDs include retained version-history changes. Missing or inaccessible specs
|
|
357
|
+
are omitted from a plural result as the terminal polling signal; authorized
|
|
358
|
+
query failures remain errors.
|
|
359
|
+
\`app_observer\` grants access to the
|
|
360
|
+
read-only procedure family; Airlock assignments and
|
|
361
|
+
\`observer_access.observer_roles\` grant spec visibility. Observe-only calls do
|
|
362
|
+
not consume a named seat. Account-wide access requires \`app_admin\` or the
|
|
363
|
+
locked \`global_observer\` Airlock role, which cannot own specs, receive
|
|
364
|
+
guest/reviewer grants, or manage child roles. Account-wide auditors should call
|
|
365
|
+
\`observe.observers(...)\` for flattened observer scope and license posture;
|
|
366
|
+
\`active_seat_observer_only\` requires explicit \`admin.unassign_license\` to
|
|
367
|
+
stop future seat billing. Observer setup should use a dedicated Snowflake
|
|
368
|
+
username and account role, the \`<APP_NAME>.app_observer\` application role, a
|
|
369
|
+
distinct Airlock role/assignment, and explicit per-spec \`observer_access\`.
|
|
370
|
+
Verify it with \`observe.observers\` and audit structured assignment before/after
|
|
371
|
+
evidence with \`observe.admin_activity(..., action_family => 'assignments')\`.
|
|
372
|
+
Every mutating human or automated agent must keep an attributable Snowflake
|
|
373
|
+
principal. Use explicit Airlock delegation when it acts for someone else; never
|
|
374
|
+
turn workspace membership or chat approval into Airlock authority. Treat
|
|
375
|
+
\`EVENT_CONTEXT.snowflake_agent_active\` as informational provenance only. A
|
|
376
|
+
\`DELEGATED_ACTION\` event records an authorized attempt, not proof of commitment.
|
|
377
|
+
Store each supported mutation as an \`airlock.commitment-receipt/v1\`, preserving
|
|
378
|
+
the authenticated actor, delegation identity derived from the actual call
|
|
379
|
+
arguments, stable status/code/issues, target, and timestamp. Follow successful
|
|
380
|
+
direct file mutations with \`agent.file_state\` to retain \`UPLOAD_ID\`,
|
|
381
|
+
\`FILE_STATE_ID\`, and workflow state. Leave delegated \`committed_state\` null
|
|
382
|
+
unless the actor performs a separate independently authorized observation.
|
|
383
|
+
Do not use retired admin
|
|
233
384
|
read wrappers such as \`admin.list_specs\`, \`admin.describe_role\`, or
|
|
234
385
|
\`admin.list_events\`. If a reference spec declares \`restricted_reference\` or
|
|
235
386
|
\`reference_config.restricted_reference\`, do not enumerate the protected
|