@danielblomma/cortex-mcp 2.0.5 → 2.0.7

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.
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Capability registry for the harness. A capability is a least-privilege
5
+ * profile referenced by a workflow stage's `capability` field. The
6
+ * pre-tool-use hook reads the active stage's capability and uses it to
7
+ * gate the agent's tool calls.
8
+ *
9
+ * The capability defines:
10
+ * - read_globs paths the agent may read (empty = no restriction)
11
+ * - write_globs paths the agent may modify (empty = read-only)
12
+ * - tools_allowed which tool names the agent may call (empty = all)
13
+ *
14
+ * Glob patterns use minimatch syntax. The harness ships a default set
15
+ * keyed by the names referenced in default-workflows.ts; orgs can ship
16
+ * additional capabilities later via cortex-web sync.
17
+ */
18
+
19
+ const slugSchema = z
20
+ .string()
21
+ .min(1)
22
+ .max(80)
23
+ .regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$/);
24
+
25
+ export const capabilityDefinitionSchema = z.object({
26
+ name: slugSchema,
27
+ description: z.string().min(1).max(500),
28
+ read_globs: z.array(z.string().min(1)).default([]),
29
+ write_globs: z.array(z.string().min(1)).default([]),
30
+ tools_allowed: z.array(z.string().min(1)).default([]),
31
+ });
32
+
33
+ export type CapabilityDefinition = z.infer<typeof capabilityDefinitionSchema>;
34
+
35
+ /**
36
+ * Default capability profiles that ship with Cortex. Names match the
37
+ * `capability` fields referenced by SECURE_BUILD_WORKFLOW.
38
+ *
39
+ * tools_allowed is intentionally empty for most profiles to mean
40
+ * "no per-tool restriction beyond what the file globs already imply".
41
+ * The hook layer checks file paths first, tool name second.
42
+ */
43
+ export const DEFAULT_CAPABILITIES: Record<string, CapabilityDefinition> = {
44
+ planner: {
45
+ name: "planner",
46
+ description:
47
+ "Read-only profile for stages that produce planning artifacts. " +
48
+ "Can read the whole repo and call context tools, cannot modify any files.",
49
+ read_globs: ["**"],
50
+ write_globs: [],
51
+ tools_allowed: [],
52
+ },
53
+ reviewer: {
54
+ name: "reviewer",
55
+ description:
56
+ "Read-only profile for review stages. Same access as planner; the " +
57
+ "review artifact itself is written by the harness, not by an Edit tool.",
58
+ read_globs: ["**"],
59
+ write_globs: [],
60
+ tools_allowed: [],
61
+ },
62
+ builder: {
63
+ name: "builder",
64
+ description:
65
+ "Build profile. May edit source and test files but not config, " +
66
+ "lockfiles, CI workflows, or anything outside the obvious app surface.",
67
+ read_globs: ["**"],
68
+ write_globs: ["src/**", "tests/**", "test/**", "lib/**", "app/**", "components/**"],
69
+ tools_allowed: [],
70
+ },
71
+ tester: {
72
+ name: "tester",
73
+ description:
74
+ "Mutation/test profile. Read-only on production code, may edit only " +
75
+ "test files. Used by mutation-testing or coverage-improvement stages.",
76
+ read_globs: ["**"],
77
+ write_globs: ["tests/**", "test/**", "**/*.test.ts", "**/*.test.tsx", "**/*.test.mjs", "**/*.spec.ts"],
78
+ tools_allowed: [],
79
+ },
80
+ "security-reviewer": {
81
+ name: "security-reviewer",
82
+ description:
83
+ "Security review profile. Read-only across the repo. Produces a " +
84
+ "report artifact; no source modifications allowed.",
85
+ read_globs: ["**"],
86
+ write_globs: [],
87
+ tools_allowed: [],
88
+ },
89
+ human: {
90
+ name: "human",
91
+ description:
92
+ "Sentinel capability for human approval stages. The harness does not " +
93
+ "invoke an agent for this stage; the human writes the artifact directly. " +
94
+ "If a tool call somehow reaches the hook under this capability, it is " +
95
+ "blocked because no automation should be running.",
96
+ read_globs: [],
97
+ write_globs: [],
98
+ tools_allowed: [],
99
+ },
100
+ };
@@ -0,0 +1,83 @@
1
+ import type { WorkflowDefinition } from "./schemas.js";
2
+
3
+ /**
4
+ * The default secure-build workflow that ships with Cortex. Organizations
5
+ * can override this from cortex-web later (Phase 2 of the harness rollout);
6
+ * until then this is the workflow every project gets out of the box.
7
+ */
8
+ export const SECURE_BUILD_WORKFLOW: WorkflowDefinition = {
9
+ id: "secure-build",
10
+ description:
11
+ "Plan → Review → Build → Review → Mutation Tests → Security Review → Human Approval. " +
12
+ "The default Cortex Harness workflow for AI-driven development on production code.",
13
+ version: 1,
14
+ stages: [
15
+ {
16
+ name: "plan",
17
+ artifact: "plan.md",
18
+ reads: [],
19
+ required_fields: ["files_targeted", "constraints"],
20
+ capability: "planner",
21
+ description:
22
+ "Produce a step-by-step implementation plan grounded in the repo's rules and memory.",
23
+ },
24
+ {
25
+ name: "plan-review",
26
+ artifact: "plan-review.md",
27
+ reads: ["plan"],
28
+ required_fields: ["approved", "blocking_comments"],
29
+ capability: "reviewer",
30
+ description:
31
+ "Review the plan for architectural fit and rule compliance before any code is written.",
32
+ },
33
+ {
34
+ name: "build",
35
+ artifact: "changes.md",
36
+ reads: ["plan", "plan-review"],
37
+ required_fields: ["files_changed"],
38
+ capability: "builder",
39
+ description:
40
+ "Implement the approved plan. Produces the diff manifest used by the downstream reviewers.",
41
+ },
42
+ {
43
+ name: "build-review",
44
+ artifact: "build-review.md",
45
+ reads: ["plan", "changes"],
46
+ required_fields: ["approved", "blocking_comments"],
47
+ capability: "reviewer",
48
+ description:
49
+ "Review the implementation against the plan and the project's rules.",
50
+ },
51
+ {
52
+ name: "mutation",
53
+ artifact: "mutation-report.md",
54
+ reads: ["changes"],
55
+ required_fields: ["score", "survived"],
56
+ capability: "tester",
57
+ description:
58
+ "Run mutation tests on the changed files. Report score + surviving mutants.",
59
+ },
60
+ {
61
+ name: "security",
62
+ artifact: "security-report.md",
63
+ reads: ["changes"],
64
+ required_fields: ["findings", "severity_summary"],
65
+ capability: "security-reviewer",
66
+ description:
67
+ "Security review focused on the diff: injection, authn/authz, secrets, dependency risk.",
68
+ },
69
+ {
70
+ name: "approval",
71
+ artifact: "approval.md",
72
+ reads: ["plan", "changes", "build-review", "mutation", "security"],
73
+ required_fields: ["approved", "approver"],
74
+ capability: "human",
75
+ description:
76
+ "Human sign-off. Pulls every prior artifact for the approver to read; the approver writes the artifact.",
77
+ },
78
+ ],
79
+ };
80
+
81
+ export const DEFAULT_WORKFLOWS: Record<string, WorkflowDefinition> = {
82
+ [SECURE_BUILD_WORKFLOW.id]: SECURE_BUILD_WORKFLOW,
83
+ };
@@ -0,0 +1,206 @@
1
+ import { isAbsolute, relative } from "node:path";
2
+ import { minimatch } from "minimatch";
3
+ import { readRunState } from "./artifact-io.js";
4
+ import { DEFAULT_CAPABILITIES, type CapabilityDefinition } from "./capabilities.js";
5
+ import { workflowDefinitionSchema, type WorkflowDefinition } from "./schemas.js";
6
+ import { DEFAULT_WORKFLOWS } from "./default-workflows.js";
7
+
8
+ /**
9
+ * Pre-tool-use enforcement for the harness. Pure function: takes the tool
10
+ * call shape Claude Code emits, looks up the active workflow stage's
11
+ * capability, returns allow/deny + reason. The hook wires this into the
12
+ * stdin/exit-code dance.
13
+ *
14
+ * "Active task" is identified by env var CORTEX_ACTIVE_TASK_ID. The
15
+ * harness sets this when invoking an agent for a stage; outside the
16
+ * harness, the env var is unset and this evaluator is a no-op (returns
17
+ * { allowed: true }).
18
+ */
19
+
20
+ export type ToolCall = {
21
+ toolName: string;
22
+ toolInput: Record<string, unknown>;
23
+ };
24
+
25
+ export type EnforcementResult =
26
+ | { allowed: true; reason?: string }
27
+ | { allowed: false; reason: string };
28
+
29
+ export type EvaluateOptions = {
30
+ cwd: string;
31
+ taskId: string;
32
+ call: ToolCall;
33
+ workflows?: Record<string, WorkflowDefinition>;
34
+ capabilities?: Record<string, CapabilityDefinition>;
35
+ };
36
+
37
+ /**
38
+ * Tool names that are pure mutations of the file system. Edits and writes
39
+ * gate against `write_globs`. Bash is treated as a mutation by default
40
+ * because we cannot reliably extract paths from arbitrary shell — agents
41
+ * running in restricted-write capabilities lose Bash unless the
42
+ * capability explicitly allow-lists it.
43
+ */
44
+ const MUTATING_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
45
+
46
+ /**
47
+ * Tool names that read but do not mutate. Gate against `read_globs`.
48
+ */
49
+ const READING_TOOLS = new Set(["Read", "Grep", "Glob", "NotebookRead"]);
50
+
51
+ export function evaluateToolCall(options: EvaluateOptions): EnforcementResult {
52
+ const state = readRunState(options.cwd, options.taskId);
53
+ if (!state) {
54
+ return { allowed: true, reason: "no run state — harness not active" };
55
+ }
56
+ if (state.outcome !== "in_progress" || !state.current_stage) {
57
+ return {
58
+ allowed: true,
59
+ reason: `run not in progress (outcome=${state.outcome}) — no capability gate to apply`,
60
+ };
61
+ }
62
+
63
+ const workflows = options.workflows ?? DEFAULT_WORKFLOWS;
64
+ const workflow = workflows[state.workflow_id];
65
+ if (!workflow) {
66
+ return {
67
+ allowed: false,
68
+ reason: `unknown workflow_id ${state.workflow_id}; cannot resolve capability for current stage`,
69
+ };
70
+ }
71
+ // Validate so corrupt input doesn't slip through.
72
+ workflowDefinitionSchema.parse(workflow);
73
+
74
+ const stage = workflow.stages.find((s) => s.name === state.current_stage);
75
+ if (!stage) {
76
+ return {
77
+ allowed: false,
78
+ reason: `current stage ${state.current_stage} is not defined in workflow ${workflow.id}`,
79
+ };
80
+ }
81
+
82
+ if (!stage.capability) {
83
+ return { allowed: true, reason: "stage has no capability declared" };
84
+ }
85
+
86
+ const capabilities = options.capabilities ?? DEFAULT_CAPABILITIES;
87
+ const capability = capabilities[stage.capability];
88
+ if (!capability) {
89
+ return {
90
+ allowed: false,
91
+ reason: `capability ${stage.capability} (referenced by stage ${stage.name}) is not in the registry`,
92
+ };
93
+ }
94
+
95
+ return evaluateAgainstCapability(capability, options.call, options.cwd);
96
+ }
97
+
98
+ function evaluateAgainstCapability(
99
+ capability: CapabilityDefinition,
100
+ call: ToolCall,
101
+ cwd: string,
102
+ ): EnforcementResult {
103
+ // 1. tools_allowed: empty = no restriction; otherwise tool must be in the list.
104
+ if (
105
+ capability.tools_allowed.length > 0 &&
106
+ !capability.tools_allowed.includes(call.toolName)
107
+ ) {
108
+ return {
109
+ allowed: false,
110
+ reason: `capability ${capability.name} does not allow tool ${call.toolName}`,
111
+ };
112
+ }
113
+
114
+ const isMutation = MUTATING_TOOLS.has(call.toolName);
115
+ const isRead = READING_TOOLS.has(call.toolName);
116
+
117
+ // Bash is special: with restricted write_globs we have to assume the
118
+ // worst (since the shell can write anywhere). Block unless capability
119
+ // explicitly allow-lists Bash via tools_allowed.
120
+ if (call.toolName === "Bash") {
121
+ const isAllowedViaToolList = capability.tools_allowed.includes("Bash");
122
+ const writesUnrestricted = capability.write_globs.length === 0;
123
+ if (writesUnrestricted && !isAllowedViaToolList) {
124
+ return {
125
+ allowed: false,
126
+ reason: `capability ${capability.name} is read-only; Bash can mutate the filesystem and is not allow-listed`,
127
+ };
128
+ }
129
+ return { allowed: true };
130
+ }
131
+
132
+ if (isMutation) {
133
+ if (capability.write_globs.length === 0) {
134
+ return {
135
+ allowed: false,
136
+ reason: `capability ${capability.name} is read-only; ${call.toolName} cannot run`,
137
+ };
138
+ }
139
+ const targetPath = extractFilePath(call.toolInput);
140
+ if (!targetPath) {
141
+ return {
142
+ allowed: false,
143
+ reason: `${call.toolName} did not include a file_path; cannot verify against capability ${capability.name}`,
144
+ };
145
+ }
146
+ const relPath = toRepoRelative(cwd, targetPath);
147
+ if (!matchesAnyGlob(relPath, capability.write_globs)) {
148
+ return {
149
+ allowed: false,
150
+ reason: `path ${relPath} is outside capability ${capability.name}'s write_globs (${capability.write_globs.join(", ")})`,
151
+ };
152
+ }
153
+ return { allowed: true };
154
+ }
155
+
156
+ if (isRead) {
157
+ if (capability.read_globs.length === 0) {
158
+ // No reads allowed at all — only the human capability lands here.
159
+ return {
160
+ allowed: false,
161
+ reason: `capability ${capability.name} does not permit any read operations`,
162
+ };
163
+ }
164
+ const targetPath = extractFilePath(call.toolInput);
165
+ if (!targetPath) {
166
+ // Some read tools (Grep, Glob) operate on the whole repo; allow
167
+ // through if the capability has any read access at all.
168
+ return { allowed: true };
169
+ }
170
+ const relPath = toRepoRelative(cwd, targetPath);
171
+ if (!matchesAnyGlob(relPath, capability.read_globs)) {
172
+ return {
173
+ allowed: false,
174
+ reason: `path ${relPath} is outside capability ${capability.name}'s read_globs (${capability.read_globs.join(", ")})`,
175
+ };
176
+ }
177
+ return { allowed: true };
178
+ }
179
+
180
+ // Unknown tool — fall through to allow if not explicitly restricted.
181
+ return { allowed: true };
182
+ }
183
+
184
+ function extractFilePath(toolInput: Record<string, unknown>): string | null {
185
+ const candidates = ["file_path", "path", "notebook_path"];
186
+ for (const key of candidates) {
187
+ const value = toolInput[key];
188
+ if (typeof value === "string" && value.length > 0) return value;
189
+ }
190
+ return null;
191
+ }
192
+
193
+ function toRepoRelative(cwd: string, targetPath: string): string {
194
+ if (!isAbsolute(targetPath)) return targetPath;
195
+ const rel = relative(cwd, targetPath);
196
+ // If the path is outside the repo, return the absolute form so glob
197
+ // matches (which expect repo-relative) reliably miss.
198
+ if (rel.startsWith("..")) return targetPath;
199
+ return rel;
200
+ }
201
+
202
+ function matchesAnyGlob(path: string, globs: string[]): boolean {
203
+ return globs.some((pattern) =>
204
+ minimatch(path, pattern, { dot: true, nocase: false }),
205
+ );
206
+ }
@@ -0,0 +1,220 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { artifactPath, readRunState } from "./artifact-io.js";
3
+ import { workflowDefinitionSchema, type WorkflowDefinition } from "./schemas.js";
4
+
5
+ /**
6
+ * Composes the prompt one stage's agent sees. Pure function over the
7
+ * persisted run state plus the workflow definition — no agent invocation,
8
+ * no MCP, no daemon. The harness later wraps this into an MCP call or a
9
+ * CLI invocation.
10
+ *
11
+ * Design: the agent gets four sections in a fixed order so it can anchor
12
+ * on them reliably:
13
+ *
14
+ * TASK — what the developer asked for, copied verbatim from RunState
15
+ * STAGE — what *this* stage is supposed to produce
16
+ * HANDOFFS — every prior-stage artifact the new stage declared in `reads`,
17
+ * inlined raw (frontmatter + body) so the agent sees structured
18
+ * outcomes alongside the reasoning
19
+ * OUTPUT — exact frontmatter contract the agent must satisfy plus the
20
+ * expected artifact filename
21
+ *
22
+ * Capability constraints (which files the agent may edit, which tools it
23
+ * may call) are NOT enforced by the prompt — they're enforced by hooks
24
+ * downstream. The capability key is surfaced in the prompt as a label so
25
+ * the agent knows under what role it's running, but the real gate is
26
+ * pre-tool-use.
27
+ */
28
+
29
+ export type ComposedEnvelope = {
30
+ /** The full prompt the agent will receive. */
31
+ prompt: string;
32
+ /** Expected artifact filename the agent must produce. */
33
+ expectedArtifact: string;
34
+ /** Frontmatter keys the agent must populate (beyond stage/status/references). */
35
+ requiredFields: string[];
36
+ /** Capability key the stage runs under (informational). */
37
+ capability: string | null;
38
+ };
39
+
40
+ export type ComposeStageEnvelopeOptions = {
41
+ cwd: string;
42
+ taskId: string;
43
+ workflow: WorkflowDefinition;
44
+ /**
45
+ * Defaults to the run's current_stage. Pass an explicit stageName when
46
+ * dry-running an envelope without driving state forward.
47
+ */
48
+ stageName?: string;
49
+ };
50
+
51
+ export function composeStageEnvelope(
52
+ options: ComposeStageEnvelopeOptions,
53
+ ): ComposedEnvelope {
54
+ const workflow = workflowDefinitionSchema.parse(options.workflow);
55
+ const state = readRunState(options.cwd, options.taskId);
56
+ if (!state) {
57
+ throw new Error(
58
+ `No run state found for task ${options.taskId}. Call createRun() first.`,
59
+ );
60
+ }
61
+ if (state.workflow_id !== workflow.id) {
62
+ throw new Error(
63
+ `Workflow mismatch: run was started with ${state.workflow_id}, envelope was composed with ${workflow.id}`,
64
+ );
65
+ }
66
+
67
+ const stageName = options.stageName ?? state.current_stage;
68
+ if (!stageName) {
69
+ throw new Error(
70
+ `Run ${options.taskId} is not at any stage (outcome=${state.outcome}). Cannot compose envelope.`,
71
+ );
72
+ }
73
+ const stage = workflow.stages.find((s) => s.name === stageName);
74
+ if (!stage) {
75
+ throw new Error(
76
+ `Stage ${stageName} is not defined in workflow ${workflow.id}`,
77
+ );
78
+ }
79
+
80
+ const handoffs: string[] = [];
81
+ for (const readName of stage.reads) {
82
+ const priorStage = workflow.stages.find((s) => s.name === readName);
83
+ if (!priorStage) {
84
+ throw new Error(
85
+ `Stage ${stageName} declares reads from unknown stage ${readName}`,
86
+ );
87
+ }
88
+ const priorRecord = state.stages.find((r) => r.name === readName);
89
+ if (!priorRecord || priorRecord.status === "pending" || !priorRecord.artifact) {
90
+ throw new Error(
91
+ `Stage ${stageName} requires artifact from ${readName}, but it has not been produced yet`,
92
+ );
93
+ }
94
+ const path = artifactPath(options.cwd, options.taskId, priorRecord.artifact);
95
+ let raw: string;
96
+ try {
97
+ raw = readFileSync(path, "utf8");
98
+ } catch (err) {
99
+ throw new Error(
100
+ `Failed to read handoff artifact for ${readName} at ${path}: ${
101
+ err instanceof Error ? err.message : String(err)
102
+ }`,
103
+ );
104
+ }
105
+ handoffs.push(renderHandoff(readName, priorRecord.artifact, raw));
106
+ }
107
+
108
+ const requiredFields = stage.required_fields;
109
+ const capability = stage.capability ?? null;
110
+
111
+ const prompt = renderPrompt({
112
+ taskDescription: state.task_description,
113
+ workflowId: workflow.id,
114
+ workflowDescription: workflow.description,
115
+ stageName: stage.name,
116
+ stageDescription: stage.description,
117
+ expectedArtifact: stage.artifact,
118
+ requiredFields,
119
+ capability,
120
+ handoffs,
121
+ });
122
+
123
+ return {
124
+ prompt,
125
+ expectedArtifact: stage.artifact,
126
+ requiredFields,
127
+ capability,
128
+ };
129
+ }
130
+
131
+ function renderHandoff(
132
+ stageName: string,
133
+ artifactName: string,
134
+ rawArtifact: string,
135
+ ): string {
136
+ return [
137
+ `--- handoff:${stageName} (${artifactName}) ---`,
138
+ rawArtifact.trim(),
139
+ `--- end handoff:${stageName} ---`,
140
+ ].join("\n");
141
+ }
142
+
143
+ type RenderPromptOptions = {
144
+ taskDescription: string;
145
+ workflowId: string;
146
+ workflowDescription: string;
147
+ stageName: string;
148
+ stageDescription: string;
149
+ expectedArtifact: string;
150
+ requiredFields: string[];
151
+ capability: string | null;
152
+ handoffs: string[];
153
+ };
154
+
155
+ function renderPrompt(o: RenderPromptOptions): string {
156
+ const sections: string[] = [];
157
+
158
+ sections.push(
159
+ [
160
+ `# TASK`,
161
+ ``,
162
+ o.taskDescription.trim(),
163
+ ``,
164
+ `Workflow: ${o.workflowId} — ${o.workflowDescription}`,
165
+ ].join("\n"),
166
+ );
167
+
168
+ sections.push(
169
+ [
170
+ `# STAGE: ${o.stageName}`,
171
+ ``,
172
+ o.stageDescription.trim(),
173
+ ``,
174
+ o.capability
175
+ ? `Running under capability: \`${o.capability}\` (file and tool restrictions are enforced by Cortex hooks at tool-use time, not by you).`
176
+ : `No capability constraint declared for this stage.`,
177
+ ].join("\n"),
178
+ );
179
+
180
+ if (o.handoffs.length === 0) {
181
+ sections.push(
182
+ [`# HANDOFFS`, ``, `_No prior-stage artifacts; this is the first stage._`].join(
183
+ "\n",
184
+ ),
185
+ );
186
+ } else {
187
+ sections.push(
188
+ [
189
+ `# HANDOFFS`,
190
+ ``,
191
+ `The following stages have already run. Each artifact below is the complete file as it lives on disk; use the frontmatter for structured outcomes and the body for reasoning.`,
192
+ ``,
193
+ ...o.handoffs,
194
+ ].join("\n"),
195
+ );
196
+ }
197
+
198
+ const requiredLines =
199
+ o.requiredFields.length === 0
200
+ ? `_No additional required fields beyond the harness defaults._`
201
+ : o.requiredFields.map((f) => `- \`${f}\``).join("\n");
202
+
203
+ sections.push(
204
+ [
205
+ `# OUTPUT`,
206
+ ``,
207
+ `Produce a single markdown file named \`${o.expectedArtifact}\` with YAML frontmatter on top.`,
208
+ ``,
209
+ `Required frontmatter fields (in addition to \`stage\`, \`status\`, \`references\`, \`written_at\` which the harness manages):`,
210
+ ``,
211
+ requiredLines,
212
+ ``,
213
+ `Body: clear, well-structured markdown explaining your reasoning. Cite handoff artifacts by stage name when relevant.`,
214
+ ``,
215
+ `If you cannot complete this stage (missing context, blocking concern, conflicting prior decisions), set \`status: blocked\` in frontmatter and explain why in the body — do not fabricate work.`,
216
+ ].join("\n"),
217
+ );
218
+
219
+ return sections.join("\n\n");
220
+ }
@@ -0,0 +1,9 @@
1
+ export * from "./schemas.js";
2
+ export * from "./artifact-io.js";
3
+ export * from "./run-lifecycle.js";
4
+ export * from "./envelope.js";
5
+ export * from "./default-workflows.js";
6
+ export * from "./mcp-tools.js";
7
+ export * from "./capabilities.js";
8
+ export * from "./enforcement.js";
9
+ export * from "./synced-registry.js";