@reunionstudio/airlock-mcp 0.1.8 → 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.
@@ -229,6 +229,17 @@ 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
+
232
243
  Treat every mutating human or automated agent as an attributable Snowflake
233
244
  principal. Direct work uses that principal's Airlock assignments. Work for
234
245
  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.8",
4
+ "version": "0.1.9",
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.8",
18
+ "@reunionstudio/airlock-mcp@0.1.9",
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(...)` before retrieving a
82
- complete descriptor, file list, or governed dataset. Cache the
83
- authorization-scoped `STATE_TOKEN` and perform the heavier read only when it
84
- changes. Poll `airlock.agent.list_my_work(...)` independently because deadlines
85
- and expectation windows can change with time without rotating a spec token.
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(...)` and
91
- `observe.file_state(...)` equivalents. A missing object or revoked observer
92
- grant returns no row, which is the terminal polling signal; authorized query
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,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
- - Exposes guidance only; it does not read local secrets, call Snowflake, mutate
16
- files, or contact network services.
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
 
@@ -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 the actor's visible contract, data,
30
- and access state;
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 scoped observer principal;
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` before larger reads. Cache its
213
- authorization-scoped `STATE_TOKEN` and retrieve descriptors, file lists, or
214
- governed data only when it changes. Poll `agent.list_my_work` independently
215
- because deadlines and expectation windows can change without rotating the spec
216
- token. Once the app tracks one logical file,
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` equivalents. A missing
221
- object or revoked grant returns no row as the terminal polling signal;
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reunionstudio/airlock-mcp",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Single-install MCP interface for Airlock agents.",
5
5
  "type": "module",
6
6
  "repository": {
package/setup.py CHANGED
@@ -27,7 +27,7 @@ def data_files():
27
27
 
28
28
  setup(
29
29
  name="airlock-mcp",
30
- version="0.1.8",
30
+ version="0.1.9",
31
31
  description="Codex-first workbench and CLI for drafting Airlock specs.",
32
32
  packages=find_packages("src"),
33
33
  package_dir={"": "src"},
@@ -1,3 +1,3 @@
1
1
  """Airlock MCP CLI package."""
2
2
 
3
- __version__ = "0.1.8"
3
+ __version__ = "0.1.9"
@@ -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 { PROTOCOL_VERSION, airlockPrompt, gettingStartedText } from "./text.mjs";
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.8",
53
+ version: "0.1.9",
48
54
  },
49
55
  instructions:
50
- "Airlock MCP helps agents improve processes with Airlock specs and build apps or workflows that use existing specs. Use airlock_start for orientation or the airlock_* tools to bootstrap, draft, check, summarize, export, and render specs.",
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: gettingStartedText("Home"),
154
+ text,
139
155
  },
140
156
  ],
141
157
  });
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. Prefer observe payloads such as
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, cache the role-scoped
66
- \`agent.spec_state(...).STATE_TOKEN\` and do larger reads only when it changes.
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\` and \`observe.file_state\`
71
- equivalents. A missing object or revoked observer grant returns no row as the
72
- terminal polling signal; authorized query failures remain errors. Holding
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
@@ -258,13 +346,16 @@ read-side discovery with observe payloads such as \`observe.procedures\`,
258
346
  \`observe.explain_access\`, \`observe.health\`, \`observe.activity\`,
259
347
  \`observe.admin_activity\`, \`observe.spec_admin_activity\`, and \`observe.billing_events\`;
260
348
  for \`alter_spec\` activity, use \`CHANGED_SECTIONS\` and \`CHANGED_FIELDS\` to
261
- triage what changed before fetching version snapshots. For polling loops, poll
262
- \`agent.spec_state\` and compare its scoped \`STATE_TOKEN\` before larger reads;
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;
263
353
  compare \`agent.file_state(...).FILE_STATE_ID\` for one selected file. Observer
264
- principals use \`observe.spec_state\` and \`observe.file_state\` for
265
- authorization-scoped read-only state. File-state UUIDs include retained
266
- version-history changes. A missing object or revoked grant returns no row as
267
- the terminal polling signal; authorized query failures remain errors.
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.
268
359
  \`app_observer\` grants access to the
269
360
  read-only procedure family; Airlock assignments and
270
361
  \`observer_access.observer_roles\` grant spec visibility. Observe-only calls do