@synth-deploy/server 0.1.0
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/dist/agent/debrief-retention.d.ts +12 -0
- package/dist/agent/debrief-retention.d.ts.map +1 -0
- package/dist/agent/debrief-retention.js +27 -0
- package/dist/agent/debrief-retention.js.map +1 -0
- package/dist/agent/envoy-client.d.ts +216 -0
- package/dist/agent/envoy-client.d.ts.map +1 -0
- package/dist/agent/envoy-client.js +266 -0
- package/dist/agent/envoy-client.js.map +1 -0
- package/dist/agent/envoy-registry.d.ts +102 -0
- package/dist/agent/envoy-registry.d.ts.map +1 -0
- package/dist/agent/envoy-registry.js +319 -0
- package/dist/agent/envoy-registry.js.map +1 -0
- package/dist/agent/health-checker.d.ts +39 -0
- package/dist/agent/health-checker.d.ts.map +1 -0
- package/dist/agent/health-checker.js +49 -0
- package/dist/agent/health-checker.js.map +1 -0
- package/dist/agent/mcp-client-manager.d.ts +36 -0
- package/dist/agent/mcp-client-manager.d.ts.map +1 -0
- package/dist/agent/mcp-client-manager.js +106 -0
- package/dist/agent/mcp-client-manager.js.map +1 -0
- package/dist/agent/stale-deployment-detector.d.ts +15 -0
- package/dist/agent/stale-deployment-detector.d.ts.map +1 -0
- package/dist/agent/stale-deployment-detector.js +50 -0
- package/dist/agent/stale-deployment-detector.js.map +1 -0
- package/dist/agent/step-runner.d.ts +31 -0
- package/dist/agent/step-runner.d.ts.map +1 -0
- package/dist/agent/step-runner.js +80 -0
- package/dist/agent/step-runner.js.map +1 -0
- package/dist/agent/synth-agent.d.ts +168 -0
- package/dist/agent/synth-agent.d.ts.map +1 -0
- package/dist/agent/synth-agent.js +1195 -0
- package/dist/agent/synth-agent.js.map +1 -0
- package/dist/api/agent.d.ts +36 -0
- package/dist/api/agent.d.ts.map +1 -0
- package/dist/api/agent.js +867 -0
- package/dist/api/agent.js.map +1 -0
- package/dist/api/api-keys.d.ts +4 -0
- package/dist/api/api-keys.d.ts.map +1 -0
- package/dist/api/api-keys.js +118 -0
- package/dist/api/api-keys.js.map +1 -0
- package/dist/api/artifacts.d.ts +5 -0
- package/dist/api/artifacts.d.ts.map +1 -0
- package/dist/api/artifacts.js +142 -0
- package/dist/api/artifacts.js.map +1 -0
- package/dist/api/auth.d.ts +4 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +280 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/deployments.d.ts +11 -0
- package/dist/api/deployments.d.ts.map +1 -0
- package/dist/api/deployments.js +1098 -0
- package/dist/api/deployments.js.map +1 -0
- package/dist/api/environments.d.ts +5 -0
- package/dist/api/environments.d.ts.map +1 -0
- package/dist/api/environments.js +69 -0
- package/dist/api/environments.js.map +1 -0
- package/dist/api/envoy-reports.d.ts +17 -0
- package/dist/api/envoy-reports.d.ts.map +1 -0
- package/dist/api/envoy-reports.js +138 -0
- package/dist/api/envoy-reports.js.map +1 -0
- package/dist/api/envoys.d.ts +5 -0
- package/dist/api/envoys.d.ts.map +1 -0
- package/dist/api/envoys.js +192 -0
- package/dist/api/envoys.js.map +1 -0
- package/dist/api/fleet.d.ts +11 -0
- package/dist/api/fleet.d.ts.map +1 -0
- package/dist/api/fleet.js +394 -0
- package/dist/api/fleet.js.map +1 -0
- package/dist/api/graph.d.ts +8 -0
- package/dist/api/graph.d.ts.map +1 -0
- package/dist/api/graph.js +355 -0
- package/dist/api/graph.js.map +1 -0
- package/dist/api/health.d.ts +20 -0
- package/dist/api/health.d.ts.map +1 -0
- package/dist/api/health.js +248 -0
- package/dist/api/health.js.map +1 -0
- package/dist/api/idp-schemas.d.ts +41 -0
- package/dist/api/idp-schemas.d.ts.map +1 -0
- package/dist/api/idp-schemas.js +17 -0
- package/dist/api/idp-schemas.js.map +1 -0
- package/dist/api/idp.d.ts +6 -0
- package/dist/api/idp.d.ts.map +1 -0
- package/dist/api/idp.js +620 -0
- package/dist/api/idp.js.map +1 -0
- package/dist/api/intake.d.ts +10 -0
- package/dist/api/intake.d.ts.map +1 -0
- package/dist/api/intake.js +418 -0
- package/dist/api/intake.js.map +1 -0
- package/dist/api/partitions.d.ts +5 -0
- package/dist/api/partitions.d.ts.map +1 -0
- package/dist/api/partitions.js +113 -0
- package/dist/api/partitions.js.map +1 -0
- package/dist/api/progress-event-store.d.ts +62 -0
- package/dist/api/progress-event-store.d.ts.map +1 -0
- package/dist/api/progress-event-store.js +118 -0
- package/dist/api/progress-event-store.js.map +1 -0
- package/dist/api/schemas.d.ts +1000 -0
- package/dist/api/schemas.d.ts.map +1 -0
- package/dist/api/schemas.js +328 -0
- package/dist/api/schemas.js.map +1 -0
- package/dist/api/security-boundaries.d.ts +4 -0
- package/dist/api/security-boundaries.d.ts.map +1 -0
- package/dist/api/security-boundaries.js +32 -0
- package/dist/api/security-boundaries.js.map +1 -0
- package/dist/api/settings.d.ts +4 -0
- package/dist/api/settings.d.ts.map +1 -0
- package/dist/api/settings.js +99 -0
- package/dist/api/settings.js.map +1 -0
- package/dist/api/system.d.ts +75 -0
- package/dist/api/system.d.ts.map +1 -0
- package/dist/api/system.js +558 -0
- package/dist/api/system.js.map +1 -0
- package/dist/api/telemetry.d.ts +4 -0
- package/dist/api/telemetry.d.ts.map +1 -0
- package/dist/api/telemetry.js +24 -0
- package/dist/api/telemetry.js.map +1 -0
- package/dist/api/users.d.ts +4 -0
- package/dist/api/users.d.ts.map +1 -0
- package/dist/api/users.js +173 -0
- package/dist/api/users.js.map +1 -0
- package/dist/archive-unpacker.d.ts +24 -0
- package/dist/archive-unpacker.d.ts.map +1 -0
- package/dist/archive-unpacker.js +239 -0
- package/dist/archive-unpacker.js.map +1 -0
- package/dist/artifact-analyzer.d.ts +59 -0
- package/dist/artifact-analyzer.d.ts.map +1 -0
- package/dist/artifact-analyzer.js +334 -0
- package/dist/artifact-analyzer.js.map +1 -0
- package/dist/auth/idp/index.d.ts +9 -0
- package/dist/auth/idp/index.d.ts.map +1 -0
- package/dist/auth/idp/index.js +5 -0
- package/dist/auth/idp/index.js.map +1 -0
- package/dist/auth/idp/ldap.d.ts +56 -0
- package/dist/auth/idp/ldap.d.ts.map +1 -0
- package/dist/auth/idp/ldap.js +276 -0
- package/dist/auth/idp/ldap.js.map +1 -0
- package/dist/auth/idp/oidc.d.ts +27 -0
- package/dist/auth/idp/oidc.d.ts.map +1 -0
- package/dist/auth/idp/oidc.js +97 -0
- package/dist/auth/idp/oidc.js.map +1 -0
- package/dist/auth/idp/role-mapping.d.ts +9 -0
- package/dist/auth/idp/role-mapping.d.ts.map +1 -0
- package/dist/auth/idp/role-mapping.js +16 -0
- package/dist/auth/idp/role-mapping.js.map +1 -0
- package/dist/auth/idp/saml.d.ts +40 -0
- package/dist/auth/idp/saml.d.ts.map +1 -0
- package/dist/auth/idp/saml.js +117 -0
- package/dist/auth/idp/saml.js.map +1 -0
- package/dist/auth/idp/types.d.ts +23 -0
- package/dist/auth/idp/types.d.ts.map +1 -0
- package/dist/auth/idp/types.js +2 -0
- package/dist/auth/idp/types.js.map +1 -0
- package/dist/fleet/fleet-executor.d.ts +35 -0
- package/dist/fleet/fleet-executor.d.ts.map +1 -0
- package/dist/fleet/fleet-executor.js +228 -0
- package/dist/fleet/fleet-executor.js.map +1 -0
- package/dist/fleet/fleet-store.d.ts +13 -0
- package/dist/fleet/fleet-store.d.ts.map +1 -0
- package/dist/fleet/fleet-store.js +13 -0
- package/dist/fleet/fleet-store.js.map +1 -0
- package/dist/fleet/index.d.ts +5 -0
- package/dist/fleet/index.d.ts.map +1 -0
- package/dist/fleet/index.js +4 -0
- package/dist/fleet/index.js.map +1 -0
- package/dist/fleet/representative-selector.d.ts +15 -0
- package/dist/fleet/representative-selector.d.ts.map +1 -0
- package/dist/fleet/representative-selector.js +71 -0
- package/dist/fleet/representative-selector.js.map +1 -0
- package/dist/graph/graph-executor.d.ts +36 -0
- package/dist/graph/graph-executor.d.ts.map +1 -0
- package/dist/graph/graph-executor.js +348 -0
- package/dist/graph/graph-executor.js.map +1 -0
- package/dist/graph/graph-inference.d.ts +22 -0
- package/dist/graph/graph-inference.d.ts.map +1 -0
- package/dist/graph/graph-inference.js +149 -0
- package/dist/graph/graph-inference.js.map +1 -0
- package/dist/graph/graph-store.d.ts +12 -0
- package/dist/graph/graph-store.d.ts.map +1 -0
- package/dist/graph/graph-store.js +61 -0
- package/dist/graph/graph-store.js.map +1 -0
- package/dist/graph/index.d.ts +5 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +4 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +837 -0
- package/dist/index.js.map +1 -0
- package/dist/intake/index.d.ts +6 -0
- package/dist/intake/index.d.ts.map +1 -0
- package/dist/intake/index.js +5 -0
- package/dist/intake/index.js.map +1 -0
- package/dist/intake/intake-processor.d.ts +17 -0
- package/dist/intake/intake-processor.d.ts.map +1 -0
- package/dist/intake/intake-processor.js +99 -0
- package/dist/intake/intake-processor.js.map +1 -0
- package/dist/intake/intake-store.d.ts +7 -0
- package/dist/intake/intake-store.d.ts.map +1 -0
- package/dist/intake/intake-store.js +7 -0
- package/dist/intake/intake-store.js.map +1 -0
- package/dist/intake/registry-poller.d.ts +41 -0
- package/dist/intake/registry-poller.d.ts.map +1 -0
- package/dist/intake/registry-poller.js +202 -0
- package/dist/intake/registry-poller.js.map +1 -0
- package/dist/intake/webhook-handlers.d.ts +37 -0
- package/dist/intake/webhook-handlers.d.ts.map +1 -0
- package/dist/intake/webhook-handlers.js +268 -0
- package/dist/intake/webhook-handlers.js.map +1 -0
- package/dist/logger.d.ts +5 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +15 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp/resources.d.ts +9 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +72 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +15 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +20 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +88 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/middleware/auth.d.ts +29 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +76 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/permissions.d.ts +13 -0
- package/dist/middleware/permissions.d.ts.map +1 -0
- package/dist/middleware/permissions.js +32 -0
- package/dist/middleware/permissions.js.map +1 -0
- package/dist/pattern-store.d.ts +104 -0
- package/dist/pattern-store.d.ts.map +1 -0
- package/dist/pattern-store.js +299 -0
- package/dist/pattern-store.js.map +1 -0
- package/package.json +54 -0
- package/src/agent/debrief-retention.ts +44 -0
- package/src/agent/envoy-client.ts +474 -0
- package/src/agent/envoy-registry.ts +384 -0
- package/src/agent/health-checker.ts +70 -0
- package/src/agent/mcp-client-manager.ts +131 -0
- package/src/agent/stale-deployment-detector.ts +79 -0
- package/src/agent/step-runner.ts +124 -0
- package/src/agent/synth-agent.ts +1567 -0
- package/src/api/agent.ts +1075 -0
- package/src/api/api-keys.ts +129 -0
- package/src/api/artifacts.ts +194 -0
- package/src/api/auth.ts +320 -0
- package/src/api/deployments.ts +1347 -0
- package/src/api/environments.ts +97 -0
- package/src/api/envoy-reports.ts +159 -0
- package/src/api/envoys.ts +237 -0
- package/src/api/fleet.ts +510 -0
- package/src/api/graph.ts +516 -0
- package/src/api/health.ts +311 -0
- package/src/api/idp-schemas.ts +19 -0
- package/src/api/idp.ts +735 -0
- package/src/api/intake.ts +537 -0
- package/src/api/partitions.ts +147 -0
- package/src/api/progress-event-store.ts +153 -0
- package/src/api/schemas.ts +376 -0
- package/src/api/security-boundaries.ts +54 -0
- package/src/api/settings.ts +118 -0
- package/src/api/system.ts +704 -0
- package/src/api/telemetry.ts +32 -0
- package/src/api/users.ts +210 -0
- package/src/archive-unpacker.ts +271 -0
- package/src/artifact-analyzer.ts +438 -0
- package/src/auth/idp/index.ts +8 -0
- package/src/auth/idp/ldap.ts +340 -0
- package/src/auth/idp/oidc.ts +117 -0
- package/src/auth/idp/role-mapping.ts +22 -0
- package/src/auth/idp/saml.ts +148 -0
- package/src/auth/idp/types.ts +22 -0
- package/src/fleet/fleet-executor.ts +309 -0
- package/src/fleet/fleet-store.ts +13 -0
- package/src/fleet/index.ts +4 -0
- package/src/fleet/representative-selector.ts +83 -0
- package/src/graph/graph-executor.ts +446 -0
- package/src/graph/graph-inference.ts +184 -0
- package/src/graph/graph-store.ts +75 -0
- package/src/graph/index.ts +4 -0
- package/src/index.ts +916 -0
- package/src/intake/index.ts +5 -0
- package/src/intake/intake-processor.ts +111 -0
- package/src/intake/intake-store.ts +7 -0
- package/src/intake/registry-poller.ts +230 -0
- package/src/intake/webhook-handlers.ts +328 -0
- package/src/logger.ts +19 -0
- package/src/mcp/resources.ts +98 -0
- package/src/mcp/server.ts +34 -0
- package/src/mcp/tools.ts +117 -0
- package/src/middleware/auth.ts +103 -0
- package/src/middleware/permissions.ts +35 -0
- package/src/pattern-store.ts +409 -0
- package/tests/agent-mode.test.ts +536 -0
- package/tests/api-handlers.test.ts +1245 -0
- package/tests/archive-unpacker.test.ts +179 -0
- package/tests/artifact-analyzer.test.ts +240 -0
- package/tests/auth-middleware.test.ts +189 -0
- package/tests/decision-diary.test.ts +957 -0
- package/tests/diary-reader.test.ts +782 -0
- package/tests/envoy-client.test.ts +342 -0
- package/tests/envoy-reports.test.ts +156 -0
- package/tests/mcp-tools.test.ts +213 -0
- package/tests/orchestration.test.ts +536 -0
- package/tests/partition-deletion.test.ts +143 -0
- package/tests/partition-isolation.test.ts +830 -0
- package/tests/pattern-store.test.ts +371 -0
- package/tests/rbac-enforcement.test.ts +409 -0
- package/tests/ssrf-validation.test.ts +56 -0
- package/tests/stale-deployment.test.ts +85 -0
- package/tests/step-runner.test.ts +308 -0
- package/tests/ui-journey.test.ts +330 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import type { DebriefReader } from "@synth-deploy/core";
|
|
4
|
+
import type { DeploymentStore } from "../agent/synth-agent.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register MCP resources. These expose Synth state to MCP clients
|
|
8
|
+
* as readable data — Debrief entries, deployment records, etc.
|
|
9
|
+
*/
|
|
10
|
+
export function registerResources(
|
|
11
|
+
mcp: McpServer,
|
|
12
|
+
debrief: DebriefReader,
|
|
13
|
+
deployments: DeploymentStore,
|
|
14
|
+
): void {
|
|
15
|
+
// Recent debrief entries — scoped to a specific partition
|
|
16
|
+
mcp.registerResource(
|
|
17
|
+
"recent-debrief-entries",
|
|
18
|
+
new ResourceTemplate("debrief://partition/{partitionId}/recent", {
|
|
19
|
+
list: undefined,
|
|
20
|
+
}),
|
|
21
|
+
{
|
|
22
|
+
title: "Recent Debrief Entries (Partition-Scoped)",
|
|
23
|
+
description:
|
|
24
|
+
"The most recent Debrief entries for a specific partition. " +
|
|
25
|
+
"Each entry records what the agent decided and why, in plain language.",
|
|
26
|
+
mimeType: "application/json",
|
|
27
|
+
},
|
|
28
|
+
async (uri, { partitionId }) => ({
|
|
29
|
+
contents: [
|
|
30
|
+
{
|
|
31
|
+
uri: uri.href,
|
|
32
|
+
text: JSON.stringify(debrief.getByPartition(partitionId as string).slice(-20), null, 2),
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Debrief entries for a specific deployment
|
|
39
|
+
mcp.registerResource(
|
|
40
|
+
"deployment-debrief",
|
|
41
|
+
new ResourceTemplate("debrief://deployment/{deploymentId}", {
|
|
42
|
+
list: undefined,
|
|
43
|
+
}),
|
|
44
|
+
{
|
|
45
|
+
title: "Deployment Debrief",
|
|
46
|
+
description:
|
|
47
|
+
"All Debrief entries for a specific deployment, showing the full reasoning chain.",
|
|
48
|
+
mimeType: "application/json",
|
|
49
|
+
},
|
|
50
|
+
async (uri, { deploymentId }) => {
|
|
51
|
+
const deployment = deployments.get(deploymentId as string);
|
|
52
|
+
if (!deployment) {
|
|
53
|
+
return { contents: [{ uri: uri.href, text: JSON.stringify({ error: "Deployment not found" }) }] };
|
|
54
|
+
}
|
|
55
|
+
const entries = debrief.getByDeployment(deploymentId as string);
|
|
56
|
+
return {
|
|
57
|
+
contents: [
|
|
58
|
+
{
|
|
59
|
+
uri: uri.href,
|
|
60
|
+
text: JSON.stringify(entries, null, 2),
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Deployment record
|
|
68
|
+
mcp.registerResource(
|
|
69
|
+
"deployment",
|
|
70
|
+
new ResourceTemplate("deployment://{deploymentId}", {
|
|
71
|
+
list: async () => ({
|
|
72
|
+
resources: deployments.list().map((d) => ({
|
|
73
|
+
uri: `deployment://${d.id}`,
|
|
74
|
+
name: `${d.artifactId} v${d.version} → ${d.environmentId}`,
|
|
75
|
+
})),
|
|
76
|
+
}),
|
|
77
|
+
}),
|
|
78
|
+
{
|
|
79
|
+
title: "Deployment Record",
|
|
80
|
+
description: "Full deployment record including status, variables, and debrief entry references.",
|
|
81
|
+
mimeType: "application/json",
|
|
82
|
+
},
|
|
83
|
+
async (uri, { deploymentId }) => {
|
|
84
|
+
const deployment = deployments.get(deploymentId as string);
|
|
85
|
+
if (!deployment) {
|
|
86
|
+
return { contents: [{ uri: uri.href, text: "Not found" }] };
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
contents: [
|
|
90
|
+
{
|
|
91
|
+
uri: uri.href,
|
|
92
|
+
text: JSON.stringify(deployment, null, 2),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { DebriefWriter, DebriefReader, IPartitionStore, IEnvironmentStore, IArtifactStore } from "@synth-deploy/core";
|
|
3
|
+
import type { SynthAgent, DeploymentStore } from "../agent/synth-agent.js";
|
|
4
|
+
import { registerTools } from "./tools.js";
|
|
5
|
+
import { registerResources } from "./resources.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Create and configure the MCP server with all Synth tools and resources.
|
|
9
|
+
*/
|
|
10
|
+
export function createMcpServer(deps: {
|
|
11
|
+
agent: SynthAgent;
|
|
12
|
+
debrief: DebriefWriter & DebriefReader;
|
|
13
|
+
partitions: IPartitionStore;
|
|
14
|
+
environments: IEnvironmentStore;
|
|
15
|
+
deployments: DeploymentStore;
|
|
16
|
+
artifactStore: IArtifactStore;
|
|
17
|
+
}): McpServer {
|
|
18
|
+
const mcp = new McpServer(
|
|
19
|
+
{
|
|
20
|
+
name: "synth",
|
|
21
|
+
version: "0.1.0",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
capabilities: {
|
|
25
|
+
logging: {},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
registerTools(mcp, deps.agent, deps.partitions, deps.environments, deps.deployments, deps.artifactStore);
|
|
31
|
+
registerResources(mcp, deps.debrief, deps.deployments);
|
|
32
|
+
|
|
33
|
+
return mcp;
|
|
34
|
+
}
|
package/src/mcp/tools.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import type { SynthAgent, DeploymentStore } from "../agent/synth-agent.js";
|
|
4
|
+
import type { IArtifactStore, IEnvironmentStore, IPartitionStore } from "@synth-deploy/core";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register MCP tools on the server. These are the actions MCP clients
|
|
8
|
+
* (Claude, agents, CLI tools) can invoke.
|
|
9
|
+
*/
|
|
10
|
+
export function registerTools(
|
|
11
|
+
mcp: McpServer,
|
|
12
|
+
agent: SynthAgent,
|
|
13
|
+
partitions: IPartitionStore,
|
|
14
|
+
environments: IEnvironmentStore,
|
|
15
|
+
deployments: DeploymentStore,
|
|
16
|
+
artifactStore: IArtifactStore,
|
|
17
|
+
): void {
|
|
18
|
+
mcp.registerTool(
|
|
19
|
+
"trigger-deployment",
|
|
20
|
+
{
|
|
21
|
+
title: "Trigger Deployment",
|
|
22
|
+
description:
|
|
23
|
+
"Trigger a deployment of an artifact to an environment. " +
|
|
24
|
+
"The server agent will resolve variables, make decisions, and record everything to the Debrief.",
|
|
25
|
+
inputSchema: {
|
|
26
|
+
artifactId: z.string().describe("The artifact to deploy"),
|
|
27
|
+
environmentId: z.string().describe("Target environment ID"),
|
|
28
|
+
partitionId: z.string().optional().describe("Target partition ID (optional)"),
|
|
29
|
+
version: z.string().describe("Version to deploy"),
|
|
30
|
+
variables: z.record(z.string()).optional().describe("Override variables for this deployment"),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
async ({ artifactId, environmentId, partitionId, version, variables }) => {
|
|
34
|
+
const artifact = artifactStore.get(artifactId);
|
|
35
|
+
if (!artifact) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: "text", text: `Error: Artifact not found: ${artifactId}` }],
|
|
38
|
+
isError: true,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const environment = environments.get(environmentId);
|
|
43
|
+
if (!environment) {
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: `Error: Environment not found: ${environmentId}` }],
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (partitionId) {
|
|
51
|
+
const partition = partitions.get(partitionId);
|
|
52
|
+
if (!partition) {
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: `Error: Partition not found: ${partitionId}` }],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const deployment = await agent.triggerDeployment({
|
|
61
|
+
artifactId,
|
|
62
|
+
artifactVersionId: version,
|
|
63
|
+
environmentId,
|
|
64
|
+
partitionId,
|
|
65
|
+
triggeredBy: "agent",
|
|
66
|
+
variables,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: JSON.stringify(
|
|
74
|
+
{
|
|
75
|
+
deploymentId: deployment.id,
|
|
76
|
+
status: deployment.status,
|
|
77
|
+
version: deployment.version,
|
|
78
|
+
debriefEntries: deployment.debriefEntryIds.length,
|
|
79
|
+
},
|
|
80
|
+
null,
|
|
81
|
+
2,
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
mcp.registerTool(
|
|
90
|
+
"get-deployment-status",
|
|
91
|
+
{
|
|
92
|
+
title: "Get Deployment Status",
|
|
93
|
+
description: "Get the current status and Debrief entries for a deployment.",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
deploymentId: z.string().describe("The deployment ID to check"),
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
async ({ deploymentId }) => {
|
|
99
|
+
const deployment = deployments.get(deploymentId);
|
|
100
|
+
if (!deployment) {
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: `Error: Deployment not found: ${deploymentId}` }],
|
|
103
|
+
isError: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: JSON.stringify(deployment, null, 2),
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
|
|
2
|
+
import { SignJWT, jwtVerify } from "jose";
|
|
3
|
+
import type { IUserStore, IUserRoleStore, ISessionStore, UserId, Permission } from "@synth-deploy/core";
|
|
4
|
+
|
|
5
|
+
export interface AuthenticatedUser {
|
|
6
|
+
id: UserId;
|
|
7
|
+
email: string;
|
|
8
|
+
name: string;
|
|
9
|
+
permissions: Permission[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module "fastify" {
|
|
13
|
+
interface FastifyRequest {
|
|
14
|
+
user?: AuthenticatedUser;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const EXEMPT_ROUTES = ["/health", "/api/health", "/api/auth/login", "/api/auth/register", "/api/auth/refresh", "/api/auth/status", "/api/auth/providers", "/api/envoy/report"];
|
|
19
|
+
const EXEMPT_PREFIXES = ["/api/auth/oidc/", "/api/auth/callback/oidc/", "/api/auth/saml/", "/api/auth/callback/saml/", "/api/auth/ldap/", "/api/intake/webhook/"];
|
|
20
|
+
// Envoy callback endpoints — validated by envoy token, not user JWT
|
|
21
|
+
const EXEMPT_PATTERNS = [/^\/api\/deployments\/[^/]+\/progress$/];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Registers JWT-based authentication middleware on a Fastify instance.
|
|
25
|
+
*
|
|
26
|
+
* All /api/ and /mcp routes require a valid JWT Bearer token,
|
|
27
|
+
* except routes listed in EXEMPT_ROUTES. Static file serving and
|
|
28
|
+
* health endpoints are always accessible.
|
|
29
|
+
*/
|
|
30
|
+
export function registerAuthMiddleware(
|
|
31
|
+
app: FastifyInstance,
|
|
32
|
+
userStore: IUserStore,
|
|
33
|
+
userRoleStore: IUserRoleStore,
|
|
34
|
+
sessionStore: ISessionStore,
|
|
35
|
+
jwtSecret: Uint8Array,
|
|
36
|
+
): { enabled: boolean } {
|
|
37
|
+
app.addHook("onRequest", async (request: FastifyRequest, reply: FastifyReply) => {
|
|
38
|
+
// Skip exempt routes
|
|
39
|
+
if (EXEMPT_ROUTES.some((r) => request.url.startsWith(r))) return;
|
|
40
|
+
// Skip OIDC auth routes (dynamic paths)
|
|
41
|
+
if (EXEMPT_PREFIXES.some((p) => request.url.startsWith(p))) return;
|
|
42
|
+
// Skip envoy callback endpoints (validated by envoy token in the route handler)
|
|
43
|
+
if (EXEMPT_PATTERNS.some((p) => p.test(request.url))) return;
|
|
44
|
+
// Also skip static file serving (non-API routes), but NOT /mcp — it requires auth
|
|
45
|
+
if (!request.url.startsWith("/api/") && !request.url.startsWith("/mcp")) return;
|
|
46
|
+
|
|
47
|
+
// Accept token from Authorization header or ?token= query param
|
|
48
|
+
// (EventSource API cannot send headers, so SSE endpoints use query param)
|
|
49
|
+
const authHeader = request.headers.authorization;
|
|
50
|
+
const queryToken = (request.query as Record<string, string>)?.token;
|
|
51
|
+
const rawToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : queryToken;
|
|
52
|
+
if (!rawToken) {
|
|
53
|
+
reply.status(401).send({ error: "Authentication required" });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const token = rawToken;
|
|
58
|
+
try {
|
|
59
|
+
const { payload } = await jwtVerify(token, jwtSecret);
|
|
60
|
+
const userId = payload.sub as UserId;
|
|
61
|
+
const user = userStore.getById(userId);
|
|
62
|
+
if (!user) {
|
|
63
|
+
reply.status(401).send({ error: "User not found" });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const session = sessionStore.getByToken(token);
|
|
67
|
+
if (!session) {
|
|
68
|
+
reply.status(401).send({ error: "Session expired" });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const permissions = userRoleStore.getUserPermissions(userId);
|
|
72
|
+
request.user = { id: userId, email: user.email, name: user.name, permissions };
|
|
73
|
+
} catch {
|
|
74
|
+
reply.status(401).send({ error: "Invalid token" });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return { enabled: true };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function generateTokens(
|
|
82
|
+
userId: UserId,
|
|
83
|
+
jwtSecret: Uint8Array,
|
|
84
|
+
): Promise<{ token: string; refreshToken: string; expiresAt: Date }> {
|
|
85
|
+
const sessionTtl = process.env.SYNTH_SESSION_TTL ?? "8h";
|
|
86
|
+
const sessionTtlMs = sessionTtl.endsWith("h")
|
|
87
|
+
? parseInt(sessionTtl) * 60 * 60 * 1000
|
|
88
|
+
: sessionTtl.endsWith("m")
|
|
89
|
+
? parseInt(sessionTtl) * 60 * 1000
|
|
90
|
+
: 8 * 60 * 60 * 1000;
|
|
91
|
+
|
|
92
|
+
const token = await new SignJWT({ sub: userId })
|
|
93
|
+
.setProtectedHeader({ alg: "HS256" })
|
|
94
|
+
.setExpirationTime(sessionTtl)
|
|
95
|
+
.sign(jwtSecret);
|
|
96
|
+
|
|
97
|
+
const refreshToken = await new SignJWT({ sub: userId, type: "refresh" })
|
|
98
|
+
.setProtectedHeader({ alg: "HS256" })
|
|
99
|
+
.setExpirationTime("7d")
|
|
100
|
+
.sign(jwtSecret);
|
|
101
|
+
|
|
102
|
+
return { token, refreshToken, expiresAt: new Date(Date.now() + sessionTtlMs) };
|
|
103
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FastifyRequest, FastifyReply } from "fastify";
|
|
2
|
+
import type { Permission, EnterpriseFeature } from "@synth-deploy/core";
|
|
3
|
+
import { requireEnterprise, ENTERPRISE_FEATURES } from "@synth-deploy/core";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns a Fastify preHandler that checks whether the authenticated user
|
|
7
|
+
* has all of the specified permissions.
|
|
8
|
+
*/
|
|
9
|
+
export function requirePermission(...permissions: Permission[]) {
|
|
10
|
+
return async (request: FastifyRequest, reply: FastifyReply) => {
|
|
11
|
+
const user = request.user;
|
|
12
|
+
if (!user) {
|
|
13
|
+
reply.status(401).send({ error: "Authentication required" });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const missing = permissions.filter((p) => !user.permissions.includes(p));
|
|
17
|
+
if (missing.length > 0) {
|
|
18
|
+
reply.status(403).send({
|
|
19
|
+
error: "Forbidden",
|
|
20
|
+
required: permissions,
|
|
21
|
+
message: `This action requires: ${missing.join(", ")}`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns a Fastify preHandler that gates an enterprise-only feature.
|
|
29
|
+
* Throws EditionError (caught by global error handler → 402) on Community edition.
|
|
30
|
+
*/
|
|
31
|
+
export function requireEdition(feature: EnterpriseFeature) {
|
|
32
|
+
return async (_request: FastifyRequest, _reply: FastifyReply) => {
|
|
33
|
+
requireEnterprise(feature);
|
|
34
|
+
};
|
|
35
|
+
}
|