@skaile/workspaces 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/{asset-feeds-WKIKSZ6Z.js → asset-feeds-77KLWCBP.js} +9 -9
  3. package/dist/{asset-feeds-WKIKSZ6Z.js.map → asset-feeds-77KLWCBP.js.map} +1 -1
  4. package/dist/asset-manager/index.js +7 -7
  5. package/dist/asset-manager/installer.js +6 -6
  6. package/dist/base-assets/connectors/deploy.js +7 -7
  7. package/dist/base-assets/connectors/devserver.js +7 -7
  8. package/dist/base-assets/connectors/flow/adapter.js +7 -7
  9. package/dist/base-assets/connectors/flow/run-flow.js +8 -8
  10. package/dist/base-assets/connectors/flow.js +7 -7
  11. package/dist/base-assets/connectors/git.js +7 -7
  12. package/dist/base-assets/connectors/gmail.js +7 -7
  13. package/dist/base-assets/connectors/googledrive.js +7 -7
  14. package/dist/base-assets/connectors/local.js +7 -7
  15. package/dist/base-assets/connectors/mattermost.js +7 -7
  16. package/dist/base-assets/connectors/memory.js +7 -7
  17. package/dist/base-assets/connectors/minio.js +7 -7
  18. package/dist/base-assets/connectors/postgres.js +7 -7
  19. package/dist/base-assets/connectors/s3.js +7 -7
  20. package/dist/base-assets/connectors/sharepoint.js +7 -7
  21. package/dist/base-assets/connectors/sqlite.js +7 -7
  22. package/dist/base-assets/connectors/static-server.js +7 -7
  23. package/dist/base-assets/connectors/tunnel.js +7 -7
  24. package/dist/base-assets/connectors/webdav.js +7 -7
  25. package/dist/base-assets/connectors/xstate-store.js +7 -7
  26. package/dist/base-assets/connectors/xstate.js +7 -7
  27. package/dist/{chunk-542K7SR6.js → chunk-3QTZWPGH.js} +36 -7
  28. package/dist/chunk-3QTZWPGH.js.map +1 -0
  29. package/dist/{chunk-46COM7M5.js → chunk-4FADEVBN.js} +4 -4
  30. package/dist/{chunk-46COM7M5.js.map → chunk-4FADEVBN.js.map} +1 -1
  31. package/dist/{chunk-AFLH7B64.js → chunk-4FJE6BI6.js} +3 -3
  32. package/dist/{chunk-AFLH7B64.js.map → chunk-4FJE6BI6.js.map} +1 -1
  33. package/dist/{chunk-J2TITSXF.js → chunk-4QVFQEY2.js} +2 -2
  34. package/dist/{chunk-J2TITSXF.js.map → chunk-4QVFQEY2.js.map} +1 -1
  35. package/dist/{chunk-N6TA6RSH.js → chunk-B4ZXBH57.js} +7 -7
  36. package/dist/{chunk-N6TA6RSH.js.map → chunk-B4ZXBH57.js.map} +1 -1
  37. package/dist/{chunk-JQBHCJ6N.js → chunk-BJWUSHC4.js} +317 -110
  38. package/dist/chunk-BJWUSHC4.js.map +1 -0
  39. package/dist/{chunk-2F3RUZXC.js → chunk-DCAWIRD6.js} +15 -6
  40. package/dist/chunk-DCAWIRD6.js.map +1 -0
  41. package/dist/{chunk-OYRW5RCM.js → chunk-FJFHJBGS.js} +5 -5
  42. package/dist/{chunk-OYRW5RCM.js.map → chunk-FJFHJBGS.js.map} +1 -1
  43. package/dist/{chunk-DH4N5AW4.js → chunk-GL45UNVS.js} +3 -3
  44. package/dist/{chunk-DH4N5AW4.js.map → chunk-GL45UNVS.js.map} +1 -1
  45. package/dist/{chunk-LJ52ZKIU.js → chunk-KT3CK26V.js} +3 -3
  46. package/dist/{chunk-LJ52ZKIU.js.map → chunk-KT3CK26V.js.map} +1 -1
  47. package/dist/{chunk-2RFOFHSM.js → chunk-QXC62DOF.js} +4 -4
  48. package/dist/{chunk-2RFOFHSM.js.map → chunk-QXC62DOF.js.map} +1 -1
  49. package/dist/{chunk-ODPII24X.js → chunk-SETTLPBD.js} +3 -3
  50. package/dist/{chunk-ODPII24X.js.map → chunk-SETTLPBD.js.map} +1 -1
  51. package/dist/{chunk-5ESCS2OS.js → chunk-UD4ZLXGS.js} +4 -4
  52. package/dist/{chunk-5ESCS2OS.js.map → chunk-UD4ZLXGS.js.map} +1 -1
  53. package/dist/{chunk-YX3UWPJ5.js → chunk-WIAHJOMG.js} +19 -49
  54. package/dist/chunk-WIAHJOMG.js.map +1 -0
  55. package/dist/{chunk-HIIARTRZ.js → chunk-XMP6XTMF.js} +4 -4
  56. package/dist/{chunk-HIIARTRZ.js.map → chunk-XMP6XTMF.js.map} +1 -1
  57. package/dist/{chunk-Z3M5K67G.js → chunk-XVL22AWE.js} +3 -3
  58. package/dist/{chunk-Z3M5K67G.js.map → chunk-XVL22AWE.js.map} +1 -1
  59. package/dist/cli/index.js +160 -46
  60. package/dist/cli/index.js.map +1 -1
  61. package/dist/cli/src/commands/project.d.ts.map +1 -1
  62. package/dist/cli/src/commands/validate.d.ts.map +1 -1
  63. package/dist/connectors/config.js +6 -6
  64. package/dist/connectors/index.js +7 -7
  65. package/dist/core/index.js +5 -5
  66. package/dist/core/manifest.js +2 -2
  67. package/dist/core/models.js +1 -1
  68. package/dist/core/runtime-assets.js +4 -4
  69. package/dist/core/src/index.d.ts +2 -2
  70. package/dist/core/src/index.d.ts.map +1 -1
  71. package/dist/core/src/models.d.ts +10 -3
  72. package/dist/core/src/models.d.ts.map +1 -1
  73. package/dist/core/src/workspace-config.d.ts +21 -0
  74. package/dist/core/src/workspace-config.d.ts.map +1 -1
  75. package/dist/core/workspace-config.js +3 -3
  76. package/dist/deploy/index.js +5 -5
  77. package/dist/discovery/index.js +3 -3
  78. package/dist/{ensure-sources-OJUBGX6Z.js → ensure-sources-7MOOKY3K.js} +9 -9
  79. package/dist/{ensure-sources-OJUBGX6Z.js.map → ensure-sources-7MOOKY3K.js.map} +1 -1
  80. package/dist/library/index.js +12 -4
  81. package/dist/library/src/install/install-from-manifest.d.ts.map +1 -1
  82. package/dist/open-library-GW7DWWNZ.js +21 -0
  83. package/dist/{open-library-67FSSQWE.js.map → open-library-GW7DWWNZ.js.map} +1 -1
  84. package/dist/{plugin-store-IZ5SCRAV.js → plugin-store-R32NH7JE.js} +7 -7
  85. package/dist/{plugin-store-IZ5SCRAV.js.map → plugin-store-R32NH7JE.js.map} +1 -1
  86. package/dist/runner/index.js +9 -9
  87. package/dist/runner/src/external-mcp.d.ts +112 -0
  88. package/dist/runner/src/external-mcp.d.ts.map +1 -0
  89. package/dist/runner/src/resources.d.ts +9 -1
  90. package/dist/runner/src/resources.d.ts.map +1 -1
  91. package/dist/runner/src/serve.d.ts.map +1 -1
  92. package/dist/runner/src/session-builder.d.ts +12 -0
  93. package/dist/runner/src/session-builder.d.ts.map +1 -1
  94. package/dist/sdk/asset-manager.js +7 -7
  95. package/dist/sdk/core.js +5 -5
  96. package/dist/sdk/index.js +9 -9
  97. package/dist/sdk/runner.js +9 -9
  98. package/dist/{setup-J7CYEQOF.js → setup-SRPBQOHY.js} +7 -7
  99. package/dist/{setup-J7CYEQOF.js.map → setup-SRPBQOHY.js.map} +1 -1
  100. package/dist/store-client-INZD2RYD.js +14 -0
  101. package/dist/{store-client-AEI6Y3KD.js.map → store-client-INZD2RYD.js.map} +1 -1
  102. package/dist/tui/index.js +9 -9
  103. package/dist/types/src/install-manifest.d.ts +1 -1
  104. package/dist/types/src/install-manifest.d.ts.map +1 -1
  105. package/dist/workspace-plugin/index.js +1 -1
  106. package/package.json +1 -1
  107. package/dist/chunk-2F3RUZXC.js.map +0 -1
  108. package/dist/chunk-542K7SR6.js.map +0 -1
  109. package/dist/chunk-JQBHCJ6N.js.map +0 -1
  110. package/dist/chunk-YX3UWPJ5.js.map +0 -1
  111. package/dist/open-library-67FSSQWE.js +0 -13
  112. package/dist/store-client-AEI6Y3KD.js +0 -14
