@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.
- package/.agents/skills/airlock-mcp/SKILL.md +6 -0
- package/.cortex-plugin/plugin.json +2 -2
- package/SECURITY.md +6 -0
- package/docs/workflows.md +5 -0
- package/package.json +2 -2
- package/patterns/okf-knowledge-bundle/README.md +42 -3
- package/patterns/okf-knowledge-bundle/sample.records.json +2 -2
- package/patterns/okf-knowledge-bundle/spec.config.json +1 -1
- package/setup.py +1 -1
- package/src/airlock_mcp/__init__.py +1 -1
- package/src/airlock_mcp/app_context.py +8 -0
- package/src/airlock_mcp/bootstrap.py +7 -0
- package/src/airlock_mcp/launcher.py +10 -0
- package/src/mcp.mjs +17 -7
- package/src/text.mjs +5 -0
- package/src/workbench.mjs +11 -8
|
@@ -83,6 +83,12 @@ deterministic checker for local spec drafts.
|
|
|
83
83
|
`airlock.admin.sync_okf_bundle_metadata(...)`, and exposes authoritative
|
|
84
84
|
accepted context through `AIRLOCK_DATA.ACTIVE.V_OKF_CONCEPT_METADATA`.
|
|
85
85
|
Draft and rejected bundles are not authoritative agent context.
|
|
86
|
+
Use OKF v0.2 `generated`, `sources`, `verified`, and lifecycle metadata;
|
|
87
|
+
legacy v0.1 bundles still work. See the pattern README for field shapes.
|
|
88
|
+
Verification and `status: stable` are producer claims, not Airlock approval
|
|
89
|
+
or authenticated identities. Neither `human:` nor other producer labels
|
|
90
|
+
grant authority. Never execute bundle computations or attesters as part of
|
|
91
|
+
validation/loading. Airlock retains its bounded Markdown-only profile.
|
|
86
92
|
6. Use `airlock-mcp import-spec <json-file> <name>` when starting from a
|
|
87
93
|
spec-library file, an exported `SPEC_CONFIG`, or an existing canonical config.
|
|
88
94
|
If the source is from `airlock-specs`, treat it as a reusable draft pattern.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "airlock-mcp",
|
|
3
3
|
"description": "Governed Airlock spec, observation, and commitment guidance for Snowflake principals.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.10",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Reunion Studio",
|
|
7
7
|
"url": "https://github.com/reunionstudio/airlock-mcp"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"command": "npx",
|
|
16
16
|
"args": [
|
|
17
17
|
"-y",
|
|
18
|
-
"@reunionstudio/airlock-mcp@0.1.
|
|
18
|
+
"@reunionstudio/airlock-mcp@0.1.10",
|
|
19
19
|
"server"
|
|
20
20
|
]
|
|
21
21
|
}
|
package/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.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Single-install MCP interface for Airlock agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"airlock-mcp": "bin/airlock-mcp.mjs"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
|
-
"test": "node test/smoke.mjs"
|
|
25
|
+
"test": "node test/smoke.mjs && node test/mcp-boundaries.mjs"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
28
|
"bin",
|
|
@@ -4,7 +4,7 @@ Use `okf-knowledge-bundle` when the team wants governed Markdown knowledge for
|
|
|
4
4
|
people and agents to use as accepted business context.
|
|
5
5
|
|
|
6
6
|
This pattern is for Airlock's `okf_knowledge_bundle` payload adapter. It is
|
|
7
|
-
|
|
7
|
+
aligned with Google's Open Knowledge Format v0.2, but Airlock applies a narrower
|
|
8
8
|
profile so accepted bundles can become governed context instead of loose wiki
|
|
9
9
|
content.
|
|
10
10
|
|
|
@@ -25,9 +25,44 @@ content.
|
|
|
25
25
|
- Every non-reserved Markdown file is a concept document with YAML frontmatter.
|
|
26
26
|
- Concept documents require `type`.
|
|
27
27
|
- Recommended concept fields are `title`, `description`, `resource`, `tags`,
|
|
28
|
-
`
|
|
28
|
+
`generated`, and `sources`. Legacy v0.1 `timestamp` and `source_links` work too.
|
|
29
|
+
- New root indexes should declare `okf_version: "0.2"`.
|
|
29
30
|
- `index.md` and `log.md` are reserved at every hierarchy level.
|
|
30
31
|
|
|
32
|
+
## V0.2 Example
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
---
|
|
36
|
+
type: Metric
|
|
37
|
+
title: Net Revenue
|
|
38
|
+
generated: {by: process:finance-context, at: "2026-09-01T00:00:00Z"}
|
|
39
|
+
verified: {by: process:finance-check, at: "2026-09-02T00:00:00Z"}
|
|
40
|
+
status: stable
|
|
41
|
+
stale_after: "2026-10-01T00:00:00Z"
|
|
42
|
+
sources:
|
|
43
|
+
- id: revenue-policy
|
|
44
|
+
resource: https://example.com/revenue-policy
|
|
45
|
+
---
|
|
46
|
+
Revenue less refunds and adjustments.[^revenue-policy]
|
|
47
|
+
|
|
48
|
+
[^revenue-policy]: Finance revenue policy
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Airlock normalizes a single `verified` object to an array and preserves source
|
|
52
|
+
IDs, producer identities, freshness, and extension fields in each parsed row's
|
|
53
|
+
`metadata`. All v0.2 datetimes need an explicit UTC offset. Optional families
|
|
54
|
+
may be omitted. External body links can be declared by `sources[].resource`
|
|
55
|
+
or legacy `source_links`. The existing timestamp projection uses `generated.at`
|
|
56
|
+
when `generated` exists, otherwise legacy `timestamp`.
|
|
57
|
+
|
|
58
|
+
`status: stable` and `verified` are producer claims, not Airlock approval or
|
|
59
|
+
authenticated identities. A `human:` verifier has no extra authority. Airlock
|
|
60
|
+
workflow, PDP, and separately configured Expectations remain authoritative;
|
|
61
|
+
`stale_after` does not create an Expectation. `Attested Computation` descriptors
|
|
62
|
+
can be stored, but Airlock does not execute computations or attesters, fetch
|
|
63
|
+
their resources, or verify their results. Bundles remain Markdown-only with
|
|
64
|
+
bounded size and strict paths/body links, not universal OKF v0.2 packages.
|
|
65
|
+
|
|
31
66
|
## Airlock Contract
|
|
32
67
|
|
|
33
68
|
The spec must declare:
|
|
@@ -54,7 +89,11 @@ authoritative only from:
|
|
|
54
89
|
AIRLOCK_DATA.ACTIVE.V_OKF_CONCEPT_METADATA
|
|
55
90
|
```
|
|
56
91
|
|
|
57
|
-
Draft and rejected bundles are not authoritative agent context.
|
|
92
|
+
Draft and rejected bundles are not authoritative agent context. In the view,
|
|
93
|
+
normalized frontmatter is under `METADATA:metadata`, not top-level columns
|
|
94
|
+
named after every new OKF field. Verify that the installed Airlock release
|
|
95
|
+
includes the v0.2 metadata update before relying on its normalization; updating
|
|
96
|
+
this local MCP package does not deploy the Native App.
|
|
58
97
|
|
|
59
98
|
## Local Workbench Notes
|
|
60
99
|
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
{
|
|
6
6
|
"concept_path": "runbooks/monthly-close",
|
|
7
7
|
"concept_type": "Runbook",
|
|
8
|
-
"bundle_version": "2026.
|
|
8
|
+
"bundle_version": "2026.09"
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"concept_path": "metrics/net-revenue",
|
|
12
12
|
"concept_type": "Metric",
|
|
13
|
-
"bundle_version": "2026.
|
|
13
|
+
"bundle_version": "2026.09"
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
}
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"sample_data": {
|
|
56
56
|
"sample_data_acknowledged": true,
|
|
57
57
|
"sample_rows": [],
|
|
58
|
-
"edited_sample_data": "concept_path,concept_type,bundle_version\nrunbooks/monthly-close,Runbook,2026.
|
|
58
|
+
"edited_sample_data": "concept_path,concept_type,bundle_version\nrunbooks/monthly-close,Runbook,2026.09\nmetrics/net-revenue,Metric,2026.09\n"
|
|
59
59
|
},
|
|
60
60
|
"attachment_policy": {
|
|
61
61
|
"attachments_enabled": true,
|
package/setup.py
CHANGED
|
@@ -20,6 +20,14 @@ The canonical specs live in the specs repo or installed Airlock. Files here are
|
|
|
20
20
|
snapshots, samples, generated helpers, and planning context for app development.
|
|
21
21
|
Refresh them when the canonical spec changes.
|
|
22
22
|
|
|
23
|
+
For governed knowledge, the `okf-knowledge-bundle` pattern uses Airlock's bounded
|
|
24
|
+
Markdown-only OKF v0.2 profile with legacy v0.1 support. Producer `verified`,
|
|
25
|
+
`generated`, lifecycle, and computation metadata do not grant Airlock authority
|
|
26
|
+
or approval. Loading never executes computations or attesters. Consume current
|
|
27
|
+
accepted context through `AIRLOCK_DATA.ACTIVE.V_OKF_CONCEPT_METADATA`; normalized
|
|
28
|
+
frontmatter is under `METADATA:metadata`. Verify the installed Airlock release
|
|
29
|
+
supports v0.2 normalization before relying on it.
|
|
30
|
+
|
|
23
31
|
Airlock's built-in Streamlit Native App is a generic operating and fallback
|
|
24
32
|
surface. A purpose-built app is the preferred place for domain-specific
|
|
25
33
|
summaries, calculations, evidence layout, terminology, and controls when those
|
|
@@ -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.
|
|
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
|
|
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:
|
|
88
|
+
enum: patternValues,
|
|
86
89
|
description: "Pattern to inspect.",
|
|
87
90
|
},
|
|
88
91
|
files: {
|
|
@@ -106,7 +109,7 @@ export const WORKBENCH_TOOLS = [
|
|
|
106
109
|
},
|
|
107
110
|
pattern: {
|
|
108
111
|
type: "string",
|
|
109
|
-
enum:
|
|
112
|
+
enum: patternValues,
|
|
110
113
|
description: "Starting pattern. Defaults to blank.",
|
|
111
114
|
},
|
|
112
115
|
output: {
|
|
@@ -330,12 +333,12 @@ function cliArgsForTool(name, args = {}) {
|
|
|
330
333
|
case "airlock_list_patterns":
|
|
331
334
|
return { cwd, cliArgs: ["list-patterns"] };
|
|
332
335
|
case "airlock_show_pattern": {
|
|
333
|
-
const cliArgs = ["show-pattern", requiredEnum(args, "pattern",
|
|
336
|
+
const cliArgs = ["show-pattern", requiredEnum(args, "pattern", patternValues)];
|
|
334
337
|
if (optionalBoolean(args, "files")) cliArgs.push("--files");
|
|
335
338
|
return { cwd, cliArgs };
|
|
336
339
|
}
|
|
337
340
|
case "airlock_init_workspace": {
|
|
338
|
-
const cliArgs = ["init", requiredString(args, "name"), "--pattern", optionalEnum(args, "pattern",
|
|
341
|
+
const cliArgs = ["init", requiredString(args, "name"), "--pattern", optionalEnum(args, "pattern", patternValues, "blank")];
|
|
339
342
|
maybePush(cliArgs, "--output", optionalString(args, "output", undefined));
|
|
340
343
|
if (force) cliArgs.push("--force");
|
|
341
344
|
return { cwd, cliArgs };
|
|
@@ -374,15 +377,15 @@ function cliArgsForTool(name, args = {}) {
|
|
|
374
377
|
export function runWorkbenchCli(cliArgs, { cwd, env = process.env, spawn = spawnSync } = {}) {
|
|
375
378
|
const root = packageRoot();
|
|
376
379
|
const python = env.AIRLOCK_MCP_PYTHON || env.PYTHON || "python3";
|
|
377
|
-
|
|
378
|
-
const
|
|
380
|
+
// Keep cwd for workspace paths, but never import Python code from that project.
|
|
381
|
+
const launcher = path.join(root, "src", "airlock_mcp", "launcher.py");
|
|
382
|
+
const result = spawn(python, ["-I", launcher, ...cliArgs], {
|
|
379
383
|
cwd,
|
|
380
384
|
encoding: "utf8",
|
|
381
385
|
maxBuffer: OUTPUT_MAX_BUFFER,
|
|
382
386
|
env: {
|
|
383
387
|
...env,
|
|
384
388
|
AIRLOCK_MCP_HOME: root,
|
|
385
|
-
PYTHONPATH: pythonPath,
|
|
386
389
|
},
|
|
387
390
|
});
|
|
388
391
|
|