@sweny-ai/core 0.1.126 → 0.1.127

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/claude.d.ts CHANGED
@@ -78,7 +78,9 @@ export interface ClaudeClientOptions {
78
78
  logger?: Logger;
79
79
  /** Default tool context for standalone usage (not via executor) */
80
80
  defaultContext?: ToolContext;
81
- /** External MCP servers (GitHub, Linear, Sentry, etc.) — merged with core skill tools */
81
+ /** Catalog defaults, overridden by per-run skill servers and explicit mcpServers. */
82
+ defaultMcpServers?: Record<string, McpServerConfig>;
83
+ /** Explicit external MCP servers, overriding defaults and per-run skill servers. */
82
84
  mcpServers?: Record<string, McpServerConfig>;
83
85
  /**
84
86
  * Extra env var names passed to the agent subprocess on top of the
@@ -123,6 +125,7 @@ export declare class ClaudeClient implements Claude {
123
125
  private logger;
124
126
  private defaultContext;
125
127
  private mcpServers;
128
+ private defaultMcpServers;
126
129
  private envPassthrough;
127
130
  private sandboxMode;
128
131
  private sandboxAllowedDomains;
@@ -153,6 +156,8 @@ export declare class ClaudeClient implements Claude {
153
156
  timeoutMs?: number;
154
157
  /** Caller-supplied abort signal. Aborting it interrupts the query. */
155
158
  signal?: AbortSignal;
159
+ /** MCP servers declared by the current node's skills. */
160
+ mcpServers?: Record<string, McpServerConfig>;
156
161
  /** Dry run (#380): no external MCP servers, no write-capable built-ins. */
157
162
  readOnly?: boolean;
158
163
  /** Env var names + sandbox hosts this node's skills need (#360). */
package/dist/claude.js CHANGED
@@ -211,6 +211,7 @@ export class ClaudeClient {
211
211
  logger;
212
212
  defaultContext;
213
213
  mcpServers;
214
+ defaultMcpServers;
214
215
  envPassthrough;
215
216
  sandboxMode;
216
217
  sandboxAllowedDomains;
@@ -224,6 +225,7 @@ export class ClaudeClient {
224
225
  this.logger = opts.logger ?? consoleLogger;
225
226
  this.defaultContext = opts.defaultContext ?? { config: {}, logger: this.logger };
226
227
  this.mcpServers = opts.mcpServers ?? {};
228
+ this.defaultMcpServers = opts.defaultMcpServers ?? {};
227
229
  this.envPassthrough = opts.envPassthrough;
228
230
  this.envScope = opts.envScope;
229
231
  this.sandboxMode = opts.sandbox;
@@ -339,7 +341,9 @@ export class ClaudeClient {
339
341
  const abort = makeAbort(timeoutMs, signal);
340
342
  let stream;
341
343
  try {
342
- const allMcpServers = readOnly ? {} : { ...this.mcpServers };
344
+ const allMcpServers = readOnly
345
+ ? {}
346
+ : { ...this.defaultMcpServers, ...opts.mcpServers, ...this.mcpServers };
343
347
  if (sdkTools.length > 0)
344
348
  allMcpServers["sweny-core"] = mcpServer;
345
349
  stream = query({
package/dist/cli/main.js CHANGED
@@ -204,7 +204,8 @@ triageCmd.action(async (options) => {
204
204
  maxTurns: config.maxInvestigateTurns || 50,
205
205
  cwd: process.cwd(),
206
206
  logger: consoleLogger,
207
- mcpServers,
207
+ defaultMcpServers: mcpServers,
208
+ mcpServers: config.mcpServers,
208
209
  });
209
210
  // ── Progress display state ─────────────────────────────────
210
211
  const FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -513,7 +514,8 @@ implementCmd.action(async (issueId, options) => {
513
514
  maxTurns: config.maxImplementTurns || 40,
514
515
  cwd: process.cwd(),
515
516
  logger: consoleLogger,
516
- mcpServers,
517
+ defaultMcpServers: mcpServers,
518
+ mcpServers: config.mcpServers,
517
519
  });
518
520
  console.log(chalk.cyan(`\n sweny implement ${issueId}\n`));
519
521
  const implRunStart = Date.now();
@@ -756,7 +758,8 @@ export async function workflowRunAction(file, options) {
756
758
  maxTurns: config.maxInvestigateTurns || 50,
757
759
  cwd: process.cwd(),
758
760
  logger: runLogger,
759
- mcpServers,
761
+ defaultMcpServers: mcpServers,
762
+ mcpServers: config.mcpServers,
760
763
  model: workflow.model,
761
764
  });
762
765
  // Track per-node entry time to compute elapsed on exit
package/dist/executor.js CHANGED
@@ -148,12 +148,16 @@ export async function execute(workflow, input, options) {
148
148
  logger.info(` dry run: withheld write tools: ${skippedWrites.join(", ")}`, { node: currentId });
149
149
  }
150
150
  const skillInstructions = resolveSkillInstructions(node.skills, skills);
151
+ const skillMcpServers = resolveSkillMcpServers(node.skills, skills);
151
152
  // Runtime guard: if this node declares skills but none resolved, the node
152
153
  // cannot do its job (e.g. "create a Linear issue" with no linear skill).
153
154
  // The startup validate() warns about this possibility, but only throw when
154
155
  // the node is actually reached — unreachable nodes with missing skills are fine.
155
- // Instruction-only skills (no tools) are valid — they inject context into the prompt.
156
- if (node.skills.length > 0 && resolvedSkillTools.length === 0 && skillInstructions.length === 0) {
156
+ // Instruction-only and resolved MCP-only skills are valid capabilities.
157
+ if (node.skills.length > 0 &&
158
+ resolvedSkillTools.length === 0 &&
159
+ skillInstructions.length === 0 &&
160
+ Object.keys(skillMcpServers).length === 0) {
157
161
  throw new Error(`Node "${currentId}" requires skills [${node.skills.join(", ")}] but none are configured. ` +
158
162
  `Set the required environment variables and try again.`);
159
163
  }
@@ -241,6 +245,7 @@ export async function execute(workflow, input, options) {
241
245
  signal,
242
246
  timeoutMs,
243
247
  agentAccess: resolveAgentAccess(node.skills, skills),
248
+ ...(dryRun ? {} : { mcpServers: skillMcpServers }),
244
249
  ...(dryRun ? { readOnly: true } : {}),
245
250
  onProgress: (message) => {
246
251
  safeObserve(observer, { type: "node:progress", node: currentId, message }, logger);
@@ -721,6 +726,13 @@ function dryRunNotice(skippedWrites) {
721
726
  `Do not create, modify, post, or send anything. Do the analysis, and where this step would ` +
722
727
  `normally write, describe exactly what it would have written instead.`);
723
728
  }
729
+ /** Only this node's resolved skills contribute external servers. */
730
+ function resolveSkillMcpServers(skillIds, skills) {
731
+ return Object.fromEntries(skillIds.flatMap((id) => {
732
+ const mcp = skills.get(id)?.mcp;
733
+ return mcp ? [[id, { ...mcp, type: mcp.type ?? (mcp.command ? "stdio" : "http") }]] : [];
734
+ }));
735
+ }
724
736
  function resolveTools(skillIds, skills) {
725
737
  return skillIds
726
738
  .map((id) => skills.get(id))
@@ -1023,6 +1035,14 @@ async function resolveNext(workflow, current, results, input, claude, observer,
1023
1035
  * Validate a workflow definition before execution.
1024
1036
  */
1025
1037
  function validate(workflow, skills) {
1038
+ for (const [id, def] of Object.entries(workflow.skills ?? {})) {
1039
+ if (!def.instruction?.trim()) {
1040
+ throw new Error(`Inline skill "${id}" must provide a non-empty instruction`);
1041
+ }
1042
+ if (id === "sweny-core" && def.mcp) {
1043
+ throw new Error(`Skill "sweny-core" is reserved for the engine MCP server; use a different skill ID`);
1044
+ }
1045
+ }
1026
1046
  if (!workflow.nodes[workflow.entry]) {
1027
1047
  throw new Error(`Entry node "${workflow.entry}" not found`);
1028
1048
  }
@@ -1036,6 +1056,12 @@ function validate(workflow, skills) {
1036
1056
  for (const [nodeId, node] of Object.entries(workflow.nodes)) {
1037
1057
  if (node.skills.length === 0)
1038
1058
  continue;
1059
+ for (const id of node.skills) {
1060
+ const skill = skills.get(id);
1061
+ if (id === "sweny-core" && skill?.mcp) {
1062
+ throw new Error(`Skill "sweny-core" is reserved for the engine MCP server; use a different skill ID (node "${nodeId}")`);
1063
+ }
1064
+ }
1039
1065
  const available = node.skills.filter((id) => skills.has(id));
1040
1066
  if (available.length === 0) {
1041
1067
  consoleLogger.warn(`Node "${nodeId}" has no available skills (needs one of: ${node.skills.join(", ")})`);
package/dist/schema.d.ts CHANGED
@@ -3357,11 +3357,7 @@ export declare const workflowJsonSchema: {
3357
3357
  readonly description: "Inline skill definitions scoped to this workflow";
3358
3358
  readonly additionalProperties: {
3359
3359
  readonly type: "object";
3360
- readonly anyOf: readonly [{
3361
- readonly required: readonly ["instruction"];
3362
- }, {
3363
- readonly required: readonly ["mcp"];
3364
- }];
3360
+ readonly required: readonly ["instruction"];
3365
3361
  readonly additionalProperties: false;
3366
3362
  readonly properties: {
3367
3363
  readonly name: {
@@ -3372,6 +3368,7 @@ export declare const workflowJsonSchema: {
3372
3368
  };
3373
3369
  readonly instruction: {
3374
3370
  readonly type: "string";
3371
+ readonly pattern: "\\S";
3375
3372
  readonly description: "Natural language expertise injected into the node prompt";
3376
3373
  };
3377
3374
  readonly mcp: {
package/dist/schema.js CHANGED
@@ -63,8 +63,8 @@ export const skillDefinitionZ = z
63
63
  mcp: mcpServerConfigZ.optional(),
64
64
  })
65
65
  .strict()
66
- .refine((s) => s.instruction || s.mcp, {
67
- message: "Inline skill must provide instruction, mcp, or both",
66
+ .refine((s) => Boolean(s.instruction?.trim()), {
67
+ message: "Inline skill must provide a non-empty instruction",
68
68
  });
69
69
  export const skillZ = z
70
70
  .object({
@@ -489,12 +489,18 @@ export function validateWorkflow(workflow, knownSkills) {
489
489
  });
490
490
  }
491
491
  }
492
- // Inline skill definitions must have instruction or mcp
492
+ // Inline skills need usage instructions; the engine server name is reserved.
493
493
  for (const [skillId, def] of Object.entries(workflow.skills ?? {})) {
494
- if (!def.instruction && !def.mcp) {
494
+ if (!def.instruction?.trim()) {
495
495
  errors.push({
496
496
  code: "INVALID_INLINE_SKILL",
497
- message: `Inline skill "${skillId}" must provide at least instruction or mcp`,
497
+ message: `Inline skill "${skillId}" must provide a non-empty instruction`,
498
+ });
499
+ }
500
+ if (skillId === "sweny-core" && def.mcp) {
501
+ errors.push({
502
+ code: "INVALID_INLINE_SKILL",
503
+ message: `Skill "sweny-core" is reserved for the engine MCP server; use a different skill ID`,
498
504
  });
499
505
  }
500
506
  }
@@ -945,14 +951,18 @@ export const workflowJsonSchema = {
945
951
  description: "Inline skill definitions scoped to this workflow",
946
952
  additionalProperties: {
947
953
  type: "object",
948
- // Fix #4: an inline skill must provide instruction, mcp, or both.
949
- anyOf: [{ required: ["instruction"] }, { required: ["mcp"] }],
954
+ // Inline skills need usable instructions, including when they declare MCP.
955
+ required: ["instruction"],
950
956
  // Round 2: reject unknown keys to match Zod skillDefinitionZ.strict().
951
957
  additionalProperties: false,
952
958
  properties: {
953
959
  name: { type: "string" },
954
960
  description: { type: "string" },
955
- instruction: { type: "string", description: "Natural language expertise injected into the node prompt" },
961
+ instruction: {
962
+ type: "string",
963
+ pattern: "\\S",
964
+ description: "Natural language expertise injected into the node prompt",
965
+ },
956
966
  mcp: {
957
967
  type: "object",
958
968
  description: "External MCP server definition",
package/dist/types.d.ts CHANGED
@@ -604,6 +604,8 @@ export interface Claude {
604
604
  outputSchema?: JSONSchema;
605
605
  /** Called with status messages while Claude is working (tool name, etc.) */
606
606
  onProgress?: (message: string) => void;
607
+ /** MCP servers declared by this node's resolved skills. Explicit client configs win. */
608
+ mcpServers?: Record<string, McpServerConfig>;
607
609
  /** Per-node turn limit. Overrides the client default when set. */
608
610
  maxTurns?: number;
609
611
  /** Built-in SDK tool names to disallow for this node (e.g. ["Bash"]). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.126",
3
+ "version": "0.1.127",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"