@@ -1,4 +1,4 @@
1
- import { WorkspacePlugin } from './chunk-J2TITSXF.js';
1
+ import { WorkspacePlugin } from './chunk-4QVFQEY2.js';
2
2
  import { WebSocketServerTransport } from './chunk-WQ7DE5UC.js';
3
3
  import { assembleSystemPrompt, buildCapabilitiesPromptSection } from './chunk-W3UDISS2.js';
4
4
  import { PROTOCOL_VERSION } from './chunk-TDSRLMDB.js';
@@ -6,13 +6,14 @@ import { EventNormalizer } from './chunk-M5TE6YI5.js';
6
6
  import { classifyClaudeSdkError } from './chunk-DQWREFRQ.js';
7
7
  import { createDriver } from './chunk-6VTG73UY.js';
8
8
  import { deployCatalogEntry, undeployCatalogEntry } from './chunk-LV2HPH3C.js';
9
- import { registerBuiltinConnectors, findMissingPackages, installNpmPackages, ConnectorManager, ConnectorStartupError, buildConnectorPromptSection, buildSdkConnectorTools } from './chunk-HIIARTRZ.js';
10
- import { loadConnectorDeclarations, PreMintedSecretProvider, InMemorySecretProvider } from './chunk-ODPII24X.js';
9
+ import { registerBuiltinConnectors, findMissingPackages, installNpmPackages, ConnectorManager, ConnectorStartupError, buildConnectorPromptSection, buildSdkConnectorTools } from './chunk-XMP6XTMF.js';
10
+ import { loadConnectorDeclarations, PreMintedSecretProvider, InMemorySecretProvider } from './chunk-SETTLPBD.js';
11
11
  import { renderStimulusPrompt, buildOrchestratorPrompt } from './chunk-GZWJGNNN.js';
