@sweny-ai/core 0.1.77 → 0.1.79
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/browser.d.ts +11 -5
- package/dist/browser.js +11 -11
- package/dist/claude.d.ts +16 -0
- package/dist/claude.js +98 -9
- package/dist/cli/config.d.ts +22 -0
- package/dist/cli/config.js +56 -9
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.js +56 -57
- package/dist/cli/marketplace.js +35 -9
- package/dist/cli/publish.d.ts +10 -0
- package/dist/cli/publish.js +22 -14
- package/dist/loader.d.ts +40 -0
- package/dist/loader.js +73 -0
- package/dist/mcp-catalog.d.ts +47 -0
- package/dist/mcp-catalog.js +304 -0
- package/dist/mcp.d.ts +6 -9
- package/dist/mcp.js +24 -314
- package/dist/schema.d.ts +183 -151
- package/dist/schema.js +49 -7
- package/dist/skills/custom-loader.d.ts +40 -0
- package/dist/skills/custom-loader.js +107 -32
- package/dist/skills/index.d.ts +13 -1
- package/dist/skills/index.js +12 -3
- package/dist/studio.js +4 -1
- package/dist/templates.d.ts +29 -8
- package/dist/templates.js +30 -20
- package/dist/types.d.ts +12 -0
- package/dist/verify.js +96 -7
- package/package.json +8 -4
- package/dist/__tests__/cascade.test.d.ts +0 -11
- package/dist/__tests__/cascade.test.js +0 -452
- package/dist/__tests__/claude.test.d.ts +0 -1
- package/dist/__tests__/claude.test.js +0 -427
- package/dist/__tests__/custom-loader.test.d.ts +0 -1
- package/dist/__tests__/custom-loader.test.js +0 -116
- package/dist/__tests__/executor.test.d.ts +0 -1
- package/dist/__tests__/executor.test.js +0 -1635
- package/dist/__tests__/integration/datadog.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/datadog.integration.test.js +0 -23
- package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/e2e-workflow.integration.test.js +0 -75
- package/dist/__tests__/integration/github.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/github.integration.test.js +0 -37
- package/dist/__tests__/integration/harness.d.ts +0 -24
- package/dist/__tests__/integration/harness.js +0 -34
- package/dist/__tests__/integration/linear.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/linear.integration.test.js +0 -15
- package/dist/__tests__/integration/sentry.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/sentry.integration.test.js +0 -20
- package/dist/__tests__/integration/slack.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/slack.integration.test.js +0 -22
- package/dist/__tests__/integration/sources.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/sources.integration.test.js +0 -123
- package/dist/__tests__/publish.test.d.ts +0 -1
- package/dist/__tests__/publish.test.js +0 -180
- package/dist/__tests__/requires.test.d.ts +0 -1
- package/dist/__tests__/requires.test.js +0 -82
- package/dist/__tests__/retry.test.d.ts +0 -1
- package/dist/__tests__/retry.test.js +0 -213
- package/dist/__tests__/schema.test.d.ts +0 -1
- package/dist/__tests__/schema.test.js +0 -600
- package/dist/__tests__/skill-config-integration.test.d.ts +0 -1
- package/dist/__tests__/skill-config-integration.test.js +0 -204
- package/dist/__tests__/skills-index.test.d.ts +0 -1
- package/dist/__tests__/skills-index.test.js +0 -119
- package/dist/__tests__/skills.test.d.ts +0 -1
- package/dist/__tests__/skills.test.js +0 -565
- package/dist/__tests__/spec-conformance.test.d.ts +0 -18
- package/dist/__tests__/spec-conformance.test.js +0 -1084
- package/dist/__tests__/studio.test.d.ts +0 -1
- package/dist/__tests__/studio.test.js +0 -403
- package/dist/__tests__/templates.test.d.ts +0 -1
- package/dist/__tests__/templates.test.js +0 -49
- package/dist/__tests__/testing.test.d.ts +0 -1
- package/dist/__tests__/testing.test.js +0 -242
- package/dist/__tests__/verify.test.d.ts +0 -1
- package/dist/__tests__/verify.test.js +0 -517
- package/dist/__tests__/version-consistency.test.d.ts +0 -1
- package/dist/__tests__/version-consistency.test.js +0 -312
- package/dist/__tests__/workflow-builder.test.d.ts +0 -1
- package/dist/__tests__/workflow-builder.test.js +0 -110
- package/dist/__tests__/workflow-yaml.test.d.ts +0 -1
- package/dist/__tests__/workflow-yaml.test.js +0 -201
- package/dist/cli/__tests__/diagram.test.d.ts +0 -1
- package/dist/cli/__tests__/diagram.test.js +0 -225
- package/dist/cli/__tests__/report-cloud.test.d.ts +0 -1
- package/dist/cli/__tests__/report-cloud.test.js +0 -165
- package/dist/cli/__tests__/upgrade.test.d.ts +0 -1
- package/dist/cli/__tests__/upgrade.test.js +0 -317
- package/dist/cli/__tests__/version-check.test.d.ts +0 -1
- package/dist/cli/__tests__/version-check.test.js +0 -209
- package/dist/cli/config.test.d.ts +0 -1
- package/dist/cli/config.test.js +0 -200
- package/dist/cli/credentials.test.d.ts +0 -1
- package/dist/cli/credentials.test.js +0 -172
- package/dist/cli/e2e.test.d.ts +0 -1
- package/dist/cli/e2e.test.js +0 -690
- package/dist/cli/marketplace.test.d.ts +0 -1
- package/dist/cli/marketplace.test.js +0 -355
- package/dist/cli/new.test.d.ts +0 -1
- package/dist/cli/new.test.js +0 -1043
- package/dist/cli/output.test.d.ts +0 -1
- package/dist/cli/output.test.js +0 -180
- package/dist/cli/publish.test.d.ts +0 -1
- package/dist/cli/publish.test.js +0 -610
- package/dist/cli/renderer.test.d.ts +0 -1
- package/dist/cli/renderer.test.js +0 -302
- package/dist/executor.test.d.ts +0 -1
- package/dist/executor.test.js +0 -618
- package/dist/mcp.test.d.ts +0 -1
- package/dist/mcp.test.js +0 -735
- package/dist/mermaid.test.d.ts +0 -1
- package/dist/mermaid.test.js +0 -256
- package/dist/sources.test.d.ts +0 -1
- package/dist/sources.test.js +0 -265
- package/dist/sources.validation.test.d.ts +0 -12
- package/dist/sources.validation.test.js +0 -380
- package/dist/workflow-builder.test.d.ts +0 -1
- package/dist/workflow-builder.test.js +0 -123
package/dist/cli/publish.js
CHANGED
|
@@ -14,7 +14,7 @@ import chalk from "chalk";
|
|
|
14
14
|
import { parse as parseYaml } from "yaml";
|
|
15
15
|
import { parseWorkflow, validateWorkflow } from "../schema.js";
|
|
16
16
|
import { builtinSkills } from "../skills/index.js";
|
|
17
|
-
import {
|
|
17
|
+
import { discoverSkillsWithDiagnostics } from "../skills/custom-loader.js";
|
|
18
18
|
// ── Types ──────────────────────────────────────────────────────────────
|
|
19
19
|
const VALID_CATEGORIES = ["triage", "security", "devops", "code-review", "testing", "content", "ops"];
|
|
20
20
|
const VALID_SKILL_ID = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
@@ -35,8 +35,12 @@ export function validateWorkflowFile(filePath) {
|
|
|
35
35
|
}
|
|
36
36
|
try {
|
|
37
37
|
const workflow = parseWorkflow(parsed);
|
|
38
|
-
// Build known skill IDs from builtins + custom + inline workflow skills
|
|
39
|
-
|
|
38
|
+
// Build known skill IDs from builtins + custom + inline workflow skills.
|
|
39
|
+
// Surface skill-loader diagnostics (malformed frontmatter, invalid ids,
|
|
40
|
+
// duplicate overrides) so publishers don't ship broken dependencies.
|
|
41
|
+
const { skills: customSkills, warnings: skillWarnings } = discoverSkillsWithDiagnostics();
|
|
42
|
+
for (const w of skillWarnings)
|
|
43
|
+
warnings.push(w.message);
|
|
40
44
|
const allSkillIds = new Set([...builtinSkills.map((s) => s.id), ...customSkills.map((s) => s.id)]);
|
|
41
45
|
// Inline skills defined in the workflow's skills block (a Record<id, def>) are also valid
|
|
42
46
|
if (workflow.skills) {
|
|
@@ -343,12 +347,7 @@ async function openGitHubPR(type, id, sourcePath, meta) {
|
|
|
343
347
|
}
|
|
344
348
|
else {
|
|
345
349
|
const destDir = path.join(tmpDir, "skills", "community", id);
|
|
346
|
-
|
|
347
|
-
// Copy entire skill directory
|
|
348
|
-
const files = fs.readdirSync(sourcePath);
|
|
349
|
-
for (const file of files) {
|
|
350
|
-
fs.copyFileSync(path.join(sourcePath, file), path.join(destDir, file));
|
|
351
|
-
}
|
|
350
|
+
copySkillDir(sourcePath, destDir);
|
|
352
351
|
execFileSync("git", ["-C", tmpDir, "add", `skills/community/${id}`], { stdio: "pipe" });
|
|
353
352
|
}
|
|
354
353
|
s.message("Pushing...");
|
|
@@ -396,16 +395,25 @@ async function saveLocally(type, id, sourcePath) {
|
|
|
396
395
|
}
|
|
397
396
|
else {
|
|
398
397
|
const destDir = path.join(outputDir, id);
|
|
399
|
-
|
|
400
|
-
const files = fs.readdirSync(sourcePath);
|
|
401
|
-
for (const file of files) {
|
|
402
|
-
fs.copyFileSync(path.join(sourcePath, file), path.join(destDir, file));
|
|
403
|
-
}
|
|
398
|
+
copySkillDir(sourcePath, destDir);
|
|
404
399
|
p.log.success(`Saved to ${chalk.cyan(destDir)}`);
|
|
405
400
|
p.log.info(`Submit manually: copy to skills/community/ in a fork of swenyai/marketplace`);
|
|
406
401
|
return { type, id, name: id, outputPath: destDir };
|
|
407
402
|
}
|
|
408
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Recursively copy a skill source directory to its destination (Fix #17).
|
|
406
|
+
*
|
|
407
|
+
* Previously a flat `fs.readdirSync` + `fs.copyFileSync` loop: skills with
|
|
408
|
+
* nested directories (scripts/, references/, assets/) either silently
|
|
409
|
+
* shipped incomplete or — more often — threw EISDIR on the first subdir.
|
|
410
|
+
*
|
|
411
|
+
* Exported for tests; node:fs cp's recursive mode preserves structure.
|
|
412
|
+
*/
|
|
413
|
+
export function copySkillDir(sourceDir, destDir) {
|
|
414
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
415
|
+
fs.cpSync(sourceDir, destDir, { recursive: true });
|
|
416
|
+
}
|
|
409
417
|
// ── Command registration ───────────────────────────────────────────────
|
|
410
418
|
export function registerPublishCommand(program) {
|
|
411
419
|
program
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow loader — read, parse, validate.
|
|
3
|
+
*
|
|
4
|
+
* The canonical path for CLI flows that read a workflow off disk. Composes
|
|
5
|
+
* file I/O + YAML/JSON parse + Zod schema parse + structural validation and
|
|
6
|
+
* returns a single tagged result so callers can render errors uniformly.
|
|
7
|
+
*
|
|
8
|
+
* Use this instead of calling `parseWorkflow` / `validateWorkflow` directly
|
|
9
|
+
* so the three user-facing surfaces (`sweny workflow run`, `sweny workflow
|
|
10
|
+
* validate`, `sweny publish`) agree on what "valid" means.
|
|
11
|
+
*/
|
|
12
|
+
import type { Workflow } from "./types.js";
|
|
13
|
+
import { type WorkflowError } from "./schema.js";
|
|
14
|
+
/** Structural/schema error. `code` is present only for structural errors. */
|
|
15
|
+
export interface LoaderError {
|
|
16
|
+
message: string;
|
|
17
|
+
code?: WorkflowError["code"] | "IO" | "PARSE" | "SCHEMA";
|
|
18
|
+
nodeId?: string;
|
|
19
|
+
}
|
|
20
|
+
export type LoaderResult = {
|
|
21
|
+
ok: true;
|
|
22
|
+
workflow: Workflow;
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
errors: LoaderError[];
|
|
26
|
+
};
|
|
27
|
+
export interface LoaderOptions {
|
|
28
|
+
/** Optional set of known skill IDs for UNKNOWN_SKILL validation. */
|
|
29
|
+
knownSkills?: Set<string>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Load a workflow YAML or JSON file, parse with the canonical Zod schema,
|
|
33
|
+
* and run structural validation. All three phases contribute to `errors`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function loadAndValidateWorkflow(filePath: string, options?: LoaderOptions): LoaderResult;
|
|
36
|
+
/**
|
|
37
|
+
* Validate an already-parsed workflow object (e.g. from a marketplace fetch
|
|
38
|
+
* or Studio import). Same schema + structural passes as the file loader.
|
|
39
|
+
*/
|
|
40
|
+
export declare function validateParsed(raw: unknown, options?: LoaderOptions): LoaderResult;
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow loader — read, parse, validate.
|
|
3
|
+
*
|
|
4
|
+
* The canonical path for CLI flows that read a workflow off disk. Composes
|
|
5
|
+
* file I/O + YAML/JSON parse + Zod schema parse + structural validation and
|
|
6
|
+
* returns a single tagged result so callers can render errors uniformly.
|
|
7
|
+
*
|
|
8
|
+
* Use this instead of calling `parseWorkflow` / `validateWorkflow` directly
|
|
9
|
+
* so the three user-facing surfaces (`sweny workflow run`, `sweny workflow
|
|
10
|
+
* validate`, `sweny publish`) agree on what "valid" means.
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from "node:fs";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import { parse as parseYaml } from "yaml";
|
|
15
|
+
import { ZodError } from "zod";
|
|
16
|
+
import { workflowZ, validateWorkflow } from "./schema.js";
|
|
17
|
+
/**
|
|
18
|
+
* Load a workflow YAML or JSON file, parse with the canonical Zod schema,
|
|
19
|
+
* and run structural validation. All three phases contribute to `errors`.
|
|
20
|
+
*/
|
|
21
|
+
export function loadAndValidateWorkflow(filePath, options = {}) {
|
|
22
|
+
let content;
|
|
23
|
+
try {
|
|
24
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28
|
+
return { ok: false, errors: [{ message: `Could not read "${filePath}": ${msg}`, code: "IO" }] };
|
|
29
|
+
}
|
|
30
|
+
let raw;
|
|
31
|
+
try {
|
|
32
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
33
|
+
raw = ext === ".yaml" || ext === ".yml" ? parseYaml(content) : JSON.parse(content);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
37
|
+
return { ok: false, errors: [{ message: `Could not parse "${filePath}": ${msg}`, code: "PARSE" }] };
|
|
38
|
+
}
|
|
39
|
+
return validateParsed(raw, options);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validate an already-parsed workflow object (e.g. from a marketplace fetch
|
|
43
|
+
* or Studio import). Same schema + structural passes as the file loader.
|
|
44
|
+
*/
|
|
45
|
+
export function validateParsed(raw, options = {}) {
|
|
46
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
47
|
+
const kind = raw === null ? "null" : Array.isArray(raw) ? "array" : typeof raw;
|
|
48
|
+
return { ok: false, errors: [{ message: `Expected a workflow object, got ${kind}`, code: "SCHEMA" }] };
|
|
49
|
+
}
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = workflowZ.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (err instanceof ZodError) {
|
|
56
|
+
const errors = err.issues.map((iss) => ({
|
|
57
|
+
message: `${iss.path.length > 0 ? iss.path.join(".") + ": " : ""}${iss.message}`,
|
|
58
|
+
code: "SCHEMA",
|
|
59
|
+
}));
|
|
60
|
+
return { ok: false, errors };
|
|
61
|
+
}
|
|
62
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
63
|
+
return { ok: false, errors: [{ message: msg, code: "SCHEMA" }] };
|
|
64
|
+
}
|
|
65
|
+
const structural = validateWorkflow(parsed, options.knownSkills);
|
|
66
|
+
if (structural.length > 0) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
errors: structural.map((e) => ({ message: e.message, code: e.code, nodeId: e.nodeId })),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { ok: true, workflow: parsed };
|
|
73
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Provider Catalog — single source of truth for provider/MCP wiring.
|
|
3
|
+
*
|
|
4
|
+
* Before this catalog existed, `buildSkillMcpServers` (skill-driven) and
|
|
5
|
+
* `buildAutoMcpServers` (flag-driven, legacy triage/implement) each encoded
|
|
6
|
+
* the same transport/auth/env for every provider. Drift was inevitable.
|
|
7
|
+
*
|
|
8
|
+
* Each entry declares:
|
|
9
|
+
* - which trigger paths (skill id, provider flag, workspace tool) wire it
|
|
10
|
+
* - how to build the McpServerConfig from credentials
|
|
11
|
+
* - whether the server uses stdio or http transport
|
|
12
|
+
* - if stdio: why the `npx -y` policy exception is acceptable (Fix #18)
|
|
13
|
+
*
|
|
14
|
+
* Adding a provider is a one-place edit. A module-load assertion and a
|
|
15
|
+
* test enforce that every stdio entry declares its exception reason.
|
|
16
|
+
*/
|
|
17
|
+
import type { McpServerConfig } from "./types.js";
|
|
18
|
+
export interface McpCatalogEntry {
|
|
19
|
+
/** Canonical server id. Used as the key in the returned `Record<string, McpServerConfig>`. */
|
|
20
|
+
id: string;
|
|
21
|
+
/** Which trigger paths wire this server. Most entries match both skill-driven and flag-driven paths. */
|
|
22
|
+
triggers: {
|
|
23
|
+
/** Skill IDs that should wire this server (skill-driven path). */
|
|
24
|
+
skill?: string[];
|
|
25
|
+
/** Matching values for `sourceControlProvider` (flag-driven path). */
|
|
26
|
+
sourceControl?: string[];
|
|
27
|
+
/** Matching values for `issueTrackerProvider` (flag-driven path). */
|
|
28
|
+
issueTracker?: string[];
|
|
29
|
+
/** Matching entries in `observabilityProviders` (flag-driven path). */
|
|
30
|
+
observability?: string[];
|
|
31
|
+
/** Matching entries in `workspaceTools` (flag-driven path). */
|
|
32
|
+
workspaceTool?: string[];
|
|
33
|
+
};
|
|
34
|
+
/** Build the MCP server config from creds. Returns undefined if required creds are missing. */
|
|
35
|
+
wire: (creds: Record<string, string>) => McpServerConfig | undefined;
|
|
36
|
+
/** Transport family. Informational + policy enforcement (Fix #18). */
|
|
37
|
+
transport: "http" | "stdio";
|
|
38
|
+
/**
|
|
39
|
+
* For stdio servers, why `npx -y` is allowed. Null forbids the exception.
|
|
40
|
+
* Http servers must set this to null (no exception needed).
|
|
41
|
+
*
|
|
42
|
+
* ARCHITECTURE.md documents the acceptable reasons (official first-party
|
|
43
|
+
* vendor server with no public HTTP endpoint, etc.).
|
|
44
|
+
*/
|
|
45
|
+
npxExceptionReason: string | null;
|
|
46
|
+
}
|
|
47
|
+
export declare const MCP_CATALOG: readonly McpCatalogEntry[];
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Provider Catalog — single source of truth for provider/MCP wiring.
|
|
3
|
+
*
|
|
4
|
+
* Before this catalog existed, `buildSkillMcpServers` (skill-driven) and
|
|
5
|
+
* `buildAutoMcpServers` (flag-driven, legacy triage/implement) each encoded
|
|
6
|
+
* the same transport/auth/env for every provider. Drift was inevitable.
|
|
7
|
+
*
|
|
8
|
+
* Each entry declares:
|
|
9
|
+
* - which trigger paths (skill id, provider flag, workspace tool) wire it
|
|
10
|
+
* - how to build the McpServerConfig from credentials
|
|
11
|
+
* - whether the server uses stdio or http transport
|
|
12
|
+
* - if stdio: why the `npx -y` policy exception is acceptable (Fix #18)
|
|
13
|
+
*
|
|
14
|
+
* Adding a provider is a one-place edit. A module-load assertion and a
|
|
15
|
+
* test enforce that every stdio entry declares its exception reason.
|
|
16
|
+
*/
|
|
17
|
+
// ── Catalog entries ─────────────────────────────────────────────────
|
|
18
|
+
const github = {
|
|
19
|
+
id: "github",
|
|
20
|
+
triggers: {
|
|
21
|
+
skill: ["github"],
|
|
22
|
+
sourceControl: ["github"],
|
|
23
|
+
issueTracker: ["github-issues"],
|
|
24
|
+
},
|
|
25
|
+
transport: "stdio",
|
|
26
|
+
npxExceptionReason: "Official @modelcontextprotocol/server-github; no public HTTP MCP endpoint.",
|
|
27
|
+
wire: (creds) => {
|
|
28
|
+
if (!creds.GITHUB_TOKEN)
|
|
29
|
+
return undefined;
|
|
30
|
+
return {
|
|
31
|
+
type: "stdio",
|
|
32
|
+
command: "npx",
|
|
33
|
+
args: ["-y", "@modelcontextprotocol/server-github@latest"],
|
|
34
|
+
env: { GITHUB_PERSONAL_ACCESS_TOKEN: creds.GITHUB_TOKEN },
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const gitlab = {
|
|
39
|
+
id: "gitlab",
|
|
40
|
+
triggers: {
|
|
41
|
+
skill: ["gitlab"],
|
|
42
|
+
sourceControl: ["gitlab"],
|
|
43
|
+
},
|
|
44
|
+
transport: "stdio",
|
|
45
|
+
npxExceptionReason: "Official @modelcontextprotocol/server-gitlab; no public HTTP MCP endpoint.",
|
|
46
|
+
wire: (creds) => {
|
|
47
|
+
if (!creds.GITLAB_TOKEN)
|
|
48
|
+
return undefined;
|
|
49
|
+
const env = { GITLAB_PERSONAL_ACCESS_TOKEN: creds.GITLAB_TOKEN };
|
|
50
|
+
const baseUrl = creds.GITLAB_URL || "https://gitlab.com";
|
|
51
|
+
if (baseUrl !== "https://gitlab.com")
|
|
52
|
+
env.GITLAB_API_URL = `${baseUrl}/api/v4`;
|
|
53
|
+
return {
|
|
54
|
+
type: "stdio",
|
|
55
|
+
command: "npx",
|
|
56
|
+
args: ["-y", "@modelcontextprotocol/server-gitlab@latest"],
|
|
57
|
+
env,
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const linear = {
|
|
62
|
+
id: "linear",
|
|
63
|
+
triggers: {
|
|
64
|
+
skill: ["linear"],
|
|
65
|
+
issueTracker: ["linear"],
|
|
66
|
+
},
|
|
67
|
+
transport: "http",
|
|
68
|
+
npxExceptionReason: null,
|
|
69
|
+
wire: (creds) => {
|
|
70
|
+
if (!creds.LINEAR_API_KEY)
|
|
71
|
+
return undefined;
|
|
72
|
+
return {
|
|
73
|
+
type: "http",
|
|
74
|
+
url: "https://mcp.linear.app/mcp",
|
|
75
|
+
headers: { Authorization: `Bearer ${creds.LINEAR_API_KEY}` },
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
const jira = {
|
|
80
|
+
id: "jira",
|
|
81
|
+
triggers: {
|
|
82
|
+
skill: ["jira"],
|
|
83
|
+
issueTracker: ["jira"],
|
|
84
|
+
},
|
|
85
|
+
transport: "stdio",
|
|
86
|
+
npxExceptionReason: "Atlassian has no first-party MCP server; @sooperset/mcp-atlassian is the de-facto community standard. Revisit if Atlassian ships one.",
|
|
87
|
+
wire: (creds) => {
|
|
88
|
+
if (!creds.JIRA_URL || !creds.JIRA_EMAIL || !creds.JIRA_API_TOKEN)
|
|
89
|
+
return undefined;
|
|
90
|
+
return {
|
|
91
|
+
type: "stdio",
|
|
92
|
+
command: "npx",
|
|
93
|
+
args: ["-y", "@sooperset/mcp-atlassian@latest"],
|
|
94
|
+
env: {
|
|
95
|
+
JIRA_URL: creds.JIRA_URL,
|
|
96
|
+
JIRA_EMAIL: creds.JIRA_EMAIL,
|
|
97
|
+
JIRA_API_TOKEN: creds.JIRA_API_TOKEN,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
const datadog = {
|
|
103
|
+
id: "datadog",
|
|
104
|
+
triggers: {
|
|
105
|
+
skill: ["datadog"],
|
|
106
|
+
observability: ["datadog"],
|
|
107
|
+
},
|
|
108
|
+
transport: "http",
|
|
109
|
+
npxExceptionReason: null,
|
|
110
|
+
wire: (creds) => {
|
|
111
|
+
if (!creds.DD_API_KEY || !creds.DD_APP_KEY)
|
|
112
|
+
return undefined;
|
|
113
|
+
return {
|
|
114
|
+
type: "http",
|
|
115
|
+
url: "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp",
|
|
116
|
+
headers: { DD_API_KEY: creds.DD_API_KEY, DD_APPLICATION_KEY: creds.DD_APP_KEY },
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
const sentry = {
|
|
121
|
+
id: "sentry",
|
|
122
|
+
triggers: {
|
|
123
|
+
skill: ["sentry"],
|
|
124
|
+
observability: ["sentry"],
|
|
125
|
+
},
|
|
126
|
+
transport: "stdio",
|
|
127
|
+
npxExceptionReason: "Official @sentry/mcp-server; no public HTTP MCP endpoint.",
|
|
128
|
+
wire: (creds) => {
|
|
129
|
+
if (!creds.SENTRY_AUTH_TOKEN)
|
|
130
|
+
return undefined;
|
|
131
|
+
const env = { SENTRY_ACCESS_TOKEN: creds.SENTRY_AUTH_TOKEN };
|
|
132
|
+
const sentryUrl = creds.SENTRY_URL;
|
|
133
|
+
if (sentryUrl && sentryUrl !== "https://sentry.io") {
|
|
134
|
+
try {
|
|
135
|
+
env.SENTRY_HOST = new URL(sentryUrl).hostname;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// malformed URL — leave SENTRY_HOST unset, server defaults to sentry.io
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
type: "stdio",
|
|
143
|
+
command: "npx",
|
|
144
|
+
args: ["-y", "@sentry/mcp-server@latest"],
|
|
145
|
+
env,
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
const newrelic = {
|
|
150
|
+
id: "newrelic",
|
|
151
|
+
triggers: {
|
|
152
|
+
skill: ["newrelic"],
|
|
153
|
+
observability: ["newrelic"],
|
|
154
|
+
},
|
|
155
|
+
transport: "http",
|
|
156
|
+
npxExceptionReason: null,
|
|
157
|
+
wire: (creds) => {
|
|
158
|
+
if (!creds.NEW_RELIC_API_KEY)
|
|
159
|
+
return undefined;
|
|
160
|
+
const region = creds.NEW_RELIC_REGION;
|
|
161
|
+
const url = region === "eu" ? "https://mcp.eu.newrelic.com/mcp/" : "https://mcp.newrelic.com/mcp/";
|
|
162
|
+
return {
|
|
163
|
+
type: "http",
|
|
164
|
+
url,
|
|
165
|
+
headers: { "Api-Key": creds.NEW_RELIC_API_KEY },
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
const betterstack = {
|
|
170
|
+
id: "betterstack",
|
|
171
|
+
triggers: {
|
|
172
|
+
skill: ["betterstack"],
|
|
173
|
+
observability: ["betterstack"],
|
|
174
|
+
},
|
|
175
|
+
transport: "http",
|
|
176
|
+
npxExceptionReason: null,
|
|
177
|
+
wire: (creds) => {
|
|
178
|
+
const token = creds.BETTERSTACK_API_TOKEN || creds.BETTERSTACK_TELEMETRY_TOKEN || creds.BETTERSTACK_UPTIME_TOKEN;
|
|
179
|
+
if (!token)
|
|
180
|
+
return undefined;
|
|
181
|
+
return {
|
|
182
|
+
type: "http",
|
|
183
|
+
url: "https://mcp.betterstack.com",
|
|
184
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
const slack = {
|
|
189
|
+
id: "slack",
|
|
190
|
+
triggers: {
|
|
191
|
+
skill: ["slack"],
|
|
192
|
+
workspaceTool: ["slack"],
|
|
193
|
+
},
|
|
194
|
+
transport: "stdio",
|
|
195
|
+
npxExceptionReason: "Official @modelcontextprotocol/server-slack; no public HTTP MCP endpoint.",
|
|
196
|
+
wire: (creds) => {
|
|
197
|
+
if (!creds.SLACK_BOT_TOKEN)
|
|
198
|
+
return undefined;
|
|
199
|
+
const env = { SLACK_BOT_TOKEN: creds.SLACK_BOT_TOKEN };
|
|
200
|
+
if (creds.SLACK_TEAM_ID)
|
|
201
|
+
env.SLACK_TEAM_ID = creds.SLACK_TEAM_ID;
|
|
202
|
+
return {
|
|
203
|
+
type: "stdio",
|
|
204
|
+
command: "npx",
|
|
205
|
+
args: ["-y", "@modelcontextprotocol/server-slack@latest"],
|
|
206
|
+
env,
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
const notion = {
|
|
211
|
+
id: "notion",
|
|
212
|
+
triggers: {
|
|
213
|
+
skill: ["notion"],
|
|
214
|
+
workspaceTool: ["notion"],
|
|
215
|
+
},
|
|
216
|
+
transport: "stdio",
|
|
217
|
+
npxExceptionReason: "Official @notionhq/notion-mcp-server; no public HTTP MCP endpoint.",
|
|
218
|
+
wire: (creds) => {
|
|
219
|
+
const token = creds.NOTION_TOKEN || creds.NOTION_API_KEY;
|
|
220
|
+
if (!token)
|
|
221
|
+
return undefined;
|
|
222
|
+
return {
|
|
223
|
+
type: "stdio",
|
|
224
|
+
command: "npx",
|
|
225
|
+
args: ["-y", "@notionhq/notion-mcp-server@latest"],
|
|
226
|
+
env: { NOTION_TOKEN: token },
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
const pagerduty = {
|
|
231
|
+
id: "pagerduty",
|
|
232
|
+
triggers: {
|
|
233
|
+
skill: ["pagerduty"],
|
|
234
|
+
workspaceTool: ["pagerduty"],
|
|
235
|
+
},
|
|
236
|
+
transport: "http",
|
|
237
|
+
npxExceptionReason: null,
|
|
238
|
+
wire: (creds) => {
|
|
239
|
+
if (!creds.PAGERDUTY_API_TOKEN)
|
|
240
|
+
return undefined;
|
|
241
|
+
return {
|
|
242
|
+
type: "http",
|
|
243
|
+
url: "https://mcp.pagerduty.com/mcp",
|
|
244
|
+
headers: { Authorization: `Token token=${creds.PAGERDUTY_API_TOKEN}` },
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
const monday = {
|
|
249
|
+
id: "monday",
|
|
250
|
+
triggers: {
|
|
251
|
+
skill: ["monday"],
|
|
252
|
+
workspaceTool: ["monday"],
|
|
253
|
+
},
|
|
254
|
+
transport: "stdio",
|
|
255
|
+
npxExceptionReason: "Official @mondaydotcomorg/monday-api-mcp; no public HTTP MCP endpoint.",
|
|
256
|
+
wire: (creds) => {
|
|
257
|
+
if (!creds.MONDAY_TOKEN)
|
|
258
|
+
return undefined;
|
|
259
|
+
return {
|
|
260
|
+
type: "stdio",
|
|
261
|
+
command: "npx",
|
|
262
|
+
args: ["-y", "@mondaydotcomorg/monday-api-mcp@latest"],
|
|
263
|
+
env: { MONDAY_TOKEN: creds.MONDAY_TOKEN },
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
// asana was removed in Fix #15 — absent from SUPPORTED_WORKSPACE_TOOLS for
|
|
268
|
+
// some time, and the asana-mcp package is community-maintained without a
|
|
269
|
+
// clear first-party alternative. Add back in a dedicated catalog entry if
|
|
270
|
+
// there's demand and a policy-compliant server to wire.
|
|
271
|
+
// ── Assembled catalog ────────────────────────────────────────────────
|
|
272
|
+
export const MCP_CATALOG = [
|
|
273
|
+
github,
|
|
274
|
+
gitlab,
|
|
275
|
+
linear,
|
|
276
|
+
jira,
|
|
277
|
+
datadog,
|
|
278
|
+
sentry,
|
|
279
|
+
newrelic,
|
|
280
|
+
betterstack,
|
|
281
|
+
slack,
|
|
282
|
+
notion,
|
|
283
|
+
pagerduty,
|
|
284
|
+
monday,
|
|
285
|
+
];
|
|
286
|
+
// ── Policy enforcement ──────────────────────────────────────────────
|
|
287
|
+
/**
|
|
288
|
+
* Assert the `npx -y` policy (Fix #18). Thrown at module load and also
|
|
289
|
+
* asserted by the test suite, so a stdio entry missing `npxExceptionReason`
|
|
290
|
+
* never reaches shipping.
|
|
291
|
+
*/
|
|
292
|
+
function assertNpxPolicyCompliant(entries) {
|
|
293
|
+
for (const entry of entries) {
|
|
294
|
+
if (entry.transport === "stdio" && entry.npxExceptionReason === null) {
|
|
295
|
+
throw new Error(`MCP catalog entry "${entry.id}" uses stdio transport but declares no npxExceptionReason. ` +
|
|
296
|
+
`Stdio entries must document why the npx -y exception is acceptable. See ARCHITECTURE.md.`);
|
|
297
|
+
}
|
|
298
|
+
if (entry.transport === "http" && entry.npxExceptionReason !== null) {
|
|
299
|
+
throw new Error(`MCP catalog entry "${entry.id}" uses http transport but declares an npxExceptionReason. ` +
|
|
300
|
+
`Only stdio entries need an exception reason; set it to null for http entries.`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
assertNpxPolicyCompliant(MCP_CATALOG);
|
package/dist/mcp.d.ts
CHANGED
|
@@ -12,16 +12,13 @@ export interface SkillMcpOptions {
|
|
|
12
12
|
/**
|
|
13
13
|
* Build MCP server configs for the skills a workflow references.
|
|
14
14
|
*
|
|
15
|
-
* For each
|
|
16
|
-
*
|
|
15
|
+
* For each entry in MCP_CATALOG whose skill triggers intersect
|
|
16
|
+
* `referencedSkills`, wire its MCP if credentials are present. Skills with
|
|
17
|
+
* no MCP variant (e.g. `notification`, `supabase`) are silently skipped —
|
|
18
|
+
* their in-process tools are wired separately by the executor. Unknown
|
|
19
|
+
* skill IDs pass through; the engine's hard-fail validation handles them.
|
|
17
20
|
*
|
|
18
|
-
*
|
|
19
|
-
* skipped — their in-process tools are wired separately by the executor.
|
|
20
|
-
*
|
|
21
|
-
* Unknown skill IDs (not built-in, not in this MCP catalog) are silently
|
|
22
|
-
* skipped here; the engine's hard-fail validation handles them upstream.
|
|
23
|
-
*
|
|
24
|
-
* User-supplied servers always win on key conflict.
|
|
21
|
+
* Inline skill-declared servers and user-supplied servers layer on top.
|
|
25
22
|
*/
|
|
26
23
|
export declare function buildSkillMcpServers(opts: SkillMcpOptions): Record<string, McpServerConfig>;
|
|
27
24
|
/**
|