@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
|
@@ -1,44 +1,25 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type ConsentServer, createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
3
|
+
import { cursorListInputSchema } from "../../../shared/cursor-list-tool.ts";
|
|
4
|
+
import { requiredBaseUrl, requiredEnv } from "../../../shared/env-json-api.ts";
|
|
3
5
|
import { searchToolInputSchema } from "../../../shared/mcp-search-tool.ts";
|
|
4
6
|
import { fetchWithTimeout, mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
5
7
|
import {
|
|
6
8
|
runReadOnlyMcpConnector,
|
|
7
9
|
type ZodToolRegistrar,
|
|
8
10
|
} from "../../../shared/run-read-only-mcp-connector.ts";
|
|
11
|
+
import { asRecord } from "../../../shared/search-filter.ts";
|
|
9
12
|
import { filterLookerDashboards } from "./search-filter.ts";
|
|
10
13
|
|
|
11
|
-
function trimTrailingSlash(s: string): string {
|
|
12
|
-
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
14
|
function apiBase(): string {
|
|
16
|
-
|
|
17
|
-
if (v === undefined || v === "") {
|
|
18
|
-
throw new Error("LOOKER_BASE_URL is not set");
|
|
19
|
-
}
|
|
20
|
-
return trimTrailingSlash(v);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function clientId(): string {
|
|
24
|
-
const v = process.env["LOOKER_CLIENT_ID"]?.trim();
|
|
25
|
-
if (v === undefined || v === "") {
|
|
26
|
-
throw new Error("LOOKER_CLIENT_ID is not set");
|
|
27
|
-
}
|
|
28
|
-
return v;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function clientSecret(): string {
|
|
32
|
-
const v = process.env["LOOKER_CLIENT_SECRET"]?.trim();
|
|
33
|
-
if (v === undefined || v === "") {
|
|
34
|
-
throw new Error("LOOKER_CLIENT_SECRET is not set");
|
|
35
|
-
}
|
|
36
|
-
return v;
|
|
15
|
+
return requiredBaseUrl("LOOKER_BASE_URL");
|
|
37
16
|
}
|
|
38
17
|
|
|
39
18
|
async function lookerLogin(): Promise<string> {
|
|
40
19
|
const base = apiBase();
|
|
41
|
-
const
|
|
20
|
+
const clientId = encodeURIComponent(requiredEnv("LOOKER_CLIENT_ID"));
|
|
21
|
+
const clientSecret = encodeURIComponent(requiredEnv("LOOKER_CLIENT_SECRET"));
|
|
22
|
+
const body = `client_id=${clientId}&client_secret=${clientSecret}`;
|
|
42
23
|
const res = await fetchWithTimeout(`${base}/api/4.0/login`, {
|
|
43
24
|
method: "POST",
|
|
44
25
|
headers: {
|
|
@@ -51,11 +32,7 @@ async function lookerLogin(): Promise<string> {
|
|
|
51
32
|
if (!res.ok) {
|
|
52
33
|
throw new Error(`Looker login ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
53
34
|
}
|
|
54
|
-
const
|
|
55
|
-
const root =
|
|
56
|
-
parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
57
|
-
? (parsed as Record<string, unknown>)
|
|
58
|
-
: null;
|
|
35
|
+
const root = asRecord(JSON.parse(text) as unknown);
|
|
59
36
|
const token = typeof root?.["access_token"] === "string" ? root["access_token"] : null;
|
|
60
37
|
if (token === null || token === "") {
|
|
61
38
|
throw new Error("Looker login response missing access_token");
|
|
@@ -93,6 +70,25 @@ function offsetCursor(cursor: string | null | undefined): number {
|
|
|
93
70
|
: Math.max(0, Number.parseInt(cursor, 10) || 0);
|
|
94
71
|
}
|
|
95
72
|
|
|
73
|
+
const PAGE_LIMIT_DEFAULT = 200;
|
|
74
|
+
const PAGE_LIMIT_MAX = 500;
|
|
75
|
+
|
|
76
|
+
/** The collections exposed as an offset-cursor listing. */
|
|
77
|
+
const PAGINATED_LISTS = [
|
|
78
|
+
{
|
|
79
|
+
tool: "looker_list",
|
|
80
|
+
path: "/api/4.0/dashboards",
|
|
81
|
+
description:
|
|
82
|
+
"List Looker dashboards (`GET /api/4.0/dashboards`). Requires a client-credentials login first. Paginated: `cursor` (opaque offset) + `limit` (default 200, max 500) → `{ items, nextCursor }`.",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
tool: "looker_models_list",
|
|
86
|
+
path: "/api/4.0/lookml_models",
|
|
87
|
+
description:
|
|
88
|
+
"List LookML models (`GET /api/4.0/lookml_models`) for dashboard→table lineage. Paginated: `cursor` (opaque offset) + `limit` (default 200, max 500) → `{ items, nextCursor }`. Returns raw models (explores/views nested); the gateway flattens them.",
|
|
89
|
+
},
|
|
90
|
+
] as const;
|
|
91
|
+
|
|
96
92
|
export function registerLookerTools(reg: ZodToolRegistrar, server: unknown): void {
|
|
97
93
|
// Despite the read-only helper's name, this connector exposes write tools. The consent
|
|
98
94
|
// kit needs the real server, which the helper now passes through as its second argument.
|
|
@@ -102,43 +98,18 @@ export function registerLookerTools(reg: ZodToolRegistrar, server: unknown): voi
|
|
|
102
98
|
scopeKinds: ["resource"],
|
|
103
99
|
});
|
|
104
100
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
cursor: z.string().nullable().optional(),
|
|
110
|
-
limit: z.number().int().min(1).max(500).optional(),
|
|
111
|
-
}),
|
|
112
|
-
async (p) => {
|
|
113
|
-
const limit = p.limit ?? 200;
|
|
101
|
+
// Both listings are the same offset-cursor page over a different collection.
|
|
102
|
+
for (const { tool, path, description } of PAGINATED_LISTS) {
|
|
103
|
+
reg(tool, description, cursorListInputSchema(PAGE_LIMIT_MAX), async (p) => {
|
|
104
|
+
const limit = p.limit ?? PAGE_LIMIT_DEFAULT;
|
|
114
105
|
const offset = offsetCursor(p.cursor);
|
|
115
|
-
const
|
|
116
|
-
const items = await getArray(token, "/api/4.0/dashboards", { limit, offset });
|
|
106
|
+
const items = await getArray(await lookerLogin(), path, { limit, offset });
|
|
117
107
|
return jsonResult({
|
|
118
108
|
items,
|
|
119
109
|
nextCursor: items.length === limit ? String(offset + limit) : null,
|
|
120
110
|
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
reg(
|
|
125
|
-
"looker_models_list",
|
|
126
|
-
"List LookML models (`GET /api/4.0/lookml_models`) for dashboard→table lineage. Paginated: `cursor` (opaque offset) + `limit` (default 200, max 500) → `{ items, nextCursor }`. Returns raw models (explores/views nested); the gateway flattens them.",
|
|
127
|
-
z.object({
|
|
128
|
-
cursor: z.string().nullable().optional(),
|
|
129
|
-
limit: z.number().int().min(1).max(500).optional(),
|
|
130
|
-
}),
|
|
131
|
-
async (p) => {
|
|
132
|
-
const limit = p.limit ?? 200;
|
|
133
|
-
const offset = offsetCursor(p.cursor);
|
|
134
|
-
const token = await lookerLogin();
|
|
135
|
-
const items = await getArray(token, "/api/4.0/lookml_models", { limit, offset });
|
|
136
|
-
return jsonResult({
|
|
137
|
-
items,
|
|
138
|
-
nextCursor: items.length === limit ? String(offset + limit) : null,
|
|
139
|
-
});
|
|
140
|
-
},
|
|
141
|
-
);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
142
113
|
|
|
143
114
|
reg(
|
|
144
115
|
"looker_get",
|
|
@@ -149,13 +120,7 @@ export function registerLookerTools(reg: ZodToolRegistrar, server: unknown): voi
|
|
|
149
120
|
async (p) => {
|
|
150
121
|
const token = await lookerLogin();
|
|
151
122
|
const dashboards = await listDashboards(token);
|
|
152
|
-
const found = dashboards.find((d) =>
|
|
153
|
-
const obj =
|
|
154
|
-
d !== null && typeof d === "object" && !Array.isArray(d)
|
|
155
|
-
? (d as Record<string, unknown>)
|
|
156
|
-
: null;
|
|
157
|
-
return obj?.["id"] === p.id;
|
|
158
|
-
});
|
|
123
|
+
const found = dashboards.find((d) => asRecord(d)?.["id"] === p.id);
|
|
159
124
|
if (found === undefined) {
|
|
160
125
|
throw new Error(`Looker dashboard not found: ${p.id}`);
|
|
161
126
|
}
|
|
@@ -1,59 +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 BASE = "https://api.mendeley.com";
|
|
8
|
-
const DOC_ACCEPT = "application/vnd.mendeley-document.1+json";
|
|
9
|
-
|
|
10
|
-
function accessToken(): string {
|
|
11
|
-
const t = process.env["MENDELEY_ACCESS_TOKEN"]?.trim();
|
|
12
|
-
if (t === undefined || t === "") {
|
|
13
|
-
throw new Error("MENDELEY_ACCESS_TOKEN is not set");
|
|
14
|
-
}
|
|
15
|
-
return t;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function mendeleyGet(path: string): Promise<unknown> {
|
|
19
|
-
const res = await fetch(`${BASE}${path}`, {
|
|
20
|
-
headers: { Authorization: `Bearer ${accessToken()}`, Accept: DOC_ACCEPT },
|
|
21
|
-
});
|
|
22
|
-
const text = await res.text();
|
|
23
|
-
if (!res.ok) {
|
|
24
|
-
// Include the HTTP status so an expired token (401) surfaces explicitly to the
|
|
25
|
-
// gateway client; matches the zotero server's error shape.
|
|
26
|
-
throw new Error(`Mendeley ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
27
|
-
}
|
|
28
|
-
return JSON.parse(text) as unknown;
|
|
29
|
-
}
|
|
2
|
+
import { registerMendeleyTools } from "./tools.ts";
|
|
30
3
|
|
|
31
4
|
await runReadOnlyMcpConnector("nimbus-mendeley", (reg) => {
|
|
32
|
-
reg
|
|
33
|
-
"mendeley_list",
|
|
34
|
-
"List the user's Mendeley library documents (`GET /documents?view=all&limit=100`). Returns the raw JSON array of document objects — each is `{ id, title, type, authors, year, source, identifiers: { doi, ... }, keywords, abstract, last_modified, websites, ... }`.",
|
|
35
|
-
z.object({}),
|
|
36
|
-
async () => {
|
|
37
|
-
return jsonResult(await mendeleyGet(`/documents?view=all&limit=100`));
|
|
38
|
-
},
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
reg(
|
|
42
|
-
"mendeley_get",
|
|
43
|
-
"Fetch one Mendeley document by its id (`GET /documents/{id}?view=all`). Returns the document object directly (NOT wrapped in an array). Throws when no match is found.",
|
|
44
|
-
z.object({ id: z.string().min(1) }),
|
|
45
|
-
async (p) => {
|
|
46
|
-
return jsonResult(await mendeleyGet(`/documents/${encodeURIComponent(p.id)}?view=all`));
|
|
47
|
-
},
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
reg(
|
|
51
|
-
"mendeley_search",
|
|
52
|
-
"Substring search across the first page of the user's library documents. Matches the query against the title, document type, abstract, source, DOI, formatted author names, and keywords (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
53
|
-
z.object({ query: z.string().min(1), limit: z.number().int().min(1).max(100).optional() }),
|
|
54
|
-
async (p) => {
|
|
55
|
-
const root = await mendeleyGet(`/documents?view=all&limit=100`);
|
|
56
|
-
return matchesResult(root, filterMendeleyDocuments, p);
|
|
57
|
-
},
|
|
58
|
-
);
|
|
5
|
+
registerMendeleyTools(reg);
|
|
59
6
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
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 { filterMendeleyDocuments } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const BASE = "https://api.mendeley.com";
|
|
9
|
+
const DOC_ACCEPT = "application/vnd.mendeley-document.1+json";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Failures carry the HTTP status (`Mendeley <status>: …`) so an expired token (401) surfaces
|
|
13
|
+
* explicitly to the gateway client; the same error shape as the zotero connector's.
|
|
14
|
+
*/
|
|
15
|
+
const mendeleyGet = createJsonGetter({
|
|
16
|
+
base: BASE,
|
|
17
|
+
label: "Mendeley",
|
|
18
|
+
// `extra` replaces the default `Accept` in place: Mendeley serves documents under its own
|
|
19
|
+
// vendor media type.
|
|
20
|
+
headers: envAuthHeaders({ env: "MENDELEY_ACCESS_TOKEN", extra: { Accept: DOC_ACCEPT } }),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
24
|
+
export const MENDELEY_TOOL_NAMES = ["mendeley_get", "mendeley_list", "mendeley_search"] as const;
|
|
25
|
+
|
|
26
|
+
export function registerMendeleyTools(reg: ZodToolRegistrar): void {
|
|
27
|
+
reg(
|
|
28
|
+
"mendeley_list",
|
|
29
|
+
"List the user's Mendeley library documents (`GET /documents?view=all&limit=100`). Returns the raw JSON array of document objects — each is `{ id, title, type, authors, year, source, identifiers: { doi, ... }, keywords, abstract, last_modified, websites, ... }`.",
|
|
30
|
+
z.object({}),
|
|
31
|
+
async () => {
|
|
32
|
+
return jsonResult(await mendeleyGet(`/documents?view=all&limit=100`));
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
reg(
|
|
37
|
+
"mendeley_get",
|
|
38
|
+
"Fetch one Mendeley document by its id (`GET /documents/{id}?view=all`). Returns the document object directly (NOT wrapped in an array). Throws when no match is found.",
|
|
39
|
+
z.object({ id: z.string().min(1) }),
|
|
40
|
+
async (p) => {
|
|
41
|
+
return jsonResult(await mendeleyGet(`/documents/${encodeURIComponent(p.id)}?view=all`));
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
reg(
|
|
46
|
+
"mendeley_search",
|
|
47
|
+
"Substring search across the first page of the user's library documents. Matches the query against the title, document type, abstract, source, DOI, formatted author names, and keywords (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
48
|
+
z.object({ query: z.string().min(1), limit: z.number().int().min(1).max(100).optional() }),
|
|
49
|
+
async (p) => {
|
|
50
|
+
const root = await mendeleyGet(`/documents?view=all&limit=100`);
|
|
51
|
+
return matchesResult(root, filterMendeleyDocuments, p);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -1,61 +1,6 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { matchesResult, 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.mercury.com";
|
|
8
|
-
|
|
9
|
-
function apiToken(): string {
|
|
10
|
-
const t = process.env["MERCURY_TOKEN"]?.trim();
|
|
11
|
-
if (t === undefined || t === "") {
|
|
12
|
-
throw new Error("MERCURY_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 mercuryGet(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(`Mercury ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
26
|
-
}
|
|
27
|
-
return JSON.parse(text) as unknown;
|
|
28
|
-
}
|
|
2
|
+
import { registerMercuryTools } from "./tools.ts";
|
|
29
3
|
|
|
30
4
|
await runReadOnlyMcpConnector("nimbus-mercury", (reg) => {
|
|
31
|
-
reg
|
|
32
|
-
"mercury_list",
|
|
33
|
-
"List the user's Mercury bank accounts (`GET /api/v1/accounts`). Returns the `{ accounts: [...] }` envelope. There is no pagination — Mercury returns the full account list in one call.",
|
|
34
|
-
z.object({}),
|
|
35
|
-
async () => {
|
|
36
|
-
return jsonResult(await mercuryGet(`/api/v1/accounts`));
|
|
37
|
-
},
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
reg(
|
|
41
|
-
"mercury_get",
|
|
42
|
-
"Fetch one Mercury account by its id (`GET /api/v1/account/{id}`, note the singular `account` in the path). Returns the account object directly. Throws when no match is found.",
|
|
43
|
-
z.object({
|
|
44
|
-
id: z.string().min(1),
|
|
45
|
-
}),
|
|
46
|
-
async (p) => {
|
|
47
|
-
return jsonResult(await mercuryGet(`/api/v1/account/${encodeURIComponent(p.id)}`));
|
|
48
|
-
},
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
reg(
|
|
52
|
-
"mercury_search",
|
|
53
|
-
"Substring search across the user's Mercury accounts. Matches the query against id, name, status, type, kind, and the legal business name (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
54
|
-
searchToolInputSchema(100),
|
|
55
|
-
async (p) => {
|
|
56
|
-
const root = await mercuryGet(`/api/v1/accounts`);
|
|
57
|
-
const accounts = (root as { accounts?: unknown[] } | null)?.accounts;
|
|
58
|
-
return matchesResult(accounts, filterMercuryAccounts, p);
|
|
59
|
-
},
|
|
60
|
-
);
|
|
5
|
+
registerMercuryTools(reg);
|
|
61
6
|
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, envAuthHeaders } from "../../../shared/env-json-api.ts";
|
|
3
|
+
import { matchesResult, 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 { filterMercuryAccounts } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const BASE = "https://api.mercury.com";
|
|
9
|
+
|
|
10
|
+
const mercuryGet = createJsonGetter({
|
|
11
|
+
base: BASE,
|
|
12
|
+
label: "Mercury",
|
|
13
|
+
headers: envAuthHeaders({ env: "MERCURY_TOKEN" }),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
17
|
+
export const MERCURY_TOOL_NAMES = ["mercury_get", "mercury_list", "mercury_search"] as const;
|
|
18
|
+
|
|
19
|
+
export function registerMercuryTools(reg: ZodToolRegistrar): void {
|
|
20
|
+
reg(
|
|
21
|
+
"mercury_list",
|
|
22
|
+
"List the user's Mercury bank accounts (`GET /api/v1/accounts`). Returns the `{ accounts: [...] }` envelope. There is no pagination — Mercury returns the full account list in one call.",
|
|
23
|
+
z.object({}),
|
|
24
|
+
async () => {
|
|
25
|
+
return jsonResult(await mercuryGet(`/api/v1/accounts`));
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
reg(
|
|
30
|
+
"mercury_get",
|
|
31
|
+
"Fetch one Mercury account by its id (`GET /api/v1/account/{id}`, note the singular `account` in the path). Returns the account object directly. Throws when no match is found.",
|
|
32
|
+
z.object({
|
|
33
|
+
id: z.string().min(1),
|
|
34
|
+
}),
|
|
35
|
+
async (p) => {
|
|
36
|
+
return jsonResult(await mercuryGet(`/api/v1/account/${encodeURIComponent(p.id)}`));
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
reg(
|
|
41
|
+
"mercury_search",
|
|
42
|
+
"Substring search across the user's Mercury accounts. Matches the query against id, name, status, type, kind, and the legal business name (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
43
|
+
searchToolInputSchema(100),
|
|
44
|
+
async (p) => {
|
|
45
|
+
const root = await mercuryGet(`/api/v1/accounts`);
|
|
46
|
+
const accounts = (root as { accounts?: unknown[] } | null)?.accounts;
|
|
47
|
+
return matchesResult(accounts, filterMercuryAccounts, p);
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
asRecord,
|
|
3
2
|
makeQueryFilter,
|
|
3
|
+
recordFieldsFromKeys,
|
|
4
4
|
type SearchMatchOptions,
|
|
5
5
|
} from "../../../shared/search-filter.ts";
|
|
6
6
|
|
|
7
7
|
export type MetabaseSearchMatchOptions = SearchMatchOptions;
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function fieldsOf(item: unknown): readonly string[] | null {
|
|
15
|
-
const row = asRecord(item);
|
|
16
|
-
if (row === undefined) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
return [stringAt(row, "name"), stringAt(row, "description")];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const filterMetabaseDashboards = makeQueryFilter(fieldsOf);
|
|
9
|
+
export const filterMetabaseDashboards = makeQueryFilter(
|
|
10
|
+
recordFieldsFromKeys(["name", "description"]),
|
|
11
|
+
);
|
|
@@ -1,83 +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
|
-
function trimTrailingSlash(s: string): string {
|
|
8
|
-
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function apiBase(): string {
|
|
12
|
-
const v = process.env["METABASE_URL"]?.trim();
|
|
13
|
-
if (v === undefined || v === "") {
|
|
14
|
-
throw new Error("METABASE_URL is not set");
|
|
15
|
-
}
|
|
16
|
-
return trimTrailingSlash(v);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function authHeader(): Record<string, string> {
|
|
20
|
-
const k = process.env["METABASE_API_KEY"]?.trim();
|
|
21
|
-
if (k === undefined || k === "") {
|
|
22
|
-
throw new Error("METABASE_API_KEY is not set");
|
|
23
|
-
}
|
|
24
|
-
return { "x-api-key": k, Accept: "application/json" };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function mbGet(path: string): Promise<unknown> {
|
|
28
|
-
const res = await fetch(`${apiBase()}${path}`, { headers: authHeader() });
|
|
29
|
-
const text = await res.text();
|
|
30
|
-
if (!res.ok) {
|
|
31
|
-
throw new Error(`Metabase ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
32
|
-
}
|
|
33
|
-
return JSON.parse(text) as unknown;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function dashboardsFrom(root: unknown): unknown[] {
|
|
37
|
-
if (Array.isArray(root)) {
|
|
38
|
-
return root;
|
|
39
|
-
}
|
|
40
|
-
const data = (root as { data?: unknown } | null)?.data;
|
|
41
|
-
return Array.isArray(data) ? data : [];
|
|
42
|
-
}
|
|
2
|
+
import { registerMetabaseTools } from "./tools.ts";
|
|
43
3
|
|
|
44
4
|
await runReadOnlyMcpConnector("nimbus-metabase", (reg) => {
|
|
45
|
-
reg
|
|
46
|
-
"metabase_list",
|
|
47
|
-
"List Metabase dashboards (`GET /api/dashboard`). Metabase returns the full list in one response; `limit` (default 200) caps the returned list client-side.",
|
|
48
|
-
z.object({
|
|
49
|
-
limit: z.number().int().min(1).max(500).optional(),
|
|
50
|
-
}),
|
|
51
|
-
async (p) => {
|
|
52
|
-
const root = await mbGet("/api/dashboard");
|
|
53
|
-
const dashboards = dashboardsFrom(root);
|
|
54
|
-
const cap = p.limit ?? 200;
|
|
55
|
-
return jsonResult({ items: dashboards.slice(0, cap) });
|
|
56
|
-
},
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
reg(
|
|
60
|
-
"metabase_get",
|
|
61
|
-
"Fetch one Metabase dashboard by numeric id (`/api/dashboard/{id}`). Throws when no dashboard with that id exists.",
|
|
62
|
-
z.object({
|
|
63
|
-
id: z.number().int(),
|
|
64
|
-
}),
|
|
65
|
-
async (p) => {
|
|
66
|
-
return jsonResult(await mbGet(`/api/dashboard/${encodeURIComponent(String(p.id))}`));
|
|
67
|
-
},
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
reg(
|
|
71
|
-
"metabase_search",
|
|
72
|
-
"Substring search across Metabase dashboards. Matches the query (case-insensitive) against dashboard name and description. Returns a `{ matches: [...] }` envelope.",
|
|
73
|
-
searchToolInputSchema(200),
|
|
74
|
-
async (p) => {
|
|
75
|
-
const root = await mbGet("/api/dashboard");
|
|
76
|
-
const matches = filterMetabaseDashboards(dashboardsFrom(root), {
|
|
77
|
-
query: p.query,
|
|
78
|
-
limit: p.limit,
|
|
79
|
-
});
|
|
80
|
-
return jsonResult({ matches });
|
|
81
|
-
},
|
|
82
|
-
);
|
|
5
|
+
registerMetabaseTools(reg);
|
|
83
6
|
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createJsonGetter, envAuthHeaders, requiredBaseUrl } 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 { filterMetabaseDashboards } from "./search-filter.ts";
|
|
7
|
+
|
|
8
|
+
const mbGet = createJsonGetter({
|
|
9
|
+
base: () => requiredBaseUrl("METABASE_URL"),
|
|
10
|
+
label: "Metabase",
|
|
11
|
+
headers: envAuthHeaders({ env: "METABASE_API_KEY", scheme: "", header: "x-api-key" }),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
function dashboardsFrom(root: unknown): unknown[] {
|
|
15
|
+
if (Array.isArray(root)) {
|
|
16
|
+
return root;
|
|
17
|
+
}
|
|
18
|
+
const data = (root as { data?: unknown } | null)?.data;
|
|
19
|
+
return Array.isArray(data) ? data : [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
23
|
+
export const METABASE_TOOL_NAMES = ["metabase_get", "metabase_list", "metabase_search"] as const;
|
|
24
|
+
|
|
25
|
+
export function registerMetabaseTools(reg: ZodToolRegistrar): void {
|
|
26
|
+
reg(
|
|
27
|
+
"metabase_list",
|
|
28
|
+
"List Metabase dashboards (`GET /api/dashboard`). Metabase returns the full list in one response; `limit` (default 200) caps the returned list client-side.",
|
|
29
|
+
z.object({
|
|
30
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
31
|
+
}),
|
|
32
|
+
async (p) => {
|
|
33
|
+
const root = await mbGet("/api/dashboard");
|
|
34
|
+
const dashboards = dashboardsFrom(root);
|
|
35
|
+
const cap = p.limit ?? 200;
|
|
36
|
+
return jsonResult({ items: dashboards.slice(0, cap) });
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
reg(
|
|
41
|
+
"metabase_get",
|
|
42
|
+
"Fetch one Metabase dashboard by numeric id (`/api/dashboard/{id}`). Throws when no dashboard with that id exists.",
|
|
43
|
+
z.object({
|
|
44
|
+
id: z.number().int(),
|
|
45
|
+
}),
|
|
46
|
+
async (p) => {
|
|
47
|
+
return jsonResult(await mbGet(`/api/dashboard/${encodeURIComponent(String(p.id))}`));
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
reg(
|
|
52
|
+
"metabase_search",
|
|
53
|
+
"Substring search across Metabase dashboards. Matches the query (case-insensitive) against dashboard name and description. Returns a `{ matches: [...] }` envelope.",
|
|
54
|
+
searchToolInputSchema(200),
|
|
55
|
+
async (p) => {
|
|
56
|
+
const root = await mbGet("/api/dashboard");
|
|
57
|
+
const matches = filterMetabaseDashboards(dashboardsFrom(root), {
|
|
58
|
+
query: p.query,
|
|
59
|
+
limit: p.limit,
|
|
60
|
+
});
|
|
61
|
+
return jsonResult({ matches });
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -1,76 +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.miro.com";
|
|
8
|
-
|
|
9
|
-
function apiToken(): string {
|
|
10
|
-
const t = process.env["MIRO_TOKEN"]?.trim();
|
|
11
|
-
if (t === undefined || t === "") {
|
|
12
|
-
throw new Error("MIRO_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 miroGet(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(`Miro ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
26
|
-
}
|
|
27
|
-
return JSON.parse(text) as unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function boardsFrom(root: unknown): unknown[] {
|
|
31
|
-
const data = (root as { data?: unknown } | null)?.data;
|
|
32
|
-
return Array.isArray(data) ? data : [];
|
|
33
|
-
}
|
|
2
|
+
import { registerMiroTools } from "./tools.ts";
|
|
34
3
|
|
|
35
4
|
await runReadOnlyMcpConnector("nimbus-miro", (reg) => {
|
|
36
|
-
reg
|
|
37
|
-
"miro_list",
|
|
38
|
-
"List the authenticated user's Miro boards (GET /v2/boards?limit=N&cursor=<cursor>). Returns the { data: [...], cursor?, total, size } envelope — `data` holds the board objects, each shaped { id, name, description, createdAt, modifiedAt, owner: { name }, viewLink, ... }; `cursor` (when present) is the opaque token for the next page. `limit` (default 50, max 50) caps the page_size of the single first-page request.",
|
|
39
|
-
z.object({
|
|
40
|
-
limit: z.number().int().min(1).max(50).optional(),
|
|
41
|
-
cursor: z.string().optional(),
|
|
42
|
-
}),
|
|
43
|
-
async (p) => {
|
|
44
|
-
const params = new URLSearchParams({ limit: String(p.limit ?? 50) });
|
|
45
|
-
if (p.cursor !== undefined && p.cursor !== "") {
|
|
46
|
-
params.set("cursor", p.cursor);
|
|
47
|
-
}
|
|
48
|
-
return jsonResult(await miroGet(`/v2/boards?${params.toString()}`));
|
|
49
|
-
},
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
reg(
|
|
53
|
-
"miro_get",
|
|
54
|
-
"Fetch one Miro board by its id (GET /v2/boards/{boardId}). Returns the board object directly (NOT wrapped in `data`). Throws when no board with that id exists.",
|
|
55
|
-
z.object({
|
|
56
|
-
id: z.string().min(1),
|
|
57
|
-
}),
|
|
58
|
-
async (p) => {
|
|
59
|
-
return jsonResult(await miroGet(`/v2/boards/${encodeURIComponent(p.id)}`));
|
|
60
|
-
},
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
reg(
|
|
64
|
-
"miro_search",
|
|
65
|
-
"**Substring search over the FIRST PAGE only** (up to 50 most-recently-listed boards) of the authenticated user's Miro boards. This tool fetches GET /v2/boards?limit=50 once and matches the query locally (case-insensitive) against the board name, description, and owner name. **Boards beyond the first page are not searchable here — query the local Nimbus index instead for full coverage.** Returns a { matches: [...] } envelope.",
|
|
66
|
-
searchToolInputSchema(50),
|
|
67
|
-
async (p) => {
|
|
68
|
-
const root = await miroGet("/v2/boards?limit=50");
|
|
69
|
-
const matches = filterMiroBoards(boardsFrom(root), {
|
|
70
|
-
query: p.query,
|
|
71
|
-
limit: p.limit,
|
|
72
|
-
});
|
|
73
|
-
return jsonResult({ matches });
|
|
74
|
-
},
|
|
75
|
-
);
|
|
5
|
+
registerMiroTools(reg);
|
|
76
6
|
});
|