@reunionstudio/airlock-mcp 0.1.9 → 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.
@@ -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.
@@ -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.9",
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.9",
18
+ "@reunionstudio/airlock-mcp@0.1.10",
19
19
  "server"
20
20
  ]
21
21
  }
package/SECURITY.md CHANGED
@@ -12,6 +12,12 @@ 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
+ - 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.
15
21
  - Does not read local secrets, call Snowflake, or contact network services.
16
22
  Workbench tools write only the project files explicitly requested by the
17
23
  caller.
package/docs/workflows.md CHANGED
@@ -425,6 +425,11 @@ Do not treat draft or rejected Markdown bundles as authoritative agent context.
425
425
  The MCP local checker validates the spec workspace; bundle validation belongs
426
426
  to Airlock's OKF validator and installed procedures.
427
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
+
428
433
  ## Start From A Known Process
429
434
 
430
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.9",
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
- inspired by Google's Open Knowledge Format, but Airlock applies a narrower
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
- `timestamp`, and `source_links`.
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.06"
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.06"
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.06\nmetrics/net-revenue,Metric,2026.06\n"
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
@@ -27,7 +27,7 @@ def data_files():
27
27
 
28
28
  setup(
29
29
  name="airlock-mcp",
30
- version="0.1.9",
30
+ version="0.1.10",
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.9"
3
+ __version__ = "0.1.10"
@@ -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
@@ -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,
@@ -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
@@ -38,6 +38,21 @@ export function makeError(id, code, message) {
38
38
  }
39
39
 
40
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
+
41
56
  const { id, method, params } = message;
42
57
 
43
58
  if (method === "initialize") {
@@ -50,7 +65,7 @@ export function handleMcpRequest(message) {
50
65
  },
51
66
  serverInfo: {
52
67
  name: "airlock",
53
- version: "0.1.9",
68
+ version: "0.1.10",
54
69
  },
55
70
  instructions:
56
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.",
@@ -183,18 +198,13 @@ export async function runServer({ input = process.stdin, output = process.stdout
183
198
  continue;
184
199
  }
185
200
 
186
- if (message.id === undefined) {
187
- handleMcpRequest(message);
188
- continue;
189
- }
190
-
191
201
  try {
192
202
  const response = handleMcpRequest(message);
193
203
  if (response) {
194
204
  output.write(encodeMessage(response));
195
205
  }
196
206
  } catch (error) {
197
- output.write(encodeMessage(makeError(message.id, -32603, error.message)));
207
+ output.write(encodeMessage(makeError(message?.id ?? null, -32603, error.message)));
198
208
  }
199
209
  }
200
210
  }
package/src/text.mjs CHANGED
@@ -332,6 +332,11 @@ creates a spec with \`core_config.payload_adapter\` set to
332
332
  \`airlock.admin.load_okf_bundle(...)\`, can sync parsed metadata with
333
333
  \`airlock.admin.sync_okf_bundle_metadata(...)\`, and exposes authoritative
334
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.
335
340
 
336
341
  Give Codex the messy version of the process. Airlock MCP should help turn it
337
342
  into a small first Airlock spec and a plan for more.
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: ["blank", "okf-knowledge-bundle", "posts"],
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: ["blank", "okf-knowledge-bundle", "posts"],
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", ["blank", "posts"])];
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", ["blank", "posts"], "blank")];
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
- const pythonPath = [path.join(root, "src"), env.PYTHONPATH].filter(Boolean).join(path.delimiter);
378
- const result = spawn(python, ["-m", "airlock_mcp", ...cliArgs], {
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