@nimbus-dev/connectors 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/LICENSE +662 -0
- package/NOTICE +40 -0
- package/README.md +111 -0
- package/connectors/airflow/nimbus.extension.json +16 -0
- package/connectors/airflow/src/search-filter.ts +38 -0
- package/connectors/airflow/src/server.ts +91 -0
- package/connectors/apple/nimbus.extension.json +40 -0
- package/connectors/apple/src/apple-mail-core.ts +43 -0
- package/connectors/apple/src/caldav-core.ts +116 -0
- package/connectors/apple/src/calendar-tools.ts +278 -0
- package/connectors/apple/src/server.ts +541 -0
- package/connectors/apple/src/tools.ts +138 -0
- package/connectors/argocd/nimbus.extension.json +16 -0
- package/connectors/argocd/src/search-filter.ts +24 -0
- package/connectors/argocd/src/server.ts +158 -0
- package/connectors/athena/nimbus.extension.json +15 -0
- package/connectors/athena/src/server.ts +6 -0
- package/connectors/athena/src/tools.ts +181 -0
- package/connectors/aws/nimbus.extension.json +21 -0
- package/connectors/aws/src/server.ts +191 -0
- package/connectors/azure/nimbus.extension.json +15 -0
- package/connectors/azure/src/server.ts +139 -0
- package/connectors/bigeye/nimbus.extension.json +16 -0
- package/connectors/bigeye/src/search-filter.ts +22 -0
- package/connectors/bigeye/src/server.ts +161 -0
- package/connectors/bigquery/nimbus.extension.json +15 -0
- package/connectors/bigquery/src/server.ts +6 -0
- package/connectors/bigquery/src/tools.ts +175 -0
- package/connectors/bitbucket/nimbus.extension.json +15 -0
- package/connectors/bitbucket/src/server.ts +252 -0
- package/connectors/bitrise/nimbus.extension.json +15 -0
- package/connectors/bitrise/src/search-filter.ts +11 -0
- package/connectors/bitrise/src/server.ts +83 -0
- package/connectors/canva/nimbus.extension.json +15 -0
- package/connectors/canva/src/search-filter.ts +23 -0
- package/connectors/canva/src/server.ts +77 -0
- package/connectors/circleci/nimbus.extension.json +15 -0
- package/connectors/circleci/src/server.ts +184 -0
- package/connectors/cloud-logging/nimbus.extension.json +15 -0
- package/connectors/cloud-logging/src/server.ts +6 -0
- package/connectors/cloud-logging/src/tools.ts +115 -0
- package/connectors/cloudwatch/nimbus.extension.json +15 -0
- package/connectors/cloudwatch/src/server.ts +6 -0
- package/connectors/cloudwatch/src/tools.ts +144 -0
- package/connectors/codemagic/nimbus.extension.json +15 -0
- package/connectors/codemagic/src/search-filter.ts +11 -0
- package/connectors/codemagic/src/server.ts +74 -0
- package/connectors/confluence/nimbus.extension.json +15 -0
- package/connectors/confluence/src/kb-append.ts +25 -0
- package/connectors/confluence/src/server.ts +309 -0
- package/connectors/dagster/nimbus.extension.json +16 -0
- package/connectors/dagster/src/search-filter.ts +53 -0
- package/connectors/dagster/src/server.ts +218 -0
- package/connectors/databricks/nimbus.extension.json +16 -0
- package/connectors/databricks/src/search-filter.ts +28 -0
- package/connectors/databricks/src/server.ts +81 -0
- package/connectors/datadog/nimbus.extension.json +15 -0
- package/connectors/datadog/src/server.ts +56 -0
- package/connectors/dataprofile/nimbus.extension.json +16 -0
- package/connectors/dataprofile/src/profile.ts +298 -0
- package/connectors/dataprofile/src/server.ts +6 -0
- package/connectors/dataprofile/src/tools.ts +69 -0
- package/connectors/dbt/nimbus.extension.json +16 -0
- package/connectors/dbt/src/search-filter.ts +28 -0
- package/connectors/dbt/src/server.ts +96 -0
- package/connectors/dependencytrack/nimbus.extension.json +16 -0
- package/connectors/dependencytrack/src/search-filter.ts +24 -0
- package/connectors/dependencytrack/src/server.ts +80 -0
- package/connectors/discord/nimbus.extension.json +15 -0
- package/connectors/discord/src/server.ts +93 -0
- package/connectors/elasticsearch/nimbus.extension.json +16 -0
- package/connectors/elasticsearch/src/server.ts +6 -0
- package/connectors/elasticsearch/src/tools.ts +110 -0
- package/connectors/fastmail/nimbus.extension.json +16 -0
- package/connectors/fastmail/src/jmap-core.ts +86 -0
- package/connectors/fastmail/src/server.ts +194 -0
- package/connectors/fastmail/src/tools.ts +143 -0
- package/connectors/figma/nimbus.extension.json +15 -0
- package/connectors/figma/src/search-filter.ts +24 -0
- package/connectors/figma/src/server.ts +117 -0
- package/connectors/firebase/nimbus.extension.json +15 -0
- package/connectors/firebase/src/search-filter.ts +30 -0
- package/connectors/firebase/src/server.ts +94 -0
- package/connectors/firebase/src/token.ts +41 -0
- package/connectors/flagsmith/nimbus.extension.json +15 -0
- package/connectors/flagsmith/src/search-filter.ts +29 -0
- package/connectors/flagsmith/src/server.ts +102 -0
- package/connectors/flux/nimbus.extension.json +16 -0
- package/connectors/flux/src/search-filter.ts +42 -0
- package/connectors/flux/src/server.ts +182 -0
- package/connectors/gcp/nimbus.extension.json +15 -0
- package/connectors/gcp/src/server.ts +140 -0
- package/connectors/github/nimbus.extension.json +15 -0
- package/connectors/github/src/server.ts +347 -0
- package/connectors/github-actions/nimbus.extension.json +15 -0
- package/connectors/github-actions/src/server.ts +213 -0
- package/connectors/gitlab/nimbus.extension.json +15 -0
- package/connectors/gitlab/src/server.ts +392 -0
- package/connectors/gmail/nimbus.extension.json +15 -0
- package/connectors/gmail/src/server.ts +277 -0
- package/connectors/google-drive/nimbus.extension.json +15 -0
- package/connectors/google-drive/src/drive-query.ts +3 -0
- package/connectors/google-drive/src/server.ts +531 -0
- package/connectors/google-meet/nimbus.extension.json +15 -0
- package/connectors/google-meet/src/server.ts +89 -0
- package/connectors/google-photos/nimbus.extension.json +15 -0
- package/connectors/google-photos/src/server.ts +139 -0
- package/connectors/grafana/nimbus.extension.json +15 -0
- package/connectors/grafana/src/server.ts +61 -0
- package/connectors/great-expectations/nimbus.extension.json +16 -0
- package/connectors/great-expectations/src/gx-parse.ts +292 -0
- package/connectors/great-expectations/src/server.ts +6 -0
- package/connectors/great-expectations/src/tools.ts +91 -0
- package/connectors/greenhouse/nimbus.extension.json +15 -0
- package/connectors/greenhouse/src/search-filter.ts +65 -0
- package/connectors/greenhouse/src/server.ts +67 -0
- package/connectors/hubspot/nimbus.extension.json +15 -0
- package/connectors/hubspot/src/search-filter.ts +28 -0
- package/connectors/hubspot/src/server.ts +81 -0
- package/connectors/iac/nimbus.extension.json +14 -0
- package/connectors/iac/src/server.ts +145 -0
- package/connectors/imap/nimbus.extension.json +16 -0
- package/connectors/imap/src/imap-core.ts +110 -0
- package/connectors/imap/src/server.ts +232 -0
- package/connectors/imap/src/tools.ts +40 -0
- package/connectors/intercom/nimbus.extension.json +15 -0
- package/connectors/intercom/src/search-filter.ts +46 -0
- package/connectors/intercom/src/server.ts +65 -0
- package/connectors/jenkins/nimbus.extension.json +15 -0
- package/connectors/jenkins/src/jenkins-api.ts +156 -0
- package/connectors/jenkins/src/server.ts +223 -0
- package/connectors/jira/nimbus.extension.json +15 -0
- package/connectors/jira/src/server.ts +309 -0
- package/connectors/kubernetes/nimbus.extension.json +15 -0
- package/connectors/kubernetes/src/server.ts +163 -0
- package/connectors/launchdarkly/nimbus.extension.json +15 -0
- package/connectors/launchdarkly/src/search-filter.ts +31 -0
- package/connectors/launchdarkly/src/server.ts +79 -0
- package/connectors/lever/nimbus.extension.json +15 -0
- package/connectors/lever/src/search-filter.ts +35 -0
- package/connectors/lever/src/server.ts +64 -0
- package/connectors/linear/nimbus.extension.json +15 -0
- package/connectors/linear/src/server.ts +467 -0
- package/connectors/localdb/nimbus.extension.json +16 -0
- package/connectors/localdb/src/server.ts +6 -0
- package/connectors/localdb/src/sql-scan.ts +158 -0
- package/connectors/localdb/src/tools.ts +67 -0
- package/connectors/looker/nimbus.extension.json +16 -0
- package/connectors/looker/src/search-filter.ts +22 -0
- package/connectors/looker/src/server.ts +254 -0
- package/connectors/mendeley/nimbus.extension.json +15 -0
- package/connectors/mendeley/src/search-filter.ts +68 -0
- package/connectors/mendeley/src/server.ts +59 -0
- package/connectors/mercury/nimbus.extension.json +15 -0
- package/connectors/mercury/src/search-filter.ts +11 -0
- package/connectors/mercury/src/server.ts +61 -0
- package/connectors/metabase/nimbus.extension.json +16 -0
- package/connectors/metabase/src/search-filter.ts +22 -0
- package/connectors/metabase/src/server.ts +83 -0
- package/connectors/miro/nimbus.extension.json +15 -0
- package/connectors/miro/src/search-filter.ts +24 -0
- package/connectors/miro/src/server.ts +76 -0
- package/connectors/mlflow/nimbus.extension.json +16 -0
- package/connectors/mlflow/src/search-filter.ts +38 -0
- package/connectors/mlflow/src/server.ts +167 -0
- package/connectors/monte-carlo/nimbus.extension.json +16 -0
- package/connectors/monte-carlo/src/search-filter.ts +11 -0
- package/connectors/monte-carlo/src/server.ts +241 -0
- package/connectors/netlify/nimbus.extension.json +15 -0
- package/connectors/netlify/src/search-filter.ts +37 -0
- package/connectors/netlify/src/server.ts +64 -0
- package/connectors/newrelic/nimbus.extension.json +15 -0
- package/connectors/newrelic/src/server.ts +48 -0
- package/connectors/notion/nimbus.extension.json +15 -0
- package/connectors/notion/src/kb-append.ts +23 -0
- package/connectors/notion/src/server.ts +343 -0
- package/connectors/obsidian/nimbus.extension.json +19 -0
- package/connectors/obsidian/src/server.ts +433 -0
- package/connectors/onedrive/nimbus.extension.json +15 -0
- package/connectors/onedrive/src/server.ts +256 -0
- package/connectors/outlook/nimbus.extension.json +15 -0
- package/connectors/outlook/src/server.ts +358 -0
- package/connectors/outlook/src/tool-scope-policy.ts +53 -0
- package/connectors/pagerduty/nimbus.extension.json +15 -0
- package/connectors/pagerduty/src/server.ts +140 -0
- package/connectors/pipedrive/nimbus.extension.json +15 -0
- package/connectors/pipedrive/src/search-filter.ts +11 -0
- package/connectors/pipedrive/src/server.ts +62 -0
- package/connectors/powerbi/nimbus.extension.json +16 -0
- package/connectors/powerbi/src/search-filter.ts +22 -0
- package/connectors/powerbi/src/server.ts +249 -0
- package/connectors/prefect/nimbus.extension.json +16 -0
- package/connectors/prefect/src/search-filter.ts +40 -0
- package/connectors/prefect/src/server.ts +94 -0
- package/connectors/protonmail/nimbus.extension.json +16 -0
- package/connectors/protonmail/src/mail-core.ts +96 -0
- package/connectors/protonmail/src/server.ts +231 -0
- package/connectors/protonmail/src/tools.ts +45 -0
- package/connectors/raindrop/nimbus.extension.json +15 -0
- package/connectors/raindrop/src/search-filter.ts +20 -0
- package/connectors/raindrop/src/server.ts +107 -0
- package/connectors/ramp/nimbus.extension.json +15 -0
- package/connectors/ramp/src/search-filter.ts +37 -0
- package/connectors/ramp/src/server.ts +109 -0
- package/connectors/readwise/nimbus.extension.json +15 -0
- package/connectors/readwise/src/search-filter.ts +64 -0
- package/connectors/readwise/src/server.ts +92 -0
- package/connectors/sagemaker/nimbus.extension.json +15 -0
- package/connectors/sagemaker/src/server.ts +6 -0
- package/connectors/sagemaker/src/tools.ts +121 -0
- package/connectors/salesforce/nimbus.extension.json +15 -0
- package/connectors/salesforce/src/search-filter.ts +23 -0
- package/connectors/salesforce/src/server.ts +94 -0
- package/connectors/semgrep/nimbus.extension.json +15 -0
- package/connectors/semgrep/src/search-filter.ts +31 -0
- package/connectors/semgrep/src/server.ts +108 -0
- package/connectors/sentry/nimbus.extension.json +15 -0
- package/connectors/sentry/src/server.ts +66 -0
- package/connectors/slack/nimbus.extension.json +15 -0
- package/connectors/slack/src/server.ts +360 -0
- package/connectors/snowflake/nimbus.extension.json +16 -0
- package/connectors/snowflake/src/search-filter.ts +26 -0
- package/connectors/snowflake/src/server.ts +236 -0
- package/connectors/snyk/nimbus.extension.json +15 -0
- package/connectors/snyk/src/search-filter.ts +28 -0
- package/connectors/snyk/src/server.ts +144 -0
- package/connectors/sonarqube/nimbus.extension.json +15 -0
- package/connectors/sonarqube/src/search-filter.ts +28 -0
- package/connectors/sonarqube/src/server.ts +115 -0
- package/connectors/stackoverflow/nimbus.extension.json +15 -0
- package/connectors/stackoverflow/src/search-filter.ts +52 -0
- package/connectors/stackoverflow/src/server.ts +81 -0
- package/connectors/storybook/nimbus.extension.json +16 -0
- package/connectors/storybook/src/server.ts +6 -0
- package/connectors/storybook/src/storybook-parse.ts +67 -0
- package/connectors/storybook/src/tools.ts +68 -0
- package/connectors/stripe/nimbus.extension.json +15 -0
- package/connectors/stripe/src/search-filter.ts +19 -0
- package/connectors/stripe/src/server.ts +65 -0
- package/connectors/superset/nimbus.extension.json +16 -0
- package/connectors/superset/src/search-filter.ts +22 -0
- package/connectors/superset/src/server.ts +119 -0
- package/connectors/tableau/nimbus.extension.json +16 -0
- package/connectors/tableau/src/search-filter.ts +22 -0
- package/connectors/tableau/src/server.ts +248 -0
- package/connectors/teams/nimbus.extension.json +20 -0
- package/connectors/teams/src/bot-send.ts +72 -0
- package/connectors/teams/src/server.ts +257 -0
- package/connectors/testflight/nimbus.extension.json +15 -0
- package/connectors/testflight/src/jwt.ts +27 -0
- package/connectors/testflight/src/search-filter.ts +30 -0
- package/connectors/testflight/src/server.ts +74 -0
- package/connectors/vercel/nimbus.extension.json +15 -0
- package/connectors/vercel/src/search-filter.ts +34 -0
- package/connectors/vercel/src/server.ts +79 -0
- package/connectors/vertex-ai/nimbus.extension.json +15 -0
- package/connectors/vertex-ai/src/server.ts +6 -0
- package/connectors/vertex-ai/src/tools.ts +145 -0
- package/connectors/wiz/nimbus.extension.json +15 -0
- package/connectors/wiz/src/search-filter.ts +50 -0
- package/connectors/wiz/src/server.ts +192 -0
- package/connectors/workday/nimbus.extension.json +19 -0
- package/connectors/workday/src/search-filter.ts +5 -0
- package/connectors/workday/src/server.ts +78 -0
- package/connectors/workday/src/tools.ts +1 -0
- package/connectors/zendesk/nimbus.extension.json +16 -0
- package/connectors/zendesk/src/search-filter.ts +11 -0
- package/connectors/zendesk/src/server.ts +77 -0
- package/connectors/zoom/nimbus.extension.json +15 -0
- package/connectors/zoom/src/encode-meeting-path.ts +5 -0
- package/connectors/zoom/src/recordings-window.ts +34 -0
- package/connectors/zoom/src/search-filter.ts +42 -0
- package/connectors/zoom/src/server.ts +98 -0
- package/connectors/zotero/nimbus.extension.json +15 -0
- package/connectors/zotero/src/search-filter.ts +81 -0
- package/connectors/zotero/src/server.ts +82 -0
- package/package.json +152 -0
- package/shared/atlassian-json-fetch.ts +42 -0
- package/shared/audit-chain.ts +133 -0
- package/shared/connector-mode.ts +60 -0
- package/shared/consent-kit.ts +323 -0
- package/shared/fetch-bearer-json.ts +18 -0
- package/shared/header-safe.ts +28 -0
- package/shared/imap-bodystructure.ts +113 -0
- package/shared/imap-mail-core.ts +57 -0
- package/shared/imap-tool-kit.ts +293 -0
- package/shared/join-api-path.ts +18 -0
- package/shared/kb-markdown.ts +142 -0
- package/shared/mcp-search-tool.ts +37 -0
- package/shared/mcp-tool-kit.ts +35 -0
- package/shared/nimbus-spawn.ts +128 -0
- package/shared/rest-tool-kit.ts +18 -0
- package/shared/run-cli-json.ts +55 -0
- package/shared/run-read-only-mcp-connector.ts +59 -0
- package/shared/safe-cli-arg.ts +55 -0
- package/shared/search-filter.ts +140 -0
- package/shared/strip-trailing-slashes.ts +7 -0
- package/shared/write-scope.ts +58 -0
- package/standalone/src/bin.ts +19 -0
- package/standalone/src/launcher.ts +186 -0
- package/standalone/tsconfig.json +8 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
5
|
+
import { joinApiPath } from "../../../shared/join-api-path.ts";
|
|
6
|
+
import {
|
|
7
|
+
createRegisterSimpleTool,
|
|
8
|
+
createZodToolRegistrar,
|
|
9
|
+
mcpJsonResultIfOk,
|
|
10
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
11
|
+
|
|
12
|
+
const BB_API = "https://api.bitbucket.org/2.0";
|
|
13
|
+
|
|
14
|
+
function requireUsername(): string {
|
|
15
|
+
const t = process.env["BITBUCKET_USERNAME"];
|
|
16
|
+
if (t === undefined || t === "") {
|
|
17
|
+
throw new Error("BITBUCKET_USERNAME is not set");
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function requireAppPassword(): string {
|
|
23
|
+
const t = process.env["BITBUCKET_APP_PASSWORD"];
|
|
24
|
+
if (t === undefined || t === "") {
|
|
25
|
+
throw new Error("BITBUCKET_APP_PASSWORD is not set");
|
|
26
|
+
}
|
|
27
|
+
return t;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function basicAuthHeader(): string {
|
|
31
|
+
const user = requireUsername();
|
|
32
|
+
const pass = requireAppPassword();
|
|
33
|
+
const b = Buffer.from(`${user}:${pass}`, "utf8").toString("base64");
|
|
34
|
+
return `Basic ${b}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function splitRepoFull(full: string): { workspace: string; repoSlug: string } {
|
|
38
|
+
const i = full.indexOf("/");
|
|
39
|
+
if (i <= 0 || i === full.length - 1) {
|
|
40
|
+
throw new Error("repoFull must be workspace/repo_slug");
|
|
41
|
+
}
|
|
42
|
+
return { workspace: full.slice(0, i), repoSlug: full.slice(i + 1) };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function bbFetch(
|
|
46
|
+
path: string,
|
|
47
|
+
init?: RequestInit,
|
|
48
|
+
): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
|
|
49
|
+
const url = joinApiPath(BB_API, path);
|
|
50
|
+
const baseHeaders: Record<string, string> = {
|
|
51
|
+
Authorization: basicAuthHeader(),
|
|
52
|
+
Accept: "application/json",
|
|
53
|
+
};
|
|
54
|
+
const extra = init?.headers as Record<string, string> | undefined;
|
|
55
|
+
const headers = extra === undefined ? baseHeaders : { ...baseHeaders, ...extra };
|
|
56
|
+
const res = await fetch(url, {
|
|
57
|
+
...init,
|
|
58
|
+
headers,
|
|
59
|
+
});
|
|
60
|
+
const text = await res.text();
|
|
61
|
+
let json: unknown;
|
|
62
|
+
try {
|
|
63
|
+
json = JSON.parse(text) as unknown;
|
|
64
|
+
} catch {
|
|
65
|
+
json = null;
|
|
66
|
+
}
|
|
67
|
+
return { ok: res.ok, status: res.status, json, text };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const server = new McpServer({ name: "nimbus-bitbucket", version: "0.1.0" });
|
|
71
|
+
|
|
72
|
+
const registerSimpleTool = createRegisterSimpleTool(server);
|
|
73
|
+
const reg = createZodToolRegistrar(registerSimpleTool);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Every MUTATING bitbucket tool goes through here. Outside the gateway this adds the
|
|
77
|
+
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
78
|
+
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
79
|
+
*/
|
|
80
|
+
const registerWriteTool = createWriteToolRegistrar(server, {
|
|
81
|
+
connector: "bitbucket",
|
|
82
|
+
scopeEnv: "NIMBUS_MCP_BITBUCKET_WRITE_SCOPE",
|
|
83
|
+
scopeKinds: ["repo"],
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const repoFullArg = z.object({
|
|
87
|
+
repoFull: z
|
|
88
|
+
.string()
|
|
89
|
+
.min(3)
|
|
90
|
+
.describe("Repository full name: workspace/repo_slug (e.g. myteam/my-service)"),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const bitbucketRepoListSchema = z.object({
|
|
94
|
+
pagelen: z.number().int().min(1).max(100).optional(),
|
|
95
|
+
page: z.string().max(2000).optional().describe("Opaque page URL or token from a prior next link"),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
reg(
|
|
99
|
+
"bitbucket_repo_list",
|
|
100
|
+
"List repositories where the authenticated user is a member.",
|
|
101
|
+
bitbucketRepoListSchema,
|
|
102
|
+
async (parsed) => {
|
|
103
|
+
if (parsed.page?.startsWith("http")) {
|
|
104
|
+
const res = await bbFetch(parsed.page);
|
|
105
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
106
|
+
}
|
|
107
|
+
const qs = new URLSearchParams();
|
|
108
|
+
qs.set("role", "member");
|
|
109
|
+
qs.set("pagelen", String(parsed.pagelen ?? 30));
|
|
110
|
+
const res = await bbFetch(`/repositories?${qs.toString()}`);
|
|
111
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const bitbucketPrListSchema = repoFullArg.extend({
|
|
116
|
+
state: z.enum(["OPEN", "MERGED", "DECLINED", "SUPERSEDED"]).optional(),
|
|
117
|
+
pagelen: z.number().int().min(1).max(100).optional(),
|
|
118
|
+
page: z.string().max(2000).optional().describe("Opaque next URL from a prior response"),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
reg(
|
|
122
|
+
"bitbucket_pr_list",
|
|
123
|
+
"List pull requests for a repository.",
|
|
124
|
+
bitbucketPrListSchema,
|
|
125
|
+
async (parsed) => {
|
|
126
|
+
if (parsed.page?.startsWith("http")) {
|
|
127
|
+
const res = await bbFetch(parsed.page);
|
|
128
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
129
|
+
}
|
|
130
|
+
const { workspace, repoSlug } = splitRepoFull(parsed.repoFull);
|
|
131
|
+
const base = `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repoSlug)}/pullrequests`;
|
|
132
|
+
const qs = new URLSearchParams();
|
|
133
|
+
qs.set("pagelen", String(parsed.pagelen ?? 30));
|
|
134
|
+
qs.set("sort", "-updated_on");
|
|
135
|
+
if (parsed.state !== undefined) {
|
|
136
|
+
qs.set("q", `state="${parsed.state}"`);
|
|
137
|
+
}
|
|
138
|
+
const res = await bbFetch(`${base}?${qs.toString()}`);
|
|
139
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const bitbucketPrGetSchema = repoFullArg.extend({
|
|
144
|
+
pullRequestId: z.number().int().min(1),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
reg(
|
|
148
|
+
"bitbucket_pr_get",
|
|
149
|
+
"Get a single pull request by numeric id.",
|
|
150
|
+
bitbucketPrGetSchema,
|
|
151
|
+
async (parsed) => {
|
|
152
|
+
const { workspace, repoSlug } = splitRepoFull(parsed.repoFull);
|
|
153
|
+
const path = `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repoSlug)}/pullrequests/${String(parsed.pullRequestId)}`;
|
|
154
|
+
const res = await bbFetch(path);
|
|
155
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const bitbucketPrMergeSchema = repoFullArg.extend({
|
|
160
|
+
pullRequestId: z.number().int().min(1),
|
|
161
|
+
mergeStrategy: z.enum(["merge_commit", "squash", "fast_forward"]).optional(),
|
|
162
|
+
message: z.string().max(32_768).optional(),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
registerWriteTool(
|
|
166
|
+
"bitbucket_pr_merge",
|
|
167
|
+
{
|
|
168
|
+
mutates: "bitbucket.pr.merge",
|
|
169
|
+
recoverable: true,
|
|
170
|
+
scopeTargetOf: (p) => ({ kind: "repo", value: p.repoFull }),
|
|
171
|
+
},
|
|
172
|
+
"Merge a pull request.",
|
|
173
|
+
bitbucketPrMergeSchema,
|
|
174
|
+
async (parsed) => {
|
|
175
|
+
const { workspace, repoSlug } = splitRepoFull(parsed.repoFull);
|
|
176
|
+
const path = `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repoSlug)}/pullrequests/${String(parsed.pullRequestId)}/merge`;
|
|
177
|
+
const body: Record<string, unknown> = { type: "pullrequest" };
|
|
178
|
+
if (parsed.mergeStrategy !== undefined) {
|
|
179
|
+
body["merge_strategy"] = parsed.mergeStrategy;
|
|
180
|
+
}
|
|
181
|
+
if (parsed.message !== undefined) {
|
|
182
|
+
body["message"] = parsed.message;
|
|
183
|
+
}
|
|
184
|
+
const res = await bbFetch(path, {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
body: JSON.stringify(body),
|
|
188
|
+
});
|
|
189
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const bitbucketRepoPagedSchema = repoFullArg.extend({
|
|
194
|
+
pagelen: z.number().int().min(1).max(100).optional(),
|
|
195
|
+
page: z.string().max(2000).optional(),
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
reg(
|
|
199
|
+
"bitbucket_pipeline_list",
|
|
200
|
+
"List Pipelines runs for a repository.",
|
|
201
|
+
bitbucketRepoPagedSchema,
|
|
202
|
+
async (parsed) => {
|
|
203
|
+
if (parsed.page?.startsWith("http")) {
|
|
204
|
+
const res = await bbFetch(parsed.page);
|
|
205
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
206
|
+
}
|
|
207
|
+
const { workspace, repoSlug } = splitRepoFull(parsed.repoFull);
|
|
208
|
+
const base = `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repoSlug)}/pipelines/`;
|
|
209
|
+
const qs = new URLSearchParams();
|
|
210
|
+
qs.set("pagelen", String(parsed.pagelen ?? 30));
|
|
211
|
+
const res = await bbFetch(`${base}?${qs.toString()}`);
|
|
212
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const bitbucketPipelineGetSchema = repoFullArg.extend({
|
|
217
|
+
pipelineUuid: z.string().min(8).max(128),
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
reg(
|
|
221
|
+
"bitbucket_pipeline_get",
|
|
222
|
+
"Get a single pipeline run by UUID.",
|
|
223
|
+
bitbucketPipelineGetSchema,
|
|
224
|
+
async (parsed) => {
|
|
225
|
+
const { workspace, repoSlug } = splitRepoFull(parsed.repoFull);
|
|
226
|
+
const encUuid = encodeURIComponent(parsed.pipelineUuid);
|
|
227
|
+
const path = `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repoSlug)}/pipelines/${encUuid}`;
|
|
228
|
+
const res = await bbFetch(path);
|
|
229
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
230
|
+
},
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
reg(
|
|
234
|
+
"bitbucket_issue_list",
|
|
235
|
+
"List issues for a repository (issue tracker must be enabled).",
|
|
236
|
+
bitbucketRepoPagedSchema,
|
|
237
|
+
async (parsed) => {
|
|
238
|
+
if (parsed.page?.startsWith("http")) {
|
|
239
|
+
const res = await bbFetch(parsed.page);
|
|
240
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
241
|
+
}
|
|
242
|
+
const { workspace, repoSlug } = splitRepoFull(parsed.repoFull);
|
|
243
|
+
const base = `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repoSlug)}/issues`;
|
|
244
|
+
const qs = new URLSearchParams();
|
|
245
|
+
qs.set("pagelen", String(parsed.pagelen ?? 30));
|
|
246
|
+
const res = await bbFetch(`${base}?${qs.toString()}`);
|
|
247
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const transport = new StdioServerTransport();
|
|
252
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.bitrise",
|
|
3
|
+
"displayName": "Bitrise",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Bitrise REST connector (mobile CI builds + apps). Read-only; surfaces build status, workflow id, branch and commit so the local index can correlate mobile CI runs back to PRs and incidents.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["api.bitrise.io"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": [],
|
|
13
|
+
"syncInterval": 600,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fieldsFromKeys,
|
|
3
|
+
makeQueryFilter,
|
|
4
|
+
type SearchMatchOptions,
|
|
5
|
+
} from "../../../shared/search-filter.ts";
|
|
6
|
+
|
|
7
|
+
export type BitriseSearchMatchOptions = SearchMatchOptions;
|
|
8
|
+
|
|
9
|
+
export const filterBitriseBuilds = makeQueryFilter(
|
|
10
|
+
fieldsFromKeys(["branch", "commit_message", "triggered_workflow", "status_text"]),
|
|
11
|
+
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { matchesResult } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
4
|
+
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
+
import { filterBitriseBuilds } from "./search-filter.ts";
|
|
6
|
+
|
|
7
|
+
const BITRISE_API = "https://api.bitrise.io";
|
|
8
|
+
|
|
9
|
+
function authHeader(): Record<string, string> {
|
|
10
|
+
const t = process.env["BITRISE_TOKEN"]?.trim();
|
|
11
|
+
if (t === undefined || t === "") {
|
|
12
|
+
throw new Error("BITRISE_TOKEN is not set");
|
|
13
|
+
}
|
|
14
|
+
return { Authorization: t, Accept: "application/json" };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function bitriseGet(path: string): Promise<unknown> {
|
|
18
|
+
const res = await fetch(`${BITRISE_API}${path}`, { headers: authHeader() });
|
|
19
|
+
const text = await res.text();
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
throw new Error(`Bitrise ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
22
|
+
}
|
|
23
|
+
return JSON.parse(text) as unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await runReadOnlyMcpConnector("nimbus-bitrise", (reg) => {
|
|
27
|
+
reg(
|
|
28
|
+
"bitrise_list",
|
|
29
|
+
"List Bitrise resources. When `appSlug` is omitted, returns the user's accessible apps via `/v0.1/me/apps`. When `appSlug` is provided, returns the app's recent builds via `/v0.1/apps/<slug>/builds` (status filter and limit optional).",
|
|
30
|
+
z.object({
|
|
31
|
+
appSlug: z.string().min(1).optional(),
|
|
32
|
+
status: z.enum(["not-finished", "successful", "failed", "aborted"]).optional(),
|
|
33
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
34
|
+
}),
|
|
35
|
+
async (p) => {
|
|
36
|
+
if (p.appSlug === undefined) {
|
|
37
|
+
const limit = p.limit ?? 50;
|
|
38
|
+
return jsonResult(await bitriseGet(`/v0.1/me/apps?limit=${String(limit)}`));
|
|
39
|
+
}
|
|
40
|
+
const params: string[] = [];
|
|
41
|
+
params.push(`limit=${String(p.limit ?? 50)}`);
|
|
42
|
+
if (p.status !== undefined) {
|
|
43
|
+
const map = { "not-finished": 0, successful: 1, failed: 2, aborted: 3 } as const;
|
|
44
|
+
params.push(`status=${String(map[p.status])}`);
|
|
45
|
+
}
|
|
46
|
+
const qs = `?${params.join("&")}`;
|
|
47
|
+
return jsonResult(
|
|
48
|
+
await bitriseGet(`/v0.1/apps/${encodeURIComponent(p.appSlug)}/builds${qs}`),
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
reg(
|
|
54
|
+
"bitrise_get",
|
|
55
|
+
"Fetch a single Bitrise resource. When `buildSlug` is provided, returns the build via `/v0.1/apps/<appSlug>/builds/<buildSlug>`. When omitted, returns the app via `/v0.1/apps/<appSlug>`.",
|
|
56
|
+
z.object({
|
|
57
|
+
appSlug: z.string().min(1),
|
|
58
|
+
buildSlug: z.string().min(1).optional(),
|
|
59
|
+
}),
|
|
60
|
+
async (p) => {
|
|
61
|
+
if (p.buildSlug === undefined) {
|
|
62
|
+
return jsonResult(await bitriseGet(`/v0.1/apps/${encodeURIComponent(p.appSlug)}`));
|
|
63
|
+
}
|
|
64
|
+
const path = `/v0.1/apps/${encodeURIComponent(p.appSlug)}/builds/${encodeURIComponent(p.buildSlug)}`;
|
|
65
|
+
return jsonResult(await bitriseGet(path));
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
reg(
|
|
70
|
+
"bitrise_search",
|
|
71
|
+
"Substring search across a Bitrise app's recent builds. Matches the query against `branch`, `commit_message`, `triggered_workflow`, and `status_text` (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
72
|
+
z.object({
|
|
73
|
+
appSlug: z.string().min(1),
|
|
74
|
+
query: z.string().min(1),
|
|
75
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
76
|
+
}),
|
|
77
|
+
async (p) => {
|
|
78
|
+
const root = await bitriseGet(`/v0.1/apps/${encodeURIComponent(p.appSlug)}/builds?limit=50`);
|
|
79
|
+
const data = (root as { data?: unknown[] } | null)?.data;
|
|
80
|
+
return matchesResult(data, filterBitriseBuilds, p);
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.canva",
|
|
3
|
+
"displayName": "Canva",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Canva connector (read-only). Surfaces the authenticated user's designs as canva:design items in the local index via GET /rest/v1/designs — the design title, the create timestamp, the last-modified timestamp, the edit url, the view url, and the thumbnail url. Bearer OAuth auth (Authorization: Bearer <token>; short-lived access token refreshed by the Gateway via the 3-legged OAuth authorization-code flow with PKCE; never logged); fixed SaaS host api.canva.com. The list endpoint paginates by the response continuation query param until absent; the gateway-side syncable walks a single forward pass per cycle, page-capped. Read-only — hitlRequired is intentionally empty. Sparse structured design metadata; the canonical url is the design view url.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["api.canva.com"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": [],
|
|
13
|
+
"syncInterval": 600,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asObjectish,
|
|
3
|
+
makeQueryFilter,
|
|
4
|
+
type SearchMatchOptions,
|
|
5
|
+
stringField,
|
|
6
|
+
} from "../../../shared/search-filter.ts";
|
|
7
|
+
|
|
8
|
+
export type CanvaSearchMatchOptions = SearchMatchOptions;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Canva design objects are flat — `{ id, title, urls: { edit_url, view_url },
|
|
12
|
+
* thumbnail: { url, ... }, ... }`. Designs carry no description or owner field,
|
|
13
|
+
* so match against the design title (case-insensitive substring).
|
|
14
|
+
*/
|
|
15
|
+
function fieldsOf(item: unknown): readonly string[] | null {
|
|
16
|
+
const row = asObjectish(item);
|
|
17
|
+
if (row === undefined) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return [stringField(row, "title")];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const filterCanvaDesigns = makeQueryFilter(fieldsOf);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
3
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
4
|
+
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
+
import { filterCanvaDesigns } from "./search-filter.ts";
|
|
6
|
+
|
|
7
|
+
const BASE = "https://api.canva.com";
|
|
8
|
+
|
|
9
|
+
function apiToken(): string {
|
|
10
|
+
const t = process.env["CANVA_TOKEN"]?.trim();
|
|
11
|
+
if (t === undefined || t === "") {
|
|
12
|
+
throw new Error("CANVA_TOKEN is not set");
|
|
13
|
+
}
|
|
14
|
+
return t;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function authHeader(): Record<string, string> {
|
|
18
|
+
return { Authorization: `Bearer ${apiToken()}`, Accept: "application/json" };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function canvaGet(path: string): Promise<unknown> {
|
|
22
|
+
const res = await fetch(`${BASE}${path}`, { headers: authHeader() });
|
|
23
|
+
const text = await res.text();
|
|
24
|
+
if (!res.ok) {
|
|
25
|
+
throw new Error(`Canva ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
26
|
+
}
|
|
27
|
+
return JSON.parse(text) as unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function designsFrom(root: unknown): unknown[] {
|
|
31
|
+
const items = (root as { items?: unknown } | null)?.items;
|
|
32
|
+
return Array.isArray(items) ? items : [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await runReadOnlyMcpConnector("nimbus-canva", (reg) => {
|
|
36
|
+
reg(
|
|
37
|
+
"canva_list",
|
|
38
|
+
"List the authenticated user's Canva designs (GET /rest/v1/designs?continuation=<token>). Returns the { items: [...], continuation? } envelope — `items` holds the design objects, each shaped { id, title, created_at, updated_at, urls: { edit_url, view_url }, thumbnail: { url, width, height }, ... }; `continuation` (when present) is the opaque token for the next page.",
|
|
39
|
+
z.object({
|
|
40
|
+
continuation: z.string().optional(),
|
|
41
|
+
}),
|
|
42
|
+
async (p) => {
|
|
43
|
+
const params = new URLSearchParams();
|
|
44
|
+
if (p.continuation !== undefined && p.continuation !== "") {
|
|
45
|
+
params.set("continuation", p.continuation);
|
|
46
|
+
}
|
|
47
|
+
const qs = params.toString();
|
|
48
|
+
const query = qs === "" ? "" : `?${qs}`;
|
|
49
|
+
return jsonResult(await canvaGet(`/rest/v1/designs${query}`));
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
reg(
|
|
54
|
+
"canva_get",
|
|
55
|
+
"Fetch one Canva design by its id (GET /rest/v1/designs/{designId}). Returns the { design: { id, title, created_at, updated_at, urls, thumbnail, ... } } envelope. Throws when no design with that id exists.",
|
|
56
|
+
z.object({
|
|
57
|
+
id: z.string().min(1),
|
|
58
|
+
}),
|
|
59
|
+
async (p) => {
|
|
60
|
+
return jsonResult(await canvaGet(`/rest/v1/designs/${encodeURIComponent(p.id)}`));
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
reg(
|
|
65
|
+
"canva_search",
|
|
66
|
+
"**Substring search over the FIRST PAGE only** of the authenticated user's Canva designs. This tool fetches GET /rest/v1/designs once and matches the query locally (case-insensitive) against the design title. **Designs beyond the first page are not searchable here — query the local Nimbus index instead for full coverage.** Returns a { matches: [...] } envelope.",
|
|
67
|
+
searchToolInputSchema(100),
|
|
68
|
+
async (p) => {
|
|
69
|
+
const root = await canvaGet("/rest/v1/designs");
|
|
70
|
+
const matches = filterCanvaDesigns(designsFrom(root), {
|
|
71
|
+
query: p.query,
|
|
72
|
+
limit: p.limit,
|
|
73
|
+
});
|
|
74
|
+
return jsonResult({ matches });
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.circleci",
|
|
3
|
+
"displayName": "CircleCI",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "CircleCI v2 connector (pipelines, workflows, jobs). Mutations require Gateway HITL.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["circleci.com", "api.circleci.com"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": ["write", "delete"],
|
|
13
|
+
"syncInterval": 120,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
5
|
+
import {
|
|
6
|
+
createRegisterSimpleTool,
|
|
7
|
+
createZodToolRegistrar,
|
|
8
|
+
mcpJsonResult as jsonResult,
|
|
9
|
+
requireProcessEnv,
|
|
10
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
11
|
+
import { makeRestToolRegistrar } from "../../../shared/rest-tool-kit.ts";
|
|
12
|
+
|
|
13
|
+
const CCI_API = "https://circleci.com/api/v2";
|
|
14
|
+
|
|
15
|
+
function projectPathSegments(projectSlug: string): string {
|
|
16
|
+
return projectSlug
|
|
17
|
+
.split("/")
|
|
18
|
+
.map((s) => s.trim())
|
|
19
|
+
.filter((s) => s.length > 0)
|
|
20
|
+
.map((s) => encodeURIComponent(s))
|
|
21
|
+
.join("/");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function circleciFetch(
|
|
25
|
+
token: string,
|
|
26
|
+
path: string,
|
|
27
|
+
init?: RequestInit,
|
|
28
|
+
): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
|
|
29
|
+
const url = path.startsWith("http") ? path : `${CCI_API}${path}`;
|
|
30
|
+
const res = await fetch(url, {
|
|
31
|
+
...init,
|
|
32
|
+
headers: {
|
|
33
|
+
Accept: "application/json",
|
|
34
|
+
"Circle-Token": token,
|
|
35
|
+
...(init?.headers as Record<string, string> | undefined),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
const text = await res.text();
|
|
39
|
+
let json: unknown;
|
|
40
|
+
try {
|
|
41
|
+
json = JSON.parse(text) as unknown;
|
|
42
|
+
} catch {
|
|
43
|
+
json = null;
|
|
44
|
+
}
|
|
45
|
+
return { ok: res.ok, status: res.status, json, text };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const mcp = new McpServer({ name: "nimbus-circleci", version: "0.1.0" });
|
|
49
|
+
const reg = createZodToolRegistrar(createRegisterSimpleTool(mcp));
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Every MUTATING circleci tool goes through here. Outside the gateway this adds the
|
|
53
|
+
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
54
|
+
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
55
|
+
*/
|
|
56
|
+
const registerWriteTool = createWriteToolRegistrar(mcp, {
|
|
57
|
+
connector: "circleci",
|
|
58
|
+
scopeEnv: "NIMBUS_MCP_CIRCLECI_WRITE_SCOPE",
|
|
59
|
+
scopeKinds: ["project"],
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/** Standard CircleCI read tool: token → circleciFetch(buildPath) → mcpJsonResultIfOk("CircleCI"). */
|
|
63
|
+
const registerCciTool = makeRestToolRegistrar({
|
|
64
|
+
registrar: reg,
|
|
65
|
+
tokenEnv: "CIRCLECI_API_TOKEN",
|
|
66
|
+
serviceLabel: "CircleCI",
|
|
67
|
+
fetch: circleciFetch,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const projectSlugSchema = z.object({
|
|
71
|
+
projectSlug: z
|
|
72
|
+
.string()
|
|
73
|
+
.min(3)
|
|
74
|
+
.describe("CircleCI project slug, e.g. gh/org/repo or bb/workspace/repo"),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
registerCciTool(
|
|
78
|
+
"circleci_pipeline_list",
|
|
79
|
+
"List pipelines for a CircleCI project.",
|
|
80
|
+
projectSlugSchema.extend({
|
|
81
|
+
pageToken: z.string().min(1).optional(),
|
|
82
|
+
}),
|
|
83
|
+
(parsed) => {
|
|
84
|
+
const base = `/project/${projectPathSegments(parsed.projectSlug)}/pipeline`;
|
|
85
|
+
const u = new URL(`${CCI_API}${base}`);
|
|
86
|
+
if (parsed.pageToken !== undefined) {
|
|
87
|
+
u.searchParams.set("page-token", parsed.pageToken);
|
|
88
|
+
}
|
|
89
|
+
return `${u.pathname}${u.search}`;
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
registerCciTool(
|
|
94
|
+
"circleci_pipeline_get",
|
|
95
|
+
"Get a pipeline by UUID.",
|
|
96
|
+
z.object({ pipelineId: z.uuid() }),
|
|
97
|
+
(parsed) => `/pipeline/${encodeURIComponent(parsed.pipelineId)}`,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
registerCciTool(
|
|
101
|
+
"circleci_workflow_list",
|
|
102
|
+
"List workflows for a pipeline.",
|
|
103
|
+
z.object({ pipelineId: z.uuid() }),
|
|
104
|
+
(parsed) => `/pipeline/${encodeURIComponent(parsed.pipelineId)}/workflow`,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
registerCciTool(
|
|
108
|
+
"circleci_job_list",
|
|
109
|
+
"List jobs for a workflow.",
|
|
110
|
+
z.object({ workflowId: z.uuid() }),
|
|
111
|
+
(parsed) => `/workflow/${encodeURIComponent(parsed.workflowId)}/job`,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
registerCciTool(
|
|
115
|
+
"circleci_job_artifacts",
|
|
116
|
+
"List artifact metadata for a job number under a project.",
|
|
117
|
+
projectSlugSchema.extend({
|
|
118
|
+
jobNumber: z.number().int().min(1),
|
|
119
|
+
}),
|
|
120
|
+
(parsed) =>
|
|
121
|
+
`/project/${projectPathSegments(parsed.projectSlug)}/job/${String(parsed.jobNumber)}/artifacts`,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
registerWriteTool(
|
|
125
|
+
"circleci_pipeline_trigger",
|
|
126
|
+
{
|
|
127
|
+
mutates: "circleci.pipeline.trigger",
|
|
128
|
+
recoverable: true,
|
|
129
|
+
scopeTargetOf: (p) => ({ kind: "project", value: p.projectSlug }),
|
|
130
|
+
},
|
|
131
|
+
"Trigger a new pipeline on a branch (or tag).",
|
|
132
|
+
projectSlugSchema.extend({
|
|
133
|
+
branch: z.string().min(1).optional(),
|
|
134
|
+
tag: z.string().min(1).optional(),
|
|
135
|
+
parameters: z.record(z.string(), z.string()).optional(),
|
|
136
|
+
}),
|
|
137
|
+
async (parsed) => {
|
|
138
|
+
const token = requireProcessEnv("CIRCLECI_API_TOKEN");
|
|
139
|
+
const path = `/project/${projectPathSegments(parsed.projectSlug)}/pipeline`;
|
|
140
|
+
const body: Record<string, unknown> = {};
|
|
141
|
+
if (parsed.tag !== undefined && parsed.tag !== "") {
|
|
142
|
+
body["tag"] = parsed.tag;
|
|
143
|
+
} else {
|
|
144
|
+
body["branch"] = parsed.branch ?? "main";
|
|
145
|
+
}
|
|
146
|
+
if (parsed.parameters !== undefined && Object.keys(parsed.parameters).length > 0) {
|
|
147
|
+
body["parameters"] = parsed.parameters;
|
|
148
|
+
}
|
|
149
|
+
const res = await circleciFetch(token, path, {
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: { "Content-Type": "application/json" },
|
|
152
|
+
body: JSON.stringify(body),
|
|
153
|
+
});
|
|
154
|
+
if (!res.ok) {
|
|
155
|
+
throw new Error(`CircleCI trigger ${String(res.status)}: ${res.text.slice(0, 400)}`);
|
|
156
|
+
}
|
|
157
|
+
return jsonResult(res.json ?? { ok: true, raw: res.text });
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
registerWriteTool(
|
|
162
|
+
"circleci_job_cancel",
|
|
163
|
+
{
|
|
164
|
+
mutates: "circleci.job.cancel",
|
|
165
|
+
recoverable: true,
|
|
166
|
+
scopeTargetOf: (p) => ({ kind: "project", value: p.projectSlug }),
|
|
167
|
+
},
|
|
168
|
+
"Cancel a running job by project slug and job number.",
|
|
169
|
+
projectSlugSchema.extend({
|
|
170
|
+
jobNumber: z.number().int().min(1),
|
|
171
|
+
}),
|
|
172
|
+
async (parsed) => {
|
|
173
|
+
const token = requireProcessEnv("CIRCLECI_API_TOKEN");
|
|
174
|
+
const path = `/project/${projectPathSegments(parsed.projectSlug)}/job/${String(parsed.jobNumber)}/cancel`;
|
|
175
|
+
const res = await circleciFetch(token, path, { method: "POST" });
|
|
176
|
+
if (!res.ok) {
|
|
177
|
+
throw new Error(`CircleCI job cancel ${String(res.status)}: ${res.text.slice(0, 400)}`);
|
|
178
|
+
}
|
|
179
|
+
return jsonResult(res.json ?? { ok: true });
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const transport = new StdioServerTransport();
|
|
184
|
+
await mcp.connect(transport);
|