@sweny-ai/core 0.1.77 → 0.1.78

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 (119) hide show
  1. package/dist/browser.d.ts +11 -5
  2. package/dist/browser.js +11 -11
  3. package/dist/claude.d.ts +16 -0
  4. package/dist/claude.js +98 -9
  5. package/dist/cli/config.d.ts +22 -0
  6. package/dist/cli/config.js +56 -9
  7. package/dist/cli/main.d.ts +1 -1
  8. package/dist/cli/main.js +56 -57
  9. package/dist/cli/marketplace.js +35 -9
  10. package/dist/cli/publish.d.ts +10 -0
  11. package/dist/cli/publish.js +22 -14
  12. package/dist/loader.d.ts +40 -0
  13. package/dist/loader.js +73 -0
  14. package/dist/mcp-catalog.d.ts +47 -0
  15. package/dist/mcp-catalog.js +304 -0
  16. package/dist/mcp.d.ts +6 -9
  17. package/dist/mcp.js +24 -314
  18. package/dist/schema.d.ts +183 -151
  19. package/dist/schema.js +49 -7
  20. package/dist/skills/custom-loader.d.ts +40 -0
  21. package/dist/skills/custom-loader.js +107 -32
  22. package/dist/skills/index.d.ts +13 -1
  23. package/dist/skills/index.js +12 -3
  24. package/dist/studio.js +4 -1
  25. package/dist/templates.d.ts +29 -8
  26. package/dist/templates.js +30 -20
  27. package/dist/types.d.ts +12 -0
  28. package/dist/verify.js +42 -4
  29. package/package.json +8 -4
  30. package/dist/__tests__/cascade.test.d.ts +0 -11
  31. package/dist/__tests__/cascade.test.js +0 -452
  32. package/dist/__tests__/claude.test.d.ts +0 -1
  33. package/dist/__tests__/claude.test.js +0 -427
  34. package/dist/__tests__/custom-loader.test.d.ts +0 -1
  35. package/dist/__tests__/custom-loader.test.js +0 -116
  36. package/dist/__tests__/executor.test.d.ts +0 -1
  37. package/dist/__tests__/executor.test.js +0 -1635
  38. package/dist/__tests__/integration/datadog.integration.test.d.ts +0 -1
  39. package/dist/__tests__/integration/datadog.integration.test.js +0 -23
  40. package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +0 -1
  41. package/dist/__tests__/integration/e2e-workflow.integration.test.js +0 -75
  42. package/dist/__tests__/integration/github.integration.test.d.ts +0 -1
  43. package/dist/__tests__/integration/github.integration.test.js +0 -37
  44. package/dist/__tests__/integration/harness.d.ts +0 -24
  45. package/dist/__tests__/integration/harness.js +0 -34
  46. package/dist/__tests__/integration/linear.integration.test.d.ts +0 -1
  47. package/dist/__tests__/integration/linear.integration.test.js +0 -15
  48. package/dist/__tests__/integration/sentry.integration.test.d.ts +0 -1
  49. package/dist/__tests__/integration/sentry.integration.test.js +0 -20
  50. package/dist/__tests__/integration/slack.integration.test.d.ts +0 -1
  51. package/dist/__tests__/integration/slack.integration.test.js +0 -22
  52. package/dist/__tests__/integration/sources.integration.test.d.ts +0 -1
  53. package/dist/__tests__/integration/sources.integration.test.js +0 -123
  54. package/dist/__tests__/publish.test.d.ts +0 -1
  55. package/dist/__tests__/publish.test.js +0 -180
  56. package/dist/__tests__/requires.test.d.ts +0 -1
  57. package/dist/__tests__/requires.test.js +0 -82
  58. package/dist/__tests__/retry.test.d.ts +0 -1
  59. package/dist/__tests__/retry.test.js +0 -213
  60. package/dist/__tests__/schema.test.d.ts +0 -1
  61. package/dist/__tests__/schema.test.js +0 -600
  62. package/dist/__tests__/skill-config-integration.test.d.ts +0 -1
  63. package/dist/__tests__/skill-config-integration.test.js +0 -204
  64. package/dist/__tests__/skills-index.test.d.ts +0 -1
  65. package/dist/__tests__/skills-index.test.js +0 -119
  66. package/dist/__tests__/skills.test.d.ts +0 -1
  67. package/dist/__tests__/skills.test.js +0 -565
  68. package/dist/__tests__/spec-conformance.test.d.ts +0 -18
  69. package/dist/__tests__/spec-conformance.test.js +0 -1084
  70. package/dist/__tests__/studio.test.d.ts +0 -1
  71. package/dist/__tests__/studio.test.js +0 -403
  72. package/dist/__tests__/templates.test.d.ts +0 -1
  73. package/dist/__tests__/templates.test.js +0 -49
  74. package/dist/__tests__/testing.test.d.ts +0 -1
  75. package/dist/__tests__/testing.test.js +0 -242
  76. package/dist/__tests__/verify.test.d.ts +0 -1
  77. package/dist/__tests__/verify.test.js +0 -517
  78. package/dist/__tests__/version-consistency.test.d.ts +0 -1
  79. package/dist/__tests__/version-consistency.test.js +0 -312
  80. package/dist/__tests__/workflow-builder.test.d.ts +0 -1
  81. package/dist/__tests__/workflow-builder.test.js +0 -110
  82. package/dist/__tests__/workflow-yaml.test.d.ts +0 -1
  83. package/dist/__tests__/workflow-yaml.test.js +0 -201
  84. package/dist/cli/__tests__/diagram.test.d.ts +0 -1
  85. package/dist/cli/__tests__/diagram.test.js +0 -225
  86. package/dist/cli/__tests__/report-cloud.test.d.ts +0 -1
  87. package/dist/cli/__tests__/report-cloud.test.js +0 -165
  88. package/dist/cli/__tests__/upgrade.test.d.ts +0 -1
  89. package/dist/cli/__tests__/upgrade.test.js +0 -317
  90. package/dist/cli/__tests__/version-check.test.d.ts +0 -1
  91. package/dist/cli/__tests__/version-check.test.js +0 -209
  92. package/dist/cli/config.test.d.ts +0 -1
  93. package/dist/cli/config.test.js +0 -200
  94. package/dist/cli/credentials.test.d.ts +0 -1
  95. package/dist/cli/credentials.test.js +0 -172
  96. package/dist/cli/e2e.test.d.ts +0 -1
  97. package/dist/cli/e2e.test.js +0 -690
  98. package/dist/cli/marketplace.test.d.ts +0 -1
  99. package/dist/cli/marketplace.test.js +0 -355
  100. package/dist/cli/new.test.d.ts +0 -1
  101. package/dist/cli/new.test.js +0 -1043
  102. package/dist/cli/output.test.d.ts +0 -1
  103. package/dist/cli/output.test.js +0 -180
  104. package/dist/cli/publish.test.d.ts +0 -1
  105. package/dist/cli/publish.test.js +0 -610
  106. package/dist/cli/renderer.test.d.ts +0 -1
  107. package/dist/cli/renderer.test.js +0 -302
  108. package/dist/executor.test.d.ts +0 -1
  109. package/dist/executor.test.js +0 -618
  110. package/dist/mcp.test.d.ts +0 -1
  111. package/dist/mcp.test.js +0 -735
  112. package/dist/mermaid.test.d.ts +0 -1
  113. package/dist/mermaid.test.js +0 -256
  114. package/dist/sources.test.d.ts +0 -1
  115. package/dist/sources.test.js +0 -265
  116. package/dist/sources.validation.test.d.ts +0 -12
  117. package/dist/sources.validation.test.js +0 -380
  118. package/dist/workflow-builder.test.d.ts +0 -1
  119. package/dist/workflow-builder.test.js +0 -123
