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.
Files changed (262) hide show
  1. package/README.ja.md +61 -7
  2. package/README.md +61 -7
  3. package/dist/cli/commands/add-auth.d.ts.map +1 -1
  4. package/dist/cli/commands/add-auth.js +103 -27
  5. package/dist/cli/commands/add-auth.js.map +1 -1
  6. package/dist/cli/commands/configure-mcp.d.ts +12 -0
  7. package/dist/cli/commands/configure-mcp.d.ts.map +1 -0
  8. package/dist/cli/commands/configure-mcp.js +42 -0
  9. package/dist/cli/commands/configure-mcp.js.map +1 -0
  10. package/dist/cli/commands/dev.d.ts +5 -2
  11. package/dist/cli/commands/dev.d.ts.map +1 -1
  12. package/dist/cli/commands/dev.js +143 -67
  13. package/dist/cli/commands/dev.js.map +1 -1
  14. package/dist/cli/commands/init.d.ts +14 -7
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/init.js +149 -170
  17. package/dist/cli/commands/init.js.map +1 -1
  18. package/dist/cli/commands/migrations.d.ts.map +1 -1
  19. package/dist/cli/commands/migrations.js +25 -0
  20. package/dist/cli/commands/migrations.js.map +1 -1
  21. package/dist/cli/commands/provision.d.ts +5 -0
  22. package/dist/cli/commands/provision.d.ts.map +1 -1
  23. package/dist/cli/commands/provision.js +278 -55
  24. package/dist/cli/commands/provision.js.map +1 -1
  25. package/dist/cli/commands/scaffold.d.ts +5 -1
  26. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  27. package/dist/cli/commands/scaffold.js +123 -18
  28. package/dist/cli/commands/scaffold.js.map +1 -1
  29. package/dist/cli/index.d.ts +2 -0
  30. package/dist/cli/index.d.ts.map +1 -1
  31. package/dist/cli/index.js +49 -8
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/core/config.d.ts +2 -1
  34. package/dist/core/config.d.ts.map +1 -1
  35. package/dist/core/config.js +19 -0
  36. package/dist/core/config.js.map +1 -1
  37. package/dist/core/operations/create-model.d.ts +2 -0
  38. package/dist/core/operations/create-model.d.ts.map +1 -1
  39. package/dist/core/operations/create-model.js +60 -8
  40. package/dist/core/operations/create-model.js.map +1 -1
  41. package/dist/core/operations/migration-generator.d.ts.map +1 -1
  42. package/dist/core/operations/migration-generator.js +12 -1
  43. package/dist/core/operations/migration-generator.js.map +1 -1
  44. package/dist/core/operations/model-migration.d.ts +22 -0
  45. package/dist/core/operations/model-migration.d.ts.map +1 -0
  46. package/dist/core/operations/model-migration.js +36 -0
  47. package/dist/core/operations/model-migration.js.map +1 -0
  48. package/dist/core/operations/provision-deployment.d.ts +52 -0
  49. package/dist/core/operations/provision-deployment.d.ts.map +1 -0
  50. package/dist/core/operations/provision-deployment.js +164 -0
  51. package/dist/core/operations/provision-deployment.js.map +1 -0
  52. package/dist/core/operations/provision-operations.d.ts +62 -6
  53. package/dist/core/operations/provision-operations.d.ts.map +1 -1
  54. package/dist/core/operations/provision-operations.js +653 -115
  55. package/dist/core/operations/provision-operations.js.map +1 -1
  56. package/dist/core/operations/scaffold-apply.d.ts +1 -0
  57. package/dist/core/operations/scaffold-apply.d.ts.map +1 -1
  58. package/dist/core/operations/scaffold-apply.js +1 -0
  59. package/dist/core/operations/scaffold-apply.js.map +1 -1
  60. package/dist/core/operations/scaffold-machine.d.ts +1 -0
  61. package/dist/core/operations/scaffold-machine.d.ts.map +1 -1
  62. package/dist/core/operations/scaffold-machine.js.map +1 -1
  63. package/dist/core/operations/scaffold-plan.d.ts +2 -0
  64. package/dist/core/operations/scaffold-plan.d.ts.map +1 -1
  65. package/dist/core/operations/scaffold-plan.js +3 -1
  66. package/dist/core/operations/scaffold-plan.js.map +1 -1
  67. package/dist/core/project/agent-instructions.d.ts +2 -1
  68. package/dist/core/project/agent-instructions.d.ts.map +1 -1
  69. package/dist/core/project/agent-instructions.js +15 -0
  70. package/dist/core/project/agent-instructions.js.map +1 -1
  71. package/dist/core/project/boundaries.d.ts +5 -2
  72. package/dist/core/project/boundaries.d.ts.map +1 -1
  73. package/dist/core/project/boundaries.js +51 -20
  74. package/dist/core/project/boundaries.js.map +1 -1
  75. package/dist/core/project/capabilities.d.ts +17 -0
  76. package/dist/core/project/capabilities.d.ts.map +1 -1
  77. package/dist/core/project/capabilities.js +45 -6
  78. package/dist/core/project/capabilities.js.map +1 -1
  79. package/dist/core/project/home-page.d.ts +4 -0
  80. package/dist/core/project/home-page.d.ts.map +1 -0
  81. package/dist/core/project/home-page.js +113 -0
  82. package/dist/core/project/home-page.js.map +1 -0
  83. package/dist/core/project/manifest.d.ts +3 -1
  84. package/dist/core/project/manifest.d.ts.map +1 -1
  85. package/dist/core/project/manifest.js +47 -25
  86. package/dist/core/project/manifest.js.map +1 -1
  87. package/dist/core/project/mcp-config.d.ts +26 -0
  88. package/dist/core/project/mcp-config.d.ts.map +1 -0
  89. package/dist/core/project/mcp-config.js +117 -0
  90. package/dist/core/project/mcp-config.js.map +1 -0
  91. package/dist/core/project/react-project.d.ts +26 -0
  92. package/dist/core/project/react-project.d.ts.map +1 -0
  93. package/dist/core/project/react-project.js +443 -0
  94. package/dist/core/project/react-project.js.map +1 -0
  95. package/dist/core/project/state.d.ts +7 -0
  96. package/dist/core/project/state.d.ts.map +1 -1
  97. package/dist/core/project/state.js +86 -0
  98. package/dist/core/project/state.js.map +1 -1
  99. package/dist/core/project/validation.d.ts.map +1 -1
  100. package/dist/core/project/validation.js +32 -10
  101. package/dist/core/project/validation.js.map +1 -1
  102. package/dist/core/project/workflows.d.ts.map +1 -1
  103. package/dist/core/project/workflows.js +53 -7
  104. package/dist/core/project/workflows.js.map +1 -1
  105. package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
  106. package/dist/core/scaffold/auth-generator.js +26 -27
  107. package/dist/core/scaffold/auth-generator.js.map +1 -1
  108. package/dist/core/scaffold/model-parser.d.ts +23 -0
  109. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  110. package/dist/core/scaffold/model-parser.js +407 -137
  111. package/dist/core/scaffold/model-parser.js.map +1 -1
  112. package/dist/core/scaffold/native-schema-generator.d.ts +13 -0
  113. package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
  114. package/dist/core/scaffold/native-schema-generator.js +294 -91
  115. package/dist/core/scaffold/native-schema-generator.js.map +1 -1
  116. package/dist/core/scaffold/openapi-generator.d.ts +5 -0
  117. package/dist/core/scaffold/openapi-generator.d.ts.map +1 -1
  118. package/dist/core/scaffold/openapi-generator.js +53 -3
  119. package/dist/core/scaffold/openapi-generator.js.map +1 -1
  120. package/dist/core/scaffold/react-bff-generator.d.ts +10 -0
  121. package/dist/core/scaffold/react-bff-generator.d.ts.map +1 -0
  122. package/dist/core/scaffold/react-bff-generator.js +252 -0
  123. package/dist/core/scaffold/react-bff-generator.js.map +1 -0
  124. package/dist/core/scaffold/react-ui-generator.d.ts +6 -0
  125. package/dist/core/scaffold/react-ui-generator.d.ts.map +1 -0
  126. package/dist/core/scaffold/react-ui-generator.js +41 -0
  127. package/dist/core/scaffold/react-ui-generator.js.map +1 -0
  128. package/dist/core/scaffold/ui-generator.d.ts +0 -3
  129. package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
  130. package/dist/core/scaffold/ui-generator.js +68 -7
  131. package/dist/core/scaffold/ui-generator.js.map +1 -1
  132. package/dist/core/verify/index.d.ts +4 -3
  133. package/dist/core/verify/index.d.ts.map +1 -1
  134. package/dist/core/verify/index.js +122 -5
  135. package/dist/core/verify/index.js.map +1 -1
  136. package/dist/index.d.ts +3 -3
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +3 -2
  139. package/dist/index.js.map +1 -1
  140. package/dist/machine/contracts.d.ts +1 -0
  141. package/dist/machine/contracts.d.ts.map +1 -1
  142. package/dist/machine/errors.d.ts +3 -2
  143. package/dist/machine/errors.d.ts.map +1 -1
  144. package/dist/machine/errors.js +2 -1
  145. package/dist/machine/errors.js.map +1 -1
  146. package/dist/machine/index.d.ts.map +1 -1
  147. package/dist/machine/index.js +61 -30
  148. package/dist/machine/index.js.map +1 -1
  149. package/dist/mcp/index.d.ts +6 -4
  150. package/dist/mcp/index.d.ts.map +1 -1
  151. package/dist/mcp/index.js +210 -25
  152. package/dist/mcp/index.js.map +1 -1
  153. package/dist/mcp/version-selection.d.ts +55 -0
  154. package/dist/mcp/version-selection.d.ts.map +1 -0
  155. package/dist/mcp/version-selection.js +152 -0
  156. package/dist/mcp/version-selection.js.map +1 -0
  157. package/dist/types/index.d.ts +4 -0
  158. package/dist/types/index.d.ts.map +1 -1
  159. package/dist/utils/azure-cli.d.ts +56 -0
  160. package/dist/utils/azure-cli.d.ts.map +1 -0
  161. package/dist/utils/azure-cli.js +326 -0
  162. package/dist/utils/azure-cli.js.map +1 -0
  163. package/dist/utils/azure-diagnostics.d.ts +16 -0
  164. package/dist/utils/azure-diagnostics.d.ts.map +1 -0
  165. package/dist/utils/azure-diagnostics.js +68 -0
  166. package/dist/utils/azure-diagnostics.js.map +1 -0
  167. package/dist/utils/package-manager.d.ts +2 -2
  168. package/dist/utils/package-manager.d.ts.map +1 -1
  169. package/dist/utils/package-manager.js +6 -3
  170. package/dist/utils/package-manager.js.map +1 -1
  171. package/dist/utils/python-uv.d.ts +5 -7
  172. package/dist/utils/python-uv.d.ts.map +1 -1
  173. package/dist/utils/python-uv.js +50 -24
  174. package/dist/utils/python-uv.js.map +1 -1
  175. package/dist/utils/react-dev.d.ts +4 -0
  176. package/dist/utils/react-dev.d.ts.map +1 -0
  177. package/dist/utils/react-dev.js +35 -0
  178. package/dist/utils/react-dev.js.map +1 -0
  179. package/dist/utils/semver.d.ts +14 -0
  180. package/dist/utils/semver.d.ts.map +1 -0
  181. package/dist/utils/semver.js +61 -0
  182. package/dist/utils/semver.js.map +1 -0
  183. package/package.json +7 -4
  184. package/src/__tests__/__snapshots__/boundaries.test.ts.snap +154 -0
  185. package/src/__tests__/agent-instructions.test.ts +6 -1
  186. package/src/__tests__/agent-loop-phase34.test.ts +143 -8
  187. package/src/__tests__/auth.test.ts +9 -0
  188. package/src/__tests__/azure-cli.test.ts +202 -0
  189. package/src/__tests__/boundaries.test.ts +42 -0
  190. package/src/__tests__/csharp-schema.test.ts +261 -0
  191. package/src/__tests__/dev.test.ts +13 -6
  192. package/src/__tests__/fixtures/az wrapper.cmd +2 -0
  193. package/src/__tests__/fixtures/azure-cli.cjs +153 -0
  194. package/src/__tests__/fixtures/provision-runner.cjs +28 -0
  195. package/src/__tests__/generated-project-matrix.test.ts +789 -0
  196. package/src/__tests__/home-page.test.ts +50 -0
  197. package/src/__tests__/init.test.ts +145 -5
  198. package/src/__tests__/mcp-version.test.ts +262 -0
  199. package/src/__tests__/mcp.test.ts +120 -3
  200. package/src/__tests__/multi-auth.test.ts +115 -4
  201. package/src/__tests__/package-manager.test.ts +17 -2
  202. package/src/__tests__/plan-apply.test.ts +179 -0
  203. package/src/__tests__/provision-runtime.test.ts +722 -0
  204. package/src/__tests__/python-uv.test.ts +50 -14
  205. package/src/__tests__/python-venv.test.ts +48 -0
  206. package/src/__tests__/react-dev.test.ts +72 -0
  207. package/src/__tests__/react.test.ts +838 -0
  208. package/src/__tests__/release-metadata.test.ts +121 -0
  209. package/src/__tests__/schema-analysis.test.ts +329 -0
  210. package/src/cli/commands/add-auth.ts +103 -29
  211. package/src/cli/commands/configure-mcp.ts +54 -0
  212. package/src/cli/commands/dev.ts +145 -73
  213. package/src/cli/commands/init.ts +147 -172
  214. package/src/cli/commands/migrations.ts +22 -0
  215. package/src/cli/commands/provision.ts +294 -56
  216. package/src/cli/commands/scaffold.ts +145 -22
  217. package/src/cli/index.ts +46 -10
  218. package/src/core/config.ts +21 -1
  219. package/src/core/operations/create-model.ts +51 -13
  220. package/src/core/operations/migration-generator.ts +14 -1
  221. package/src/core/operations/model-migration.ts +52 -0
  222. package/src/core/operations/provision-deployment.ts +183 -0
  223. package/src/core/operations/provision-operations.ts +662 -154
  224. package/src/core/operations/scaffold-apply.ts +2 -0
  225. package/src/core/operations/scaffold-machine.ts +1 -0
  226. package/src/core/operations/scaffold-plan.ts +5 -1
  227. package/src/core/project/agent-instructions.ts +17 -1
  228. package/src/core/project/agent-templates/AGENTS.md +113 -15
  229. package/src/core/project/agent-templates/CLAUDE.md +14 -1
  230. package/src/core/project/agent-templates/bff-routes.instructions.md +5 -3
  231. package/src/core/project/agent-templates/copilot-instructions.md +15 -1
  232. package/src/core/project/agent-templates/shared-models.instructions.md +11 -3
  233. package/src/core/project/boundaries.ts +62 -20
  234. package/src/core/project/capabilities.ts +63 -6
  235. package/src/core/project/home-page.ts +114 -0
  236. package/src/core/project/manifest.ts +52 -27
  237. package/src/core/project/mcp-config.ts +84 -0
  238. package/src/core/project/react-project.ts +529 -0
  239. package/src/core/project/state.ts +70 -0
  240. package/src/core/project/validation.ts +32 -10
  241. package/src/core/project/workflows.ts +53 -7
  242. package/src/core/scaffold/auth-generator.ts +26 -27
  243. package/src/core/scaffold/model-parser.ts +413 -129
  244. package/src/core/scaffold/native-schema-generator.ts +319 -103
  245. package/src/core/scaffold/openapi-generator.ts +63 -5
  246. package/src/core/scaffold/react-bff-generator.ts +292 -0
  247. package/src/core/scaffold/react-ui-generator.ts +51 -0
  248. package/src/core/scaffold/ui-generator.ts +70 -8
  249. package/src/core/verify/index.ts +118 -6
  250. package/src/index.ts +3 -1
  251. package/src/machine/contracts.ts +1 -0
  252. package/src/machine/errors.ts +4 -2
  253. package/src/machine/index.ts +67 -36
  254. package/src/mcp/index.ts +221 -31
  255. package/src/mcp/version-selection.ts +163 -0
  256. package/src/types/index.ts +4 -0
  257. package/src/utils/azure-cli.ts +315 -0
  258. package/src/utils/azure-diagnostics.ts +75 -0
  259. package/src/utils/package-manager.ts +7 -4
  260. package/src/utils/python-uv.ts +53 -33
  261. package/src/utils/react-dev.ts +31 -0
  262. 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 result = await execa(process.execPath, [resolveMachineCliEntrypoint(), "machine", ...args], {
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 ?? 0,
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 result = await runMachineCli(args);
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 Error(`[${parsed.error.code}] ${parsed.error.message}${statusSuffix}${detailsSuffix}`);
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 withSwallowKitMetadata<TData>(data: TData): TData & { metadata: Record<string, unknown> } {
96
- const value = data && typeof data === "object" ? data as Record<string, unknown> : { data };
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: getSwallowKitVersion(),
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
- } as TData & { metadata: Record<string, unknown> };
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
- return [
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 the project passed all checks. Set compact=true to suppress info-severity findings and keep the output small.",
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 provisioning plan. Creates billable Azure resources; approve=true is always required and must reflect explicit human consent.",
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(runMachineCli: MachineCliRunner = defaultMachineCliRunner): McpServer {
397
- const version = getSwallowKitVersion();
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
- console.error(`[swallowkit-mcp] version: ${getSwallowKitVersion()}`);
419
- const server = createSwallowKitMcpServer();
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
+ }
@@ -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;