12
- import { resolveSettings, resolveApiKey, providerEnvKey } from './chunk-Z3M5K67G.js';
13
- import { resolveRuntimeAssets } from './chunk-5ESCS2OS.js';
14
- import { loadMcpServerDeclarations, resolveSkWorkspaceConfig, resolveAgentDir, validateAssetRecipeAttr, COMPACTION_DEFAULTS } from './chunk-542K7SR6.js';
15
- import { parseAssetRef } from './chunk-YX3UWPJ5.js';
12
+ import { resolveSettings, resolveApiKey, providerEnvKey } from './chunk-XVL22AWE.js';
13
+ import { resolveDriverPaths } from './chunk-K5GBV4SA.js';
14
+ import { resolveRuntimeAssets } from './chunk-UD4ZLXGS.js';
15
+ import { resolveSkWorkspaceConfig, resolveAgentDir, stageMaterializedSkills, loadMcpServerDeclarations, COMPACTION_DEFAULTS, validateAssetRecipeAttr } from './chunk-3QTZWPGH.js';
16
+ import { parseAssetRef } from './chunk-WIAHJOMG.js';
16
17
  import { getLogStore, resolveLogStoreConfig, createLogStoreFromConfig, OnLogBridgeSink, WsLogSink, registerLogStore, resetLogStore, createLogger } from './chunk-24UIWON4.js';
17
18
  import { __require } from './chunk-NSBPE2FW.js';
18
19
  import pc from 'picocolors';
@@ -28,6 +29,10 @@ import { createHash, randomUUID } from 'crypto';
28
29
  import { execFile, spawnSync } from 'child_process';
29
30
  import { promisify } from 'util';
30
31
  import { parse } from 'yaml';
32
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
33
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
34
+ import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
35
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
31
36
  import mime from 'mime';
32
37
 
33
38
  // runner/src/logging-bootstrap.ts
@@ -850,8 +855,262 @@ function validateSubstitutedPaths(command, args) {
850
855
  return missing;
851
856
  }
852
857
 