package/dist/mcp.test.js DELETED
@@ -1,735 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { buildAutoMcpServers, buildSkillMcpServers } from "./mcp.js";
3
- // ─── Helpers ─────────────────────────────────────────────────────
4
- function cfg(overrides = {}) {
5
- return { credentials: {}, ...overrides };
6
- }
7
- // ─── Empty / no providers ─────────────────────────────────────────
8
- describe("buildAutoMcpServers", () => {
9
- it("returns empty object when no providers configured", () => {
10
- const result = buildAutoMcpServers(cfg());
11
- expect(result).toEqual({});
12
- });
13
- // ── GitHub MCP ──────────────────────────────────────────────────
14
- it("injects GitHub MCP when sourceControlProvider is github with GITHUB_TOKEN", () => {
15
- const result = buildAutoMcpServers(cfg({ sourceControlProvider: "github", credentials: { GITHUB_TOKEN: "ghp_abc" } }));
16
- expect(result["github"]).toEqual({
17
- type: "stdio",
18
- command: "npx",
19
- args: ["-y", "@modelcontextprotocol/server-github@latest"],
20
- env: { GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_abc" },
21
- });
22
- });
23
- it("injects GitHub MCP when issueTrackerProvider is github-issues with GITHUB_TOKEN", () => {
24
- const result = buildAutoMcpServers(cfg({ issueTrackerProvider: "github-issues", credentials: { GITHUB_TOKEN: "ghp_xyz" } }));
25
- expect(result["github"]).toMatchObject({
26
- type: "stdio",
27
- env: { GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_xyz" },
28
- });
29
- });
30
- it("does NOT inject GitHub MCP without GITHUB_TOKEN", () => {
31
- const result = buildAutoMcpServers(cfg({ sourceControlProvider: "github", credentials: {} }));
32
- expect(result["github"]).toBeUndefined();
33
- });
34
- // ── Linear MCP ──────────────────────────────────────────────────
35
- it("injects Linear MCP (HTTP) when issueTrackerProvider is linear with LINEAR_API_KEY", () => {
36
- const result = buildAutoMcpServers(cfg({ issueTrackerProvider: "linear", credentials: { LINEAR_API_KEY: "lin_key_abc" } }));
37
- expect(result["linear"]).toEqual({
38
- type: "http",
39
- url: "https://mcp.linear.app/mcp",
40
- headers: { Authorization: "Bearer lin_key_abc" },
41
- });
42
- });
43
- it("does NOT inject Linear MCP without LINEAR_API_KEY", () => {
44
- const result = buildAutoMcpServers(cfg({ issueTrackerProvider: "linear", credentials: {} }));
45
- expect(result["linear"]).toBeUndefined();
46
- });
47
- // ── Datadog MCP ─────────────────────────────────────────────────
48
- it("injects Datadog MCP (HTTP) when observabilityProvider is datadog with DD_API_KEY + DD_APP_KEY", () => {
49
- const result = buildAutoMcpServers(cfg({
50
- observabilityProviders: ["datadog"],
51
- credentials: { DD_API_KEY: "dd_api", DD_APP_KEY: "dd_app" },
52
- }));
53
- expect(result["datadog"]).toEqual({
54
- type: "http",
55
- url: "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp",
56
- headers: { DD_API_KEY: "dd_api", DD_APPLICATION_KEY: "dd_app" },
57
- });
58
- });
59
- it("does NOT inject Datadog MCP when DD_APP_KEY is missing", () => {
60
- const result = buildAutoMcpServers(cfg({ observabilityProviders: ["datadog"], credentials: { DD_API_KEY: "dd_api" } }));
61
- expect(result["datadog"]).toBeUndefined();
62
- });
63
- // ── Sentry MCP ──────────────────────────────────────────────────
64
- it("injects Sentry MCP (stdio) when observabilityProvider is sentry with SENTRY_AUTH_TOKEN", () => {
65
- const result = buildAutoMcpServers(cfg({ observabilityProviders: ["sentry"], credentials: { SENTRY_AUTH_TOKEN: "sntryu_abc" } }));
66
- expect(result["sentry"]).toEqual({
67
- type: "stdio",
68
- command: "npx",
69
- args: ["-y", "@sentry/mcp-server@latest"],
70
- env: { SENTRY_ACCESS_TOKEN: "sntryu_abc" },
71
- });
72
- });
73
- it("injects Sentry MCP with self-hosted SENTRY_HOST from SENTRY_URL", () => {
74
- const result = buildAutoMcpServers(cfg({
75
- observabilityProviders: ["sentry"],
76
- credentials: {
77
- SENTRY_AUTH_TOKEN: "sntryu_abc",
78
- SENTRY_URL: "https://sentry.mycompany.com",
79
- },
80
- }));
81
- expect(result["sentry"]?.env).toMatchObject({
82
- SENTRY_ACCESS_TOKEN: "sntryu_abc",
83
- SENTRY_HOST: "sentry.mycompany.com",
84
- });
85
- });
86
- it("does NOT set SENTRY_HOST when SENTRY_URL is sentry.io", () => {
87
- const result = buildAutoMcpServers(cfg({
88
- observabilityProviders: ["sentry"],
89
- credentials: {
90
- SENTRY_AUTH_TOKEN: "sntryu_abc",
91
- SENTRY_URL: "https://sentry.io",
92
- },
93
- }));
94
- expect(result["sentry"]?.env?.SENTRY_HOST).toBeUndefined();
95
- });
96
- it("handles malformed SENTRY_URL gracefully (no SENTRY_HOST set)", () => {
97
- const result = buildAutoMcpServers(cfg({
98
- observabilityProviders: ["sentry"],
99
- credentials: {
100
- SENTRY_AUTH_TOKEN: "sntryu_abc",
101
- SENTRY_URL: "not-a-url",
102
- },
103
- }));
104
- expect(result["sentry"]).toBeDefined();
105
- expect(result["sentry"]?.env?.SENTRY_HOST).toBeUndefined();
106
- });
107
- // ── GitLab MCP ──────────────────────────────────────────────────
108
- it("injects GitLab MCP with self-hosted GITLAB_API_URL", () => {
109
- const result = buildAutoMcpServers(cfg({
110
- sourceControlProvider: "gitlab",
111
- credentials: {
112
- GITLAB_TOKEN: "glpat_abc",
113
- GITLAB_URL: "https://gitlab.mycompany.com",
114
- },
115
- }));
116
- expect(result["gitlab"]).toEqual({
117
- type: "stdio",
118
- command: "npx",
119
- args: ["-y", "@modelcontextprotocol/server-gitlab@latest"],
120
- env: {
121
- GITLAB_PERSONAL_ACCESS_TOKEN: "glpat_abc",
122
- GITLAB_API_URL: "https://gitlab.mycompany.com/api/v4",
123
- },
124
- });
125
- });
126
- it("injects GitLab MCP without GITLAB_API_URL for gitlab.com", () => {
127
- const result = buildAutoMcpServers(cfg({
128
- sourceControlProvider: "gitlab",
129
- credentials: {
130
- GITLAB_TOKEN: "glpat_abc",
131
- GITLAB_URL: "https://gitlab.com",
132
- },
133
- }));
134
- expect(result["gitlab"]?.env).toEqual({ GITLAB_PERSONAL_ACCESS_TOKEN: "glpat_abc" });
135
- });
136
- it("injects GitLab MCP without GITLAB_API_URL when GITLAB_URL is absent", () => {
137
- const result = buildAutoMcpServers(cfg({
138
- sourceControlProvider: "gitlab",
139
- credentials: { GITLAB_TOKEN: "glpat_abc" },
140
- }));
141
- expect(result["gitlab"]?.env).toEqual({ GITLAB_PERSONAL_ACCESS_TOKEN: "glpat_abc" });
142
- });
143
- // ── New Relic MCP ───────────────────────────────────────────────
144
- it("injects New Relic MCP with EU region endpoint", () => {
145
- const result = buildAutoMcpServers(cfg({
146
- observabilityProviders: ["newrelic"],
147
- credentials: { NEW_RELIC_API_KEY: "NRAK-abc", NEW_RELIC_REGION: "eu" },
148
- }));
149
- expect(result["newrelic"]).toEqual({
150
- type: "http",
151
- url: "https://mcp.eu.newrelic.com/mcp/",
152
- headers: { "Api-Key": "NRAK-abc" },
153
- });
154
- });
155
- it("injects New Relic MCP with US (default) region endpoint", () => {
156
- const result = buildAutoMcpServers(cfg({
157
- observabilityProviders: ["newrelic"],
158
- credentials: { NEW_RELIC_API_KEY: "NRAK-abc" },
159
- }));
160
- expect(result["newrelic"]).toEqual({
161
- type: "http",
162
- url: "https://mcp.newrelic.com/mcp/",
163
- headers: { "Api-Key": "NRAK-abc" },
164
- });
165
- });
166
- // ── Better Stack MCP ────────────────────────────────────────────
167
- it("injects Better Stack MCP (HTTP) with Bearer token", () => {
168
- const result = buildAutoMcpServers(cfg({
169
- observabilityProviders: ["betterstack"],
170
- credentials: { BETTERSTACK_API_TOKEN: "bst_abc" },
171
- }));
172
- expect(result["betterstack"]).toEqual({
173
- type: "http",
174
- url: "https://mcp.betterstack.com",
175
- headers: { Authorization: "Bearer bst_abc" },
176
- });
177
- });
178
- it("injects multiple observability MCPs when both are configured", () => {
179
- const result = buildAutoMcpServers(cfg({
180
- observabilityProviders: ["sentry", "betterstack"],
181
- credentials: { BETTERSTACK_API_TOKEN: "bst_xyz", SENTRY_AUTH_TOKEN: "sntrx_test" },
182
- }));
183
- expect(result["betterstack"]).toEqual({
184
- type: "http",
185
- url: "https://mcp.betterstack.com",
186
- headers: { Authorization: "Bearer bst_xyz" },
187
- });
188
- // Sentry should also be present
189
- expect(result["sentry"]).toBeDefined();
190
- });
191
- // ── Jira MCP ────────────────────────────────────────────────────
192
- it("injects Jira MCP when all 3 credentials present", () => {
193
- const result = buildAutoMcpServers(cfg({
194
- issueTrackerProvider: "jira",
195
- credentials: {
196
- JIRA_URL: "https://mycompany.atlassian.net",
197
- JIRA_EMAIL: "user@example.com",
198
- JIRA_API_TOKEN: "jira_tok",
199
- },
200
- }));
201
- expect(result["jira"]).toEqual({
202
- type: "stdio",
203
- command: "npx",
204
- args: ["-y", "@sooperset/mcp-atlassian@latest"],
205
- env: {
206
- JIRA_URL: "https://mycompany.atlassian.net",
207
- JIRA_EMAIL: "user@example.com",
208
- JIRA_API_TOKEN: "jira_tok",
209
- },
210
- });
211
- });
212
- it("does NOT inject Jira MCP when JIRA_EMAIL is missing", () => {
213
- const result = buildAutoMcpServers(cfg({
214
- issueTrackerProvider: "jira",
215
- credentials: {
216
- JIRA_URL: "https://mycompany.atlassian.net",
217
- JIRA_API_TOKEN: "jira_tok",
218
- },
219
- }));
220
- expect(result["jira"]).toBeUndefined();
221
- });
222
- // ── Workspace tools ─────────────────────────────────────────────
223
- it("injects Slack when opted in with SLACK_BOT_TOKEN", () => {
224
- const result = buildAutoMcpServers(cfg({
225
- workspaceTools: ["slack"],
226
- credentials: { SLACK_BOT_TOKEN: "xoxb-abc" },
227
- }));
228
- expect(result["slack"]).toEqual({
229
- type: "stdio",
230
- command: "npx",
231
- args: ["-y", "@modelcontextprotocol/server-slack@latest"],
232
- env: { SLACK_BOT_TOKEN: "xoxb-abc" },
233
- });
234
- });
235
- it("includes SLACK_TEAM_ID when present", () => {
236
- const result = buildAutoMcpServers(cfg({
237
- workspaceTools: ["slack"],
238
- credentials: { SLACK_BOT_TOKEN: "xoxb-abc", SLACK_TEAM_ID: "T123" },
239
- }));
240
- expect(result["slack"]?.env).toMatchObject({
241
- SLACK_BOT_TOKEN: "xoxb-abc",
242
- SLACK_TEAM_ID: "T123",
243
- });
244
- });
245
- it("does NOT inject Slack without opt-in even if SLACK_BOT_TOKEN is present", () => {
246
- const result = buildAutoMcpServers(cfg({ credentials: { SLACK_BOT_TOKEN: "xoxb-abc" } }));
247
- expect(result["slack"]).toBeUndefined();
248
- });
249
- it("injects Notion when opted in with NOTION_TOKEN", () => {
250
- const result = buildAutoMcpServers(cfg({
251
- workspaceTools: ["notion"],
252
- credentials: { NOTION_TOKEN: "ntn_abc" },
253
- }));
254
- expect(result["notion"]).toEqual({
255
- type: "stdio",
256
- command: "npx",
257
- args: ["-y", "@notionhq/notion-mcp-server@latest"],
258
- env: { NOTION_TOKEN: "ntn_abc" },
259
- });
260
- });
261
- it("injects Notion with NOTION_API_KEY fallback", () => {
262
- const result = buildAutoMcpServers(cfg({
263
- workspaceTools: ["notion"],
264
- credentials: { NOTION_API_KEY: "ntn_xyz" },
265
- }));
266
- expect(result["notion"]?.env).toEqual({ NOTION_TOKEN: "ntn_xyz" });
267
- });
268
- it("injects PagerDuty when opted in with PAGERDUTY_API_TOKEN", () => {
269
- const result = buildAutoMcpServers(cfg({
270
- workspaceTools: ["pagerduty"],
271
- credentials: { PAGERDUTY_API_TOKEN: "pd_tok" },
272
- }));
273
- expect(result["pagerduty"]).toEqual({
274
- type: "http",
275
- url: "https://mcp.pagerduty.com/mcp",
276
- headers: { Authorization: "Token token=pd_tok" },
277
- });
278
- });
279
- it("injects Monday when opted in with MONDAY_TOKEN", () => {
280
- const result = buildAutoMcpServers(cfg({
281
- workspaceTools: ["monday"],
282
- credentials: { MONDAY_TOKEN: "mon_abc" },
283
- }));
284
- expect(result["monday"]).toEqual({
285
- type: "stdio",
286
- command: "npx",
287
- args: ["-y", "@mondaydotcomorg/monday-api-mcp@latest"],
288
- env: { MONDAY_TOKEN: "mon_abc" },
289
- });
290
- });
291
- it("injects Asana when opted in with ASANA_ACCESS_TOKEN", () => {
292
- const result = buildAutoMcpServers(cfg({
293
- workspaceTools: ["asana"],
294
- credentials: { ASANA_ACCESS_TOKEN: "asa_abc" },
295
- }));
296
- expect(result["asana"]).toEqual({
297
- type: "stdio",
298
- command: "npx",
299
- args: ["-y", "asana-mcp@latest"],
300
- env: { ASANA_ACCESS_TOKEN: "asa_abc" },
301
- });
302
- });
303
- // ── User MCP servers win on conflict ────────────────────────────
304
- it("user-supplied MCP servers win on key conflict", () => {
305
- const customGithub = {
306
- type: "stdio",
307
- command: "my-custom-github-mcp",
308
- args: [],
309
- };
310
- const result = buildAutoMcpServers(cfg({
311
- sourceControlProvider: "github",
312
- credentials: { GITHUB_TOKEN: "ghp_abc" },
313
- userMcpServers: { github: customGithub },
314
- }));
315
- expect(result["github"]).toEqual(customGithub);
316
- });
317
- it("user-supplied servers are included alongside auto-injected ones", () => {
318
- const myServer = { type: "http", url: "https://my-mcp.example.com" };
319
- const result = buildAutoMcpServers(cfg({
320
- sourceControlProvider: "github",
321
- credentials: { GITHUB_TOKEN: "ghp_abc" },
322
- userMcpServers: { "my-tool": myServer },
323
- }));
324
- expect(result["github"]).toBeDefined();
325
- expect(result["my-tool"]).toEqual(myServer);
326
- });
327
- // ── Multiple providers simultaneously ───────────────────────────
328
- it("injects multiple providers simultaneously", () => {
329
- const result = buildAutoMcpServers(cfg({
330
- sourceControlProvider: "github",
331
- issueTrackerProvider: "linear",
332
- observabilityProviders: ["datadog"],
333
- credentials: {
334
- GITHUB_TOKEN: "ghp_abc",
335
- LINEAR_API_KEY: "lin_key_abc",
336
- DD_API_KEY: "dd_api",
337
- DD_APP_KEY: "dd_app",
338
- },
339
- workspaceTools: ["slack"],
340
- // No SLACK_BOT_TOKEN — should not inject slack
341
- }));
342
- expect(result["github"]).toBeDefined();
343
- expect(result["linear"]).toBeDefined();
344
- expect(result["datadog"]).toBeDefined();
345
- expect(result["slack"]).toBeUndefined();
346
- });
347
- });
348
- // ─── buildSkillMcpServers (engine-driven) ────────────────────────────
349
- describe("buildSkillMcpServers", () => {
350
- it("returns empty when no skills referenced and no creds present", () => {
351
- const result = buildSkillMcpServers({
352
- referencedSkills: new Set(),
353
- credentials: {},
354
- });
355
- expect(result).toEqual({});
356
- });
357
- it("does not wire MCP for a skill not referenced by the workflow", () => {
358
- // Token is set, but the workflow doesn't ask for github → no MCP.
359
- const result = buildSkillMcpServers({
360
- referencedSkills: new Set(["linear"]),
361
- credentials: { GITHUB_TOKEN: "ghp_abc", LINEAR_API_KEY: "lin_abc" },
362
- });
363
- expect(result["github"]).toBeUndefined();
364
- expect(result["linear"]).toBeDefined();
365
- });
366
- it("does not wire MCP for a referenced skill whose creds are missing", () => {
367
- const result = buildSkillMcpServers({
368
- referencedSkills: new Set(["github", "linear"]),
369
- credentials: { LINEAR_API_KEY: "lin_abc" }, // no GITHUB_TOKEN
370
- });
371
- expect(result["github"]).toBeUndefined();
372
- expect(result["linear"]).toBeDefined();
373
- });
374
- it("wires github MCP when referenced and GITHUB_TOKEN present", () => {
375
- const result = buildSkillMcpServers({
376
- referencedSkills: new Set(["github"]),
377
- credentials: { GITHUB_TOKEN: "ghp_abc" },
378
- });
379
- expect(result["github"]).toEqual({
380
- type: "stdio",
381
- command: "npx",
382
- args: ["-y", "@modelcontextprotocol/server-github@latest"],
383
- env: { GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_abc" },
384
- });
385
- });
386
- it("wires linear MCP when referenced and LINEAR_API_KEY present", () => {
387
- const result = buildSkillMcpServers({
388
- referencedSkills: new Set(["linear"]),
389
- credentials: { LINEAR_API_KEY: "lin_abc" },
390
- });
391
- expect(result["linear"]).toEqual({
392
- type: "http",
393
- url: "https://mcp.linear.app/mcp",
394
- headers: { Authorization: "Bearer lin_abc" },
395
- });
396
- });
397
- it("wires sentry MCP with SENTRY_HOST for self-hosted SENTRY_URL", () => {
398
- const result = buildSkillMcpServers({
399
- referencedSkills: new Set(["sentry"]),
400
- credentials: { SENTRY_AUTH_TOKEN: "sntryu_abc", SENTRY_URL: "https://sentry.mycompany.com" },
401
- });
402
- expect(result["sentry"]?.env).toMatchObject({
403
- SENTRY_ACCESS_TOKEN: "sntryu_abc",
404
- SENTRY_HOST: "sentry.mycompany.com",
405
- });
406
- });
407
- it("wires datadog MCP when both keys present", () => {
408
- const result = buildSkillMcpServers({
409
- referencedSkills: new Set(["datadog"]),
410
- credentials: { DD_API_KEY: "dd_api", DD_APP_KEY: "dd_app" },
411
- });
412
- expect(result["datadog"]).toEqual({
413
- type: "http",
414
- url: "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp",
415
- headers: { DD_API_KEY: "dd_api", DD_APPLICATION_KEY: "dd_app" },
416
- });
417
- });
418
- it("does not wire datadog MCP when DD_APP_KEY is missing", () => {
419
- const result = buildSkillMcpServers({
420
- referencedSkills: new Set(["datadog"]),
421
- credentials: { DD_API_KEY: "dd_api" },
422
- });
423
- expect(result["datadog"]).toBeUndefined();
424
- });
425
- it("wires slack MCP without an opt-in flag — workflow reference is the opt-in", () => {
426
- const result = buildSkillMcpServers({
427
- referencedSkills: new Set(["slack"]),
428
- credentials: { SLACK_BOT_TOKEN: "xoxb-abc" },
429
- });
430
- expect(result["slack"]).toEqual({
431
- type: "stdio",
432
- command: "npx",
433
- args: ["-y", "@modelcontextprotocol/server-slack@latest"],
434
- env: { SLACK_BOT_TOKEN: "xoxb-abc" },
435
- });
436
- });
437
- it("wires betterstack MCP only when the workflow references betterstack", () => {
438
- // Token alone is not enough — must be referenced.
439
- const noRef = buildSkillMcpServers({
440
- referencedSkills: new Set(["sentry"]),
441
- credentials: { BETTERSTACK_API_TOKEN: "bs_abc", SENTRY_AUTH_TOKEN: "sntry_x" },
442
- });
443
- expect(noRef["betterstack"]).toBeUndefined();
444
- const withRef = buildSkillMcpServers({
445
- referencedSkills: new Set(["betterstack"]),
446
- credentials: { BETTERSTACK_API_TOKEN: "bs_abc" },
447
- });
448
- expect(withRef["betterstack"]).toEqual({
449
- type: "http",
450
- url: "https://mcp.betterstack.com",
451
- headers: { Authorization: "Bearer bs_abc" },
452
- });
453
- });
454
- it("user-supplied MCP servers win on key conflict", () => {
455
- const custom = { type: "stdio", command: "my-github", args: [] };
456
- const result = buildSkillMcpServers({
457
- referencedSkills: new Set(["github"]),
458
- credentials: { GITHUB_TOKEN: "ghp_abc" },
459
- userMcpServers: { github: custom },
460
- });
461
- expect(result["github"]).toEqual(custom);
462
- });
463
- it("user-supplied servers can add MCPs alongside auto-wired ones", () => {
464
- const my = { type: "http", url: "https://my.example.com" };
465
- const result = buildSkillMcpServers({
466
- referencedSkills: new Set(["github"]),
467
- credentials: { GITHUB_TOKEN: "ghp_abc" },
468
- userMcpServers: { "my-tool": my },
469
- });
470
- expect(result["github"]).toBeDefined();
471
- expect(result["my-tool"]).toEqual(my);
472
- });
473
- it("silently skips skill IDs with no MCP catalog entry (e.g. notification)", () => {
474
- const result = buildSkillMcpServers({
475
- referencedSkills: new Set(["notification", "supabase", "github"]),
476
- credentials: { GITHUB_TOKEN: "ghp_abc" },
477
- });
478
- expect(result["notification"]).toBeUndefined();
479
- expect(result["supabase"]).toBeUndefined();
480
- expect(result["github"]).toBeDefined();
481
- });
482
- it("wires multiple skills simultaneously", () => {
483
- const result = buildSkillMcpServers({
484
- referencedSkills: new Set(["github", "linear", "datadog", "slack"]),
485
- credentials: {
486
- GITHUB_TOKEN: "ghp_abc",
487
- LINEAR_API_KEY: "lin_abc",
488
- DD_API_KEY: "dd_api",
489
- DD_APP_KEY: "dd_app",
490
- SLACK_BOT_TOKEN: "xoxb-abc",
491
- },
492
- });
493
- expect(result["github"]).toBeDefined();
494
- expect(result["linear"]).toBeDefined();
495
- expect(result["datadog"]).toBeDefined();
496
- expect(result["slack"]).toBeDefined();
497
- });
498
- // ── GitLab ─────────────────────────────────────────────────────────
499
- it("wires gitlab MCP when referenced and GITLAB_TOKEN present", () => {
500
- const result = buildSkillMcpServers({
501
- referencedSkills: new Set(["gitlab"]),
502
- credentials: { GITLAB_TOKEN: "glpat_abc" },
503
- });
504
- expect(result["gitlab"]).toEqual({
505
- type: "stdio",
506
- command: "npx",
507
- args: ["-y", "@modelcontextprotocol/server-gitlab@latest"],
508
- env: { GITLAB_PERSONAL_ACCESS_TOKEN: "glpat_abc" },
509
- });
510
- });
511
- it("wires gitlab MCP with self-hosted GITLAB_API_URL", () => {
512
- const result = buildSkillMcpServers({
513
- referencedSkills: new Set(["gitlab"]),
514
- credentials: { GITLAB_TOKEN: "glpat_abc", GITLAB_URL: "https://gitlab.example.com" },
515
- });
516
- expect(result["gitlab"]?.env).toEqual({
517
- GITLAB_PERSONAL_ACCESS_TOKEN: "glpat_abc",
518
- GITLAB_API_URL: "https://gitlab.example.com/api/v4",
519
- });
520
- });
521
- // ── Jira ───────────────────────────────────────────────────────────
522
- it("wires jira MCP when all 3 creds present", () => {
523
- const result = buildSkillMcpServers({
524
- referencedSkills: new Set(["jira"]),
525
- credentials: {
526
- JIRA_URL: "https://acme.atlassian.net",
527
- JIRA_EMAIL: "alice@acme.com",
528
- JIRA_API_TOKEN: "tok_abc",
529
- },
530
- });
531
- expect(result["jira"]).toEqual({
532
- type: "stdio",
533
- command: "npx",
534
- args: ["-y", "@sooperset/mcp-atlassian@latest"],
535
- env: {
536
- JIRA_URL: "https://acme.atlassian.net",
537
- JIRA_EMAIL: "alice@acme.com",
538
- JIRA_API_TOKEN: "tok_abc",
539
- },
540
- });
541
- });
542
- it("does NOT wire jira MCP when JIRA_EMAIL is missing", () => {
543
- const result = buildSkillMcpServers({
544
- referencedSkills: new Set(["jira"]),
545
- credentials: { JIRA_URL: "https://acme.atlassian.net", JIRA_API_TOKEN: "tok_abc" },
546
- });
547
- expect(result["jira"]).toBeUndefined();
548
- });
549
- // ── New Relic ──────────────────────────────────────────────────────
550
- it("wires newrelic MCP with US (default) endpoint", () => {
551
- const result = buildSkillMcpServers({
552
- referencedSkills: new Set(["newrelic"]),
553
- credentials: { NEW_RELIC_API_KEY: "NRAK-abc" },
554
- });
555
- expect(result["newrelic"]).toEqual({
556
- type: "http",
557
- url: "https://mcp.newrelic.com/mcp/",
558
- headers: { "Api-Key": "NRAK-abc" },
559
- });
560
- });
561
- it("wires newrelic MCP with EU endpoint when NEW_RELIC_REGION=eu", () => {
562
- const result = buildSkillMcpServers({
563
- referencedSkills: new Set(["newrelic"]),
564
- credentials: { NEW_RELIC_API_KEY: "NRAK-abc", NEW_RELIC_REGION: "eu" },
565
- });
566
- expect(result["newrelic"]?.url).toBe("https://mcp.eu.newrelic.com/mcp/");
567
- });
568
- it("does NOT wire newrelic MCP without NEW_RELIC_API_KEY", () => {
569
- const result = buildSkillMcpServers({
570
- referencedSkills: new Set(["newrelic"]),
571
- credentials: { NEW_RELIC_REGION: "eu" },
572
- });
573
- expect(result["newrelic"]).toBeUndefined();
574
- });
575
- // ── Notion (both env-var spellings) ────────────────────────────────
576
- it("wires notion MCP with NOTION_TOKEN", () => {
577
- const result = buildSkillMcpServers({
578
- referencedSkills: new Set(["notion"]),
579
- credentials: { NOTION_TOKEN: "secret_abc" },
580
- });
581
- expect(result["notion"]).toEqual({
582
- type: "stdio",
583
- command: "npx",
584
- args: ["-y", "@notionhq/notion-mcp-server@latest"],
585
- env: { NOTION_TOKEN: "secret_abc" },
586
- });
587
- });
588
- it("wires notion MCP with NOTION_API_KEY fallback", () => {
589
- const result = buildSkillMcpServers({
590
- referencedSkills: new Set(["notion"]),
591
- credentials: { NOTION_API_KEY: "secret_xyz" },
592
- });
593
- expect(result["notion"]?.env?.NOTION_TOKEN).toBe("secret_xyz");
594
- });
595
- it("prefers NOTION_TOKEN over NOTION_API_KEY when both set", () => {
596
- const result = buildSkillMcpServers({
597
- referencedSkills: new Set(["notion"]),
598
- credentials: { NOTION_TOKEN: "primary", NOTION_API_KEY: "fallback" },
599
- });
600
- expect(result["notion"]?.env?.NOTION_TOKEN).toBe("primary");
601
- });
602
- // ── PagerDuty / Monday / Asana ─────────────────────────────────────
603
- it("wires pagerduty MCP with Token-token auth scheme", () => {
604
- const result = buildSkillMcpServers({
605
- referencedSkills: new Set(["pagerduty"]),
606
- credentials: { PAGERDUTY_API_TOKEN: "pd_abc" },
607
- });
608
- expect(result["pagerduty"]).toEqual({
609
- type: "http",
610
- url: "https://mcp.pagerduty.com/mcp",
611
- headers: { Authorization: "Token token=pd_abc" },
612
- });
613
- });
614
- it("wires monday MCP", () => {
615
- const result = buildSkillMcpServers({
616
- referencedSkills: new Set(["monday"]),
617
- credentials: { MONDAY_TOKEN: "mon_abc" },
618
- });
619
- expect(result["monday"]).toEqual({
620
- type: "stdio",
621
- command: "npx",
622
- args: ["-y", "@mondaydotcomorg/monday-api-mcp@latest"],
623
- env: { MONDAY_TOKEN: "mon_abc" },
624
- });
625
- });
626
- it("wires asana MCP", () => {
627
- const result = buildSkillMcpServers({
628
- referencedSkills: new Set(["asana"]),
629
- credentials: { ASANA_ACCESS_TOKEN: "asana_abc" },
630
- });
631
- expect(result["asana"]).toEqual({
632
- type: "stdio",
633
- command: "npx",
634
- args: ["-y", "asana-mcp@latest"],
635
- env: { ASANA_ACCESS_TOKEN: "asana_abc" },
636
- });
637
- });
638
- // ── BetterStack token fallback ─────────────────────────────────────
639
- it("wires betterstack with BETTERSTACK_TELEMETRY_TOKEN fallback", () => {
640
- const result = buildSkillMcpServers({
641
- referencedSkills: new Set(["betterstack"]),
642
- credentials: { BETTERSTACK_TELEMETRY_TOKEN: "bs_tel_abc" },
643
- });
644
- expect(result["betterstack"]).toEqual({
645
- type: "http",
646
- url: "https://mcp.betterstack.com",
647
- headers: { Authorization: "Bearer bs_tel_abc" },
648
- });
649
- });
650
- it("wires betterstack with BETTERSTACK_UPTIME_TOKEN fallback", () => {
651
- const result = buildSkillMcpServers({
652
- referencedSkills: new Set(["betterstack"]),
653
- credentials: { BETTERSTACK_UPTIME_TOKEN: "bs_up_abc" },
654
- });
655
- expect(result["betterstack"]?.headers?.Authorization).toBe("Bearer bs_up_abc");
656
- });
657
- it("prefers BETTERSTACK_API_TOKEN over telemetry/uptime fallbacks", () => {
658
- const result = buildSkillMcpServers({
659
- referencedSkills: new Set(["betterstack"]),
660
- credentials: {
661
- BETTERSTACK_API_TOKEN: "primary",
662
- BETTERSTACK_TELEMETRY_TOKEN: "fallback1",
663
- BETTERSTACK_UPTIME_TOKEN: "fallback2",
664
- },
665
- });
666
- expect(result["betterstack"]?.headers?.Authorization).toBe("Bearer primary");
667
- });
668
- // ── Skill-declared MCP servers ──────────────────────────────────────
669
- it("includes skill-declared MCP servers", () => {
670
- const result = buildSkillMcpServers({
671
- referencedSkills: new Set(["our-crm"]),
672
- credentials: {},
673
- skillMcpServers: {
674
- "our-crm": {
675
- type: "stdio",
676
- command: "npx",
677
- args: ["-y", "@company/crm-server"],
678
- env: { API_KEY: "test-key" },
679
- },
680
- },
681
- });
682
- expect(result["our-crm"]).toBeDefined();
683
- expect(result["our-crm"].command).toBe("npx");
684
- });
685
- it("skill-declared MCP is only included when skill is referenced", () => {
686
- const result = buildSkillMcpServers({
687
- referencedSkills: new Set(["github"]),
688
- credentials: { GITHUB_TOKEN: "ghp_test" },
689
- skillMcpServers: {
690
- "our-crm": {
691
- type: "stdio",
692
- command: "npx",
693
- args: ["-y", "@company/crm-server"],
694
- },
695
- },
696
- });
697
- expect(result["github"]).toBeDefined();
698
- expect(result["our-crm"]).toBeUndefined();
699
- });
700
- it("user-supplied servers win over skill-declared", () => {
701
- const result = buildSkillMcpServers({
702
- referencedSkills: new Set(["our-crm"]),
703
- credentials: {},
704
- skillMcpServers: {
705
- "our-crm": {
706
- type: "stdio",
707
- command: "npx",
708
- args: ["-y", "@company/crm-server"],
709
- },
710
- },
711
- userMcpServers: {
712
- "our-crm": {
713
- type: "http",
714
- url: "https://override.example.com",
715
- },
716
- },
717
- });
718
- expect(result["our-crm"].url).toBe("https://override.example.com");
719
- });
720
- it("skill-declared and auto-wired MCPs coexist", () => {
721
- const result = buildSkillMcpServers({
722
- referencedSkills: new Set(["github", "our-crm"]),
723
- credentials: { GITHUB_TOKEN: "ghp_test" },
724
- skillMcpServers: {
725
- "our-crm": {
726
- type: "stdio",
727
- command: "npx",
728
- args: ["-y", "@company/crm-server"],
729
- },
730
- },
731
- });
732
- expect(result["github"]).toBeDefined();
733
- expect(result["our-crm"]).toBeDefined();
734
- });
735
- });