@nimbus-dev/connectors 0.2.1 → 0.2.2
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/README.md +14 -4
- package/connectors/airflow/src/search-filter.ts +2 -15
- package/connectors/airflow/src/server.ts +2 -87
- package/connectors/airflow/src/tools.ts +74 -0
- package/connectors/apple/src/adapters.ts +326 -0
- package/connectors/apple/src/apple-mail-core.ts +4 -8
- package/connectors/apple/src/calendar-tools.ts +28 -8
- package/connectors/apple/src/server.ts +23 -512
- package/connectors/apple/src/tools.ts +18 -44
- package/connectors/argocd/src/server.ts +25 -38
- package/connectors/athena/src/tools.ts +15 -34
- package/connectors/aws/src/server.ts +2 -183
- package/connectors/aws/src/tools.ts +197 -0
- package/connectors/azure/src/server.ts +2 -131
- package/connectors/azure/src/tools.ts +133 -0
- package/connectors/bigeye/src/search-filter.ts +4 -15
- package/connectors/bigeye/src/server.ts +38 -38
- package/connectors/bigquery/src/tools.ts +1 -26
- package/connectors/bitbucket/src/server.ts +2 -244
- package/connectors/bitbucket/src/tools.ts +241 -0
- package/connectors/bitrise/src/server.ts +2 -79
- package/connectors/bitrise/src/tools.ts +76 -0
- package/connectors/canva/src/search-filter.ts +2 -11
- package/connectors/canva/src/server.ts +2 -73
- package/connectors/canva/src/tools.ts +66 -0
- package/connectors/circleci/src/server.ts +2 -176
- package/connectors/circleci/src/tools.ts +193 -0
- package/connectors/cloud-logging/src/tools.ts +14 -27
- package/connectors/cloudwatch/src/tools.ts +15 -34
- package/connectors/codemagic/src/server.ts +2 -70
- package/connectors/codemagic/src/tools.ts +71 -0
- package/connectors/confluence/src/server.ts +2 -301
- package/connectors/confluence/src/tools.ts +320 -0
- package/connectors/dagster/src/server.ts +2 -214
- package/connectors/dagster/src/tools.ts +187 -0
- package/connectors/databricks/src/search-filter.ts +7 -7
- package/connectors/databricks/src/server.ts +2 -77
- package/connectors/databricks/src/tools.ts +66 -0
- package/connectors/datadog/src/server.ts +2 -48
- package/connectors/datadog/src/tools.ts +48 -0
- package/connectors/dataprofile/src/profile.ts +16 -46
- package/connectors/dbt/src/server.ts +2 -92
- package/connectors/dbt/src/tools.ts +80 -0
- package/connectors/dependencytrack/src/server.ts +2 -76
- package/connectors/dependencytrack/src/tools.ts +65 -0
- package/connectors/discord/src/server.ts +2 -85
- package/connectors/discord/src/tools.ts +82 -0
- package/connectors/elasticsearch/src/tools.ts +7 -37
- package/connectors/fastmail/src/jmap-client.ts +202 -0
- package/connectors/fastmail/src/jmap-core.ts +8 -21
- package/connectors/fastmail/src/server.ts +2 -187
- package/connectors/fastmail/src/tools.ts +4 -23
- package/connectors/figma/src/search-filter.ts +2 -11
- package/connectors/figma/src/server.ts +2 -113
- package/connectors/figma/src/tools.ts +99 -0
- package/connectors/firebase/src/server.ts +2 -90
- package/connectors/firebase/src/token.ts +2 -5
- package/connectors/firebase/src/tools.ts +106 -0
- package/connectors/flagsmith/src/server.ts +2 -98
- package/connectors/flagsmith/src/tools.ts +95 -0
- package/connectors/flux/src/server.ts +52 -53
- package/connectors/gcp/src/server.ts +2 -132
- package/connectors/gcp/src/tools.ts +123 -0
- package/connectors/github/src/server.ts +2 -328
- package/connectors/github/src/tools.ts +311 -0
- package/connectors/github-actions/src/server.ts +2 -205
- package/connectors/github-actions/src/tools.ts +209 -0
- package/connectors/gitlab/src/server.ts +2 -384
- package/connectors/gitlab/src/tools.ts +392 -0
- package/connectors/gmail/src/server.ts +2 -270
- package/connectors/gmail/src/tools.ts +209 -0
- package/connectors/google-drive/src/server.ts +2 -524
- package/connectors/google-drive/src/tools.ts +529 -0
- package/connectors/google-meet/src/server.ts +2 -82
- package/connectors/google-meet/src/tools.ts +87 -0
- package/connectors/google-photos/src/server.ts +2 -132
- package/connectors/google-photos/src/tools.ts +138 -0
- package/connectors/grafana/src/server.ts +2 -53
- package/connectors/grafana/src/tools.ts +53 -0
- package/connectors/great-expectations/src/gx-parse.ts +14 -45
- package/connectors/greenhouse/src/server.ts +2 -63
- package/connectors/greenhouse/src/tools.ts +68 -0
- package/connectors/hubspot/src/server.ts +2 -77
- package/connectors/hubspot/src/tools.ts +70 -0
- package/connectors/iac/src/server.ts +2 -137
- package/connectors/iac/src/tools.ts +152 -0
- package/connectors/imap/src/imap-core.ts +29 -82
- package/connectors/imap/src/server.ts +33 -220
- package/connectors/intercom/src/server.ts +2 -61
- package/connectors/intercom/src/tools.ts +58 -0
- package/connectors/jenkins/src/jenkins-api.ts +2 -6
- package/connectors/jenkins/src/server.ts +2 -215
- package/connectors/jenkins/src/tools.ts +240 -0
- package/connectors/jira/src/server.ts +2 -301
- package/connectors/jira/src/tools.ts +325 -0
- package/connectors/kubernetes/src/server.ts +2 -155
- package/connectors/kubernetes/src/tools.ts +159 -0
- package/connectors/launchdarkly/src/server.ts +2 -75
- package/connectors/launchdarkly/src/tools.ts +72 -0
- package/connectors/lever/src/search-filter.ts +4 -12
- package/connectors/lever/src/server.ts +2 -60
- package/connectors/lever/src/tools.ts +61 -0
- package/connectors/linear/src/server.ts +2 -459
- package/connectors/linear/src/tools.ts +482 -0
- package/connectors/localdb/src/sql-scan.ts +13 -41
- package/connectors/localdb/src/tools.ts +1 -1
- package/connectors/looker/src/search-filter.ts +2 -15
- package/connectors/looker/src/server.ts +35 -70
- package/connectors/mendeley/src/server.ts +2 -55
- package/connectors/mendeley/src/tools.ts +54 -0
- package/connectors/mercury/src/server.ts +2 -57
- package/connectors/mercury/src/tools.ts +50 -0
- package/connectors/metabase/src/search-filter.ts +4 -15
- package/connectors/metabase/src/server.ts +2 -79
- package/connectors/metabase/src/tools.ts +64 -0
- package/connectors/miro/src/server.ts +2 -72
- package/connectors/miro/src/tools.ts +65 -0
- package/connectors/mlflow/src/search-filter.ts +3 -7
- package/connectors/mlflow/src/server.ts +20 -40
- package/connectors/monte-carlo/src/server.ts +50 -57
- package/connectors/netlify/src/search-filter.ts +6 -14
- package/connectors/netlify/src/server.ts +2 -60
- package/connectors/netlify/src/tools.ts +53 -0
- package/connectors/newrelic/src/server.ts +2 -40
- package/connectors/newrelic/src/tools.ts +37 -0
- package/connectors/notion/src/server.ts +2 -335
- package/connectors/notion/src/tools.ts +344 -0
- package/connectors/obsidian/src/server.ts +2 -425
- package/connectors/obsidian/src/tools.ts +466 -0
- package/connectors/onedrive/src/server.ts +2 -249
- package/connectors/onedrive/src/tools.ts +253 -0
- package/connectors/outlook/src/server.ts +2 -351
- package/connectors/outlook/src/tools.ts +357 -0
- package/connectors/pagerduty/src/server.ts +2 -132
- package/connectors/pagerduty/src/tools.ts +126 -0
- package/connectors/pipedrive/src/server.ts +2 -58
- package/connectors/pipedrive/src/tools.ts +63 -0
- package/connectors/powerbi/src/search-filter.ts +2 -15
- package/connectors/powerbi/src/server.ts +43 -68
- package/connectors/prefect/src/search-filter.ts +3 -16
- package/connectors/prefect/src/server.ts +2 -90
- package/connectors/prefect/src/tools.ts +83 -0
- package/connectors/protonmail/src/mail-core.ts +16 -75
- package/connectors/protonmail/src/server.ts +34 -214
- package/connectors/raindrop/src/server.ts +2 -103
- package/connectors/raindrop/src/tools.ts +103 -0
- package/connectors/ramp/src/server.ts +2 -105
- package/connectors/ramp/src/tools.ts +98 -0
- package/connectors/readwise/src/server.ts +2 -88
- package/connectors/readwise/src/tools.ts +81 -0
- package/connectors/sagemaker/src/tools.ts +19 -38
- package/connectors/salesforce/src/search-filter.ts +4 -11
- package/connectors/salesforce/src/server.ts +2 -90
- package/connectors/salesforce/src/tools.ts +75 -0
- package/connectors/semgrep/src/search-filter.ts +3 -10
- package/connectors/semgrep/src/server.ts +2 -104
- package/connectors/semgrep/src/tools.ts +92 -0
- package/connectors/sentry/src/server.ts +2 -58
- package/connectors/sentry/src/tools.ts +53 -0
- package/connectors/slack/src/server.ts +2 -352
- package/connectors/slack/src/tools.ts +376 -0
- package/connectors/snowflake/src/search-filter.ts +4 -19
- package/connectors/snowflake/src/server.ts +6 -25
- package/connectors/snyk/src/server.ts +2 -140
- package/connectors/snyk/src/tools.ts +136 -0
- package/connectors/sonarqube/src/server.ts +2 -111
- package/connectors/sonarqube/src/tools.ts +98 -0
- package/connectors/stackoverflow/src/server.ts +2 -77
- package/connectors/stackoverflow/src/tools.ts +66 -0
- package/connectors/storybook/src/storybook-parse.ts +3 -6
- package/connectors/stripe/src/server.ts +2 -61
- package/connectors/stripe/src/tools.ts +54 -0
- package/connectors/superset/src/search-filter.ts +4 -15
- package/connectors/superset/src/server.ts +2 -115
- package/connectors/superset/src/tools.ts +98 -0
- package/connectors/tableau/src/search-filter.ts +2 -15
- package/connectors/tableau/src/server.ts +13 -65
- package/connectors/teams/src/server.ts +2 -249
- package/connectors/teams/src/tools.ts +283 -0
- package/connectors/testflight/src/server.ts +2 -70
- package/connectors/testflight/src/tools.ts +79 -0
- package/connectors/vercel/src/search-filter.ts +2 -10
- package/connectors/vercel/src/server.ts +2 -75
- package/connectors/vercel/src/tools.ts +73 -0
- package/connectors/vertex-ai/src/tools.ts +23 -33
- package/connectors/wiz/src/search-filter.ts +6 -30
- package/connectors/wiz/src/server.ts +2 -188
- package/connectors/wiz/src/tools.ts +168 -0
- package/connectors/zendesk/src/server.ts +2 -73
- package/connectors/zendesk/src/tools.ts +60 -0
- package/connectors/zoom/src/search-filter.ts +1 -7
- package/connectors/zoom/src/server.ts +2 -94
- package/connectors/zoom/src/tools.ts +93 -0
- package/connectors/zotero/src/server.ts +2 -78
- package/connectors/zotero/src/tools.ts +67 -0
- package/package.json +14 -12
- package/shared/access-token-cache.ts +123 -0
- package/shared/atlassian-json-fetch.ts +6 -16
- package/shared/audit-chain.ts +13 -6
- package/shared/cli-json-kit.ts +150 -0
- package/shared/collection-tool-kit.ts +143 -0
- package/shared/connector-mode.ts +3 -2
- package/shared/cursor-list-tool.ts +29 -0
- package/shared/env-json-api.ts +209 -0
- package/shared/fetch-json-text.ts +27 -0
- package/shared/github-rest.ts +62 -0
- package/shared/graphql-json.ts +60 -0
- package/shared/imap-mail-core.ts +91 -0
- package/shared/imap-tool-kit.ts +57 -23
- package/shared/imapflow-adapter.ts +417 -0
- package/shared/rest-tool-kit.ts +77 -7
- package/shared/run-cli-json.ts +38 -16
- package/shared/search-filter.ts +58 -18
- package/shared/walk-files.ts +118 -0
- package/standalone/src/launcher.ts +19 -7
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ConsentServer } from "../../../shared/consent-kit.ts";
|
|
3
|
+
import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
4
|
+
import { resolveUrlWithBase } from "../../../shared/fetch-bearer-json.ts";
|
|
5
|
+
import {
|
|
6
|
+
createRegisterSimpleTool,
|
|
7
|
+
createZodToolRegistrar,
|
|
8
|
+
encodeBasicAuthHeader,
|
|
9
|
+
type McpListResult,
|
|
10
|
+
mcpJsonResultIfOk,
|
|
11
|
+
requireProcessEnv,
|
|
12
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
13
|
+
import { type RestFetchResult, toRestFetchResult } from "../../../shared/rest-tool-kit.ts";
|
|
14
|
+
|
|
15
|
+
const BB_API = "https://api.bitbucket.org/2.0";
|
|
16
|
+
|
|
17
|
+
function basicAuthHeader(): string {
|
|
18
|
+
const user = requireProcessEnv("BITBUCKET_USERNAME");
|
|
19
|
+
const pass = requireProcessEnv("BITBUCKET_APP_PASSWORD");
|
|
20
|
+
return encodeBasicAuthHeader(user, pass);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function splitRepoFull(full: string): { workspace: string; repoSlug: string } {
|
|
24
|
+
const i = full.indexOf("/");
|
|
25
|
+
if (i <= 0 || i === full.length - 1) {
|
|
26
|
+
throw new Error("repoFull must be workspace/repo_slug");
|
|
27
|
+
}
|
|
28
|
+
return { workspace: full.slice(0, i), repoSlug: full.slice(i + 1) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** `/repositories/<workspace>/<repo_slug>` for a `workspace/repo_slug` full name, both encoded. */
|
|
32
|
+
function repoPath(repoFull: string): string {
|
|
33
|
+
const { workspace, repoSlug } = splitRepoFull(repoFull);
|
|
34
|
+
return `/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repoSlug)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* One credentialed Bitbucket request. `path` is relative to {@link BB_API}, or an absolute URL —
|
|
39
|
+
* the `next` link of an earlier page, which a paged tool takes back as its `page` ARGUMENT, so the
|
|
40
|
+
* model chooses it. `resolveUrlWithBase` refuses an absolute URL on any other origin before
|
|
41
|
+
* anything is sent: fetched as given, it would hand that host the username and app password.
|
|
42
|
+
*/
|
|
43
|
+
async function bbFetch(path: string, init?: RequestInit): Promise<RestFetchResult> {
|
|
44
|
+
const url = resolveUrlWithBase(BB_API, path);
|
|
45
|
+
const baseHeaders: Record<string, string> = {
|
|
46
|
+
Authorization: basicAuthHeader(),
|
|
47
|
+
Accept: "application/json",
|
|
48
|
+
};
|
|
49
|
+
const extra = init?.headers as Record<string, string> | undefined;
|
|
50
|
+
const headers = extra === undefined ? baseHeaders : { ...baseHeaders, ...extra };
|
|
51
|
+
const res = await fetch(url, {
|
|
52
|
+
...init,
|
|
53
|
+
headers,
|
|
54
|
+
});
|
|
55
|
+
return toRestFetchResult(res);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A Bitbucket request answered as the tool result; Bitbucket's status and body on failure. */
|
|
59
|
+
async function bbResult(path: string, init?: RequestInit): Promise<McpListResult> {
|
|
60
|
+
const res = await bbFetch(path, init);
|
|
61
|
+
return mcpJsonResultIfOk("Bitbucket", res);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* One page of a paged list: the `next` URL from an earlier response when the caller passes one
|
|
66
|
+
* back, otherwise the first page — which `firstPage` builds only in that case.
|
|
67
|
+
*/
|
|
68
|
+
async function bbPage(page: string | undefined, firstPage: () => string): Promise<McpListResult> {
|
|
69
|
+
return bbResult(page?.startsWith("http") ? page : firstPage());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
73
|
+
export const BITBUCKET_TOOL_NAMES = [
|
|
74
|
+
"bitbucket_repo_list",
|
|
75
|
+
"bitbucket_pr_list",
|
|
76
|
+
"bitbucket_pr_get",
|
|
77
|
+
"bitbucket_pr_merge",
|
|
78
|
+
"bitbucket_pipeline_list",
|
|
79
|
+
"bitbucket_pipeline_get",
|
|
80
|
+
"bitbucket_issue_list",
|
|
81
|
+
] as const;
|
|
82
|
+
|
|
83
|
+
export function registerBitbucketTools(
|
|
84
|
+
server: ConsentServer & { tool: (...args: never) => unknown },
|
|
85
|
+
): void {
|
|
86
|
+
const registerSimpleTool = createRegisterSimpleTool(server);
|
|
87
|
+
const reg = createZodToolRegistrar(registerSimpleTool);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Every MUTATING bitbucket tool goes through here. Outside the gateway this adds the
|
|
91
|
+
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
92
|
+
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
93
|
+
*/
|
|
94
|
+
const registerWriteTool = createWriteToolRegistrar(server, {
|
|
95
|
+
connector: "bitbucket",
|
|
96
|
+
scopeEnv: "NIMBUS_MCP_BITBUCKET_WRITE_SCOPE",
|
|
97
|
+
scopeKinds: ["repo"],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const repoFullArg = z.object({
|
|
101
|
+
repoFull: z
|
|
102
|
+
.string()
|
|
103
|
+
.min(3)
|
|
104
|
+
.describe("Repository full name: workspace/repo_slug (e.g. myteam/my-service)"),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const bitbucketRepoListSchema = z.object({
|
|
108
|
+
pagelen: z.number().int().min(1).max(100).optional(),
|
|
109
|
+
page: z
|
|
110
|
+
.string()
|
|
111
|
+
.max(2000)
|
|
112
|
+
.optional()
|
|
113
|
+
.describe("Opaque page URL or token from a prior next link"),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
reg(
|
|
117
|
+
"bitbucket_repo_list",
|
|
118
|
+
"List repositories where the authenticated user is a member.",
|
|
119
|
+
bitbucketRepoListSchema,
|
|
120
|
+
async (parsed) =>
|
|
121
|
+
bbPage(parsed.page, () => {
|
|
122
|
+
const qs = new URLSearchParams();
|
|
123
|
+
qs.set("role", "member");
|
|
124
|
+
qs.set("pagelen", String(parsed.pagelen ?? 30));
|
|
125
|
+
return `/repositories?${qs.toString()}`;
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const bitbucketPrListSchema = repoFullArg.extend({
|
|
130
|
+
state: z.enum(["OPEN", "MERGED", "DECLINED", "SUPERSEDED"]).optional(),
|
|
131
|
+
pagelen: z.number().int().min(1).max(100).optional(),
|
|
132
|
+
page: z.string().max(2000).optional().describe("Opaque next URL from a prior response"),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
reg(
|
|
136
|
+
"bitbucket_pr_list",
|
|
137
|
+
"List pull requests for a repository.",
|
|
138
|
+
bitbucketPrListSchema,
|
|
139
|
+
async (parsed) =>
|
|
140
|
+
bbPage(parsed.page, () => {
|
|
141
|
+
const base = `${repoPath(parsed.repoFull)}/pullrequests`;
|
|
142
|
+
const qs = new URLSearchParams();
|
|
143
|
+
qs.set("pagelen", String(parsed.pagelen ?? 30));
|
|
144
|
+
qs.set("sort", "-updated_on");
|
|
145
|
+
if (parsed.state !== undefined) {
|
|
146
|
+
qs.set("q", `state="${parsed.state}"`);
|
|
147
|
+
}
|
|
148
|
+
return `${base}?${qs.toString()}`;
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const bitbucketPrGetSchema = repoFullArg.extend({
|
|
153
|
+
pullRequestId: z.number().int().min(1),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
reg(
|
|
157
|
+
"bitbucket_pr_get",
|
|
158
|
+
"Get a single pull request by numeric id.",
|
|
159
|
+
bitbucketPrGetSchema,
|
|
160
|
+
async (parsed) =>
|
|
161
|
+
bbResult(`${repoPath(parsed.repoFull)}/pullrequests/${String(parsed.pullRequestId)}`),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const bitbucketPrMergeSchema = repoFullArg.extend({
|
|
165
|
+
pullRequestId: z.number().int().min(1),
|
|
166
|
+
mergeStrategy: z.enum(["merge_commit", "squash", "fast_forward"]).optional(),
|
|
167
|
+
message: z.string().max(32_768).optional(),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
registerWriteTool(
|
|
171
|
+
"bitbucket_pr_merge",
|
|
172
|
+
{
|
|
173
|
+
mutates: "bitbucket.pr.merge",
|
|
174
|
+
recoverable: true,
|
|
175
|
+
scopeTargetOf: (p) => ({ kind: "repo", value: p.repoFull }),
|
|
176
|
+
},
|
|
177
|
+
"Merge a pull request.",
|
|
178
|
+
bitbucketPrMergeSchema,
|
|
179
|
+
async (parsed) => {
|
|
180
|
+
const path = `${repoPath(parsed.repoFull)}/pullrequests/${String(parsed.pullRequestId)}/merge`;
|
|
181
|
+
const body: Record<string, unknown> = { type: "pullrequest" };
|
|
182
|
+
if (parsed.mergeStrategy !== undefined) {
|
|
183
|
+
body["merge_strategy"] = parsed.mergeStrategy;
|
|
184
|
+
}
|
|
185
|
+
if (parsed.message !== undefined) {
|
|
186
|
+
body["message"] = parsed.message;
|
|
187
|
+
}
|
|
188
|
+
return bbResult(path, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: { "Content-Type": "application/json" },
|
|
191
|
+
body: JSON.stringify(body),
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const bitbucketRepoPagedSchema = repoFullArg.extend({
|
|
197
|
+
pagelen: z.number().int().min(1).max(100).optional(),
|
|
198
|
+
page: z.string().max(2000).optional(),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
reg(
|
|
202
|
+
"bitbucket_pipeline_list",
|
|
203
|
+
"List Pipelines runs for a repository.",
|
|
204
|
+
bitbucketRepoPagedSchema,
|
|
205
|
+
async (parsed) =>
|
|
206
|
+
bbPage(parsed.page, () => {
|
|
207
|
+
const base = `${repoPath(parsed.repoFull)}/pipelines/`;
|
|
208
|
+
const qs = new URLSearchParams();
|
|
209
|
+
qs.set("pagelen", String(parsed.pagelen ?? 30));
|
|
210
|
+
return `${base}?${qs.toString()}`;
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const bitbucketPipelineGetSchema = repoFullArg.extend({
|
|
215
|
+
pipelineUuid: z.string().min(8).max(128),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
reg(
|
|
219
|
+
"bitbucket_pipeline_get",
|
|
220
|
+
"Get a single pipeline run by UUID.",
|
|
221
|
+
bitbucketPipelineGetSchema,
|
|
222
|
+
async (parsed) => {
|
|
223
|
+
const base = repoPath(parsed.repoFull);
|
|
224
|
+
const encUuid = encodeURIComponent(parsed.pipelineUuid);
|
|
225
|
+
return bbResult(`${base}/pipelines/${encUuid}`);
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
reg(
|
|
230
|
+
"bitbucket_issue_list",
|
|
231
|
+
"List issues for a repository (issue tracker must be enabled).",
|
|
232
|
+
bitbucketRepoPagedSchema,
|
|
233
|
+
async (parsed) =>
|
|
234
|
+
bbPage(parsed.page, () => {
|
|
235
|
+
const base = `${repoPath(parsed.repoFull)}/issues`;
|
|
236
|
+
const qs = new URLSearchParams();
|
|
237
|
+
qs.set("pagelen", String(parsed.pagelen ?? 30));
|
|
238
|
+
return `${base}?${qs.toString()}`;
|
|
239
|
+
}),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
@@ -1,83 +1,6 @@
|
|
|
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
1
|
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
-
import {
|
|
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
|
-
}
|
|
2
|
+
import { registerBitriseTools } from "./tools.ts";
|
|
25
3
|
|
|
26
4
|
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
|
-
);
|
|
5
|
+
registerBitriseTools(reg);
|
|
83
6
|
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, envAuthHeaders } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import { matchesResult } from "../../../shared/mcp-search-tool.ts";
|
|
4
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
6
|
+
import { filterBitriseBuilds } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const BITRISE_API = "https://api.bitrise.io";
|
|
9
|
+
|
|
10
|
+
const bitriseGet = createJsonGetter({
|
|
11
|
+
base: BITRISE_API,
|
|
12
|
+
label: "Bitrise",
|
|
13
|
+
headers: envAuthHeaders({ env: "BITRISE_TOKEN", scheme: "" }),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
17
|
+
export const BITRISE_TOOL_NAMES = ["bitrise_get", "bitrise_list", "bitrise_search"] as const;
|
|
18
|
+
|
|
19
|
+
export function registerBitriseTools(reg: ZodToolRegistrar): void {
|
|
20
|
+
reg(
|
|
21
|
+
"bitrise_list",
|
|
22
|
+
"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).",
|
|
23
|
+
z.object({
|
|
24
|
+
appSlug: z.string().min(1).optional(),
|
|
25
|
+
status: z.enum(["not-finished", "successful", "failed", "aborted"]).optional(),
|
|
26
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
27
|
+
}),
|
|
28
|
+
async (p) => {
|
|
29
|
+
if (p.appSlug === undefined) {
|
|
30
|
+
const limit = p.limit ?? 50;
|
|
31
|
+
return jsonResult(await bitriseGet(`/v0.1/me/apps?limit=${String(limit)}`));
|
|
32
|
+
}
|
|
33
|
+
const params: string[] = [];
|
|
34
|
+
params.push(`limit=${String(p.limit ?? 50)}`);
|
|
35
|
+
if (p.status !== undefined) {
|
|
36
|
+
const map = { "not-finished": 0, successful: 1, failed: 2, aborted: 3 } as const;
|
|
37
|
+
params.push(`status=${String(map[p.status])}`);
|
|
38
|
+
}
|
|
39
|
+
const qs = `?${params.join("&")}`;
|
|
40
|
+
return jsonResult(
|
|
41
|
+
await bitriseGet(`/v0.1/apps/${encodeURIComponent(p.appSlug)}/builds${qs}`),
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
reg(
|
|
47
|
+
"bitrise_get",
|
|
48
|
+
"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>`.",
|
|
49
|
+
z.object({
|
|
50
|
+
appSlug: z.string().min(1),
|
|
51
|
+
buildSlug: z.string().min(1).optional(),
|
|
52
|
+
}),
|
|
53
|
+
async (p) => {
|
|
54
|
+
if (p.buildSlug === undefined) {
|
|
55
|
+
return jsonResult(await bitriseGet(`/v0.1/apps/${encodeURIComponent(p.appSlug)}`));
|
|
56
|
+
}
|
|
57
|
+
const path = `/v0.1/apps/${encodeURIComponent(p.appSlug)}/builds/${encodeURIComponent(p.buildSlug)}`;
|
|
58
|
+
return jsonResult(await bitriseGet(path));
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
reg(
|
|
63
|
+
"bitrise_search",
|
|
64
|
+
"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.",
|
|
65
|
+
z.object({
|
|
66
|
+
appSlug: z.string().min(1),
|
|
67
|
+
query: z.string().min(1),
|
|
68
|
+
limit: z.number().int().min(1).max(200).optional(),
|
|
69
|
+
}),
|
|
70
|
+
async (p) => {
|
|
71
|
+
const root = await bitriseGet(`/v0.1/apps/${encodeURIComponent(p.appSlug)}/builds?limit=50`);
|
|
72
|
+
const data = (root as { data?: unknown[] } | null)?.data;
|
|
73
|
+
return matchesResult(data, filterBitriseBuilds, p);
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
fieldsFromKeys,
|
|
3
3
|
makeQueryFilter,
|
|
4
4
|
type SearchMatchOptions,
|
|
5
|
-
stringField,
|
|
6
5
|
} from "../../../shared/search-filter.ts";
|
|
7
6
|
|
|
8
7
|
export type CanvaSearchMatchOptions = SearchMatchOptions;
|
|
@@ -12,12 +11,4 @@ export type CanvaSearchMatchOptions = SearchMatchOptions;
|
|
|
12
11
|
* thumbnail: { url, ... }, ... }`. Designs carry no description or owner field,
|
|
13
12
|
* so match against the design title (case-insensitive substring).
|
|
14
13
|
*/
|
|
15
|
-
|
|
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);
|
|
14
|
+
export const filterCanvaDesigns = makeQueryFilter(fieldsFromKeys(["title"]));
|
|
@@ -1,77 +1,6 @@
|
|
|
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
1
|
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
-
import {
|
|
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
|
-
}
|
|
2
|
+
import { registerCanvaTools } from "./tools.ts";
|
|
34
3
|
|
|
35
4
|
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
|
-
);
|
|
5
|
+
registerCanvaTools(reg);
|
|
77
6
|
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, envAuthHeaders } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
6
|
+
import { filterCanvaDesigns } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const BASE = "https://api.canva.com";
|
|
9
|
+
|
|
10
|
+
const canvaGet = createJsonGetter({
|
|
11
|
+
base: BASE,
|
|
12
|
+
label: "Canva",
|
|
13
|
+
headers: envAuthHeaders({ env: "CANVA_TOKEN" }),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function designsFrom(root: unknown): unknown[] {
|
|
17
|
+
const items = (root as { items?: unknown } | null)?.items;
|
|
18
|
+
return Array.isArray(items) ? items : [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
22
|
+
export const CANVA_TOOL_NAMES = ["canva_get", "canva_list", "canva_search"] as const;
|
|
23
|
+
|
|
24
|
+
export function registerCanvaTools(reg: ZodToolRegistrar): void {
|
|
25
|
+
reg(
|
|
26
|
+
"canva_list",
|
|
27
|
+
"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.",
|
|
28
|
+
z.object({
|
|
29
|
+
continuation: z.string().optional(),
|
|
30
|
+
}),
|
|
31
|
+
async (p) => {
|
|
32
|
+
const params = new URLSearchParams();
|
|
33
|
+
if (p.continuation !== undefined && p.continuation !== "") {
|
|
34
|
+
params.set("continuation", p.continuation);
|
|
35
|
+
}
|
|
36
|
+
const qs = params.toString();
|
|
37
|
+
const query = qs === "" ? "" : `?${qs}`;
|
|
38
|
+
return jsonResult(await canvaGet(`/rest/v1/designs${query}`));
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
reg(
|
|
43
|
+
"canva_get",
|
|
44
|
+
"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.",
|
|
45
|
+
z.object({
|
|
46
|
+
id: z.string().min(1),
|
|
47
|
+
}),
|
|
48
|
+
async (p) => {
|
|
49
|
+
return jsonResult(await canvaGet(`/rest/v1/designs/${encodeURIComponent(p.id)}`));
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
reg(
|
|
54
|
+
"canva_search",
|
|
55
|
+
"**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.",
|
|
56
|
+
searchToolInputSchema(100),
|
|
57
|
+
async (p) => {
|
|
58
|
+
const root = await canvaGet("/rest/v1/designs");
|
|
59
|
+
const matches = filterCanvaDesigns(designsFrom(root), {
|
|
60
|
+
query: p.query,
|
|
61
|
+
limit: p.limit,
|
|
62
|
+
});
|
|
63
|
+
return jsonResult({ matches });
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
}
|