858
+ // runner/src/external-mcp.ts
859
+ function resolveRecordSecrets(record, secrets) {
860
+ if (!secrets) return record;
861
+ const resolved = {};
862
+ for (const [k, v] of Object.entries(record)) {
863
+ resolved[k] = secrets.resolve(v) ?? v;
864
+ }
865
+ return resolved;
866
+ }
867
+ var RESERVED_MCP_SERVER_IDS = /* @__PURE__ */ new Set([
868
+ "skaile-connectors",
869
+ "skaile-workspace",
870
+ "skaile-capabilities"
871
+ ]);
872
+ function resolveExternalMcpDeclarations(projectDir, sessionId) {
873
+ const mcpLog = createLogger({
874
+ kind: "mcp",
875
+ subkind: "wiring",
876
+ instance: sessionId ?? "no-session"
877
+ });
878
+ const mcpDeclarations = loadMcpServerDeclarations(projectDir);
879
+ mcpLog.debug("loaded MCP declarations", { count: mcpDeclarations.length });
880
+ const recipeCache = createRecipeCache();
881
+ const nixFlakeRef = process.env.SKAILE_NIX_FLAKE_REF ?? "/etc/skaile/flake";
882
+ const resolved = [];
883
+ for (const decl of mcpDeclarations) {
884
+ if (RESERVED_MCP_SERVER_IDS.has(decl.id)) {
885
+ mcpLog.warn("MCP server skipped: reserved id", { id: decl.id });
886
+ continue;
887
+ }
888
+ let resolvedDecl = decl;
889
+ if (decl.recipe) {
890
+ const recipeLogKey = decl.recipe.flake ? { flake: decl.recipe.flake } : { attr: decl.recipe.attr };
891
+ try {
892
+ const recipeKey = decl.recipe.flake ? `mcps.${decl.id}` : decl.recipe.attr ?? `mcps.${decl.id}`;
893
+ const outPath = resolveRecipePath(nixFlakeRef, recipeKey, recipeCache);
894
+ const outPaths = /* @__PURE__ */ new Map([[decl.id, outPath]]);
895
+ const resolvedCommand = decl.command ? substituteRecipeTemplating(decl.command, outPaths) : decl.command;
896
+ const resolvedArgs = decl.args?.map((a) => substituteRecipeTemplating(a, outPaths));
897
+ const resolvedEnv = substituteRecipeMap(decl.env, outPaths);
898
+ resolvedDecl = {
899
+ ...decl,
900
+ command: resolvedCommand,
901
+ args: resolvedArgs,
902
+ env: resolvedEnv
903
+ };
904
+ const recipeLog = createLogger({
905
+ kind: "mcp",
906
+ subkind: decl.id,
907
+ instance: "recipe-resolve"
908
+ });
909
+ const allResolved = [
910
+ resolvedCommand ?? "",
911
+ ...resolvedArgs ?? [],
912
+ ...Object.values(resolvedEnv ?? {})
913
+ ];
914
+ for (const v of allResolved) {
915
+ if (v.includes("${recipe:")) {
916
+ recipeLog.warn("unresolved recipe marker after substitution", {
917
+ value: v,
918
+ declId: decl.id
919
+ });
920
+ }
921
+ }
922
+ const missingPaths = validateSubstitutedPaths(resolvedCommand, resolvedArgs);
923
+ if (missingPaths.length > 0) {
924
+ recipeLog.error("MCP server skipped: substituted path does not exist", void 0, {
925
+ id: decl.id,
926
+ ...recipeLogKey,
927
+ missing: missingPaths
928
+ });
929
+ continue;
930
+ }
931
+ recipeLog.info("recipe substitution complete", { ...recipeLogKey, outPath });
932
+ } catch (err) {
933
+ createLogger({ kind: "mcp", subkind: decl.id, instance: "recipe-resolve" }).error(
934
+ "MCP server skipped: recipe resolution failed",
935
+ err,
936
+ { id: decl.id, ...recipeLogKey, flakeRef: nixFlakeRef }
937
+ );
938
+ continue;
939
+ }
940
+ }
941
+ resolved.push(resolvedDecl);
942
+ }
943
+ return resolved;
944
+ }
945
+ var PASSTHROUGH_INPUT_ZOD = {
946
+ parse: (v) => v
947
+ };
948
+ function buildTransport(decl, secrets) {
949
+ const transport = decl.transport ?? "stdio";
950
+ if (transport === "stdio") {
951
+ if (!decl.command) {
952
+ throw new Error(`stdio MCP server '${decl.id}' is missing 'command'`);
953
+ }
954
+ const env = {
955
+ ...getDefaultEnvironment(),
956
+ ...resolveRecordSecrets(decl.env ?? {}, secrets)
957
+ };
958
+ return new StdioClientTransport({
959
+ command: decl.command,
960
+ args: decl.args ?? [],
961
+ env,
962
+ stderr: "pipe"
963
+ });
964
+ }
965
+ if (transport === "sse") {
966
+ if (!decl.url) throw new Error(`sse MCP server '${decl.id}' is missing 'url'`);
967
+ const headers = resolveRecordSecrets(decl.headers ?? {}, secrets);
968
+ return new SSEClientTransport(new URL(decl.url), { requestInit: { headers } });
969
+ }
970
+ if (transport === "http") {
971
+ if (!decl.url) throw new Error(`http MCP server '${decl.id}' is missing 'url'`);
972
+ const headers = resolveRecordSecrets(decl.headers ?? {}, secrets);
973
+ return new StreamableHTTPClientTransport(new URL(decl.url), { requestInit: { headers } });
974
+ }
975
+ throw new Error(`Unknown MCP transport '${transport}' for server '${decl.id}'`);
976
+ }
977
+ function defaultMcpClientConnector(secrets) {
978
+ return async (decl) => {
979
+ const transport = buildTransport(decl, secrets);
980
+ const client = new Client({ name: "skaile-runner", version: "1.0.0" }, { capabilities: {} });
981
+ await client.connect(transport);
982
+ return client;
983
+ };
984
+ }
985
+ var ExternalMcpManager = class {
986
+ /**
987
+ * @param registry Session capability registry to register MCP tools into.
988
+ * @param secrets Secret provider for resolving `env:` / `forge:` refs in the
989
+ * stdio subprocess env and sse/http headers.
990
+ * @param sessionId Used for the logger instance slice.
991
+ * @param connect Optional connector override (tests inject a fake client so
992
+ * no live subprocess is spawned). Defaults to the real SDK
993
+ * client + transport.
994
+ */
995
+ constructor(registry, secrets, sessionId, connect) {
996
+ this.registry = registry;
997
+ this.connect = connect ?? defaultMcpClientConnector(secrets);
998
+ this.log = createLogger({
999
+ kind: "mcp",
1000
+ subkind: "runner-managed",
1001
+ instance: sessionId ?? "no-session"
1002
+ });
1003
+ }
1004
+ registry;
1005
+ servers = [];
1006
+ log;
1007
+ connect;
1008
+ /** True when at least one external server connected and registered tools. */
1009
+ hasServers() {
1010
+ return this.servers.length > 0;
1011
+ }
1012
+ /**
1013
+ * Connect every declaration and register its tools. Per-server failures are
1014
+ * logged and skipped so a single bad server never blocks the session.
1015
+ */
1016
+ async start(declarations) {
1017
+ for (const decl of declarations) {
1018
+ try {
1019
+ await this.startServer(decl);
1020
+ } catch (err) {
1021
+ this.log.error("MCP server spawn failed; skipping", err, {
1022
+ id: decl.id,
1023
+ transport: decl.transport ?? "stdio"
1024
+ });
1025
+ }
1026
+ }
1027
+ }
1028
+ async startServer(decl) {
1029
+ const client = await this.connect(decl);
1030
+ const toolNames = [];
1031
+ try {
1032
+ const { tools } = await client.listTools();
1033
+ for (const tool of tools) {
1034
+ const cap = this.buildToolCapability(decl.id, tool, client);
1035
+ this.registry.register(cap, "agent");
1036
+ toolNames.push(cap.name);
1037
+ }
1038
+ } catch (err) {
1039
+ for (const name of toolNames) this.registry.deregister(name);
1040
+ try {
1041
+ await client.close();
1042
+ } catch {
1043
+ }
1044
+ throw err;
1045
+ }
1046
+ this.servers.push({ id: decl.id, client, toolNames });
1047
+ this.log.info("MCP server registered (runner-managed)", {
1048
+ id: decl.id,
1049
+ transport: decl.transport ?? "stdio",
1050
+ toolCount: toolNames.length,
1051
+ tools: toolNames
1052
+ });
1053
+ }
1054
+ /**
1055
+ * Wrap a single MCP tool as a {@link DefinedCapability}. The capability name
1056
+ * is `mcp__<server>__<tool>` (the naming claude-sdk wildcards and prompts
1057
+ * depend on). The handler proxies the call to the live MCP client; a tool
1058
+ * error (`isError`) is rethrown so the capability dispatch path surfaces it
1059
+ * to the LLM as a tool failure.
1060
+ */
1061
+ buildToolCapability(serverId, tool, client) {
1062
+ const name = `mcp__${serverId}__${tool.name}`;
1063
+ const inputSchema = tool.inputSchema && typeof tool.inputSchema === "object" ? tool.inputSchema : { type: "object" };
1064
+ return {
1065
+ name,
1066
+ description: tool.description ?? `MCP tool ${tool.name} from server ${serverId}`,
1067
+ inputSchema,
1068
+ side: "agent",
1069
+ origin: { kind: "mcp", serverId },
1070
+ scope: "session",
1071
+ kind: "effect",
1072
+ // LLM-only — these are agent tools, not user command-palette actions.
1073
+ audience: ["llm"],
1074
+ inputZod: PASSTHROUGH_INPUT_ZOD,
1075
+ handler: async (input, ctx) => {
1076
+ const result = await client.callTool({
1077
+ name: tool.name,
1078
+ arguments: input ?? {}
1079
+ });
1080
+ const content = result.content ?? [];
1081
+ const text = content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("\n");
1082
+ if (result.isError) {
1083
+ throw new Error(text || `MCP tool ${name} returned an error`);
1084
+ }
1085
+ ctx.log.debug("mcp tool ok", { tool: name, hasText: text.length > 0 });
1086
+ return text.length > 0 ? text : content;
1087
+ }
1088
+ };
1089
+ }
1090
+ /**
1091
+ * Deregister every tool and close every client (kills the subprocesses).
1092
+ * Best-effort: a failed close on one server does not prevent the rest.
1093
+ */
1094
+ async dispose() {
1095
+ for (const server of this.servers) {
1096
+ for (const toolName of server.toolNames) {
1097
+ this.registry.deregister(toolName);
1098
+ }
1099
+ try {
1100
+ await server.client.close();
1101
+ } catch (err) {
1102
+ this.log.warn("MCP server close failed", {
1103
+ id: server.id,
1104
+ error: err instanceof Error ? err.message : String(err)
1105
+ });
1106
+ }
1107
+ }
1108
+ this.servers.length = 0;
1109
+ }
1110
+ };
1111
+
853
1112
  // runner/src/resources.ts
