@nimbus-dev/connectors 0.2.1 → 0.2.3
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 +19 -7
- 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 +26 -43
- package/connectors/aws/src/server.ts +2 -183
- package/connectors/aws/src/tools.ts +199 -0
- package/connectors/azure/src/server.ts +2 -131
- package/connectors/azure/src/tools.ts +134 -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 +19 -35
- package/connectors/cloudwatch/src/tools.ts +20 -38
- 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 +153 -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 +160 -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 +21 -40
- 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 +601 -34
- package/shared/cli-json-kit.ts +191 -0
- package/shared/collection-tool-kit.ts +143 -0
- package/shared/connector-mode.ts +3 -2
- package/shared/consent-kit.ts +81 -26
- 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/nimbus-spawn.ts +84 -27
- package/shared/rest-tool-kit.ts +77 -7
- package/shared/run-cli-json.ts +38 -16
- package/shared/safe-cli-arg.ts +139 -17
- package/shared/search-filter.ts +58 -18
- package/shared/walk-files.ts +118 -0
- package/shared/windows-batch-args.ts +273 -0
- package/shared/write-budget.ts +100 -0
- package/standalone/src/launcher.ts +19 -7
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
type ConsentServer,
|
|
5
|
+
createWriteToolRegistrar,
|
|
6
|
+
type WriteToolConfig,
|
|
7
|
+
} from "../../../shared/consent-kit.ts";
|
|
8
|
+
import {
|
|
9
|
+
createRegisterSimpleTool,
|
|
10
|
+
createZodToolRegistrar,
|
|
11
|
+
mcpJsonResult,
|
|
12
|
+
requireProcessEnv,
|
|
13
|
+
type ZodObjectSchema,
|
|
14
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
15
|
+
import { escapeDriveQueryLiteral } from "./drive-query.ts";
|
|
16
|
+
|
|
17
|
+
const METADATA_FIELDS =
|
|
18
|
+
"id, name, mimeType, description, starred, trashed, parents, webViewLink, webContentLink, size, createdTime, modifiedTime, owners(displayName,emailAddress), shared";
|
|
19
|
+
|
|
20
|
+
const GOOGLE_APPS_EXPORT_MIME: Readonly<Record<string, string>> = {
|
|
21
|
+
"application/vnd.google-apps.document": "text/plain",
|
|
22
|
+
"application/vnd.google-apps.spreadsheet": "text/csv",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** A Drive API response's JSON; throws `Drive API <status>: <first 200 chars>` when it is not ok. */
|
|
26
|
+
async function driveJson(res: Response): Promise<unknown> {
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const body = await res.text();
|
|
29
|
+
throw new Error(`Drive API ${String(res.status)}: ${body.slice(0, 200)}`);
|
|
30
|
+
}
|
|
31
|
+
return (await res.json()) as unknown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function driveListFiles(
|
|
35
|
+
token: string,
|
|
36
|
+
pageSize: number,
|
|
37
|
+
pageToken: string | undefined,
|
|
38
|
+
q: string,
|
|
39
|
+
): Promise<unknown> {
|
|
40
|
+
const params = new URLSearchParams({
|
|
41
|
+
pageSize: String(pageSize),
|
|
42
|
+
fields:
|
|
43
|
+
"nextPageToken, files(id, name, mimeType, modifiedTime, webViewLink, size, description)",
|
|
44
|
+
q,
|
|
45
|
+
});
|
|
46
|
+
if (pageToken !== undefined && pageToken !== "") {
|
|
47
|
+
params.set("pageToken", pageToken);
|
|
48
|
+
}
|
|
49
|
+
const res = await fetch(`https://www.googleapis.com/drive/v3/files?${params.toString()}`, {
|
|
50
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
51
|
+
});
|
|
52
|
+
return driveJson(res);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function driveGetFileMetadata(token: string, fileId: string): Promise<unknown> {
|
|
56
|
+
const params = new URLSearchParams({ fields: METADATA_FIELDS });
|
|
57
|
+
const res = await fetch(
|
|
58
|
+
`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}?${params.toString()}`,
|
|
59
|
+
{ headers: { Authorization: `Bearer ${token}` } },
|
|
60
|
+
);
|
|
61
|
+
return driveJson(res);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type DownloadOk = {
|
|
65
|
+
fileId: string;
|
|
66
|
+
name: string;
|
|
67
|
+
mimeType: string;
|
|
68
|
+
encoding: "base64" | "utf-8";
|
|
69
|
+
exportMimeType?: string;
|
|
70
|
+
truncated: boolean;
|
|
71
|
+
content: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function jsonFileTooLarge(sizeBytes: number, maxBytes: number, message: string): string {
|
|
75
|
+
return JSON.stringify({
|
|
76
|
+
code: "FILE_TOO_LARGE",
|
|
77
|
+
sizeBytes,
|
|
78
|
+
maxBytes,
|
|
79
|
+
message,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function driveDownloadGoogleAppsExport(
|
|
84
|
+
token: string,
|
|
85
|
+
fileId: string,
|
|
86
|
+
name: string,
|
|
87
|
+
mimeType: string,
|
|
88
|
+
exportMime: string,
|
|
89
|
+
maxBytes: number,
|
|
90
|
+
): Promise<{ ok: true; payload: DownloadOk } | { ok: false; message: string }> {
|
|
91
|
+
const exportUrl = `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}/export?mimeType=${encodeURIComponent(exportMime)}`;
|
|
92
|
+
const res = await fetch(exportUrl, { headers: { Authorization: `Bearer ${token}` } });
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
const body = await res.text();
|
|
95
|
+
return { ok: false, message: `export ${String(res.status)}: ${body.slice(0, 200)}` };
|
|
96
|
+
}
|
|
97
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
98
|
+
const truncated = buf.byteLength > maxBytes;
|
|
99
|
+
const slice = truncated ? buf.slice(0, maxBytes) : buf;
|
|
100
|
+
const text = new TextDecoder("utf-8", { fatal: false }).decode(slice);
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
payload: {
|
|
104
|
+
fileId,
|
|
105
|
+
name,
|
|
106
|
+
mimeType,
|
|
107
|
+
encoding: "utf-8",
|
|
108
|
+
exportMimeType: exportMime,
|
|
109
|
+
truncated,
|
|
110
|
+
content: text,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function driveDownloadMediaPayload(
|
|
116
|
+
token: string,
|
|
117
|
+
fileId: string,
|
|
118
|
+
name: string,
|
|
119
|
+
mimeType: string,
|
|
120
|
+
meta: Record<string, unknown>,
|
|
121
|
+
maxBytes: number,
|
|
122
|
+
): Promise<{ ok: true; payload: DownloadOk } | { ok: false; message: string }> {
|
|
123
|
+
const sizeStr = meta["size"];
|
|
124
|
+
if (typeof sizeStr === "string" && sizeStr !== "") {
|
|
125
|
+
const n = Number.parseInt(sizeStr, 10);
|
|
126
|
+
if (Number.isFinite(n) && n > maxBytes) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
message: jsonFileTooLarge(n, maxBytes, "File exceeds maxBytes."),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const mediaUrl = `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}?alt=media`;
|
|
135
|
+
const res = await fetch(mediaUrl, { headers: { Authorization: `Bearer ${token}` } });
|
|
136
|
+
if (!res.ok) {
|
|
137
|
+
const body = await res.text();
|
|
138
|
+
return { ok: false, message: `download ${String(res.status)}: ${body.slice(0, 200)}` };
|
|
139
|
+
}
|
|
140
|
+
const contentLength = res.headers.get("content-length");
|
|
141
|
+
if (contentLength !== null && contentLength !== "") {
|
|
142
|
+
const n = Number.parseInt(contentLength, 10);
|
|
143
|
+
if (Number.isFinite(n) && n > maxBytes) {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
message: jsonFileTooLarge(n, maxBytes, "File exceeds maxBytes."),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
151
|
+
if (buf.byteLength > maxBytes) {
|
|
152
|
+
return {
|
|
153
|
+
ok: false,
|
|
154
|
+
message: jsonFileTooLarge(buf.byteLength, maxBytes, "Downloaded content exceeds maxBytes."),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const isTextLike = mimeType === "text/plain" || mimeType.startsWith("text/");
|
|
158
|
+
if (isTextLike) {
|
|
159
|
+
return {
|
|
160
|
+
ok: true,
|
|
161
|
+
payload: {
|
|
162
|
+
fileId,
|
|
163
|
+
name,
|
|
164
|
+
mimeType,
|
|
165
|
+
encoding: "utf-8",
|
|
166
|
+
truncated: false,
|
|
167
|
+
content: new TextDecoder("utf-8", { fatal: false }).decode(buf),
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
ok: true,
|
|
173
|
+
payload: {
|
|
174
|
+
fileId,
|
|
175
|
+
name,
|
|
176
|
+
mimeType,
|
|
177
|
+
encoding: "base64",
|
|
178
|
+
truncated: false,
|
|
179
|
+
content: Buffer.from(buf).toString("base64"),
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function driveDownloadFile(
|
|
185
|
+
token: string,
|
|
186
|
+
fileId: string,
|
|
187
|
+
maxBytes: number,
|
|
188
|
+
): Promise<{ ok: true; payload: DownloadOk } | { ok: false; message: string }> {
|
|
189
|
+
const params = new URLSearchParams({
|
|
190
|
+
fields: "id,name,mimeType,size,webViewLink",
|
|
191
|
+
});
|
|
192
|
+
const metaRes = await fetch(
|
|
193
|
+
`https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}?${params.toString()}`,
|
|
194
|
+
{ headers: { Authorization: `Bearer ${token}` } },
|
|
195
|
+
);
|
|
196
|
+
if (!metaRes.ok) {
|
|
197
|
+
const body = await metaRes.text();
|
|
198
|
+
return { ok: false, message: `metadata ${String(metaRes.status)}: ${body.slice(0, 200)}` };
|
|
199
|
+
}
|
|
200
|
+
const metaUnknown: unknown = await metaRes.json();
|
|
201
|
+
if (metaUnknown === null || typeof metaUnknown !== "object" || Array.isArray(metaUnknown)) {
|
|
202
|
+
return { ok: false, message: "metadata: invalid response" };
|
|
203
|
+
}
|
|
204
|
+
const meta = metaUnknown as Record<string, unknown>;
|
|
205
|
+
const mimeType = typeof meta["mimeType"] === "string" ? meta["mimeType"] : "";
|
|
206
|
+
const name = typeof meta["name"] === "string" ? meta["name"] : fileId;
|
|
207
|
+
|
|
208
|
+
if (mimeType.startsWith("application/vnd.google-apps.")) {
|
|
209
|
+
const exportMime = GOOGLE_APPS_EXPORT_MIME[mimeType];
|
|
210
|
+
if (exportMime === undefined) {
|
|
211
|
+
const webViewLink = typeof meta["webViewLink"] === "string" ? meta["webViewLink"] : null;
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
message: JSON.stringify({
|
|
215
|
+
code: "EXPORT_NOT_SUPPORTED",
|
|
216
|
+
mimeType,
|
|
217
|
+
webViewLink,
|
|
218
|
+
message:
|
|
219
|
+
"This Google Workspace type is not exported as text here; use gdrive_file_metadata for webViewLink.",
|
|
220
|
+
}),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return driveDownloadGoogleAppsExport(token, fileId, name, mimeType, exportMime, maxBytes);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return driveDownloadMediaPayload(token, fileId, name, mimeType, meta, maxBytes);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function drivePatchJson(
|
|
230
|
+
token: string,
|
|
231
|
+
fileId: string,
|
|
232
|
+
body: Record<string, unknown>,
|
|
233
|
+
query?: URLSearchParams,
|
|
234
|
+
): Promise<unknown> {
|
|
235
|
+
const path = `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}`;
|
|
236
|
+
const url = query !== undefined && [...query].length > 0 ? `${path}?${query.toString()}` : path;
|
|
237
|
+
const res = await fetch(url, {
|
|
238
|
+
method: "PATCH",
|
|
239
|
+
headers: {
|
|
240
|
+
Authorization: `Bearer ${token}`,
|
|
241
|
+
"Content-Type": "application/json",
|
|
242
|
+
},
|
|
243
|
+
body: JSON.stringify(body),
|
|
244
|
+
});
|
|
245
|
+
return driveJson(res);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function drivePostCreateMetadata(
|
|
249
|
+
token: string,
|
|
250
|
+
metadata: Record<string, unknown>,
|
|
251
|
+
): Promise<unknown> {
|
|
252
|
+
const res = await fetch("https://www.googleapis.com/drive/v3/files", {
|
|
253
|
+
method: "POST",
|
|
254
|
+
headers: {
|
|
255
|
+
Authorization: `Bearer ${token}`,
|
|
256
|
+
"Content-Type": "application/json",
|
|
257
|
+
},
|
|
258
|
+
body: JSON.stringify(metadata),
|
|
259
|
+
});
|
|
260
|
+
return driveJson(res);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function driveMultipartCreate(
|
|
264
|
+
token: string,
|
|
265
|
+
metadata: { name: string; mimeType: string; parents?: string[] },
|
|
266
|
+
mediaBody: string,
|
|
267
|
+
mediaMime: string,
|
|
268
|
+
): Promise<unknown> {
|
|
269
|
+
const boundary = `nimbus_${randomBytes(16).toString("hex")}`;
|
|
270
|
+
const metaJson = JSON.stringify(metadata);
|
|
271
|
+
const crlf = "\r\n";
|
|
272
|
+
const body = [
|
|
273
|
+
`--${boundary}`,
|
|
274
|
+
"Content-Type: application/json; charset=UTF-8",
|
|
275
|
+
"",
|
|
276
|
+
metaJson,
|
|
277
|
+
`--${boundary}`,
|
|
278
|
+
`Content-Type: ${mediaMime}`,
|
|
279
|
+
"",
|
|
280
|
+
mediaBody,
|
|
281
|
+
`--${boundary}--`,
|
|
282
|
+
].join(crlf);
|
|
283
|
+
const res = await fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart", {
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers: {
|
|
286
|
+
Authorization: `Bearer ${token}`,
|
|
287
|
+
"Content-Type": `multipart/related; boundary=${boundary}`,
|
|
288
|
+
},
|
|
289
|
+
body,
|
|
290
|
+
});
|
|
291
|
+
return driveJson(res);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function driveListParents(token: string, fileId: string): Promise<string[]> {
|
|
295
|
+
const url = `https://www.googleapis.com/drive/v3/files/${encodeURIComponent(fileId)}?fields=parents`;
|
|
296
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
297
|
+
const json = await driveJson(res);
|
|
298
|
+
if (json === null || typeof json !== "object" || Array.isArray(json)) {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
const parentsRaw = (json as Record<string, unknown>)["parents"];
|
|
302
|
+
if (!Array.isArray(parentsRaw)) {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
return parentsRaw.filter((p): p is string => typeof p === "string" && p !== "");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Tool names exposed by this connector — for contract/introspection tests. */
|
|
309
|
+
export const GOOGLE_DRIVE_TOOL_NAMES = [
|
|
310
|
+
"gdrive_file_list",
|
|
311
|
+
"gdrive_file_metadata",
|
|
312
|
+
"gdrive_file_search",
|
|
313
|
+
"gdrive_file_download",
|
|
314
|
+
"gdrive_file_create",
|
|
315
|
+
"gdrive_file_trash",
|
|
316
|
+
"gdrive_file_move",
|
|
317
|
+
"gdrive_file_rename",
|
|
318
|
+
] as const;
|
|
319
|
+
|
|
320
|
+
export function registerGoogleDriveTools(
|
|
321
|
+
server: ConsentServer & { tool: (...args: never) => unknown },
|
|
322
|
+
): void {
|
|
323
|
+
const reg = createZodToolRegistrar(createRegisterSimpleTool(server));
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Register a Drive tool whose body is the repeated shape `parse → token →
|
|
327
|
+
* data = await drive<X>(token, …) → mcpJsonResult(data)`. The parse + the
|
|
328
|
+
* `throw new Error(parsed.error.message)` live once in the shared registrar; the
|
|
329
|
+
* handler receives the validated args + the OAuth token and returns the value to
|
|
330
|
+
* wrap. Handlers needing a pre-wrap check (e.g. download's `!result.ok` throw)
|
|
331
|
+
* do it inside the handler and return the payload.
|
|
332
|
+
*/
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Every MUTATING google-drive tool goes through here. Outside the gateway this adds the
|
|
336
|
+
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
337
|
+
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
338
|
+
*/
|
|
339
|
+
const registerWriteTool = createWriteToolRegistrar(server, {
|
|
340
|
+
connector: "google-drive",
|
|
341
|
+
scopeEnv: "NIMBUS_MCP_GOOGLE_DRIVE_WRITE_SCOPE",
|
|
342
|
+
scopeKinds: ["folder", "file"],
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* The write-tool equivalent of `registerDriveTool`: identical token handling and result wrapping,
|
|
347
|
+
* routed through the write registrar.
|
|
348
|
+
*/
|
|
349
|
+
function registerDriveWriteTool<T>(
|
|
350
|
+
name: string,
|
|
351
|
+
cfg: WriteToolConfig<T>,
|
|
352
|
+
description: string,
|
|
353
|
+
schema: ZodObjectSchema<T>,
|
|
354
|
+
handler: (args: T, token: string) => Promise<unknown>,
|
|
355
|
+
): void {
|
|
356
|
+
registerWriteTool(name, cfg, description, schema, async (parsed) => {
|
|
357
|
+
const token = requireProcessEnv("GOOGLE_OAUTH_ACCESS_TOKEN");
|
|
358
|
+
return mcpJsonResult(await handler(parsed, token));
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function registerDriveTool<T>(
|
|
363
|
+
name: string,
|
|
364
|
+
description: string,
|
|
365
|
+
schema: ZodObjectSchema<T>,
|
|
366
|
+
handler: (args: T, token: string) => Promise<unknown>,
|
|
367
|
+
): void {
|
|
368
|
+
reg(name, description, schema, async (parsed) => {
|
|
369
|
+
const token = requireProcessEnv("GOOGLE_OAUTH_ACCESS_TOKEN");
|
|
370
|
+
return mcpJsonResult(await handler(parsed, token));
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const gdriveFileListArgs = z.object({
|
|
375
|
+
pageSize: z.number().int().min(1).max(100).optional(),
|
|
376
|
+
pageToken: z.string().optional(),
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
registerDriveTool(
|
|
380
|
+
"gdrive_file_list",
|
|
381
|
+
"List Google Drive files (metadata only). Supports pagination via pageToken from the previous response.",
|
|
382
|
+
gdriveFileListArgs,
|
|
383
|
+
(args, token) => driveListFiles(token, args.pageSize ?? 25, args.pageToken, "trashed = false"),
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const gdriveFileMetadataArgs = z.object({
|
|
387
|
+
fileId: z.string().min(1),
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
registerDriveTool(
|
|
391
|
+
"gdrive_file_metadata",
|
|
392
|
+
"Get metadata for a single Drive file or folder (owners, parents, links, mimeType, description).",
|
|
393
|
+
gdriveFileMetadataArgs,
|
|
394
|
+
(args, token) => driveGetFileMetadata(token, args.fileId),
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const gdriveFileSearchArgs = z.object({
|
|
398
|
+
query: z.string().min(1).max(500),
|
|
399
|
+
pageSize: z.number().int().min(1).max(100).optional(),
|
|
400
|
+
pageToken: z.string().optional(),
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
registerDriveTool(
|
|
404
|
+
"gdrive_file_search",
|
|
405
|
+
"Full-text search over Drive using the Drive search API (fullText contains your phrase). Non-trashed files only.",
|
|
406
|
+
gdriveFileSearchArgs,
|
|
407
|
+
(args, token) => {
|
|
408
|
+
const escaped = escapeDriveQueryLiteral(args.query);
|
|
409
|
+
const q = `fullText contains '${escaped}' and trashed = false`;
|
|
410
|
+
return driveListFiles(token, args.pageSize ?? 25, args.pageToken, q);
|
|
411
|
+
},
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
const gdriveFileDownloadArgs = z.object({
|
|
415
|
+
fileId: z.string().min(1),
|
|
416
|
+
maxBytes: z
|
|
417
|
+
.number()
|
|
418
|
+
.int()
|
|
419
|
+
.min(1024)
|
|
420
|
+
.max(16 * 1024 * 1024)
|
|
421
|
+
.optional(),
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
registerDriveTool(
|
|
425
|
+
"gdrive_file_download",
|
|
426
|
+
"Download file bytes (base64) or text (utf-8 for text/*). Google Docs → plain text export; Sheets → CSV. Capped by maxBytes (default 256 KiB, max 16 MiB).",
|
|
427
|
+
gdriveFileDownloadArgs,
|
|
428
|
+
async (args, token) => {
|
|
429
|
+
const result = await driveDownloadFile(token, args.fileId, args.maxBytes ?? 256 * 1024);
|
|
430
|
+
if (!result.ok) {
|
|
431
|
+
throw new Error(result.message);
|
|
432
|
+
}
|
|
433
|
+
return result.payload;
|
|
434
|
+
},
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const gdriveFileCreateArgs = z.object({
|
|
438
|
+
name: z.string().min(1).max(500),
|
|
439
|
+
mimeType: z.string().min(1).max(200).optional(),
|
|
440
|
+
parentId: z.string().min(1).optional(),
|
|
441
|
+
content: z.string().max(4_000_000).optional(),
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
registerDriveWriteTool(
|
|
445
|
+
"gdrive_file_create",
|
|
446
|
+
{
|
|
447
|
+
mutates: "google_drive.file.create",
|
|
448
|
+
recoverable: true,
|
|
449
|
+
scopeTargetOf: (p) => ({ kind: "folder", value: p.parentId ?? "root" }),
|
|
450
|
+
},
|
|
451
|
+
"Create a Google Drive file. Optional text `content` uses multipart upload. Empty file if content omitted. Requires Gateway HITL file.create.",
|
|
452
|
+
gdriveFileCreateArgs,
|
|
453
|
+
(args, token) => {
|
|
454
|
+
const mime = args.mimeType ?? "text/plain";
|
|
455
|
+
const meta: { name: string; mimeType: string; parents?: string[] } = {
|
|
456
|
+
name: args.name,
|
|
457
|
+
mimeType: mime,
|
|
458
|
+
};
|
|
459
|
+
if (args.parentId !== undefined) {
|
|
460
|
+
meta.parents = [args.parentId];
|
|
461
|
+
}
|
|
462
|
+
if (args.content !== undefined && args.content !== "") {
|
|
463
|
+
return driveMultipartCreate(token, meta, args.content, mime);
|
|
464
|
+
}
|
|
465
|
+
return drivePostCreateMetadata(token, meta);
|
|
466
|
+
},
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
const gdriveFileTrashArgs = z.object({
|
|
470
|
+
fileId: z.string().min(1),
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
registerDriveTool(
|
|
474
|
+
"gdrive_file_trash",
|
|
475
|
+
"Move a Drive file or folder to trash (recoverable). Requires Gateway HITL file.delete.",
|
|
476
|
+
gdriveFileTrashArgs,
|
|
477
|
+
(args, token) => drivePatchJson(token, args.fileId, { trashed: true }),
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
const gdriveFileMoveArgs = z.object({
|
|
481
|
+
fileId: z.string().min(1),
|
|
482
|
+
newParentId: z.string().min(1),
|
|
483
|
+
removeParentId: z.string().min(1).optional(),
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
registerDriveWriteTool(
|
|
487
|
+
"gdrive_file_move",
|
|
488
|
+
{
|
|
489
|
+
mutates: "google_drive.file.move",
|
|
490
|
+
recoverable: true,
|
|
491
|
+
scopeTargetOf: (p) => ({ kind: "file", value: p.fileId }),
|
|
492
|
+
},
|
|
493
|
+
"Move a file or folder to another parent folder (Drive parents). If removeParentId is omitted, the first current parent is used. Requires Gateway HITL file.move.",
|
|
494
|
+
gdriveFileMoveArgs,
|
|
495
|
+
async (args, token) => {
|
|
496
|
+
let remove = args.removeParentId;
|
|
497
|
+
if (remove === undefined) {
|
|
498
|
+
const parents = await driveListParents(token, args.fileId);
|
|
499
|
+
const first = parents[0];
|
|
500
|
+
if (first === undefined) {
|
|
501
|
+
throw new Error("Cannot infer removeParentId: file has no parents (may be root-only)");
|
|
502
|
+
}
|
|
503
|
+
remove = first;
|
|
504
|
+
}
|
|
505
|
+
const q = new URLSearchParams({
|
|
506
|
+
addParents: args.newParentId,
|
|
507
|
+
removeParents: remove,
|
|
508
|
+
});
|
|
509
|
+
return drivePatchJson(token, args.fileId, {}, q);
|
|
510
|
+
},
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
const gdriveFileRenameArgs = z.object({
|
|
514
|
+
fileId: z.string().min(1),
|
|
515
|
+
newName: z.string().min(1).max(500),
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
registerDriveWriteTool(
|
|
519
|
+
"gdrive_file_rename",
|
|
520
|
+
{
|
|
521
|
+
mutates: "google_drive.file.rename",
|
|
522
|
+
recoverable: true,
|
|
523
|
+
scopeTargetOf: (p) => ({ kind: "file", value: p.fileId }),
|
|
524
|
+
},
|
|
525
|
+
"Rename a Drive file or folder. Requires Gateway HITL file.rename.",
|
|
526
|
+
gdriveFileRenameArgs,
|
|
527
|
+
(args, token) => drivePatchJson(token, args.fileId, { name: args.newName }),
|
|
528
|
+
);
|
|
529
|
+
}
|
|
@@ -1,89 +1,9 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
fetchBearerAuthorizedJson,
|
|
7
|
-
resolveUrlWithBase,
|
|
8
|
-
} from "../../../shared/fetch-bearer-json.ts";
|
|
9
|
-
import { createRegisterSimpleTool, createZodToolRegistrar } from "../../../shared/mcp-tool-kit.ts";
|
|
10
|
-
import { makeRestToolRegistrar } from "../../../shared/rest-tool-kit.ts";
|
|
11
|
-
|
|
12
|
-
const MEET_BASE = "https://meet.googleapis.com/v2";
|
|
13
|
-
|
|
14
|
-
async function meetFetch(
|
|
15
|
-
token: string,
|
|
16
|
-
path: string,
|
|
17
|
-
init?: RequestInit,
|
|
18
|
-
): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
|
|
19
|
-
const url = resolveUrlWithBase(MEET_BASE, path);
|
|
20
|
-
return fetchBearerAuthorizedJson(url, token, init, { "Content-Type": "application/json" });
|
|
21
|
-
}
|
|
3
|
+
import { registerGoogleMeetTools } from "./tools.ts";
|
|
22
4
|
|
|
23
5
|
const server = new McpServer({ name: "nimbus-google-meet", version: "0.1.0" });
|
|
24
6
|
|
|
25
|
-
|
|
26
|
-
const reg = createZodToolRegistrar(registerSimpleTool);
|
|
27
|
-
|
|
28
|
-
/** Standard Meet read tool: token → meetFetch(buildPath) → mcpJsonResultIfOk("Google Meet API"). */
|
|
29
|
-
const registerMeetTool = makeRestToolRegistrar({
|
|
30
|
-
registrar: reg,
|
|
31
|
-
tokenEnv: "GOOGLE_OAUTH_ACCESS_TOKEN",
|
|
32
|
-
serviceLabel: "Google Meet API",
|
|
33
|
-
fetch: meetFetch,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const gmeetListArgs = z.object({
|
|
37
|
-
pageSize: z.number().int().min(1).max(100).optional(),
|
|
38
|
-
pageToken: z.string().optional(),
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
registerMeetTool(
|
|
42
|
-
"google_meet_list",
|
|
43
|
-
"List past Google Meet conference records (metadata: id, startTime, endTime, space). Pagination via pageToken.",
|
|
44
|
-
gmeetListArgs,
|
|
45
|
-
(parsed) => {
|
|
46
|
-
const u = new URL(`${MEET_BASE}/conferenceRecords`);
|
|
47
|
-
u.searchParams.set("pageSize", String(parsed.pageSize ?? 50));
|
|
48
|
-
if (parsed.pageToken !== undefined && parsed.pageToken !== "") {
|
|
49
|
-
u.searchParams.set("pageToken", parsed.pageToken);
|
|
50
|
-
}
|
|
51
|
-
return `${u.pathname}${u.search}`;
|
|
52
|
-
},
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
const gmeetGetArgs = z.object({
|
|
56
|
-
conferenceRecordId: z.string().min(1),
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
registerMeetTool(
|
|
60
|
-
"google_meet_get",
|
|
61
|
-
"Get a single Google Meet conference record by id (startTime, endTime, space).",
|
|
62
|
-
gmeetGetArgs,
|
|
63
|
-
(parsed) => `/conferenceRecords/${encodeURIComponent(parsed.conferenceRecordId)}`,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const gmeetSearchArgs = z.object({
|
|
67
|
-
pageSize: z.number().int().min(1).max(100).optional(),
|
|
68
|
-
pageToken: z.string().optional(),
|
|
69
|
-
filter: z.string().optional(),
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
registerMeetTool(
|
|
73
|
-
"google_meet_search",
|
|
74
|
-
"Search past conference records (metadata only). Supports the Meet API filter expression and pagination.",
|
|
75
|
-
gmeetSearchArgs,
|
|
76
|
-
(parsed) => {
|
|
77
|
-
const u = new URL(`${MEET_BASE}/conferenceRecords`);
|
|
78
|
-
u.searchParams.set("pageSize", String(parsed.pageSize ?? 50));
|
|
79
|
-
if (parsed.pageToken !== undefined && parsed.pageToken !== "") {
|
|
80
|
-
u.searchParams.set("pageToken", parsed.pageToken);
|
|
81
|
-
}
|
|
82
|
-
if (parsed.filter !== undefined && parsed.filter !== "") {
|
|
83
|
-
u.searchParams.set("filter", parsed.filter);
|
|
84
|
-
}
|
|
85
|
-
return `${u.pathname}${u.search}`;
|
|
86
|
-
},
|
|
87
|
-
);
|
|
7
|
+
registerGoogleMeetTools(server);
|
|
88
8
|
|
|
89
9
|
await server.connect(new StdioServerTransport());
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
fetchBearerAuthorizedJson,
|
|
4
|
+
resolveUrlWithBase,
|
|
5
|
+
} from "../../../shared/fetch-bearer-json.ts";
|
|
6
|
+
import { createRegisterSimpleTool, createZodToolRegistrar } from "../../../shared/mcp-tool-kit.ts";
|
|
7
|
+
import { makeRestToolRegistrar } from "../../../shared/rest-tool-kit.ts";
|
|
8
|
+
|
|
9
|
+
const MEET_BASE = "https://meet.googleapis.com/v2";
|
|
10
|
+
|
|
11
|
+
async function meetFetch(
|
|
12
|
+
token: string,
|
|
13
|
+
path: string,
|
|
14
|
+
init?: RequestInit,
|
|
15
|
+
): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
|
|
16
|
+
const url = resolveUrlWithBase(MEET_BASE, path);
|
|
17
|
+
return fetchBearerAuthorizedJson(url, token, init, { "Content-Type": "application/json" });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `/conferenceRecords` with its query: the page size (default 50), then the page token and the
|
|
22
|
+
* filter, each only when non-empty. RELATIVE to MEET_BASE, which the fetcher prefixes and which
|
|
23
|
+
* already ends in /v2 — a path taken from a full URL's `pathname` requested /v2/v2/….
|
|
24
|
+
*/
|
|
25
|
+
function conferenceRecordsPath(parsed: {
|
|
26
|
+
readonly pageSize?: number | undefined;
|
|
27
|
+
readonly pageToken?: string | undefined;
|
|
28
|
+
readonly filter?: string | undefined;
|
|
29
|
+
}): string {
|
|
30
|
+
const query = new URLSearchParams({ pageSize: String(parsed.pageSize ?? 50) });
|
|
31
|
+
if (parsed.pageToken !== undefined && parsed.pageToken !== "") {
|
|
32
|
+
query.set("pageToken", parsed.pageToken);
|
|
33
|
+
}
|
|
34
|
+
if (parsed.filter !== undefined && parsed.filter !== "") {
|
|
35
|
+
query.set("filter", parsed.filter);
|
|
36
|
+
}
|
|
37
|
+
return `/conferenceRecords?${query.toString()}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function registerGoogleMeetTools(server: { tool: (...args: never) => unknown }): void {
|
|
41
|
+
const registerSimpleTool = createRegisterSimpleTool(server);
|
|
42
|
+
const reg = createZodToolRegistrar(registerSimpleTool);
|
|
43
|
+
|
|
44
|
+
/** Standard Meet read tool: token → meetFetch(buildPath) → mcpJsonResultIfOk("Google Meet API"). */
|
|
45
|
+
const registerMeetTool = makeRestToolRegistrar({
|
|
46
|
+
registrar: reg,
|
|
47
|
+
tokenEnv: "GOOGLE_OAUTH_ACCESS_TOKEN",
|
|
48
|
+
serviceLabel: "Google Meet API",
|
|
49
|
+
fetch: meetFetch,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const gmeetListArgs = z.object({
|
|
53
|
+
pageSize: z.number().int().min(1).max(100).optional(),
|
|
54
|
+
pageToken: z.string().optional(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
registerMeetTool(
|
|
58
|
+
"google_meet_list",
|
|
59
|
+
"List past Google Meet conference records (metadata: id, startTime, endTime, space). Pagination via pageToken.",
|
|
60
|
+
gmeetListArgs,
|
|
61
|
+
conferenceRecordsPath,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const gmeetGetArgs = z.object({
|
|
65
|
+
conferenceRecordId: z.string().min(1),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
registerMeetTool(
|
|
69
|
+
"google_meet_get",
|
|
70
|
+
"Get a single Google Meet conference record by id (startTime, endTime, space).",
|
|
71
|
+
gmeetGetArgs,
|
|
72
|
+
(parsed) => `/conferenceRecords/${encodeURIComponent(parsed.conferenceRecordId)}`,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const gmeetSearchArgs = z.object({
|
|
76
|
+
pageSize: z.number().int().min(1).max(100).optional(),
|
|
77
|
+
pageToken: z.string().optional(),
|
|
78
|
+
filter: z.string().optional(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
registerMeetTool(
|
|
82
|
+
"google_meet_search",
|
|
83
|
+
"Search past conference records (metadata only). Supports the Meet API filter expression and pagination.",
|
|
84
|
+
gmeetSearchArgs,
|
|
85
|
+
conferenceRecordsPath,
|
|
86
|
+
);
|
|
87
|
+
}
|