Unified · Split
| @@ -1,7 +1,5 @@ | ||
| 1 | 1 | import { createServer, type IncomingMessage, type ServerResponse } from "node:http"; |
| 2 | −import { createHash, randomUUID } from "node:crypto"; | |
| 3 | −import { mkdir, writeFile } from "node:fs/promises"; | |
| 4 | −import { join } from "node:path"; | |
| 2 | +import { randomUUID } from "node:crypto"; | |
| 5 | 3 | |
| 6 | 4 | import { |
| 7 | 5 | HttpApi, |
| @@ -24,7 +22,6 @@ import { · 16 unmodified lines | ||
| 24 | 22 | type ControlPlaneClient, |
| 25 | 23 | type ResolveExecutionEnvironment, |
| 26 | 24 | SourceIdSchema, |
| 27 | − SourceRecipeIdSchema, | |
| 28 | 25 | SourceRecipeRevisionIdSchema, |
| 29 | 26 | } from "@executor/control-plane"; |
| 30 | 27 | import { makeSesExecutor } from "@executor/runtime-ses"; |
| @@ -41,6 +38,15 @@ import { · 10 unmodified lines | ||
| 41 | 38 | seedGithubOpenApiSourceInWorkspace, |
| 42 | 39 | } from "../cli/dev"; |
| 43 | 40 | import { createLocalExecutorServer } from "@executor/server"; |
| 41 | +import { | |
| 42 | + buildLocalSourceArtifact, | |
| 43 | + writeLocalSourceArtifact, | |
| 44 | +} from "../../../../packages/control-plane/src/runtime/local-source-artifacts"; | |
| 45 | +import { | |
| 46 | + resolveLocalWorkspaceContext, | |
| 47 | + writeProjectLocalExecutorConfig, | |
| 48 | +} from "../../../../packages/control-plane/src/runtime/local-config"; | |
| 49 | +import { materializationFromMcpManifestEntries } from "../../../../packages/control-plane/src/runtime/source-adapters/mcp"; | |
| 44 | 50 | |
| 45 | 51 | const executionResolver: ResolveExecutionEnvironment = () => |
| 46 | 52 | Effect.succeed({ |
| @@ -82,17 +88,6 @@ const createIsolatedLocalExecutorServer = ( · 35 unmodified lines | ||
| 82 | 88 | }; |
| 83 | 89 | }); |
| 84 | 90 | |
| 85 | −const stableLocalSourceId = (input: { | |
| 86 | − workspaceRoot: string; | |
| 87 | − configKey: string; | |
| 88 | −}) => | |
| 89 | − SourceIdSchema.make( | |
| 90 | − `src_local_${createHash("sha256") | |
| 91 | − .update(`${input.workspaceRoot}:${input.configKey}`) | |
| 92 | − .digest("hex") | |
| 93 | − .slice(0, 16)}`, | |
| 94 | − ); | |
| 95 | − | |
| 96 | 91 | const writeConfiguredLocalMcpSource = (input: { |
| 97 | 92 | workspaceRoot: string; |
| 98 | 93 | configKey: string; |
| @@ -101,16 +96,58 @@ const writeConfiguredLocalMcpSource = (input: { · 2 unmodified lines | ||
| 101 | 96 | namespace?: string; |
| 102 | 97 | }) => |
| 103 | 98 | Effect.promise(async () => { |
| 104 | − const sourceId = stableLocalSourceId({ | |
| 99 | + const sourceId = SourceIdSchema.make(input.configKey); | |
| 100 | + const context = await resolveLocalWorkspaceContext({ | |
| 105 | 101 | workspaceRoot: input.workspaceRoot, |
| 106 | − configKey: input.configKey, | |
| 107 | 102 | }); |
| 108 | − const recipeId = SourceRecipeIdSchema.make(`src_recipe_${sourceId}`); | |
| 109 | − const recipeRevisionId = SourceRecipeRevisionIdSchema.make(`src_recipe_rev_${sourceId}`); | |
| 110 | − const now = Date.now(); | |
| 111 | − const manifest = { | |
| 112 | − version: 1 as const, | |
| 113 | − tools: [{ | |
| 103 | + | |
| 104 | + await writeProjectLocalExecutorConfig({ | |
| 105 | + context, | |
| 106 | + config: { | |
| 107 | + sources: { | |
| 108 | + [input.configKey]: { | |
| 109 | + kind: "mcp", | |
| 110 | + name: input.name ?? "Demo", | |
| 111 | + namespace: input.namespace ?? input.configKey, | |
| 112 | + connection: { | |
| 113 | + endpoint: input.endpoint, | |
| 114 | + }, | |
| 115 | + binding: { | |
| 116 | + transport: "streamable-http", | |
| 117 | + }, | |
| 118 | + }, | |
| 119 | + }, | |
| 120 | + }, | |
| 121 | + }); | |
| 122 | + | |
| 123 | + const source = { | |
| 124 | + id: sourceId, | |
| 125 | + workspaceId: "ws_local_server_test" as never, | |
| 126 | + name: input.name ?? "Demo", | |
| 127 | + kind: "mcp" as const, | |
| 128 | + endpoint: input.endpoint, | |
| 129 | + status: "connected" as const, | |
| 130 | + enabled: true, | |
| 131 | + namespace: input.namespace ?? input.configKey, | |
| 132 | + bindingVersion: 1, | |
| 133 | + binding: { | |
| 134 | + transport: "streamable-http", | |
| 135 | + queryParams: null, | |
| 136 | + headers: null, | |
| 137 | + }, | |
| 138 | + importAuthPolicy: "reuse_runtime" as const, | |
| 139 | + importAuth: { kind: "none" as const }, | |
| 140 | + auth: { kind: "none" as const }, | |
| 141 | + sourceHash: null, | |
| 142 | + lastError: null, | |
| 143 | + createdAt: Date.now(), | |
| 144 | + updatedAt: Date.now(), | |
| 145 | + }; | |
| 146 | + | |
| 147 | + const materialization = materializationFromMcpManifestEntries({ | |
| 148 | + recipeRevisionId: SourceRecipeRevisionIdSchema.make("src_recipe_rev_materialization"), | |
| 149 | + endpoint: input.endpoint, | |
| 150 | + manifestEntries: [{ | |
| 114 | 151 | toolId: "gated_echo", |
| 115 | 152 | toolName: "gated_echo", |
| 116 | 153 | description: "Asks for approval before echoing a value", |
| @@ -125,93 +162,16 @@ const writeConfiguredLocalMcpSource = (input: { · 8 unmodified lines | ||
| 125 | 162 | additionalProperties: false, |
| 126 | 163 | }), |
| 127 | 164 | }], |
| 128 | − }; | |
| 129 | − const manifestJson = JSON.stringify(manifest); | |
| 165 | + }); | |
| 130 | 166 | |
| 131 | − await mkdir(join(input.workspaceRoot, ".executor", "artifacts", "sources"), { | |
| 132 | − recursive: true, | |
| 167 | + await writeLocalSourceArtifact({ | |
| 168 | + context, | |
| 169 | + sourceId, | |
| 170 | + artifact: buildLocalSourceArtifact({ | |
| 171 | + source, | |
| 172 | + materialization, | |
| 173 | + }), | |
| 133 | 174 | }); |
| 134 | − await writeFile( | |
| 135 | − join(input.workspaceRoot, ".executor", "executor.jsonc"), | |
| 136 | − `${JSON.stringify({ | |
| 137 | − sources: { | |
| 138 | − [input.configKey]: { | |
| 139 | − kind: "mcp", | |
| 140 | − name: input.name ?? "Demo", | |
| 141 | − namespace: input.namespace ?? input.configKey, | |
| 142 | − connection: { | |
| 143 | − endpoint: input.endpoint, | |
| 144 | − }, | |
| 145 | − binding: { | |
| 146 | − transport: "streamable-http", | |
| 147 | − }, | |
| 148 | − }, | |
| 149 | − }, | |
| 150 | − }, null, 2)}\n`, | |
| 151 | − "utf8", | |
| 152 | − ); | |
| 153 | − await writeFile( | |
| 154 | − join(input.workspaceRoot, ".executor", "artifacts", "sources", `${input.configKey}.json`), | |
| 155 | − `${JSON.stringify({ | |
| 156 | − version: 1, | |
| 157 | − sourceId, | |
| 158 | − configKey: input.configKey, | |
| 159 | − recipeId, | |
| 160 | − generatedAt: now, | |
| 161 | − revision: { | |
| 162 | − id: recipeRevisionId, | |
| 163 | − recipeId, | |
| 164 | − revisionNumber: 1, | |
| 165 | − sourceConfigJson: JSON.stringify({ | |
| 166 | − kind: "mcp", | |
| 167 | − endpoint: input.endpoint, | |
| 168 | − transport: "streamable-http", | |
| 169 | − queryParams: null, | |
| 170 | − headers: null, | |
| 171 | − }), | |
| 172 | − manifestJson, | |
| 173 | − manifestHash: null, | |
| 174 | − materializationHash: null, | |
| 175 | − createdAt: now, | |
| 176 | − updatedAt: now, | |
| 177 | − }, | |
| 178 | − documents: [{ | |
| 179 | − id: `src_recipe_doc_${sourceId}`, | |
| 180 | − recipeRevisionId, | |
| 181 | − documentKind: "mcp_manifest", | |
| 182 | − documentKey: "manifest", | |
| 183 | − contentText: manifestJson, | |
| 184 | − contentHash: `manifest_${sourceId}`, | |
| 185 | − fetchedAt: now, | |
| 186 | − createdAt: now, | |
| 187 | − updatedAt: now, | |
| 188 | − }], | |
| 189 | − schemaBundles: [], | |
| 190 | − operations: [{ | |
| 191 | − id: `src_recipe_op_${sourceId}`, | |
| 192 | − recipeRevisionId, | |
| 193 | − operationKey: "gated_echo", | |
| 194 | − transportKind: "mcp", | |
| 195 | − toolId: "gated_echo", | |
| 196 | − title: "gated_echo", | |
| 197 | − description: "Asks for approval before echoing a value", | |
| 198 | − operationKind: "unknown", | |
| 199 | − searchText: "demo.gated_echo gated echo approval", | |
| 200 | − inputSchemaJson: manifest.tools[0]!.inputSchemaJson ?? null, | |
| 201 | − outputSchemaJson: null, | |
| 202 | − providerKind: "mcp", | |
| 203 | − providerDataJson: JSON.stringify({ | |
| 204 | − kind: "mcp", | |
| 205 | − toolId: "gated_echo", | |
| 206 | − toolName: "gated_echo", | |
| 207 | − description: "Asks for approval before echoing a value", | |
| 208 | − }), | |
| 209 | − createdAt: now, | |
| 210 | − updatedAt: now, | |
| 211 | − }], | |
| 212 | − }, null, 2)}\n`, | |
| 213 | − "utf8", | |
| 214 | − ); | |
| 215 | 175 | }); |
| 216 | 176 | |
| 217 | 177 | const makeServer = createIsolatedLocalExecutorServer({ |