854
- async function buildAgentResources(projectDir, driverType, _onLog, watch, secretProvider, sessionMeta, tokenMediator, preMintedSecrets, deferFilesystemMounts) {
1113
+ async function buildAgentResources(projectDir, driverType, _onLog, watch, secretProvider, sessionMeta, tokenMediator, preMintedSecrets, deferFilesystemMounts, capabilityRegistry) {
855
1114
  const resLog = createLogger({
856
1115
  kind: "runner",
857
1116
  subkind: "resources",
@@ -920,13 +1179,9 @@ async function buildAgentResources(projectDir, driverType, _onLog, watch, secret
920
1179
  }
921
1180
  }
922
1181
  const resourcePromptSection = resourceManager ? buildConnectorPromptSection(resourceManager) : "";
923
- const mcpLog = createLogger({
924
- kind: "mcp",
925
- subkind: "wiring",
926
- instance: sessionMeta?.sessionId ?? "no-session"
927
- });
928
1182
  let mcpServers;
929
1183
  let workspacePluginToShutdown = null;
1184
+ let externalMcpManager = null;
930
1185
  if (driverType === "claude-sdk") {
931
1186
  if (resourceManager) {
932
1187
  const connectorsLog = createLogger({ kind: "mcp", subkind: "skaile-connectors" });
@@ -957,96 +1212,34 @@ async function buildAgentResources(projectDir, driverType, _onLog, watch, secret
957
1212
  mcpServers = { ...mcpServers ?? {}, "skaile-workspace": wsServer };
958
1213
  }
959
1214
  workspacePluginToShutdown = workspacePlugin;
960
- const mcpDeclarations = loadMcpServerDeclarations(projectDir);
961
- mcpLog.debug("loaded MCP declarations", { count: mcpDeclarations.length });
962
- const recipeCache = createRecipeCache();
963
- const nixFlakeRef = process.env.SKAILE_NIX_FLAKE_REF ?? "/etc/skaile/flake";
964
- for (const decl of mcpDeclarations) {
965
- if (RESERVED_MCP_SERVER_IDS.has(decl.id)) {
966
- mcpLog.warn("MCP server skipped: reserved id", { id: decl.id });
967
- continue;
968
- }
969
- let resolvedDecl = decl;
970
- if (decl.recipe) {
971
- const recipeLogKey = decl.recipe.flake ? { flake: decl.recipe.flake } : { attr: decl.recipe.attr };
972
- try {
973
- const recipeKey = decl.recipe.flake ? `mcps.${decl.id}` : decl.recipe.attr ?? `mcps.${decl.id}`;
974
- const outPath = resolveRecipePath(nixFlakeRef, recipeKey, recipeCache);
975
- const outPaths = /* @__PURE__ */ new Map([[decl.id, outPath]]);
976
- const resolvedCommand = decl.command ? substituteRecipeTemplating(decl.command, outPaths) : decl.command;
977
- const resolvedArgs = decl.args?.map((a) => substituteRecipeTemplating(a, outPaths));
978
- const resolvedEnv = substituteRecipeMap(decl.env, outPaths);
979
- resolvedDecl = {
980
- ...decl,
981
- command: resolvedCommand,
982
- args: resolvedArgs,
983
- env: resolvedEnv
984
- };
985
- const allResolved = [
986
- resolvedCommand ?? "",
987
- ...resolvedArgs ?? [],
988
- ...Object.values(resolvedEnv ?? {})
989
- ];
990
- for (const v of allResolved) {
991
- if (v.includes("${recipe:")) {
992
- const recipeLog2 = createLogger({
993
- kind: "mcp",
994
- subkind: decl.id,
995
- instance: "recipe-resolve"
996
- });
997
- recipeLog2.warn("unresolved recipe marker after substitution", {
998
- value: v,
999
- declId: decl.id
1000
- });
1001
- }
1002
- }
1003
- const missingPaths = validateSubstitutedPaths(resolvedCommand, resolvedArgs);
1004
- if (missingPaths.length > 0) {
1005
- const recipeLog2 = createLogger({
1006
- kind: "mcp",
1007
- subkind: decl.id,
1008
- instance: "recipe-resolve"
1009
- });
1010
- recipeLog2.error("MCP server skipped: substituted path does not exist", void 0, {
1011
- id: decl.id,
1012
- ...recipeLogKey,
1013
- missing: missingPaths
1014
- });
1015
- continue;
1016
- }
1017
- const recipeLog = createLogger({
1018
- kind: "mcp",
1019
- subkind: decl.id,
1020
- instance: "recipe-resolve"
1021
- });
1022
- recipeLog.info("recipe substitution complete", {
1023
- ...recipeLogKey,
1024
- outPath
1025
- });
1026
- } catch (err) {
1027
- const recipeLog = createLogger({
1028
- kind: "mcp",
1029
- subkind: decl.id,
1030
- instance: "recipe-resolve"
1031
- });
1032
- recipeLog.error("MCP server skipped: recipe resolution failed", err, {
1033
- id: decl.id,
1034
- ...recipeLogKey,
1035
- flakeRef: nixFlakeRef
1036
- });
1037
- continue;
1038
- }
1039
- }
1215
+ }
1216
+ if (capabilityRegistry) {
1217
+ const resolvedDecls = resolveExternalMcpDeclarations(projectDir, sessionMeta?.sessionId);
1218
+ if (resolvedDecls.length > 0) {
1219
+ externalMcpManager = new ExternalMcpManager(
1220
+ capabilityRegistry,
1221
+ secretProvider,
1222
+ sessionMeta?.sessionId
1223
+ );
1224
+ await externalMcpManager.start(resolvedDecls);
1225
+ }
1226
+ } else if (driverType === "claude-sdk") {
1227
+ const mcpLog = createLogger({
1228
+ kind: "mcp",
1229
+ subkind: "wiring",
1230
+ instance: sessionMeta?.sessionId ?? "no-session"
1231
+ });
1232
+ const resolvedDecls = resolveExternalMcpDeclarations(projectDir, sessionMeta?.sessionId);
1233
+ for (const decl of resolvedDecls) {
1040
1234
  const serverConfig = toSdkMcpServerConfig(
1041
- resolvedDecl,
1235
+ decl,
1042
1236
  secretProvider
1043
1237
  );
1044
1238
  if (serverConfig) {
1045
1239
  mcpServers = { ...mcpServers ?? {}, [decl.id]: serverConfig };
1046
- const declLog = createLogger({ kind: "mcp", subkind: decl.id });
1047
- declLog.info("MCP server registered", {
1240
+ createLogger({ kind: "mcp", subkind: decl.id }).info("MCP server registered", {
1048
1241
  transport: decl.transport ?? "stdio",
1049
- ...decl.transport === "stdio" || !decl.transport ? { command: resolvedDecl.command, args: resolvedDecl.args } : { url: decl.url }
1242
+ ...decl.transport === "stdio" || !decl.transport ? { command: decl.command, args: decl.args } : { url: decl.url }
1050
1243
  });
1051
1244
  } else {
1052
1245
  mcpLog.error("MCP server config invalid (translation returned null)", void 0, {
@@ -1056,7 +1249,8 @@ async function buildAgentResources(projectDir, driverType, _onLog, watch, secret
1056
1249
  }
1057
1250
  }
1058
1251
  }
1059
- if (!resourceManager && driverType !== "claude-sdk") return noopResult;
1252
+ if (!resourceManager && driverType !== "claude-sdk" && !externalMcpManager?.hasServers())
1253
+ return noopResult;
1060
1254
  const startWatching = () => {
1061
1255
  if (resourceManager && watch?.onFileChanged) {
1062
1256
  const dedupTtlMs = watch.dedupTtlMs ?? 500;
@@ -1077,6 +1271,10 @@ async function buildAgentResources(projectDir, driverType, _onLog, watch, secret
1077
1271
  }
1078
1272
  };
1079
1273
  const dispose = async () => {
1274
+ try {
1275
+ await externalMcpManager?.dispose();
1276
+ } catch {
1277
+ }
1080
1278
  if (workspacePluginToShutdown) {
1081
1279
  try {
1082
1280
  await workspacePluginToShutdown.shutdown();
@@ -1100,15 +1298,6 @@ async function buildAgentResources(projectDir, driverType, _onLog, watch, secret
1100
1298
  return noopResult;
1101
1299
  }
1102
1300
  }
1103
- var RESERVED_MCP_SERVER_IDS = /* @__PURE__ */ new Set(["skaile-connectors", "skaile-workspace"]);
1104
- function resolveRecordSecrets(record, secrets) {
1105
- if (!secrets) return record;
1106
- const resolved = {};
1107
- for (const [k, v] of Object.entries(record)) {
1108
- resolved[k] = secrets.resolve(v) ?? v;
1109
- }
1110
- return resolved;
1111
- }
1112
1301
  function toSdkMcpServerConfig(decl, secrets) {
1113
1302
  const transport = decl.transport ?? "stdio";
1114
1303
  if (transport === "stdio") {
@@ -1187,7 +1376,8 @@ async function createAgentSession(config) {
1187
1376
  },
1188
1377
  config.tokenMediator,
1189
1378
  config.preMintedSecrets,
1190
- config.deferFilesystemMounts
1379
+ config.deferFilesystemMounts,
1380
+ config.capabilityRegistry
1191
1381
  );
1192
1382
  const wsConfig = wsConfigOverride ?? resolveSkWorkspaceConfig(projectDir);
1193
1383
  const contextSection = buildContextSection(wsConfig.agent?.context);
@@ -3046,6 +3236,11 @@ async function startAgentServer(opts) {
3046
3236
  deferFilesystemMounts: true,
3047
3237
  onLog: (line) => log(`[serve] ${line}`),
3048
3238
  capabilities: buildCapabilityHooks(),
3239
+ // Hand the registry to the session builder so external (declarative /
3240
+ // recipe-backed) MCP servers are spawned by the RUNNER and their tools
3241
+ // registered as `mcp`-origin capabilities — works on every driver, no
3242
+ // dependence on the claude-agent-sdk `mcpServers` query option.
3243
+ capabilityRegistry,
3049
3244
  // v3: pre-minted credentials replace the tokenMediator for initial mints.
3050
3245
  // Refresh routes through `host.refresh_credential` (invoked from the
3051
3246
  // bridge driver's onAuthError callback). The legacy `tokenMediator`
@@ -3065,6 +3260,18 @@ async function startAgentServer(opts) {
3065
3260
  let secretProvider;
3066
3261
  let runtimeIdentity;
3067
3262
  let preMintedSecrets;
3263
+ try {
3264
+ const { skillsDir } = resolveDriverPaths({
3265
+ driver: opts.driver ?? wsConfig.agent_config?.default?.driver ?? "claude-sdk"
3266
+ });
3267
+ const stagedSkills = stageMaterializedSkills(opts.projectDir, skillsDir);
3268
+ if (stagedSkills.length > 0) {
3269
+ log(
3270
+ `[serve] staged ${stagedSkills.length} materialized skill(s): ${stagedSkills.join(", ")}`
3271
+ );
3272
+ }
3273
+ } catch {
3274
+ }
3068
3275
  if (secretsMode === "provisioned") {
3069
3276
  log("[serve] secrets mode: provisioned \u2014 waiting for provision_secrets command");
3070
3277
  } else {
@@ -4303,5 +4510,5 @@ function touchSession(state) {
4303
4510
  }
4304
4511
 
4305
4512
  export { CLAUDE_CODE_CREDENTIALS_KEY, COMPILE_MANIFEST_FILENAME, CapabilityRegistry, DEFAULT_CAPABILITY_CALL_TIMEOUT_MS, DEFAULT_COALESCE_MS, MarkdownStreamer, PreInitRingSink, agentDefinitionExists, bootstrapCapabilityRegistry, bootstrapRunnerLogStore, buildAgentResources, buildClientCapabilityHandler, buildContextSection, buildEnvironmentSection, builtinCapabilities, clearPreInitRingSink, clearSession, compileComposition, computeCapabilitySignature, createAgentSession, createSessionStimulusBus, defineCapability, deleteSession, emitSystemPromptComposed, ensureGitConfigInclude, extractClaudeAiOauthExpiresAt, getPreInitRingSink, handleMountResourceRequest, handleResourceRequest, installPreInitRingSink, listSessions, loadAgentManifest, loadCompileManifest, loadCompileManifestFromDir, loadSession, loadSessionById, newSession, registerCompositionCapabilities, rejectCapabilityOnApprovalDeny, resetRunnerLogStore, resolveAgentComposition, resolveAgentMixins, resolveBinding, resolveCapabilityCallTimeoutMs, resolveCapabilityResult, resolveComposition, resolveMixin, runAgentChat, saveSession, setCurrentSession, startAgentServer, touchSession, writeClaudeCodeCredentialsFile };
4306
- //# sourceMappingURL=chunk-JQBHCJ6N.js.map
4307
- //# sourceMappingURL=chunk-JQBHCJ6N.js.map
4513
+ //# sourceMappingURL=chunk-BJWUSHC4.js.map
4514
+ //# sourceMappingURL=chunk-BJWUSHC4.js.map