@sweny-ai/core 0.1.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.
- package/dist/__tests__/claude.test.d.ts +1 -0
- package/dist/__tests__/claude.test.js +328 -0
- package/dist/__tests__/executor.test.d.ts +1 -0
- package/dist/__tests__/executor.test.js +296 -0
- package/dist/__tests__/integration/datadog.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/datadog.integration.test.js +23 -0
- package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/e2e-workflow.integration.test.js +75 -0
- package/dist/__tests__/integration/github.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/github.integration.test.js +37 -0
- package/dist/__tests__/integration/harness.d.ts +24 -0
- package/dist/__tests__/integration/harness.js +34 -0
- package/dist/__tests__/integration/linear.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/linear.integration.test.js +15 -0
- package/dist/__tests__/integration/sentry.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/sentry.integration.test.js +20 -0
- package/dist/__tests__/integration/slack.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/slack.integration.test.js +22 -0
- package/dist/__tests__/schema.test.d.ts +1 -0
- package/dist/__tests__/schema.test.js +239 -0
- package/dist/__tests__/skills-index.test.d.ts +1 -0
- package/dist/__tests__/skills-index.test.js +122 -0
- package/dist/__tests__/skills.test.d.ts +1 -0
- package/dist/__tests__/skills.test.js +296 -0
- package/dist/__tests__/studio.test.d.ts +1 -0
- package/dist/__tests__/studio.test.js +172 -0
- package/dist/__tests__/testing.test.d.ts +1 -0
- package/dist/__tests__/testing.test.js +224 -0
- package/dist/browser.d.ts +17 -0
- package/dist/browser.js +22 -0
- package/dist/claude.d.ts +48 -0
- package/dist/claude.js +293 -0
- package/dist/cli/check.d.ts +11 -0
- package/dist/cli/check.js +237 -0
- package/dist/cli/config-file.d.ts +12 -0
- package/dist/cli/config-file.js +208 -0
- package/dist/cli/config.d.ts +77 -0
- package/dist/cli/config.js +565 -0
- package/dist/cli/main.d.ts +10 -0
- package/dist/cli/main.js +744 -0
- package/dist/cli/output.d.ts +26 -0
- package/dist/cli/output.js +357 -0
- package/dist/cli/renderer.d.ts +33 -0
- package/dist/cli/renderer.js +423 -0
- package/dist/cli/renderer.test.d.ts +1 -0
- package/dist/cli/renderer.test.js +302 -0
- package/dist/cli/setup.d.ts +11 -0
- package/dist/cli/setup.js +310 -0
- package/dist/executor.d.ts +29 -0
- package/dist/executor.js +173 -0
- package/dist/executor.test.d.ts +1 -0
- package/dist/executor.test.js +314 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +36 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.js +183 -0
- package/dist/mcp.test.d.ts +1 -0
- package/dist/mcp.test.js +334 -0
- package/dist/schema.d.ts +318 -0
- package/dist/schema.js +207 -0
- package/dist/skills/betterstack.d.ts +7 -0
- package/dist/skills/betterstack.js +114 -0
- package/dist/skills/datadog.d.ts +7 -0
- package/dist/skills/datadog.js +107 -0
- package/dist/skills/github.d.ts +8 -0
- package/dist/skills/github.js +155 -0
- package/dist/skills/index.d.ts +68 -0
- package/dist/skills/index.js +134 -0
- package/dist/skills/linear.d.ts +7 -0
- package/dist/skills/linear.js +89 -0
- package/dist/skills/notification.d.ts +11 -0
- package/dist/skills/notification.js +142 -0
- package/dist/skills/sentry.d.ts +7 -0
- package/dist/skills/sentry.js +105 -0
- package/dist/skills/slack.d.ts +8 -0
- package/dist/skills/slack.js +115 -0
- package/dist/studio.d.ts +124 -0
- package/dist/studio.js +174 -0
- package/dist/testing.d.ts +88 -0
- package/dist/testing.js +253 -0
- package/dist/types.d.ts +144 -0
- package/dist/types.js +11 -0
- package/dist/workflow-builder.d.ts +45 -0
- package/dist/workflow-builder.js +120 -0
- package/dist/workflow-builder.test.d.ts +1 -0
- package/dist/workflow-builder.test.js +117 -0
- package/dist/workflows/implement.d.ts +11 -0
- package/dist/workflows/implement.js +83 -0
- package/dist/workflows/index.d.ts +2 -0
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/triage.d.ts +18 -0
- package/dist/workflows/triage.js +108 -0
- package/package.json +83 -0
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-configure well-known MCP servers based on which providers
|
|
3
|
+
* and workspace tools the user has enabled.
|
|
4
|
+
*
|
|
5
|
+
* Category A: triggered by sourceControlProvider / issueTrackerProvider / observabilityProvider
|
|
6
|
+
* Category B: workspace tools — explicit opt-in via workspaceTools array
|
|
7
|
+
*
|
|
8
|
+
* User-supplied servers (userMcpServers) always win on key conflicts.
|
|
9
|
+
*/
|
|
10
|
+
export function buildAutoMcpServers(config) {
|
|
11
|
+
const auto = {};
|
|
12
|
+
const creds = config.credentials;
|
|
13
|
+
// ── Category A: Provider-config triggered ──────────────────────
|
|
14
|
+
// GitHub MCP — inject when using GitHub source control OR GitHub Issues tracker.
|
|
15
|
+
// @modelcontextprotocol/server-github requires GITHUB_PERSONAL_ACCESS_TOKEN.
|
|
16
|
+
const githubToken = creds.GITHUB_TOKEN;
|
|
17
|
+
if ((config.sourceControlProvider === "github" || config.issueTrackerProvider === "github-issues") && githubToken) {
|
|
18
|
+
auto["github"] = {
|
|
19
|
+
type: "stdio",
|
|
20
|
+
command: "npx",
|
|
21
|
+
args: ["-y", "@modelcontextprotocol/server-github@latest"],
|
|
22
|
+
env: { GITHUB_PERSONAL_ACCESS_TOKEN: githubToken },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// GitLab MCP — inject when source control provider is gitlab.
|
|
26
|
+
const gitlabToken = creds.GITLAB_TOKEN;
|
|
27
|
+
if (config.sourceControlProvider === "gitlab" && gitlabToken) {
|
|
28
|
+
const gitlabEnv = { GITLAB_PERSONAL_ACCESS_TOKEN: gitlabToken };
|
|
29
|
+
const baseUrl = creds.GITLAB_URL || "https://gitlab.com";
|
|
30
|
+
if (baseUrl !== "https://gitlab.com") {
|
|
31
|
+
gitlabEnv.GITLAB_API_URL = `${baseUrl}/api/v4`;
|
|
32
|
+
}
|
|
33
|
+
auto["gitlab"] = {
|
|
34
|
+
type: "stdio",
|
|
35
|
+
command: "npx",
|
|
36
|
+
args: ["-y", "@modelcontextprotocol/server-gitlab@latest"],
|
|
37
|
+
env: gitlabEnv,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
// Linear MCP — official HTTP remote MCP endpoint.
|
|
41
|
+
const linearApiKey = creds.LINEAR_API_KEY;
|
|
42
|
+
if (config.issueTrackerProvider === "linear" && linearApiKey) {
|
|
43
|
+
auto["linear"] = {
|
|
44
|
+
type: "http",
|
|
45
|
+
url: "https://mcp.linear.app/mcp",
|
|
46
|
+
headers: { Authorization: `Bearer ${linearApiKey}` },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// Jira / Confluence (Atlassian) MCP — needs all 3 credentials.
|
|
50
|
+
const jiraUrl = creds.JIRA_URL;
|
|
51
|
+
const jiraEmail = creds.JIRA_EMAIL;
|
|
52
|
+
const jiraApiToken = creds.JIRA_API_TOKEN;
|
|
53
|
+
if (config.issueTrackerProvider === "jira" && jiraUrl && jiraEmail && jiraApiToken) {
|
|
54
|
+
auto["jira"] = {
|
|
55
|
+
type: "stdio",
|
|
56
|
+
command: "npx",
|
|
57
|
+
args: ["-y", "@sooperset/mcp-atlassian@latest"],
|
|
58
|
+
env: {
|
|
59
|
+
JIRA_URL: jiraUrl,
|
|
60
|
+
JIRA_EMAIL: jiraEmail,
|
|
61
|
+
JIRA_API_TOKEN: jiraApiToken,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Datadog MCP — HTTP transport.
|
|
66
|
+
const ddApiKey = creds.DD_API_KEY;
|
|
67
|
+
const ddAppKey = creds.DD_APP_KEY;
|
|
68
|
+
if (config.observabilityProvider === "datadog" && ddApiKey && ddAppKey) {
|
|
69
|
+
auto["datadog"] = {
|
|
70
|
+
type: "http",
|
|
71
|
+
url: "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp",
|
|
72
|
+
headers: { DD_API_KEY: ddApiKey, DD_APPLICATION_KEY: ddAppKey },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Sentry MCP — @sentry/mcp-server reads SENTRY_ACCESS_TOKEN (not SENTRY_AUTH_TOKEN).
|
|
76
|
+
const sentryAuthToken = creds.SENTRY_AUTH_TOKEN;
|
|
77
|
+
if (config.observabilityProvider === "sentry" && sentryAuthToken) {
|
|
78
|
+
const sentryEnv = { SENTRY_ACCESS_TOKEN: sentryAuthToken };
|
|
79
|
+
const sentryUrl = creds.SENTRY_URL;
|
|
80
|
+
if (sentryUrl && sentryUrl !== "https://sentry.io") {
|
|
81
|
+
try {
|
|
82
|
+
sentryEnv.SENTRY_HOST = new URL(sentryUrl).hostname;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// malformed URL — leave SENTRY_HOST unset, server defaults to sentry.io
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
auto["sentry"] = {
|
|
89
|
+
type: "stdio",
|
|
90
|
+
command: "npx",
|
|
91
|
+
args: ["-y", "@sentry/mcp-server@latest"],
|
|
92
|
+
env: sentryEnv,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// New Relic MCP — HTTP transport; region-aware endpoint.
|
|
96
|
+
// Header key is "Api-Key" (not "Authorization"), unique to New Relic's MCP.
|
|
97
|
+
// Trailing slash is intentional — New Relic's MCP spec requires it.
|
|
98
|
+
const nrApiKey = creds.NEW_RELIC_API_KEY;
|
|
99
|
+
if (config.observabilityProvider === "newrelic" && nrApiKey) {
|
|
100
|
+
const nrRegion = creds.NEW_RELIC_REGION;
|
|
101
|
+
const nrEndpoint = nrRegion === "eu" ? "https://mcp.eu.newrelic.com/mcp/" : "https://mcp.newrelic.com/mcp/";
|
|
102
|
+
auto["newrelic"] = {
|
|
103
|
+
type: "http",
|
|
104
|
+
url: nrEndpoint,
|
|
105
|
+
headers: { "Api-Key": nrApiKey },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Better Stack MCP — HTTP remote MCP; Bearer token auth.
|
|
109
|
+
const bsApiToken = creds.BETTERSTACK_API_TOKEN;
|
|
110
|
+
if (config.observabilityProvider === "betterstack" && bsApiToken) {
|
|
111
|
+
auto["betterstack"] = {
|
|
112
|
+
type: "http",
|
|
113
|
+
url: "https://mcp.betterstack.com",
|
|
114
|
+
headers: { Authorization: `Bearer ${bsApiToken}` },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// ── Category B: Workspace tools (explicit opt-in) ──────────────
|
|
118
|
+
const tools = new Set(config.workspaceTools ?? []);
|
|
119
|
+
// Slack MCP — requires opt-in AND SLACK_BOT_TOKEN.
|
|
120
|
+
if (tools.has("slack")) {
|
|
121
|
+
const slackBotToken = creds.SLACK_BOT_TOKEN;
|
|
122
|
+
if (slackBotToken) {
|
|
123
|
+
const slackEnv = { SLACK_BOT_TOKEN: slackBotToken };
|
|
124
|
+
if (creds.SLACK_TEAM_ID)
|
|
125
|
+
slackEnv.SLACK_TEAM_ID = creds.SLACK_TEAM_ID;
|
|
126
|
+
auto["slack"] = {
|
|
127
|
+
type: "stdio",
|
|
128
|
+
command: "npx",
|
|
129
|
+
args: ["-y", "@modelcontextprotocol/server-slack@latest"],
|
|
130
|
+
env: slackEnv,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Notion MCP — requires opt-in; accepts NOTION_TOKEN or NOTION_API_KEY.
|
|
135
|
+
if (tools.has("notion")) {
|
|
136
|
+
const notionToken = creds.NOTION_TOKEN || creds.NOTION_API_KEY;
|
|
137
|
+
if (notionToken) {
|
|
138
|
+
auto["notion"] = {
|
|
139
|
+
type: "stdio",
|
|
140
|
+
command: "npx",
|
|
141
|
+
args: ["-y", "@notionhq/notion-mcp-server@latest"],
|
|
142
|
+
env: { NOTION_TOKEN: notionToken },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// PagerDuty MCP — HTTP remote endpoint; auth uses "Token token=<key>".
|
|
147
|
+
if (tools.has("pagerduty")) {
|
|
148
|
+
const pagerdutyToken = creds.PAGERDUTY_API_TOKEN;
|
|
149
|
+
if (pagerdutyToken) {
|
|
150
|
+
auto["pagerduty"] = {
|
|
151
|
+
type: "http",
|
|
152
|
+
url: "https://mcp.pagerduty.com/mcp",
|
|
153
|
+
headers: { Authorization: `Token token=${pagerdutyToken}` },
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Monday.com MCP — requires opt-in AND MONDAY_TOKEN.
|
|
158
|
+
if (tools.has("monday")) {
|
|
159
|
+
const mondayToken = creds.MONDAY_TOKEN;
|
|
160
|
+
if (mondayToken) {
|
|
161
|
+
auto["monday"] = {
|
|
162
|
+
type: "stdio",
|
|
163
|
+
command: "npx",
|
|
164
|
+
args: ["-y", "@mondaydotcomorg/monday-api-mcp@latest"],
|
|
165
|
+
env: { MONDAY_TOKEN: mondayToken },
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Asana MCP — requires opt-in AND ASANA_ACCESS_TOKEN.
|
|
170
|
+
if (tools.has("asana")) {
|
|
171
|
+
const asanaToken = creds.ASANA_ACCESS_TOKEN;
|
|
172
|
+
if (asanaToken) {
|
|
173
|
+
auto["asana"] = {
|
|
174
|
+
type: "stdio",
|
|
175
|
+
command: "npx",
|
|
176
|
+
args: ["-y", "asana-mcp@latest"],
|
|
177
|
+
env: { ASANA_ACCESS_TOKEN: asanaToken },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// User-supplied servers always win on key conflict.
|
|
182
|
+
return { ...auto, ...(config.userMcpServers ?? {}) };
|
|
183
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/mcp.test.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { buildAutoMcpServers } 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
|
+
observabilityProvider: "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({ observabilityProvider: "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({ observabilityProvider: "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
|
+
observabilityProvider: "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
|
+
observabilityProvider: "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
|
+
observabilityProvider: "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
|
+
observabilityProvider: "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
|
+
observabilityProvider: "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
|
+
observabilityProvider: "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
|
+
// ── Jira MCP ────────────────────────────────────────────────────
|
|
179
|
+
it("injects Jira MCP when all 3 credentials present", () => {
|
|
180
|
+
const result = buildAutoMcpServers(cfg({
|
|
181
|
+
issueTrackerProvider: "jira",
|
|
182
|
+
credentials: {
|
|
183
|
+
JIRA_URL: "https://mycompany.atlassian.net",
|
|
184
|
+
JIRA_EMAIL: "user@example.com",
|
|
185
|
+
JIRA_API_TOKEN: "jira_tok",
|
|
186
|
+
},
|
|
187
|
+
}));
|
|
188
|
+
expect(result["jira"]).toEqual({
|
|
189
|
+
type: "stdio",
|
|
190
|
+
command: "npx",
|
|
191
|
+
args: ["-y", "@sooperset/mcp-atlassian@latest"],
|
|
192
|
+
env: {
|
|
193
|
+
JIRA_URL: "https://mycompany.atlassian.net",
|
|
194
|
+
JIRA_EMAIL: "user@example.com",
|
|
195
|
+
JIRA_API_TOKEN: "jira_tok",
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
it("does NOT inject Jira MCP when JIRA_EMAIL is missing", () => {
|
|
200
|
+
const result = buildAutoMcpServers(cfg({
|
|
201
|
+
issueTrackerProvider: "jira",
|
|
202
|
+
credentials: {
|
|
203
|
+
JIRA_URL: "https://mycompany.atlassian.net",
|
|
204
|
+
JIRA_API_TOKEN: "jira_tok",
|
|
205
|
+
},
|
|
206
|
+
}));
|
|
207
|
+
expect(result["jira"]).toBeUndefined();
|
|
208
|
+
});
|
|
209
|
+
// ── Workspace tools ─────────────────────────────────────────────
|
|
210
|
+
it("injects Slack when opted in with SLACK_BOT_TOKEN", () => {
|
|
211
|
+
const result = buildAutoMcpServers(cfg({
|
|
212
|
+
workspaceTools: ["slack"],
|
|
213
|
+
credentials: { SLACK_BOT_TOKEN: "xoxb-abc" },
|
|
214
|
+
}));
|
|
215
|
+
expect(result["slack"]).toEqual({
|
|
216
|
+
type: "stdio",
|
|
217
|
+
command: "npx",
|
|
218
|
+
args: ["-y", "@modelcontextprotocol/server-slack@latest"],
|
|
219
|
+
env: { SLACK_BOT_TOKEN: "xoxb-abc" },
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
it("includes SLACK_TEAM_ID when present", () => {
|
|
223
|
+
const result = buildAutoMcpServers(cfg({
|
|
224
|
+
workspaceTools: ["slack"],
|
|
225
|
+
credentials: { SLACK_BOT_TOKEN: "xoxb-abc", SLACK_TEAM_ID: "T123" },
|
|
226
|
+
}));
|
|
227
|
+
expect(result["slack"]?.env).toMatchObject({
|
|
228
|
+
SLACK_BOT_TOKEN: "xoxb-abc",
|
|
229
|
+
SLACK_TEAM_ID: "T123",
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
it("does NOT inject Slack without opt-in even if SLACK_BOT_TOKEN is present", () => {
|
|
233
|
+
const result = buildAutoMcpServers(cfg({ credentials: { SLACK_BOT_TOKEN: "xoxb-abc" } }));
|
|
234
|
+
expect(result["slack"]).toBeUndefined();
|
|
235
|
+
});
|
|
236
|
+
it("injects Notion when opted in with NOTION_TOKEN", () => {
|
|
237
|
+
const result = buildAutoMcpServers(cfg({
|
|
238
|
+
workspaceTools: ["notion"],
|
|
239
|
+
credentials: { NOTION_TOKEN: "ntn_abc" },
|
|
240
|
+
}));
|
|
241
|
+
expect(result["notion"]).toEqual({
|
|
242
|
+
type: "stdio",
|
|
243
|
+
command: "npx",
|
|
244
|
+
args: ["-y", "@notionhq/notion-mcp-server@latest"],
|
|
245
|
+
env: { NOTION_TOKEN: "ntn_abc" },
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
it("injects Notion with NOTION_API_KEY fallback", () => {
|
|
249
|
+
const result = buildAutoMcpServers(cfg({
|
|
250
|
+
workspaceTools: ["notion"],
|
|
251
|
+
credentials: { NOTION_API_KEY: "ntn_xyz" },
|
|
252
|
+
}));
|
|
253
|
+
expect(result["notion"]?.env).toEqual({ NOTION_TOKEN: "ntn_xyz" });
|
|
254
|
+
});
|
|
255
|
+
it("injects PagerDuty when opted in with PAGERDUTY_API_TOKEN", () => {
|
|
256
|
+
const result = buildAutoMcpServers(cfg({
|
|
257
|
+
workspaceTools: ["pagerduty"],
|
|
258
|
+
credentials: { PAGERDUTY_API_TOKEN: "pd_tok" },
|
|
259
|
+
}));
|
|
260
|
+
expect(result["pagerduty"]).toEqual({
|
|
261
|
+
type: "http",
|
|
262
|
+
url: "https://mcp.pagerduty.com/mcp",
|
|
263
|
+
headers: { Authorization: "Token token=pd_tok" },
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
it("injects Monday when opted in with MONDAY_TOKEN", () => {
|
|
267
|
+
const result = buildAutoMcpServers(cfg({
|
|
268
|
+
workspaceTools: ["monday"],
|
|
269
|
+
credentials: { MONDAY_TOKEN: "mon_abc" },
|
|
270
|
+
}));
|
|
271
|
+
expect(result["monday"]).toEqual({
|
|
272
|
+
type: "stdio",
|
|
273
|
+
command: "npx",
|
|
274
|
+
args: ["-y", "@mondaydotcomorg/monday-api-mcp@latest"],
|
|
275
|
+
env: { MONDAY_TOKEN: "mon_abc" },
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
it("injects Asana when opted in with ASANA_ACCESS_TOKEN", () => {
|
|
279
|
+
const result = buildAutoMcpServers(cfg({
|
|
280
|
+
workspaceTools: ["asana"],
|
|
281
|
+
credentials: { ASANA_ACCESS_TOKEN: "asa_abc" },
|
|
282
|
+
}));
|
|
283
|
+
expect(result["asana"]).toEqual({
|
|
284
|
+
type: "stdio",
|
|
285
|
+
command: "npx",
|
|
286
|
+
args: ["-y", "asana-mcp@latest"],
|
|
287
|
+
env: { ASANA_ACCESS_TOKEN: "asa_abc" },
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
// ── User MCP servers win on conflict ────────────────────────────
|
|
291
|
+
it("user-supplied MCP servers win on key conflict", () => {
|
|
292
|
+
const customGithub = {
|
|
293
|
+
type: "stdio",
|
|
294
|
+
command: "my-custom-github-mcp",
|
|
295
|
+
args: [],
|
|
296
|
+
};
|
|
297
|
+
const result = buildAutoMcpServers(cfg({
|
|
298
|
+
sourceControlProvider: "github",
|
|
299
|
+
credentials: { GITHUB_TOKEN: "ghp_abc" },
|
|
300
|
+
userMcpServers: { github: customGithub },
|
|
301
|
+
}));
|
|
302
|
+
expect(result["github"]).toEqual(customGithub);
|
|
303
|
+
});
|
|
304
|
+
it("user-supplied servers are included alongside auto-injected ones", () => {
|
|
305
|
+
const myServer = { type: "http", url: "https://my-mcp.example.com" };
|
|
306
|
+
const result = buildAutoMcpServers(cfg({
|
|
307
|
+
sourceControlProvider: "github",
|
|
308
|
+
credentials: { GITHUB_TOKEN: "ghp_abc" },
|
|
309
|
+
userMcpServers: { "my-tool": myServer },
|
|
310
|
+
}));
|
|
311
|
+
expect(result["github"]).toBeDefined();
|
|
312
|
+
expect(result["my-tool"]).toEqual(myServer);
|
|
313
|
+
});
|
|
314
|
+
// ── Multiple providers simultaneously ───────────────────────────
|
|
315
|
+
it("injects multiple providers simultaneously", () => {
|
|
316
|
+
const result = buildAutoMcpServers(cfg({
|
|
317
|
+
sourceControlProvider: "github",
|
|
318
|
+
issueTrackerProvider: "linear",
|
|
319
|
+
observabilityProvider: "datadog",
|
|
320
|
+
credentials: {
|
|
321
|
+
GITHUB_TOKEN: "ghp_abc",
|
|
322
|
+
LINEAR_API_KEY: "lin_key_abc",
|
|
323
|
+
DD_API_KEY: "dd_api",
|
|
324
|
+
DD_APP_KEY: "dd_app",
|
|
325
|
+
},
|
|
326
|
+
workspaceTools: ["slack"],
|
|
327
|
+
// No SLACK_BOT_TOKEN — should not inject slack
|
|
328
|
+
}));
|
|
329
|
+
expect(result["github"]).toBeDefined();
|
|
330
|
+
expect(result["linear"]).toBeDefined();
|
|
331
|
+
expect(result["datadog"]).toBeDefined();
|
|
332
|
+
expect(result["slack"]).toBeUndefined();
|
|
333
|
+
});
|
|
334
|
+
});
|