swallowkit 1.1.2 → 2.0.0-alpha.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/README.ja.md +61 -7
- package/README.md +61 -7
- package/dist/cli/commands/add-auth.d.ts.map +1 -1
- package/dist/cli/commands/add-auth.js +103 -27
- package/dist/cli/commands/add-auth.js.map +1 -1
- package/dist/cli/commands/configure-mcp.d.ts +12 -0
- package/dist/cli/commands/configure-mcp.d.ts.map +1 -0
- package/dist/cli/commands/configure-mcp.js +42 -0
- package/dist/cli/commands/configure-mcp.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +5 -2
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +143 -67
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +14 -7
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +149 -170
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/migrations.d.ts.map +1 -1
- package/dist/cli/commands/migrations.js +25 -0
- package/dist/cli/commands/migrations.js.map +1 -1
- package/dist/cli/commands/provision.d.ts +5 -0
- package/dist/cli/commands/provision.d.ts.map +1 -1
- package/dist/cli/commands/provision.js +278 -55
- package/dist/cli/commands/provision.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +5 -1
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +123 -18
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +49 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +2 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +19 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/operations/create-model.d.ts +2 -0
- package/dist/core/operations/create-model.d.ts.map +1 -1
- package/dist/core/operations/create-model.js +60 -8
- package/dist/core/operations/create-model.js.map +1 -1
- package/dist/core/operations/migration-generator.d.ts.map +1 -1
- package/dist/core/operations/migration-generator.js +12 -1
- package/dist/core/operations/migration-generator.js.map +1 -1
- package/dist/core/operations/model-migration.d.ts +22 -0
- package/dist/core/operations/model-migration.d.ts.map +1 -0
- package/dist/core/operations/model-migration.js +36 -0
- package/dist/core/operations/model-migration.js.map +1 -0
- package/dist/core/operations/provision-deployment.d.ts +52 -0
- package/dist/core/operations/provision-deployment.d.ts.map +1 -0
- package/dist/core/operations/provision-deployment.js +164 -0
- package/dist/core/operations/provision-deployment.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +62 -6
- package/dist/core/operations/provision-operations.d.ts.map +1 -1
- package/dist/core/operations/provision-operations.js +653 -115
- package/dist/core/operations/provision-operations.js.map +1 -1
- package/dist/core/operations/scaffold-apply.d.ts +1 -0
- package/dist/core/operations/scaffold-apply.d.ts.map +1 -1
- package/dist/core/operations/scaffold-apply.js +1 -0
- package/dist/core/operations/scaffold-apply.js.map +1 -1
- package/dist/core/operations/scaffold-machine.d.ts +1 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
- package/dist/core/operations/scaffold-machine.js.map +1 -1
- package/dist/core/operations/scaffold-plan.d.ts +2 -0
- package/dist/core/operations/scaffold-plan.d.ts.map +1 -1
- package/dist/core/operations/scaffold-plan.js +3 -1
- package/dist/core/operations/scaffold-plan.js.map +1 -1
- package/dist/core/project/agent-instructions.d.ts +2 -1
- package/dist/core/project/agent-instructions.d.ts.map +1 -1
- package/dist/core/project/agent-instructions.js +15 -0
- package/dist/core/project/agent-instructions.js.map +1 -1
- package/dist/core/project/boundaries.d.ts +5 -2
- package/dist/core/project/boundaries.d.ts.map +1 -1
- package/dist/core/project/boundaries.js +51 -20
- package/dist/core/project/boundaries.js.map +1 -1
- package/dist/core/project/capabilities.d.ts +17 -0
- package/dist/core/project/capabilities.d.ts.map +1 -1
- package/dist/core/project/capabilities.js +45 -6
- package/dist/core/project/capabilities.js.map +1 -1
- package/dist/core/project/home-page.d.ts +4 -0
- package/dist/core/project/home-page.d.ts.map +1 -0
- package/dist/core/project/home-page.js +113 -0
- package/dist/core/project/home-page.js.map +1 -0
- package/dist/core/project/manifest.d.ts +3 -1
- package/dist/core/project/manifest.d.ts.map +1 -1
- package/dist/core/project/manifest.js +47 -25
- package/dist/core/project/manifest.js.map +1 -1
- package/dist/core/project/mcp-config.d.ts +26 -0
- package/dist/core/project/mcp-config.d.ts.map +1 -0
- package/dist/core/project/mcp-config.js +117 -0
- package/dist/core/project/mcp-config.js.map +1 -0
- package/dist/core/project/react-project.d.ts +26 -0
- package/dist/core/project/react-project.d.ts.map +1 -0
- package/dist/core/project/react-project.js +443 -0
- package/dist/core/project/react-project.js.map +1 -0
- package/dist/core/project/state.d.ts +7 -0
- package/dist/core/project/state.d.ts.map +1 -1
- package/dist/core/project/state.js +86 -0
- package/dist/core/project/state.js.map +1 -1
- package/dist/core/project/validation.d.ts.map +1 -1
- package/dist/core/project/validation.js +32 -10
- package/dist/core/project/validation.js.map +1 -1
- package/dist/core/project/workflows.d.ts.map +1 -1
- package/dist/core/project/workflows.js +53 -7
- package/dist/core/project/workflows.js.map +1 -1
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
- package/dist/core/scaffold/auth-generator.js +26 -27
- package/dist/core/scaffold/auth-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +23 -0
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +407 -137
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
- package/dist/core/scaffold/native-schema-generator.js +294 -91
- package/dist/core/scaffold/native-schema-generator.js.map +1 -1
- package/dist/core/scaffold/openapi-generator.d.ts +5 -0
- package/dist/core/scaffold/openapi-generator.d.ts.map +1 -1
- package/dist/core/scaffold/openapi-generator.js +53 -3
- package/dist/core/scaffold/openapi-generator.js.map +1 -1
- package/dist/core/scaffold/react-bff-generator.d.ts +10 -0
- package/dist/core/scaffold/react-bff-generator.d.ts.map +1 -0
- package/dist/core/scaffold/react-bff-generator.js +252 -0
- package/dist/core/scaffold/react-bff-generator.js.map +1 -0
- package/dist/core/scaffold/react-ui-generator.d.ts +6 -0
- package/dist/core/scaffold/react-ui-generator.d.ts.map +1 -0
- package/dist/core/scaffold/react-ui-generator.js +41 -0
- package/dist/core/scaffold/react-ui-generator.js.map +1 -0
- package/dist/core/scaffold/ui-generator.d.ts +0 -3
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +68 -7
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +4 -3
- package/dist/core/verify/index.d.ts.map +1 -1
- package/dist/core/verify/index.js +122 -5
- package/dist/core/verify/index.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/machine/contracts.d.ts +1 -0
- package/dist/machine/contracts.d.ts.map +1 -1
- package/dist/machine/errors.d.ts +3 -2
- package/dist/machine/errors.d.ts.map +1 -1
- package/dist/machine/errors.js +2 -1
- package/dist/machine/errors.js.map +1 -1
- package/dist/machine/index.d.ts.map +1 -1
- package/dist/machine/index.js +61 -30
- package/dist/machine/index.js.map +1 -1
- package/dist/mcp/index.d.ts +6 -4
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +210 -25
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/version-selection.d.ts +55 -0
- package/dist/mcp/version-selection.d.ts.map +1 -0
- package/dist/mcp/version-selection.js +152 -0
- package/dist/mcp/version-selection.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/azure-cli.d.ts +56 -0
- package/dist/utils/azure-cli.d.ts.map +1 -0
- package/dist/utils/azure-cli.js +326 -0
- package/dist/utils/azure-cli.js.map +1 -0
- package/dist/utils/azure-diagnostics.d.ts +16 -0
- package/dist/utils/azure-diagnostics.d.ts.map +1 -0
- package/dist/utils/azure-diagnostics.js +68 -0
- package/dist/utils/azure-diagnostics.js.map +1 -0
- package/dist/utils/package-manager.d.ts +2 -2
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +6 -3
- package/dist/utils/package-manager.js.map +1 -1
- package/dist/utils/python-uv.d.ts +5 -7
- package/dist/utils/python-uv.d.ts.map +1 -1
- package/dist/utils/python-uv.js +50 -24
- package/dist/utils/python-uv.js.map +1 -1
- package/dist/utils/react-dev.d.ts +4 -0
- package/dist/utils/react-dev.d.ts.map +1 -0
- package/dist/utils/react-dev.js +35 -0
- package/dist/utils/react-dev.js.map +1 -0
- package/dist/utils/semver.d.ts +14 -0
- package/dist/utils/semver.d.ts.map +1 -0
- package/dist/utils/semver.js +61 -0
- package/dist/utils/semver.js.map +1 -0
- package/package.json +7 -4
- package/src/__tests__/__snapshots__/boundaries.test.ts.snap +154 -0
- package/src/__tests__/agent-instructions.test.ts +6 -1
- package/src/__tests__/agent-loop-phase34.test.ts +143 -8
- package/src/__tests__/auth.test.ts +9 -0
- package/src/__tests__/azure-cli.test.ts +202 -0
- package/src/__tests__/boundaries.test.ts +42 -0
- package/src/__tests__/csharp-schema.test.ts +261 -0
- package/src/__tests__/dev.test.ts +13 -6
- package/src/__tests__/fixtures/az wrapper.cmd +2 -0
- package/src/__tests__/fixtures/azure-cli.cjs +153 -0
- package/src/__tests__/fixtures/provision-runner.cjs +28 -0
- package/src/__tests__/generated-project-matrix.test.ts +789 -0
- package/src/__tests__/home-page.test.ts +50 -0
- package/src/__tests__/init.test.ts +145 -5
- package/src/__tests__/mcp-version.test.ts +262 -0
- package/src/__tests__/mcp.test.ts +120 -3
- package/src/__tests__/multi-auth.test.ts +115 -4
- package/src/__tests__/package-manager.test.ts +17 -2
- package/src/__tests__/plan-apply.test.ts +179 -0
- package/src/__tests__/provision-runtime.test.ts +722 -0
- package/src/__tests__/python-uv.test.ts +50 -14
- package/src/__tests__/python-venv.test.ts +48 -0
- package/src/__tests__/react-dev.test.ts +72 -0
- package/src/__tests__/react.test.ts +838 -0
- package/src/__tests__/release-metadata.test.ts +121 -0
- package/src/__tests__/schema-analysis.test.ts +329 -0
- package/src/cli/commands/add-auth.ts +103 -29
- package/src/cli/commands/configure-mcp.ts +54 -0
- package/src/cli/commands/dev.ts +145 -73
- package/src/cli/commands/init.ts +147 -172
- package/src/cli/commands/migrations.ts +22 -0
- package/src/cli/commands/provision.ts +294 -56
- package/src/cli/commands/scaffold.ts +145 -22
- package/src/cli/index.ts +46 -10
- package/src/core/config.ts +21 -1
- package/src/core/operations/create-model.ts +51 -13
- package/src/core/operations/migration-generator.ts +14 -1
- package/src/core/operations/model-migration.ts +52 -0
- package/src/core/operations/provision-deployment.ts +183 -0
- package/src/core/operations/provision-operations.ts +662 -154
- package/src/core/operations/scaffold-apply.ts +2 -0
- package/src/core/operations/scaffold-machine.ts +1 -0
- package/src/core/operations/scaffold-plan.ts +5 -1
- package/src/core/project/agent-instructions.ts +17 -1
- package/src/core/project/agent-templates/AGENTS.md +113 -15
- package/src/core/project/agent-templates/CLAUDE.md +14 -1
- package/src/core/project/agent-templates/bff-routes.instructions.md +5 -3
- package/src/core/project/agent-templates/copilot-instructions.md +15 -1
- package/src/core/project/agent-templates/shared-models.instructions.md +11 -3
- package/src/core/project/boundaries.ts +62 -20
- package/src/core/project/capabilities.ts +63 -6
- package/src/core/project/home-page.ts +114 -0
- package/src/core/project/manifest.ts +52 -27
- package/src/core/project/mcp-config.ts +84 -0
- package/src/core/project/react-project.ts +529 -0
- package/src/core/project/state.ts +70 -0
- package/src/core/project/validation.ts +32 -10
- package/src/core/project/workflows.ts +53 -7
- package/src/core/scaffold/auth-generator.ts +26 -27
- package/src/core/scaffold/model-parser.ts +413 -129
- package/src/core/scaffold/native-schema-generator.ts +319 -103
- package/src/core/scaffold/openapi-generator.ts +63 -5
- package/src/core/scaffold/react-bff-generator.ts +292 -0
- package/src/core/scaffold/react-ui-generator.ts +51 -0
- package/src/core/scaffold/ui-generator.ts +70 -8
- package/src/core/verify/index.ts +118 -6
- package/src/index.ts +3 -1
- package/src/machine/contracts.ts +1 -0
- package/src/machine/errors.ts +4 -2
- package/src/machine/index.ts +67 -36
- package/src/mcp/index.ts +221 -31
- package/src/mcp/version-selection.ts +163 -0
- package/src/types/index.ts +4 -0
- package/src/utils/azure-cli.ts +315 -0
- package/src/utils/azure-diagnostics.ts +75 -0
- package/src/utils/package-manager.ts +7 -4
- package/src/utils/python-uv.ts +53 -33
- package/src/utils/react-dev.ts +31 -0
- package/src/utils/semver.ts +59 -0
package/src/mcp/index.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { spawn } from "child_process";
|
|
3
4
|
import * as path from "path";
|
|
4
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
7
|
import * as z from "zod/v4";
|
|
7
|
-
import type { MachineNextAction, MachineOperationStatus, MachineResponse } from "../machine/contracts";
|
|
8
|
+
import type { MachineErrorResponse, MachineNextAction, MachineOperationStatus, MachineResponse } from "../machine/contracts";
|
|
8
9
|
import { getSwallowKitVersion } from "../version";
|
|
10
|
+
import { detectFromProject, getCommands } from "../utils/package-manager";
|
|
11
|
+
import { provisionNextActions } from "../core/operations/provision-operations";
|
|
12
|
+
import {
|
|
13
|
+
MCP_DELEGATED_FROM_ENV,
|
|
14
|
+
McpVersionContext,
|
|
15
|
+
buildPinnedUpdateNotice,
|
|
16
|
+
decideMcpLaunch,
|
|
17
|
+
resolveProjectSwallowKit,
|
|
18
|
+
} from "./version-selection";
|
|
9
19
|
|
|
10
20
|
interface MachineSuccessPayload<TData> {
|
|
11
21
|
ok: true;
|
|
@@ -15,13 +25,18 @@ interface MachineSuccessPayload<TData> {
|
|
|
15
25
|
data: TData;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
|
-
type MachineCliRunner = (args: string[]) => Promise<{ stdout: string; stderr: string; exitCode: number }>;
|
|
19
|
-
type ToolContentResult = { content: Array<{ type: "text"; text: string }
|
|
28
|
+
type MachineCliRunner = (args: string[], signal?: AbortSignal) => Promise<{ stdout: string; stderr: string; exitCode: number }>;
|
|
29
|
+
type ToolContentResult = { content: Array<{ type: "text"; text: string }>; isError?: boolean };
|
|
30
|
+
class MachineCliError extends Error {
|
|
31
|
+
constructor(message: string, readonly response: MachineErrorResponse) {
|
|
32
|
+
super(message);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
20
35
|
type ToolDefinition = {
|
|
21
36
|
name: string;
|
|
22
37
|
description: string;
|
|
23
38
|
inputSchema: z.ZodTypeAny;
|
|
24
|
-
handler: (input: any) => Promise<ToolContentResult>;
|
|
39
|
+
handler: (input: any, signal?: AbortSignal) => Promise<ToolContentResult>;
|
|
25
40
|
};
|
|
26
41
|
type ExecaModule = typeof import("execa");
|
|
27
42
|
|
|
@@ -34,29 +49,45 @@ function resolveMachineCliEntrypoint(): string {
|
|
|
34
49
|
return path.resolve(__dirname, "..", "cli", "index.js");
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
async function defaultMachineCliRunner(args: string[]): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
52
|
+
async function defaultMachineCliRunner(args: string[], signal?: AbortSignal): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
38
53
|
const { execa } = await importModule("execa");
|
|
39
|
-
const
|
|
54
|
+
const child = execa(process.execPath, [resolveMachineCliEntrypoint(), "machine", ...args], {
|
|
40
55
|
reject: false,
|
|
41
56
|
});
|
|
42
|
-
|
|
57
|
+
const abort = () => child.kill("SIGTERM", { forceKillAfterTimeout: 6_000 });
|
|
58
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
59
|
+
if (signal?.aborted) abort();
|
|
60
|
+
let result: Awaited<typeof child>;
|
|
61
|
+
try { result = await child; } finally { signal?.removeEventListener("abort", abort); }
|
|
43
62
|
return {
|
|
44
63
|
stdout: result.stdout,
|
|
45
64
|
stderr: result.stderr,
|
|
46
|
-
exitCode: result.exitCode ??
|
|
65
|
+
exitCode: result.exitCode ?? 1,
|
|
47
66
|
};
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
async function executeMachineCommand<TData>(
|
|
51
70
|
args: string[],
|
|
52
|
-
runMachineCli: MachineCliRunner
|
|
71
|
+
runMachineCli: MachineCliRunner,
|
|
72
|
+
signal?: AbortSignal
|
|
53
73
|
): Promise<MachineSuccessPayload<TData>> {
|
|
54
|
-
const
|
|
74
|
+
const interrupted = () => {
|
|
75
|
+
const runId = args[args.indexOf("--plan") + 1];
|
|
76
|
+
const response: MachineErrorResponse = {
|
|
77
|
+
ok: false, command: args.slice(0, 2).join("-"), status: "blocked",
|
|
78
|
+
error: { code: "provision-interrupted", message: "The local MCP wait was interrupted. Azure may continue; inspect the original checkpoint. No Azure cancellation or successful result is implied.", details: { runId, outcome: "unknown", localFailure: "cancelled" } },
|
|
79
|
+
nextActions: /^[a-f0-9]{12}$/.test(runId) ? provisionNextActions(runId) : [],
|
|
80
|
+
};
|
|
81
|
+
return new MachineCliError(response.error.message, response);
|
|
82
|
+
};
|
|
83
|
+
if (signal?.aborted) throw interrupted();
|
|
84
|
+
const result = await (signal ? runMachineCli(args, signal) : runMachineCli(args));
|
|
55
85
|
|
|
56
86
|
let parsed: MachineResponse<TData>;
|
|
57
87
|
try {
|
|
58
88
|
parsed = JSON.parse(result.stdout) as MachineResponse<TData>;
|
|
59
89
|
} catch {
|
|
90
|
+
if (signal?.aborted) throw interrupted();
|
|
60
91
|
throw new Error(result.stderr || result.stdout || "Machine CLI returned invalid JSON.");
|
|
61
92
|
}
|
|
62
93
|
|
|
@@ -64,12 +95,35 @@ async function executeMachineCommand<TData>(
|
|
|
64
95
|
const detailsSuffix =
|
|
65
96
|
parsed.error.details !== undefined ? `\ndetails: ${JSON.stringify(parsed.error.details)}` : "";
|
|
66
97
|
const statusSuffix = parsed.status ? ` (status: ${parsed.status})` : "";
|
|
67
|
-
throw new
|
|
98
|
+
throw new MachineCliError(`[${parsed.error.code}] ${parsed.error.message}${statusSuffix}${detailsSuffix}`, parsed);
|
|
68
99
|
}
|
|
69
100
|
|
|
70
101
|
return parsed;
|
|
71
102
|
}
|
|
72
103
|
|
|
104
|
+
function defaultVersionContext(): McpVersionContext {
|
|
105
|
+
return {
|
|
106
|
+
mcpVersion: getSwallowKitVersion(),
|
|
107
|
+
requestedVersion: "latest",
|
|
108
|
+
projectVersion: null,
|
|
109
|
+
selection: "running-version",
|
|
110
|
+
delegated: false,
|
|
111
|
+
warnings: [],
|
|
112
|
+
nextActions: [],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function mergeNextActions(
|
|
117
|
+
first: MachineNextAction[] | undefined,
|
|
118
|
+
second: MachineNextAction[]
|
|
119
|
+
): MachineNextAction[] {
|
|
120
|
+
const merged = [...(first ?? [])];
|
|
121
|
+
for (const action of second) {
|
|
122
|
+
if (!merged.some((existing) => existing.command === action.command)) merged.push(action);
|
|
123
|
+
}
|
|
124
|
+
return merged;
|
|
125
|
+
}
|
|
126
|
+
|
|
73
127
|
function withOperationStatus<TData>(response: MachineSuccessPayload<TData>): Record<string, unknown> {
|
|
74
128
|
const data = response.data && typeof response.data === "object"
|
|
75
129
|
? (response.data as Record<string, unknown>)
|
|
@@ -92,25 +146,41 @@ function jsonTextContent(value: unknown): { content: Array<{ type: "text"; text:
|
|
|
92
146
|
};
|
|
93
147
|
}
|
|
94
148
|
|
|
95
|
-
function
|
|
96
|
-
const value = data && typeof data === "object"
|
|
149
|
+
function withVersionMetadata(data: unknown, context: McpVersionContext): Record<string, unknown> {
|
|
150
|
+
const value = data && typeof data === "object" && !Array.isArray(data)
|
|
151
|
+
? data as Record<string, unknown>
|
|
152
|
+
: { data };
|
|
97
153
|
const metadata = value.metadata && typeof value.metadata === "object"
|
|
98
154
|
? value.metadata as Record<string, unknown>
|
|
99
155
|
: {};
|
|
156
|
+
const existingNextActions = Array.isArray(value.nextActions) ? value.nextActions as MachineNextAction[] : undefined;
|
|
157
|
+
const nextActions = mergeNextActions(existingNextActions, context.nextActions);
|
|
100
158
|
|
|
101
159
|
return {
|
|
102
160
|
...value,
|
|
161
|
+
...(nextActions.length > 0 ? { nextActions } : {}),
|
|
103
162
|
metadata: {
|
|
104
163
|
...metadata,
|
|
105
|
-
swallowkitVersion:
|
|
164
|
+
swallowkitVersion: context.mcpVersion,
|
|
165
|
+
mcp: {
|
|
166
|
+
version: context.mcpVersion,
|
|
167
|
+
requestedVersion: context.requestedVersion,
|
|
168
|
+
projectVersion: context.projectVersion,
|
|
169
|
+
selection: context.selection,
|
|
170
|
+
delegated: context.delegated,
|
|
171
|
+
...(context.delegatedFrom ? { delegatedFrom: context.delegatedFrom } : {}),
|
|
172
|
+
},
|
|
173
|
+
...(context.warnings.length > 0 ? { warnings: [...context.warnings] } : {}),
|
|
106
174
|
},
|
|
107
|
-
}
|
|
175
|
+
};
|
|
108
176
|
}
|
|
109
177
|
|
|
110
178
|
export function buildSwallowKitToolDefinitions(
|
|
111
|
-
runMachineCli: MachineCliRunner = defaultMachineCliRunner
|
|
179
|
+
runMachineCli: MachineCliRunner = defaultMachineCliRunner,
|
|
180
|
+
versionContext: McpVersionContext = defaultVersionContext()
|
|
112
181
|
): ToolDefinition[] {
|
|
113
|
-
|
|
182
|
+
const withSwallowKitMetadata = (data: unknown) => withVersionMetadata(data, versionContext);
|
|
183
|
+
const definitions: ToolDefinition[] = [
|
|
114
184
|
{
|
|
115
185
|
name: "swallowkit_inspect_project",
|
|
116
186
|
description: "Return framework-owned SwallowKit project metadata.",
|
|
@@ -169,8 +239,9 @@ export function buildSwallowKitToolDefinitions(
|
|
|
169
239
|
functionsDir: z.string().optional(),
|
|
170
240
|
apiDir: z.string().optional(),
|
|
171
241
|
apiOnly: z.boolean().optional(),
|
|
242
|
+
skipInfra: z.boolean().optional().describe("Do not create or update anything under infra/; the container migration stays pending."),
|
|
172
243
|
}),
|
|
173
|
-
handler: async ({ model, functionsDir, apiDir, apiOnly }: { model: string; functionsDir?: string; apiDir?: string; apiOnly?: boolean }) => {
|
|
244
|
+
handler: async ({ model, functionsDir, apiDir, apiOnly, skipInfra }: { model: string; functionsDir?: string; apiDir?: string; apiOnly?: boolean; skipInfra?: boolean }) => {
|
|
174
245
|
const args = ["generate", "scaffold", model];
|
|
175
246
|
if (functionsDir) {
|
|
176
247
|
args.push("--functions-dir", functionsDir);
|
|
@@ -181,6 +252,9 @@ export function buildSwallowKitToolDefinitions(
|
|
|
181
252
|
if (apiOnly) {
|
|
182
253
|
args.push("--api-only");
|
|
183
254
|
}
|
|
255
|
+
if (skipInfra) {
|
|
256
|
+
args.push("--skip-infra");
|
|
257
|
+
}
|
|
184
258
|
|
|
185
259
|
const response = await executeMachineCommand(args, runMachineCli);
|
|
186
260
|
return jsonTextContent(response.data);
|
|
@@ -195,14 +269,16 @@ export function buildSwallowKitToolDefinitions(
|
|
|
195
269
|
functionsDir: z.string().optional(),
|
|
196
270
|
apiDir: z.string().optional(),
|
|
197
271
|
apiOnly: z.boolean().optional(),
|
|
272
|
+
skipInfra: z.boolean().optional().describe("Exclude all infra/ changes (container migration) from the plan."),
|
|
198
273
|
}),
|
|
199
|
-
handler: async ({ model, models, functionsDir, apiDir, apiOnly }: { model?: string; models?: string[]; functionsDir?: string; apiDir?: string; apiOnly?: boolean }) => {
|
|
274
|
+
handler: async ({ model, models, functionsDir, apiDir, apiOnly, skipInfra }: { model?: string; models?: string[]; functionsDir?: string; apiDir?: string; apiOnly?: boolean; skipInfra?: boolean }) => {
|
|
200
275
|
const targets = models && models.length > 0 ? models : model ? [model] : [];
|
|
201
276
|
if (targets.length === 0) throw new Error("Provide model or models.");
|
|
202
277
|
const args = ["plan", "scaffold", ...targets];
|
|
203
278
|
if (functionsDir) args.push("--functions-dir", functionsDir);
|
|
204
279
|
if (apiDir) args.push("--api-dir", apiDir);
|
|
205
280
|
if (apiOnly) args.push("--api-only");
|
|
281
|
+
if (skipInfra) args.push("--skip-infra");
|
|
206
282
|
|
|
207
283
|
const response = await executeMachineCommand(args, runMachineCli);
|
|
208
284
|
return jsonTextContent(withOperationStatus(response));
|
|
@@ -218,8 +294,9 @@ export function buildSwallowKitToolDefinitions(
|
|
|
218
294
|
functionsDir: z.string().optional(),
|
|
219
295
|
apiDir: z.string().optional(),
|
|
220
296
|
apiOnly: z.boolean().optional(),
|
|
297
|
+
skipInfra: z.boolean().optional().describe("Do not create or update anything under infra/ (defaults to the plan's setting)."),
|
|
221
298
|
}),
|
|
222
|
-
handler: async ({ model, planId, approve, functionsDir, apiDir, apiOnly }: { model?: string; planId?: string; approve?: boolean; functionsDir?: string; apiDir?: string; apiOnly?: boolean }) => {
|
|
299
|
+
handler: async ({ model, planId, approve, functionsDir, apiDir, apiOnly, skipInfra }: { model?: string; planId?: string; approve?: boolean; functionsDir?: string; apiDir?: string; apiOnly?: boolean; skipInfra?: boolean }) => {
|
|
223
300
|
const args = ["apply", "scaffold"];
|
|
224
301
|
if (model) args.push(model);
|
|
225
302
|
if (planId) args.push("--plan", planId);
|
|
@@ -227,6 +304,7 @@ export function buildSwallowKitToolDefinitions(
|
|
|
227
304
|
if (functionsDir) args.push("--functions-dir", functionsDir);
|
|
228
305
|
if (apiDir) args.push("--api-dir", apiDir);
|
|
229
306
|
if (apiOnly) args.push("--api-only");
|
|
307
|
+
if (skipInfra) args.push("--skip-infra");
|
|
230
308
|
|
|
231
309
|
const response = await executeMachineCommand(args, runMachineCli);
|
|
232
310
|
return jsonTextContent(withOperationStatus(response));
|
|
@@ -252,7 +330,7 @@ export function buildSwallowKitToolDefinitions(
|
|
|
252
330
|
},
|
|
253
331
|
{
|
|
254
332
|
name: "swallowkit_verify_project",
|
|
255
|
-
description: "Run verification checks (structure, drift, typecheck by default; build, lint, test and custom checks from swallowkit.config verify.checks are also available) and return machine-readable evidence. summary.done=true means
|
|
333
|
+
description: "Run verification checks (structure, drift, typecheck, backend-build by default; build, lint, test and custom checks from swallowkit.config verify.checks are also available) and return machine-readable evidence. backend-build compiles standalone C#/TypeScript Functions or checks Python syntax; unavailable toolchains are skipped with a reason. summary.done=true means no checks failed, not that skipped checks were validated or runtime/auth flows work. Set compact=true to suppress info-severity findings and keep the output small.",
|
|
256
334
|
inputSchema: z.object({
|
|
257
335
|
checks: z.array(z.string()).optional(),
|
|
258
336
|
compact: z.boolean().optional(),
|
|
@@ -362,14 +440,16 @@ export function buildSwallowKitToolDefinitions(
|
|
|
362
440
|
adoptExisting: z.boolean().optional(),
|
|
363
441
|
baseline: z.number().int().nonnegative().optional(),
|
|
364
442
|
reconcile: z.boolean().optional(),
|
|
443
|
+
resumeDeployment: z.string().optional(),
|
|
365
444
|
}),
|
|
366
|
-
handler: async ({ resourceGroup, location, swaLocation, subscription, whatIf, adoptExisting, baseline, reconcile }: { resourceGroup: string; location: string; swaLocation: string; subscription?: string; whatIf?: boolean; adoptExisting?: boolean; baseline?: number; reconcile?: boolean }) => {
|
|
445
|
+
handler: async ({ resourceGroup, location, swaLocation, subscription, whatIf, adoptExisting, baseline, reconcile, resumeDeployment }: { resourceGroup: string; location: string; swaLocation: string; subscription?: string; whatIf?: boolean; adoptExisting?: boolean; baseline?: number; reconcile?: boolean; resumeDeployment?: string }) => {
|
|
367
446
|
const args = ["plan", "provision", "--resource-group", resourceGroup, "--location", location, "--swa-location", swaLocation];
|
|
368
447
|
if (subscription) args.push("--subscription", subscription);
|
|
369
448
|
if (whatIf) args.push("--what-if");
|
|
370
449
|
if (adoptExisting) args.push("--adopt-existing");
|
|
371
450
|
if (baseline !== undefined) args.push("--baseline", String(baseline));
|
|
372
451
|
if (reconcile) args.push("--reconcile");
|
|
452
|
+
if (resumeDeployment) args.push("--resume-deployment", resumeDeployment);
|
|
373
453
|
|
|
374
454
|
const response = await executeMachineCommand(args, runMachineCli);
|
|
375
455
|
return jsonTextContent(withOperationStatus(response));
|
|
@@ -377,46 +457,156 @@ export function buildSwallowKitToolDefinitions(
|
|
|
377
457
|
},
|
|
378
458
|
{
|
|
379
459
|
name: "swallowkit_apply_provision",
|
|
380
|
-
description: "Apply an approved
|
|
460
|
+
description: "Apply or resume an approved provision checkpoint with bounded state polling. Never resubmit an attempted deployment; finish tags/ledger only after Azure Succeeded. Check status/nextActions, not just ok. approve=true always requires explicit human consent.",
|
|
381
461
|
inputSchema: z.object({
|
|
382
462
|
planId: z.string(),
|
|
383
463
|
approve: z.boolean().optional(),
|
|
464
|
+
waitSeconds: z.number().int().min(0).max(3600).optional(),
|
|
384
465
|
}),
|
|
385
|
-
handler: async ({ planId, approve }: { planId: string; approve?: boolean }) => {
|
|
466
|
+
handler: async ({ planId, approve, waitSeconds }: { planId: string; approve?: boolean; waitSeconds?: number }, signal?: AbortSignal) => {
|
|
386
467
|
const args = ["apply", "provision", "--plan", planId];
|
|
387
468
|
if (approve) args.push("--approve");
|
|
469
|
+
if (waitSeconds !== undefined) args.push("--wait-seconds", String(waitSeconds));
|
|
388
470
|
|
|
389
|
-
const response = await executeMachineCommand(args, runMachineCli);
|
|
471
|
+
const response = await executeMachineCommand(args, runMachineCli, signal);
|
|
472
|
+
return jsonTextContent(withOperationStatus(response));
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
name: "swallowkit_inspect_provision",
|
|
477
|
+
description: "Read or bounded-wait for the original provision deployment. No create, cancel, delete, tag, or ledger writes. Azure Succeeded alone is partial until the original approved checkpoint finishes bookkeeping.",
|
|
478
|
+
inputSchema: z.object({
|
|
479
|
+
planId: z.string(),
|
|
480
|
+
waitSeconds: z.number().int().min(0).max(3600).optional(),
|
|
481
|
+
}),
|
|
482
|
+
handler: async ({ planId, waitSeconds }: { planId: string; waitSeconds?: number }, signal?: AbortSignal) => {
|
|
483
|
+
const args = ["inspect", "provision", "--plan", planId];
|
|
484
|
+
if (waitSeconds !== undefined) args.push("--wait-seconds", String(waitSeconds));
|
|
485
|
+
const response = await executeMachineCommand(args, runMachineCli, signal);
|
|
390
486
|
return jsonTextContent(withOperationStatus(response));
|
|
391
487
|
},
|
|
392
488
|
},
|
|
393
489
|
];
|
|
490
|
+
|
|
491
|
+
// Every response reports which SwallowKit version answered and why it was selected.
|
|
492
|
+
return definitions.map((definition) => ({
|
|
493
|
+
...definition,
|
|
494
|
+
handler: async (input: unknown, signal?: AbortSignal) => {
|
|
495
|
+
try {
|
|
496
|
+
const result = await definition.handler(input, signal);
|
|
497
|
+
return jsonTextContent(withSwallowKitMetadata(JSON.parse(result.content[0].text)));
|
|
498
|
+
} catch (error) {
|
|
499
|
+
// Prerequisite failures carry nextActions; keep them structured so agents can follow them.
|
|
500
|
+
if (error instanceof MachineCliError && error.response.nextActions?.length) {
|
|
501
|
+
return { ...jsonTextContent(withSwallowKitMetadata(error.response)), isError: true };
|
|
502
|
+
}
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
}));
|
|
394
507
|
}
|
|
395
508
|
|
|
396
|
-
export function createSwallowKitMcpServer(
|
|
397
|
-
|
|
509
|
+
export function createSwallowKitMcpServer(
|
|
510
|
+
runMachineCli: MachineCliRunner = defaultMachineCliRunner,
|
|
511
|
+
versionContext: McpVersionContext = defaultVersionContext()
|
|
512
|
+
): McpServer {
|
|
513
|
+
const version = versionContext.mcpVersion;
|
|
398
514
|
const server = new McpServer({
|
|
399
515
|
name: "swallowkit-mcp",
|
|
400
516
|
version,
|
|
401
517
|
});
|
|
402
518
|
|
|
403
|
-
for (const tool of buildSwallowKitToolDefinitions(runMachineCli)) {
|
|
519
|
+
for (const tool of buildSwallowKitToolDefinitions(runMachineCli, versionContext)) {
|
|
404
520
|
server.registerTool(
|
|
405
521
|
tool.name,
|
|
406
522
|
{
|
|
407
523
|
description: tool.description,
|
|
408
524
|
inputSchema: tool.inputSchema,
|
|
409
525
|
},
|
|
410
|
-
tool.handler
|
|
526
|
+
(input, extra) => tool.handler(input, extra.signal)
|
|
411
527
|
);
|
|
412
528
|
}
|
|
413
529
|
|
|
414
530
|
return server;
|
|
415
531
|
}
|
|
416
532
|
|
|
533
|
+
function resolveExecCommand(cwd: string): string {
|
|
534
|
+
try {
|
|
535
|
+
return getCommands(detectFromProject(cwd)).exec;
|
|
536
|
+
} catch {
|
|
537
|
+
return "npx";
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function fetchPublishedLatest(): Promise<string | null> {
|
|
542
|
+
if (process.env.SWALLOWKIT_MCP_UPDATE_CHECK === "0") return null;
|
|
543
|
+
const registry = (process.env.npm_config_registry || "https://registry.npmjs.org").replace(/\/+$/, "");
|
|
544
|
+
try {
|
|
545
|
+
const response = await fetch(`${registry}/swallowkit/latest`, { signal: AbortSignal.timeout(3000) });
|
|
546
|
+
if (!response.ok) return null;
|
|
547
|
+
const body = await response.json() as { version?: unknown };
|
|
548
|
+
return typeof body.version === "string" ? body.version : null;
|
|
549
|
+
} catch {
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function delegateToProjectMcp(entrypoint: string, fromVersion: string): void {
|
|
555
|
+
const child = spawn(process.execPath, [entrypoint], {
|
|
556
|
+
stdio: "inherit",
|
|
557
|
+
env: { ...process.env, [MCP_DELEGATED_FROM_ENV]: fromVersion },
|
|
558
|
+
});
|
|
559
|
+
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
|
560
|
+
process.on(signal, () => child.kill(signal));
|
|
561
|
+
}
|
|
562
|
+
child.on("error", (error) => {
|
|
563
|
+
console.error(`[swallowkit-mcp] failed to start the project-installed MCP: ${error.message}`);
|
|
564
|
+
process.exitCode = 1;
|
|
565
|
+
});
|
|
566
|
+
child.on("exit", (code, signal) => {
|
|
567
|
+
process.exitCode = code ?? (signal ? 1 : 0);
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
417
571
|
export async function runMcpServer(): Promise<void> {
|
|
418
|
-
|
|
419
|
-
const
|
|
572
|
+
const selfVersion = getSwallowKitVersion();
|
|
573
|
+
const cwd = process.cwd();
|
|
574
|
+
const execCommand = resolveExecCommand(cwd);
|
|
575
|
+
const decision = decideMcpLaunch({
|
|
576
|
+
selfVersion,
|
|
577
|
+
selfPackageRoot: path.resolve(__dirname, "..", ".."),
|
|
578
|
+
env: process.env,
|
|
579
|
+
project: resolveProjectSwallowKit(cwd),
|
|
580
|
+
execCommand,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
if (decision.action === "delegate") {
|
|
584
|
+
console.error(
|
|
585
|
+
`[swallowkit-mcp] version: ${selfVersion}; delegating to the project-installed swallowkit-mcp ${decision.toVersion} (newer than the launcher).`
|
|
586
|
+
);
|
|
587
|
+
delegateToProjectMcp(decision.entrypoint, decision.fromVersion);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const context = decision.context;
|
|
592
|
+
// One write keeps the version line and its warnings together for log readers.
|
|
593
|
+
console.error([
|
|
594
|
+
`[swallowkit-mcp] version: ${selfVersion} (selection: ${context.selection}${context.delegatedFrom ? `, delegated from ${context.delegatedFrom}` : ""})`,
|
|
595
|
+
...context.warnings.map((warning) => `[swallowkit-mcp] warning: ${warning}`),
|
|
596
|
+
].join("\n"));
|
|
597
|
+
|
|
598
|
+
if (context.selection === "pinned") {
|
|
599
|
+
void fetchPublishedLatest().then((latest) => {
|
|
600
|
+
const notice = buildPinnedUpdateNotice(context, latest, execCommand);
|
|
601
|
+
if (notice) {
|
|
602
|
+
context.warnings.push(notice.warning);
|
|
603
|
+
context.nextActions.push(notice.nextAction);
|
|
604
|
+
console.error(`[swallowkit-mcp] warning: ${notice.warning}`);
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const server = createSwallowKitMcpServer(defaultMachineCliRunner, context);
|
|
420
610
|
const transport = new StdioServerTransport();
|
|
421
611
|
await server.connect(transport);
|
|
422
612
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime version selection for `swallowkit-mcp`.
|
|
3
|
+
*
|
|
4
|
+
* Policy: when launched as `latest` (or without an explicit version), use whichever is
|
|
5
|
+
* newer of this MCP package and the project-installed `swallowkit`. A newer project
|
|
6
|
+
* install takes over through its own `swallowkit-mcp`, so prerelease projects never get
|
|
7
|
+
* an older contract. An explicit SWALLOWKIT_MCP_VERSION is always honored.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import * as path from "path";
|
|
12
|
+
import type { MachineNextAction } from "../machine/contracts";
|
|
13
|
+
import { MCP_LATEST, MCP_VERSION_ENV } from "../core/project/mcp-config";
|
|
14
|
+
import { compareSemver } from "../utils/semver";
|
|
15
|
+
|
|
16
|
+
export const MCP_DELEGATED_FROM_ENV = "SWALLOWKIT_MCP_DELEGATED_FROM";
|
|
17
|
+
|
|
18
|
+
export interface ProjectSwallowKitInstall {
|
|
19
|
+
version: string;
|
|
20
|
+
packageRoot: string;
|
|
21
|
+
mcpEntrypoint: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type McpVersionSelection =
|
|
25
|
+
| "running-version"
|
|
26
|
+
| "project-version"
|
|
27
|
+
| "pinned";
|
|
28
|
+
|
|
29
|
+
export interface McpVersionContext {
|
|
30
|
+
/** Version of the MCP server answering the request. */
|
|
31
|
+
mcpVersion: string;
|
|
32
|
+
/** SWALLOWKIT_MCP_VERSION as requested by the launcher (`latest` when unset). */
|
|
33
|
+
requestedVersion: string;
|
|
34
|
+
projectVersion: string | null;
|
|
35
|
+
selection: McpVersionSelection;
|
|
36
|
+
delegated: boolean;
|
|
37
|
+
delegatedFrom?: string;
|
|
38
|
+
warnings: string[];
|
|
39
|
+
nextActions: MachineNextAction[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type McpLaunchDecision =
|
|
43
|
+
| { action: "delegate"; entrypoint: string; fromVersion: string; toVersion: string }
|
|
44
|
+
| { action: "serve"; context: McpVersionContext };
|
|
45
|
+
|
|
46
|
+
export interface McpLaunchInput {
|
|
47
|
+
selfVersion: string;
|
|
48
|
+
selfPackageRoot: string;
|
|
49
|
+
env: NodeJS.ProcessEnv;
|
|
50
|
+
project: ProjectSwallowKitInstall | null;
|
|
51
|
+
execCommand?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeRoot(value: string): string {
|
|
55
|
+
const resolved = path.resolve(value);
|
|
56
|
+
try {
|
|
57
|
+
return fs.realpathSync.native(resolved).toLowerCase();
|
|
58
|
+
} catch {
|
|
59
|
+
return resolved.toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function resolveProjectSwallowKit(cwd: string): ProjectSwallowKitInstall | null {
|
|
64
|
+
// Same lookup order as Node's node_modules resolution from the project directory.
|
|
65
|
+
let packageJsonPath: string | null = null;
|
|
66
|
+
for (let directory = path.resolve(cwd); ; directory = path.dirname(directory)) {
|
|
67
|
+
const candidate = path.join(directory, "node_modules", "swallowkit", "package.json");
|
|
68
|
+
if (fs.existsSync(candidate)) {
|
|
69
|
+
packageJsonPath = fs.realpathSync(candidate);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
if (path.dirname(directory) === directory) return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const manifest = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
|
|
77
|
+
version?: string;
|
|
78
|
+
bin?: string | Record<string, string>;
|
|
79
|
+
};
|
|
80
|
+
if (!manifest.version) return null;
|
|
81
|
+
const packageRoot = path.dirname(packageJsonPath);
|
|
82
|
+
const bin = typeof manifest.bin === "object" ? manifest.bin["swallowkit-mcp"] : undefined;
|
|
83
|
+
const candidate = path.resolve(packageRoot, bin ?? path.join("dist", "mcp", "index.js"));
|
|
84
|
+
return {
|
|
85
|
+
version: manifest.version,
|
|
86
|
+
packageRoot,
|
|
87
|
+
mcpEntrypoint: fs.existsSync(candidate) ? candidate : null,
|
|
88
|
+
};
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function decideMcpLaunch(input: McpLaunchInput): McpLaunchDecision {
|
|
95
|
+
const { selfVersion, env, project } = input;
|
|
96
|
+
const exec = input.execCommand ?? "npx";
|
|
97
|
+
const requested = (env[MCP_VERSION_ENV] ?? "").trim() || MCP_LATEST;
|
|
98
|
+
const pinned = requested !== MCP_LATEST;
|
|
99
|
+
const delegatedFrom = (env[MCP_DELEGATED_FROM_ENV] ?? "").trim() || undefined;
|
|
100
|
+
const projectVersion = project?.version ?? null;
|
|
101
|
+
const projectNewer = projectVersion ? (compareSemver(projectVersion, selfVersion) ?? 0) > 0 : false;
|
|
102
|
+
const sameInstall = project ? normalizeRoot(project.packageRoot) === normalizeRoot(input.selfPackageRoot) : false;
|
|
103
|
+
|
|
104
|
+
if (!pinned && !delegatedFrom && project && projectNewer && !sameInstall && project.mcpEntrypoint) {
|
|
105
|
+
return { action: "delegate", entrypoint: project.mcpEntrypoint, fromVersion: selfVersion, toVersion: project.version };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const warnings: string[] = [];
|
|
109
|
+
const nextActions: MachineNextAction[] = [];
|
|
110
|
+
if (projectVersion && projectNewer) {
|
|
111
|
+
warnings.push(
|
|
112
|
+
`mcp-older-than-project: swallowkit-mcp ${selfVersion} is older than the project's swallowkit ${projectVersion}. ` +
|
|
113
|
+
"Its capabilities, boundaries, and plan/apply contracts may be outdated for this project."
|
|
114
|
+
);
|
|
115
|
+
nextActions.push({
|
|
116
|
+
command: `${exec} swallowkit machine inspect capabilities`,
|
|
117
|
+
description: "Use the project-local machine interface, which matches the installed SwallowKit version, until MCP is updated.",
|
|
118
|
+
});
|
|
119
|
+
nextActions.push({
|
|
120
|
+
command: `${exec} swallowkit configure-mcp`,
|
|
121
|
+
description: pinned
|
|
122
|
+
? `Re-select the MCP version (${MCP_VERSION_ENV} is pinned to ${requested}), then restart the MCP server.`
|
|
123
|
+
: "Pin the MCP launcher to the project version, then restart the MCP server.",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
action: "serve",
|
|
129
|
+
context: {
|
|
130
|
+
mcpVersion: selfVersion,
|
|
131
|
+
requestedVersion: requested,
|
|
132
|
+
projectVersion,
|
|
133
|
+
selection: delegatedFrom ? "project-version" : pinned ? "pinned" : "running-version",
|
|
134
|
+
delegated: Boolean(delegatedFrom),
|
|
135
|
+
...(delegatedFrom ? { delegatedFrom } : {}),
|
|
136
|
+
warnings,
|
|
137
|
+
nextActions,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* For an explicitly pinned launcher, report when the published `latest` has caught up
|
|
144
|
+
* with or passed the pinned version so the pin can be removed.
|
|
145
|
+
*/
|
|
146
|
+
export function buildPinnedUpdateNotice(
|
|
147
|
+
context: McpVersionContext,
|
|
148
|
+
publishedLatest: string | null,
|
|
149
|
+
execCommand = "npx"
|
|
150
|
+
): { warning: string; nextAction: MachineNextAction } | null {
|
|
151
|
+
if (context.selection !== "pinned" || !publishedLatest) return null;
|
|
152
|
+
const comparison = compareSemver(publishedLatest, context.requestedVersion);
|
|
153
|
+
if (comparison === null || comparison < 0) return null;
|
|
154
|
+
return {
|
|
155
|
+
warning:
|
|
156
|
+
`mcp-pin-outdated: ${MCP_VERSION_ENV} is pinned to ${context.requestedVersion}, but published latest is ${publishedLatest}. ` +
|
|
157
|
+
`Switch the launcher back to "${MCP_LATEST}".`,
|
|
158
|
+
nextAction: {
|
|
159
|
+
command: `${execCommand} swallowkit configure-mcp`,
|
|
160
|
+
description: `Re-select the MCP launcher version (returns to "${MCP_LATEST}" when it is at least the project version), then restart the MCP server.`,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
export type ServerFnMode = "auto" | "force-server" | "force-client";
|
|
7
7
|
|
|
8
8
|
export type BackendLanguage = "typescript" | "csharp" | "python";
|
|
9
|
+
export type FrontendFramework = "nextjs" | "react";
|
|
9
10
|
|
|
10
11
|
// useServerFn のオプション
|
|
11
12
|
export interface UseServerFnOptions {
|
|
@@ -146,6 +147,9 @@ export interface AuthPrincipal {
|
|
|
146
147
|
|
|
147
148
|
// CLI設定の型
|
|
148
149
|
export interface SwallowKitConfig {
|
|
150
|
+
frontend?: {
|
|
151
|
+
framework?: FrontendFramework;
|
|
152
|
+
};
|
|
149
153
|
database?: {
|
|
150
154
|
connectionString?: string;
|
|
151
155
|
databaseName?: string;
|