@nimbus-dev/connectors 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +662 -0
- package/NOTICE +40 -0
- package/README.md +111 -0
- package/connectors/airflow/nimbus.extension.json +16 -0
- package/connectors/airflow/src/search-filter.ts +38 -0
- package/connectors/airflow/src/server.ts +91 -0
- package/connectors/apple/nimbus.extension.json +40 -0
- package/connectors/apple/src/apple-mail-core.ts +43 -0
- package/connectors/apple/src/caldav-core.ts +116 -0
- package/connectors/apple/src/calendar-tools.ts +278 -0
- package/connectors/apple/src/server.ts +541 -0
- package/connectors/apple/src/tools.ts +138 -0
- package/connectors/argocd/nimbus.extension.json +16 -0
- package/connectors/argocd/src/search-filter.ts +24 -0
- package/connectors/argocd/src/server.ts +158 -0
- package/connectors/athena/nimbus.extension.json +15 -0
- package/connectors/athena/src/server.ts +6 -0
- package/connectors/athena/src/tools.ts +181 -0
- package/connectors/aws/nimbus.extension.json +21 -0
- package/connectors/aws/src/server.ts +191 -0
- package/connectors/azure/nimbus.extension.json +15 -0
- package/connectors/azure/src/server.ts +139 -0
- package/connectors/bigeye/nimbus.extension.json +16 -0
- package/connectors/bigeye/src/search-filter.ts +22 -0
- package/connectors/bigeye/src/server.ts +161 -0
- package/connectors/bigquery/nimbus.extension.json +15 -0
- package/connectors/bigquery/src/server.ts +6 -0
- package/connectors/bigquery/src/tools.ts +175 -0
- package/connectors/bitbucket/nimbus.extension.json +15 -0
- package/connectors/bitbucket/src/server.ts +252 -0
- package/connectors/bitrise/nimbus.extension.json +15 -0
- package/connectors/bitrise/src/search-filter.ts +11 -0
- package/connectors/bitrise/src/server.ts +83 -0
- package/connectors/canva/nimbus.extension.json +15 -0
- package/connectors/canva/src/search-filter.ts +23 -0
- package/connectors/canva/src/server.ts +77 -0
- package/connectors/circleci/nimbus.extension.json +15 -0
- package/connectors/circleci/src/server.ts +184 -0
- package/connectors/cloud-logging/nimbus.extension.json +15 -0
- package/connectors/cloud-logging/src/server.ts +6 -0
- package/connectors/cloud-logging/src/tools.ts +115 -0
- package/connectors/cloudwatch/nimbus.extension.json +15 -0
- package/connectors/cloudwatch/src/server.ts +6 -0
- package/connectors/cloudwatch/src/tools.ts +144 -0
- package/connectors/codemagic/nimbus.extension.json +15 -0
- package/connectors/codemagic/src/search-filter.ts +11 -0
- package/connectors/codemagic/src/server.ts +74 -0
- package/connectors/confluence/nimbus.extension.json +15 -0
- package/connectors/confluence/src/kb-append.ts +25 -0
- package/connectors/confluence/src/server.ts +309 -0
- package/connectors/dagster/nimbus.extension.json +16 -0
- package/connectors/dagster/src/search-filter.ts +53 -0
- package/connectors/dagster/src/server.ts +218 -0
- package/connectors/databricks/nimbus.extension.json +16 -0
- package/connectors/databricks/src/search-filter.ts +28 -0
- package/connectors/databricks/src/server.ts +81 -0
- package/connectors/datadog/nimbus.extension.json +15 -0
- package/connectors/datadog/src/server.ts +56 -0
- package/connectors/dataprofile/nimbus.extension.json +16 -0
- package/connectors/dataprofile/src/profile.ts +298 -0
- package/connectors/dataprofile/src/server.ts +6 -0
- package/connectors/dataprofile/src/tools.ts +69 -0
- package/connectors/dbt/nimbus.extension.json +16 -0
- package/connectors/dbt/src/search-filter.ts +28 -0
- package/connectors/dbt/src/server.ts +96 -0
- package/connectors/dependencytrack/nimbus.extension.json +16 -0
- package/connectors/dependencytrack/src/search-filter.ts +24 -0
- package/connectors/dependencytrack/src/server.ts +80 -0
- package/connectors/discord/nimbus.extension.json +15 -0
- package/connectors/discord/src/server.ts +93 -0
- package/connectors/elasticsearch/nimbus.extension.json +16 -0
- package/connectors/elasticsearch/src/server.ts +6 -0
- package/connectors/elasticsearch/src/tools.ts +110 -0
- package/connectors/fastmail/nimbus.extension.json +16 -0
- package/connectors/fastmail/src/jmap-core.ts +86 -0
- package/connectors/fastmail/src/server.ts +194 -0
- package/connectors/fastmail/src/tools.ts +143 -0
- package/connectors/figma/nimbus.extension.json +15 -0
- package/connectors/figma/src/search-filter.ts +24 -0
- package/connectors/figma/src/server.ts +117 -0
- package/connectors/firebase/nimbus.extension.json +15 -0
- package/connectors/firebase/src/search-filter.ts +30 -0
- package/connectors/firebase/src/server.ts +94 -0
- package/connectors/firebase/src/token.ts +41 -0
- package/connectors/flagsmith/nimbus.extension.json +15 -0
- package/connectors/flagsmith/src/search-filter.ts +29 -0
- package/connectors/flagsmith/src/server.ts +102 -0
- package/connectors/flux/nimbus.extension.json +16 -0
- package/connectors/flux/src/search-filter.ts +42 -0
- package/connectors/flux/src/server.ts +182 -0
- package/connectors/gcp/nimbus.extension.json +15 -0
- package/connectors/gcp/src/server.ts +140 -0
- package/connectors/github/nimbus.extension.json +15 -0
- package/connectors/github/src/server.ts +347 -0
- package/connectors/github-actions/nimbus.extension.json +15 -0
- package/connectors/github-actions/src/server.ts +213 -0
- package/connectors/gitlab/nimbus.extension.json +15 -0
- package/connectors/gitlab/src/server.ts +392 -0
- package/connectors/gmail/nimbus.extension.json +15 -0
- package/connectors/gmail/src/server.ts +277 -0
- package/connectors/google-drive/nimbus.extension.json +15 -0
- package/connectors/google-drive/src/drive-query.ts +3 -0
- package/connectors/google-drive/src/server.ts +531 -0
- package/connectors/google-meet/nimbus.extension.json +15 -0
- package/connectors/google-meet/src/server.ts +89 -0
- package/connectors/google-photos/nimbus.extension.json +15 -0
- package/connectors/google-photos/src/server.ts +139 -0
- package/connectors/grafana/nimbus.extension.json +15 -0
- package/connectors/grafana/src/server.ts +61 -0
- package/connectors/great-expectations/nimbus.extension.json +16 -0
- package/connectors/great-expectations/src/gx-parse.ts +292 -0
- package/connectors/great-expectations/src/server.ts +6 -0
- package/connectors/great-expectations/src/tools.ts +91 -0
- package/connectors/greenhouse/nimbus.extension.json +15 -0
- package/connectors/greenhouse/src/search-filter.ts +65 -0
- package/connectors/greenhouse/src/server.ts +67 -0
- package/connectors/hubspot/nimbus.extension.json +15 -0
- package/connectors/hubspot/src/search-filter.ts +28 -0
- package/connectors/hubspot/src/server.ts +81 -0
- package/connectors/iac/nimbus.extension.json +14 -0
- package/connectors/iac/src/server.ts +145 -0
- package/connectors/imap/nimbus.extension.json +16 -0
- package/connectors/imap/src/imap-core.ts +110 -0
- package/connectors/imap/src/server.ts +232 -0
- package/connectors/imap/src/tools.ts +40 -0
- package/connectors/intercom/nimbus.extension.json +15 -0
- package/connectors/intercom/src/search-filter.ts +46 -0
- package/connectors/intercom/src/server.ts +65 -0
- package/connectors/jenkins/nimbus.extension.json +15 -0
- package/connectors/jenkins/src/jenkins-api.ts +156 -0
- package/connectors/jenkins/src/server.ts +223 -0
- package/connectors/jira/nimbus.extension.json +15 -0
- package/connectors/jira/src/server.ts +309 -0
- package/connectors/kubernetes/nimbus.extension.json +15 -0
- package/connectors/kubernetes/src/server.ts +163 -0
- package/connectors/launchdarkly/nimbus.extension.json +15 -0
- package/connectors/launchdarkly/src/search-filter.ts +31 -0
- package/connectors/launchdarkly/src/server.ts +79 -0
- package/connectors/lever/nimbus.extension.json +15 -0
- package/connectors/lever/src/search-filter.ts +35 -0
- package/connectors/lever/src/server.ts +64 -0
- package/connectors/linear/nimbus.extension.json +15 -0
- package/connectors/linear/src/server.ts +467 -0
- package/connectors/localdb/nimbus.extension.json +16 -0
- package/connectors/localdb/src/server.ts +6 -0
- package/connectors/localdb/src/sql-scan.ts +158 -0
- package/connectors/localdb/src/tools.ts +67 -0
- package/connectors/looker/nimbus.extension.json +16 -0
- package/connectors/looker/src/search-filter.ts +22 -0
- package/connectors/looker/src/server.ts +254 -0
- package/connectors/mendeley/nimbus.extension.json +15 -0
- package/connectors/mendeley/src/search-filter.ts +68 -0
- package/connectors/mendeley/src/server.ts +59 -0
- package/connectors/mercury/nimbus.extension.json +15 -0
- package/connectors/mercury/src/search-filter.ts +11 -0
- package/connectors/mercury/src/server.ts +61 -0
- package/connectors/metabase/nimbus.extension.json +16 -0
- package/connectors/metabase/src/search-filter.ts +22 -0
- package/connectors/metabase/src/server.ts +83 -0
- package/connectors/miro/nimbus.extension.json +15 -0
- package/connectors/miro/src/search-filter.ts +24 -0
- package/connectors/miro/src/server.ts +76 -0
- package/connectors/mlflow/nimbus.extension.json +16 -0
- package/connectors/mlflow/src/search-filter.ts +38 -0
- package/connectors/mlflow/src/server.ts +167 -0
- package/connectors/monte-carlo/nimbus.extension.json +16 -0
- package/connectors/monte-carlo/src/search-filter.ts +11 -0
- package/connectors/monte-carlo/src/server.ts +241 -0
- package/connectors/netlify/nimbus.extension.json +15 -0
- package/connectors/netlify/src/search-filter.ts +37 -0
- package/connectors/netlify/src/server.ts +64 -0
- package/connectors/newrelic/nimbus.extension.json +15 -0
- package/connectors/newrelic/src/server.ts +48 -0
- package/connectors/notion/nimbus.extension.json +15 -0
- package/connectors/notion/src/kb-append.ts +23 -0
- package/connectors/notion/src/server.ts +343 -0
- package/connectors/obsidian/nimbus.extension.json +19 -0
- package/connectors/obsidian/src/server.ts +433 -0
- package/connectors/onedrive/nimbus.extension.json +15 -0
- package/connectors/onedrive/src/server.ts +256 -0
- package/connectors/outlook/nimbus.extension.json +15 -0
- package/connectors/outlook/src/server.ts +358 -0
- package/connectors/outlook/src/tool-scope-policy.ts +53 -0
- package/connectors/pagerduty/nimbus.extension.json +15 -0
- package/connectors/pagerduty/src/server.ts +140 -0
- package/connectors/pipedrive/nimbus.extension.json +15 -0
- package/connectors/pipedrive/src/search-filter.ts +11 -0
- package/connectors/pipedrive/src/server.ts +62 -0
- package/connectors/powerbi/nimbus.extension.json +16 -0
- package/connectors/powerbi/src/search-filter.ts +22 -0
- package/connectors/powerbi/src/server.ts +249 -0
- package/connectors/prefect/nimbus.extension.json +16 -0
- package/connectors/prefect/src/search-filter.ts +40 -0
- package/connectors/prefect/src/server.ts +94 -0
- package/connectors/protonmail/nimbus.extension.json +16 -0
- package/connectors/protonmail/src/mail-core.ts +96 -0
- package/connectors/protonmail/src/server.ts +231 -0
- package/connectors/protonmail/src/tools.ts +45 -0
- package/connectors/raindrop/nimbus.extension.json +15 -0
- package/connectors/raindrop/src/search-filter.ts +20 -0
- package/connectors/raindrop/src/server.ts +107 -0
- package/connectors/ramp/nimbus.extension.json +15 -0
- package/connectors/ramp/src/search-filter.ts +37 -0
- package/connectors/ramp/src/server.ts +109 -0
- package/connectors/readwise/nimbus.extension.json +15 -0
- package/connectors/readwise/src/search-filter.ts +64 -0
- package/connectors/readwise/src/server.ts +92 -0
- package/connectors/sagemaker/nimbus.extension.json +15 -0
- package/connectors/sagemaker/src/server.ts +6 -0
- package/connectors/sagemaker/src/tools.ts +121 -0
- package/connectors/salesforce/nimbus.extension.json +15 -0
- package/connectors/salesforce/src/search-filter.ts +23 -0
- package/connectors/salesforce/src/server.ts +94 -0
- package/connectors/semgrep/nimbus.extension.json +15 -0
- package/connectors/semgrep/src/search-filter.ts +31 -0
- package/connectors/semgrep/src/server.ts +108 -0
- package/connectors/sentry/nimbus.extension.json +15 -0
- package/connectors/sentry/src/server.ts +66 -0
- package/connectors/slack/nimbus.extension.json +15 -0
- package/connectors/slack/src/server.ts +360 -0
- package/connectors/snowflake/nimbus.extension.json +16 -0
- package/connectors/snowflake/src/search-filter.ts +26 -0
- package/connectors/snowflake/src/server.ts +236 -0
- package/connectors/snyk/nimbus.extension.json +15 -0
- package/connectors/snyk/src/search-filter.ts +28 -0
- package/connectors/snyk/src/server.ts +144 -0
- package/connectors/sonarqube/nimbus.extension.json +15 -0
- package/connectors/sonarqube/src/search-filter.ts +28 -0
- package/connectors/sonarqube/src/server.ts +115 -0
- package/connectors/stackoverflow/nimbus.extension.json +15 -0
- package/connectors/stackoverflow/src/search-filter.ts +52 -0
- package/connectors/stackoverflow/src/server.ts +81 -0
- package/connectors/storybook/nimbus.extension.json +16 -0
- package/connectors/storybook/src/server.ts +6 -0
- package/connectors/storybook/src/storybook-parse.ts +67 -0
- package/connectors/storybook/src/tools.ts +68 -0
- package/connectors/stripe/nimbus.extension.json +15 -0
- package/connectors/stripe/src/search-filter.ts +19 -0
- package/connectors/stripe/src/server.ts +65 -0
- package/connectors/superset/nimbus.extension.json +16 -0
- package/connectors/superset/src/search-filter.ts +22 -0
- package/connectors/superset/src/server.ts +119 -0
- package/connectors/tableau/nimbus.extension.json +16 -0
- package/connectors/tableau/src/search-filter.ts +22 -0
- package/connectors/tableau/src/server.ts +248 -0
- package/connectors/teams/nimbus.extension.json +20 -0
- package/connectors/teams/src/bot-send.ts +72 -0
- package/connectors/teams/src/server.ts +257 -0
- package/connectors/testflight/nimbus.extension.json +15 -0
- package/connectors/testflight/src/jwt.ts +27 -0
- package/connectors/testflight/src/search-filter.ts +30 -0
- package/connectors/testflight/src/server.ts +74 -0
- package/connectors/vercel/nimbus.extension.json +15 -0
- package/connectors/vercel/src/search-filter.ts +34 -0
- package/connectors/vercel/src/server.ts +79 -0
- package/connectors/vertex-ai/nimbus.extension.json +15 -0
- package/connectors/vertex-ai/src/server.ts +6 -0
- package/connectors/vertex-ai/src/tools.ts +145 -0
- package/connectors/wiz/nimbus.extension.json +15 -0
- package/connectors/wiz/src/search-filter.ts +50 -0
- package/connectors/wiz/src/server.ts +192 -0
- package/connectors/workday/nimbus.extension.json +19 -0
- package/connectors/workday/src/search-filter.ts +5 -0
- package/connectors/workday/src/server.ts +78 -0
- package/connectors/workday/src/tools.ts +1 -0
- package/connectors/zendesk/nimbus.extension.json +16 -0
- package/connectors/zendesk/src/search-filter.ts +11 -0
- package/connectors/zendesk/src/server.ts +77 -0
- package/connectors/zoom/nimbus.extension.json +15 -0
- package/connectors/zoom/src/encode-meeting-path.ts +5 -0
- package/connectors/zoom/src/recordings-window.ts +34 -0
- package/connectors/zoom/src/search-filter.ts +42 -0
- package/connectors/zoom/src/server.ts +98 -0
- package/connectors/zotero/nimbus.extension.json +15 -0
- package/connectors/zotero/src/search-filter.ts +81 -0
- package/connectors/zotero/src/server.ts +82 -0
- package/package.json +152 -0
- package/shared/atlassian-json-fetch.ts +42 -0
- package/shared/audit-chain.ts +133 -0
- package/shared/connector-mode.ts +60 -0
- package/shared/consent-kit.ts +323 -0
- package/shared/fetch-bearer-json.ts +18 -0
- package/shared/header-safe.ts +28 -0
- package/shared/imap-bodystructure.ts +113 -0
- package/shared/imap-mail-core.ts +57 -0
- package/shared/imap-tool-kit.ts +293 -0
- package/shared/join-api-path.ts +18 -0
- package/shared/kb-markdown.ts +142 -0
- package/shared/mcp-search-tool.ts +37 -0
- package/shared/mcp-tool-kit.ts +35 -0
- package/shared/nimbus-spawn.ts +128 -0
- package/shared/rest-tool-kit.ts +18 -0
- package/shared/run-cli-json.ts +55 -0
- package/shared/run-read-only-mcp-connector.ts +59 -0
- package/shared/safe-cli-arg.ts +55 -0
- package/shared/search-filter.ts +140 -0
- package/shared/strip-trailing-slashes.ts +7 -0
- package/shared/write-scope.ts +58 -0
- package/standalone/src/bin.ts +19 -0
- package/standalone/src/launcher.ts +186 -0
- package/standalone/tsconfig.json +8 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
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 PHOTOS_BASE = "https://photoslibrary.googleapis.com/v1";
|
|
13
|
+
|
|
14
|
+
async function photosFetch(
|
|
15
|
+
token: string,
|
|
16
|
+
path: string,
|
|
17
|
+
init?: RequestInit,
|
|
18
|
+
): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
|
|
19
|
+
const url = resolveUrlWithBase(PHOTOS_BASE, path);
|
|
20
|
+
return fetchBearerAuthorizedJson(url, token, init, { "Content-Type": "application/json" });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const server = new McpServer({ name: "nimbus-google-photos", version: "0.1.0" });
|
|
24
|
+
|
|
25
|
+
const registerSimpleTool = createRegisterSimpleTool(server);
|
|
26
|
+
const reg = createZodToolRegistrar(registerSimpleTool);
|
|
27
|
+
|
|
28
|
+
/** Standard Photos tool: token → photosFetch(buildPath[, buildInit]) → mcpJsonResultIfOk("Google Photos API"). */
|
|
29
|
+
const registerPhotosTool = makeRestToolRegistrar({
|
|
30
|
+
registrar: reg,
|
|
31
|
+
tokenEnv: "GOOGLE_OAUTH_ACCESS_TOKEN",
|
|
32
|
+
serviceLabel: "Google Photos API",
|
|
33
|
+
fetch: photosFetch,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const gphotosAlbumListArgs = z.object({
|
|
37
|
+
pageSize: z.number().int().min(1).max(50).optional(),
|
|
38
|
+
pageToken: z.string().optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
registerPhotosTool(
|
|
42
|
+
"gphotos_album_list",
|
|
43
|
+
"List Google Photos albums (metadata). Pagination via pageToken.",
|
|
44
|
+
gphotosAlbumListArgs,
|
|
45
|
+
(parsed) => {
|
|
46
|
+
const u = new URL(`${PHOTOS_BASE}/albums`);
|
|
47
|
+
u.searchParams.set("pageSize", String(parsed.pageSize ?? 25));
|
|
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 gphotosAlbumGetArgs = z.object({
|
|
56
|
+
albumId: z.string().min(1),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
registerPhotosTool(
|
|
60
|
+
"gphotos_album_get",
|
|
61
|
+
"Get a single album by id (title, mediaItemsCount, coverPhotoBaseUrl).",
|
|
62
|
+
gphotosAlbumGetArgs,
|
|
63
|
+
(parsed) => `/albums/${encodeURIComponent(parsed.albumId)}`,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const gphotosMediaListArgs = z.object({
|
|
67
|
+
albumId: z.string().min(1).optional(),
|
|
68
|
+
pageSize: z.number().int().min(1).max(100).optional(),
|
|
69
|
+
pageToken: z.string().optional(),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
registerPhotosTool(
|
|
73
|
+
"gphotos_media_list",
|
|
74
|
+
"List media items (metadata + baseUrl/productUrl only). Optional albumId scopes to one album.",
|
|
75
|
+
gphotosMediaListArgs,
|
|
76
|
+
() => "/mediaItems:search",
|
|
77
|
+
(parsed) => {
|
|
78
|
+
const body: Record<string, unknown> = {
|
|
79
|
+
pageSize: parsed.pageSize ?? 50,
|
|
80
|
+
};
|
|
81
|
+
if (parsed.pageToken !== undefined && parsed.pageToken !== "") {
|
|
82
|
+
body["pageToken"] = parsed.pageToken;
|
|
83
|
+
}
|
|
84
|
+
if (parsed.albumId !== undefined && parsed.albumId !== "") {
|
|
85
|
+
body["albumId"] = parsed.albumId;
|
|
86
|
+
}
|
|
87
|
+
return { method: "POST", body: JSON.stringify(body) };
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const gphotosMediaGetArgs = z.object({
|
|
92
|
+
mediaItemId: z.string().min(1),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
registerPhotosTool(
|
|
96
|
+
"gphotos_media_get",
|
|
97
|
+
"Get a single media item metadata by id.",
|
|
98
|
+
gphotosMediaGetArgs,
|
|
99
|
+
(parsed) => `/mediaItems/${encodeURIComponent(parsed.mediaItemId)}`,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const gphotosMediaSearchArgs = z.object({
|
|
103
|
+
albumId: z.string().min(1).optional(),
|
|
104
|
+
pageSize: z.number().int().min(1).max(100).optional(),
|
|
105
|
+
pageToken: z.string().optional(),
|
|
106
|
+
includeArchivedMedia: z.boolean().optional(),
|
|
107
|
+
excludeNonAppCreatedData: z.boolean().optional(),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
registerPhotosTool(
|
|
111
|
+
"gphotos_media_search",
|
|
112
|
+
"Search media items (metadata only). Optional album filter; supports pagination.",
|
|
113
|
+
gphotosMediaSearchArgs,
|
|
114
|
+
() => "/mediaItems:search",
|
|
115
|
+
(parsed) => {
|
|
116
|
+
const body: Record<string, unknown> = {
|
|
117
|
+
pageSize: parsed.pageSize ?? 50,
|
|
118
|
+
};
|
|
119
|
+
if (parsed.pageToken !== undefined && parsed.pageToken !== "") {
|
|
120
|
+
body["pageToken"] = parsed.pageToken;
|
|
121
|
+
}
|
|
122
|
+
if (parsed.albumId !== undefined && parsed.albumId !== "") {
|
|
123
|
+
body["albumId"] = parsed.albumId;
|
|
124
|
+
}
|
|
125
|
+
const filters: Record<string, unknown> = {};
|
|
126
|
+
if (parsed.includeArchivedMedia === true) {
|
|
127
|
+
filters["includeArchivedMedia"] = true;
|
|
128
|
+
}
|
|
129
|
+
if (parsed.excludeNonAppCreatedData === true) {
|
|
130
|
+
filters["excludeNonAppCreatedData"] = true;
|
|
131
|
+
}
|
|
132
|
+
if (Object.keys(filters).length > 0) {
|
|
133
|
+
body["filters"] = filters;
|
|
134
|
+
}
|
|
135
|
+
return { method: "POST", body: JSON.stringify(body) };
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
await server.connect(new StdioServerTransport());
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.grafana",
|
|
3
|
+
"displayName": "Grafana",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Grafana HTTP API connector (dashboards, datasources, alerting). Always user-configured — the target host is read from grafana.url at spawn time and merged into permissions.network by the Gateway. Read-focused.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": []
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": [],
|
|
13
|
+
"syncInterval": 600,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createRegisterSimpleTool,
|
|
7
|
+
createZodToolRegistrar,
|
|
8
|
+
mcpJsonResult as jsonResult,
|
|
9
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
10
|
+
|
|
11
|
+
function baseUrl(): string {
|
|
12
|
+
const u = process.env["GRAFANA_URL"]?.trim();
|
|
13
|
+
if (u === undefined || u === "") {
|
|
14
|
+
throw new Error("GRAFANA_URL is not set");
|
|
15
|
+
}
|
|
16
|
+
return u.replace(/\/$/, "");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function authHeaders(): Record<string, string> {
|
|
20
|
+
const tok = process.env["GRAFANA_API_TOKEN"]?.trim();
|
|
21
|
+
if (tok === undefined || tok === "") {
|
|
22
|
+
throw new Error("GRAFANA_API_TOKEN is not set");
|
|
23
|
+
}
|
|
24
|
+
return { Authorization: `Bearer ${tok}`, Accept: "application/json" };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function grafanaGet(path: string): Promise<unknown> {
|
|
28
|
+
const pathPart = path.startsWith("/") ? path : `/${path}`;
|
|
29
|
+
const res = await fetch(`${baseUrl()}${pathPart}`, {
|
|
30
|
+
headers: authHeaders(),
|
|
31
|
+
});
|
|
32
|
+
const text = await res.text();
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(`Grafana ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(text) as unknown;
|
|
38
|
+
} catch {
|
|
39
|
+
return { raw: text };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const mcp = new McpServer({ name: "nimbus-grafana", version: "0.1.0" });
|
|
44
|
+
const reg = createZodToolRegistrar(createRegisterSimpleTool(mcp));
|
|
45
|
+
|
|
46
|
+
reg("grafana_alert_list", "List alert rules (Ruler API).", z.object({}), async () =>
|
|
47
|
+
jsonResult(await grafanaGet("/api/ruler/grafana/api/v1/rules")),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
reg(
|
|
51
|
+
"grafana_dashboard_list",
|
|
52
|
+
"Search dashboards.",
|
|
53
|
+
z.object({ query: z.string().optional() }),
|
|
54
|
+
async (p) => {
|
|
55
|
+
const q = p.query ?? "";
|
|
56
|
+
return jsonResult(await grafanaGet(`/api/search?type=dash-db&query=${encodeURIComponent(q)}`));
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const transport = new StdioServerTransport();
|
|
61
|
+
await mcp.connect(transport);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.great-expectations",
|
|
3
|
+
"displayName": "Great Expectations",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Great Expectations validation-result metadata connector (Tier-3, no-row-data). Reads GX validation-result JSON artefacts written by CI runs from a configured local directory and indexes the validation METADATA per expectation — suite name, batch id, expectation type, column, success/failure, the aggregate observed value (a count/fraction/mean metric), element and unexpected counts, and run time. It NEVER reads or indexes the failing-data SAMPLES (unexpected_list / partial_unexpected_list / partial_unexpected_index_list / unexpected_index_list / partial_unexpected_counts) — those carry real data cell values (row data) and are stripped on read. No network, no live credentials. Read-only; no write tools.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": [],
|
|
11
|
+
"filesystem": { "read": [], "write": [] }
|
|
12
|
+
},
|
|
13
|
+
"hitlRequired": [],
|
|
14
|
+
"syncInterval": 600,
|
|
15
|
+
"minNimbusVersion": "0.2.0"
|
|
16
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tier-3 no-row-data reader for Great Expectations validation-result JSON
|
|
6
|
+
* artefacts. This module is the MCP-server-side counterpart of the gateway's
|
|
7
|
+
* pure `mapGreatExpectationsResultToItem` mapper: BOTH strip the failing-data
|
|
8
|
+
* SAMPLE lists so no real data cell value ever leaves the artefact dir.
|
|
9
|
+
*
|
|
10
|
+
* GX validation-result `result` objects carry aggregate metrics (observed_value
|
|
11
|
+
* scalar, element_count, unexpected_count, unexpected_percent) AND, for failing
|
|
12
|
+
* expectations, sampled rows of the actual unexpected DATA in:
|
|
13
|
+
* - unexpected_list
|
|
14
|
+
* - partial_unexpected_list
|
|
15
|
+
* - partial_unexpected_index_list
|
|
16
|
+
* - unexpected_index_list
|
|
17
|
+
* - partial_unexpected_counts
|
|
18
|
+
* Those carry real data cell values (row data) and MUST NEVER be read into a
|
|
19
|
+
* tool result. We copy ONLY the aggregate scalars; if observed_value is itself
|
|
20
|
+
* an array/object of sampled values, it is DROPPED.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const MAX_FILES = 1000;
|
|
24
|
+
const MAX_FILE_BYTES = 4 * 1024 * 1024; // skip artefacts larger than ~4 MiB
|
|
25
|
+
const MAX_WALK_DEPTH = 12;
|
|
26
|
+
|
|
27
|
+
/** Sample/row-data keys that MUST never be copied out of a GX `result` object. */
|
|
28
|
+
export const FORBIDDEN_RESULT_KEYS: ReadonlySet<string> = new Set([
|
|
29
|
+
"unexpected_list",
|
|
30
|
+
"partial_unexpected_list",
|
|
31
|
+
"partial_unexpected_index_list",
|
|
32
|
+
"unexpected_index_list",
|
|
33
|
+
"partial_unexpected_counts",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
export interface GxExpectationMeta {
|
|
37
|
+
readonly suiteName: string;
|
|
38
|
+
readonly batchId: string;
|
|
39
|
+
readonly runId: string | null;
|
|
40
|
+
readonly runTime: string | null;
|
|
41
|
+
readonly expectationType: string;
|
|
42
|
+
readonly column: string | null;
|
|
43
|
+
readonly success: boolean;
|
|
44
|
+
/** Aggregate scalar only — never an array/object of sampled values. */
|
|
45
|
+
readonly observedValue: number | string | boolean | null;
|
|
46
|
+
readonly elementCount: number | null;
|
|
47
|
+
readonly unexpectedCount: number | null;
|
|
48
|
+
readonly unexpectedPercent: number | null;
|
|
49
|
+
readonly successPercent: number | null;
|
|
50
|
+
/** Stable composite id: `<suite>::<batch>::<expectationType>::<column ?? "_">`. */
|
|
51
|
+
readonly externalId: string;
|
|
52
|
+
/** Source artefact path (relative to the results dir). */
|
|
53
|
+
readonly sourceFile: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isRecord(v: unknown): v is Record<string, unknown> {
|
|
57
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function strField(r: Record<string, unknown>, key: string): string | null {
|
|
61
|
+
const v = r[key];
|
|
62
|
+
return typeof v === "string" && v !== "" ? v : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function numField(r: Record<string, unknown>, key: string): number | null {
|
|
66
|
+
const v = r[key];
|
|
67
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Pull the aggregate observed value ONLY when it is a scalar (number / string /
|
|
72
|
+
* boolean). Some expectations (e.g. distinct-value checks) return an array or
|
|
73
|
+
* object of sampled values for observed_value — those are DROPPED (null) so no
|
|
74
|
+
* row data leaks.
|
|
75
|
+
*/
|
|
76
|
+
function scalarObservedValue(result: Record<string, unknown>): number | string | boolean | null {
|
|
77
|
+
const v = result["observed_value"];
|
|
78
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
79
|
+
return v;
|
|
80
|
+
}
|
|
81
|
+
if (typeof v === "string" || typeof v === "boolean") {
|
|
82
|
+
return v;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function deriveRunId(meta: Record<string, unknown>): string | null {
|
|
88
|
+
const runId = meta["run_id"];
|
|
89
|
+
if (typeof runId === "string" && runId !== "") {
|
|
90
|
+
return runId;
|
|
91
|
+
}
|
|
92
|
+
if (isRecord(runId)) {
|
|
93
|
+
return strField(runId, "run_name") ?? strField(runId, "run_time");
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function deriveRunTime(meta: Record<string, unknown>): string | null {
|
|
99
|
+
const runId = meta["run_id"];
|
|
100
|
+
if (isRecord(runId)) {
|
|
101
|
+
const rt = strField(runId, "run_time");
|
|
102
|
+
if (rt !== null) {
|
|
103
|
+
return rt;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return strField(meta, "run_time") ?? strField(meta, "validation_time");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function deriveBatchId(meta: Record<string, unknown>): string {
|
|
110
|
+
const direct = strField(meta, "batch_id") ?? strField(meta, "active_batch_definition_id") ?? null;
|
|
111
|
+
if (direct !== null) {
|
|
112
|
+
return direct;
|
|
113
|
+
}
|
|
114
|
+
const def = meta["active_batch_definition"];
|
|
115
|
+
if (isRecord(def)) {
|
|
116
|
+
const name =
|
|
117
|
+
strField(def, "batch_identifiers") ??
|
|
118
|
+
strField(def, "data_asset_name") ??
|
|
119
|
+
strField(def, "datasource_name");
|
|
120
|
+
if (name !== null) {
|
|
121
|
+
return name;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const spec = meta["batch_spec"];
|
|
125
|
+
if (isRecord(spec)) {
|
|
126
|
+
const p = strField(spec, "path") ?? strField(spec, "table_name");
|
|
127
|
+
if (p !== null) {
|
|
128
|
+
return p;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return "_";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function clampId(id: string): string {
|
|
135
|
+
// Keep composite external ids bounded; hash-suffix when overly long.
|
|
136
|
+
if (id.length <= 256) {
|
|
137
|
+
return id;
|
|
138
|
+
}
|
|
139
|
+
let h = 0;
|
|
140
|
+
for (let i = 0; i < id.length; i += 1) {
|
|
141
|
+
h = (h * 31 + (id.codePointAt(i) ?? 0)) | 0; // NOSONAR S7767: `| 0` is 32-bit wraparound (Java-style hashCode), not truncation; Math.trunc would let h exceed 2^53 and corrupt the hash.
|
|
142
|
+
}
|
|
143
|
+
return `${id.slice(0, 240)}#${(h >>> 0).toString(16)}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Parse one GX validation-result JSON document into per-expectation metadata
|
|
148
|
+
* rows — METADATA / AGGREGATES ONLY. The forbidden sample lists are never read.
|
|
149
|
+
*/
|
|
150
|
+
export function parseValidationResult(parsed: unknown, sourceFile: string): GxExpectationMeta[] {
|
|
151
|
+
const root = isRecord(parsed) ? parsed : undefined;
|
|
152
|
+
if (root === undefined) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
const meta = isRecord(root["meta"]) ? root["meta"] : {};
|
|
156
|
+
const suiteName = strField(meta, "expectation_suite_name") ?? "_";
|
|
157
|
+
const batchId = deriveBatchId(meta);
|
|
158
|
+
const runId = deriveRunId(meta);
|
|
159
|
+
const runTime = deriveRunTime(meta);
|
|
160
|
+
|
|
161
|
+
const statistics = isRecord(root["statistics"]) ? root["statistics"] : {};
|
|
162
|
+
const successPercent = numField(statistics, "success_percent");
|
|
163
|
+
|
|
164
|
+
const results = Array.isArray(root["results"]) ? root["results"] : [];
|
|
165
|
+
const out: GxExpectationMeta[] = [];
|
|
166
|
+
for (const entry of results) {
|
|
167
|
+
if (!isRecord(entry)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const config = isRecord(entry["expectation_config"]) ? entry["expectation_config"] : {};
|
|
171
|
+
const expectationType = strField(config, "expectation_type") ?? "unknown";
|
|
172
|
+
const kwargs = isRecord(config["kwargs"]) ? config["kwargs"] : {};
|
|
173
|
+
const column = strField(kwargs, "column");
|
|
174
|
+
const success = entry["success"] === true;
|
|
175
|
+
|
|
176
|
+
const result = isRecord(entry["result"]) ? entry["result"] : {};
|
|
177
|
+
const observedValue = scalarObservedValue(result);
|
|
178
|
+
const elementCount = numField(result, "element_count");
|
|
179
|
+
const unexpectedCount = numField(result, "unexpected_count");
|
|
180
|
+
const unexpectedPercent = numField(result, "unexpected_percent");
|
|
181
|
+
|
|
182
|
+
const externalId = clampId(`${suiteName}::${batchId}::${expectationType}::${column ?? "_"}`);
|
|
183
|
+
|
|
184
|
+
out.push({
|
|
185
|
+
suiteName,
|
|
186
|
+
batchId,
|
|
187
|
+
runId,
|
|
188
|
+
runTime,
|
|
189
|
+
expectationType,
|
|
190
|
+
column,
|
|
191
|
+
success,
|
|
192
|
+
observedValue,
|
|
193
|
+
elementCount,
|
|
194
|
+
unexpectedCount,
|
|
195
|
+
unexpectedPercent,
|
|
196
|
+
successPercent,
|
|
197
|
+
externalId,
|
|
198
|
+
sourceFile,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Resolve the configured GX results dir from the environment. Throws when unset
|
|
206
|
+
* so the MCP tools surface a clear error rather than reading an empty path.
|
|
207
|
+
*/
|
|
208
|
+
export function resultsDir(): string {
|
|
209
|
+
const dir = process.env["GREAT_EXPECTATIONS_RESULTS_DIR"]?.trim();
|
|
210
|
+
if (dir === undefined || dir === "") {
|
|
211
|
+
throw new Error("GREAT_EXPECTATIONS_RESULTS_DIR is not set");
|
|
212
|
+
}
|
|
213
|
+
return resolve(dir);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Path-traversal guard: confirm `candidate` (an absolute resolved path) is the
|
|
218
|
+
* results dir itself or a descendant of it. Rejects `..`-escaping inputs. This
|
|
219
|
+
* is the filesystem analog of the argv flag-smuggling guard.
|
|
220
|
+
*/
|
|
221
|
+
export function assertWithinResultsDir(candidate: string, root: string): void {
|
|
222
|
+
const rel = relative(root, candidate);
|
|
223
|
+
if (rel === "") {
|
|
224
|
+
return; // the dir itself
|
|
225
|
+
}
|
|
226
|
+
if (rel.startsWith("..") || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
227
|
+
throw new Error("path escapes the configured Great Expectations results dir");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Recursively collect `*.json` artefact paths under `root` (bounded). */
|
|
232
|
+
async function collectJsonFiles(root: string): Promise<string[]> {
|
|
233
|
+
const found: string[] = [];
|
|
234
|
+
async function walk(dir: string, depth: number): Promise<void> {
|
|
235
|
+
if (depth > MAX_WALK_DEPTH || found.length >= MAX_FILES) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
let entries: import("node:fs").Dirent[];
|
|
239
|
+
try {
|
|
240
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
241
|
+
} catch {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
for (const entry of entries) {
|
|
245
|
+
if (found.length >= MAX_FILES) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const full = join(dir, entry.name);
|
|
249
|
+
if (entry.isDirectory()) {
|
|
250
|
+
await walk(full, depth + 1);
|
|
251
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".json")) {
|
|
252
|
+
found.push(full);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
await walk(root, 0);
|
|
257
|
+
return found;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function readArtefact(path: string): Promise<unknown> {
|
|
261
|
+
try {
|
|
262
|
+
// Read first (no check-then-use race): readFile throws on a directory or a
|
|
263
|
+
// missing file, and the buffer length bounds the size — no stat-gated read.
|
|
264
|
+
const buf = await readFile(path);
|
|
265
|
+
if (buf.byteLength > MAX_FILE_BYTES) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
return JSON.parse(buf.toString("utf8")) as unknown;
|
|
269
|
+
} catch {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Read all GX validation-result artefacts under the configured results dir and
|
|
276
|
+
* return the flattened per-expectation METADATA rows. Bad / oversized /
|
|
277
|
+
* unparseable artefacts are skipped. NEVER reads any sample list.
|
|
278
|
+
*/
|
|
279
|
+
export async function listAllExpectations(): Promise<GxExpectationMeta[]> {
|
|
280
|
+
const root = resultsDir();
|
|
281
|
+
const files = await collectJsonFiles(root);
|
|
282
|
+
const out: GxExpectationMeta[] = [];
|
|
283
|
+
for (const file of files) {
|
|
284
|
+
assertWithinResultsDir(file, root);
|
|
285
|
+
const parsed = await readArtefact(file);
|
|
286
|
+
if (parsed === null) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
out.push(...parseValidationResult(parsed, relative(root, file)));
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
2
|
+
import { registerGreatExpectationsTools } from "./tools.ts";
|
|
3
|
+
|
|
4
|
+
await runReadOnlyMcpConnector("nimbus-great-expectations", (reg) => {
|
|
5
|
+
registerGreatExpectationsTools(reg);
|
|
6
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { mcpJsonResult as jsonResult } from "../../../shared/mcp-tool-kit.ts";
|
|
3
|
+
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
4
|
+
import { type GxExpectationMeta, listAllExpectations } from "./gx-parse.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Great Expectations (Tier-3, no-row-data) MCP tool surface. ALL tools index
|
|
8
|
+
* validation-result METADATA only (suite name, batch id, expectation type,
|
|
9
|
+
* column, success/failure, the aggregate observed value, element/unexpected
|
|
10
|
+
* counts, run time). NONE return the failing-data SAMPLE lists (unexpected_list
|
|
11
|
+
* / partial_unexpected_list / …) — those carry real data cell values (row data)
|
|
12
|
+
* and are stripped by `gx-parse.ts` before they ever reach a tool result. The
|
|
13
|
+
* names are introspected by the `assertNoRowDataTools` contract test, so they
|
|
14
|
+
* must never contain a row-data segment (rows/records/sample/values/scan/…).
|
|
15
|
+
*/
|
|
16
|
+
export const GREAT_EXPECTATIONS_TOOL_NAMES = [
|
|
17
|
+
"great_expectations_list",
|
|
18
|
+
"great_expectations_get",
|
|
19
|
+
"great_expectations_search",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
function toEnvelope(m: GxExpectationMeta): Record<string, unknown> {
|
|
23
|
+
return {
|
|
24
|
+
externalId: m.externalId,
|
|
25
|
+
suiteName: m.suiteName,
|
|
26
|
+
batchId: m.batchId,
|
|
27
|
+
runId: m.runId,
|
|
28
|
+
runTime: m.runTime,
|
|
29
|
+
expectationType: m.expectationType,
|
|
30
|
+
column: m.column,
|
|
31
|
+
success: m.success,
|
|
32
|
+
observedValue: m.observedValue,
|
|
33
|
+
elementCount: m.elementCount,
|
|
34
|
+
unexpectedCount: m.unexpectedCount,
|
|
35
|
+
unexpectedPercent: m.unexpectedPercent,
|
|
36
|
+
successPercent: m.successPercent,
|
|
37
|
+
sourceFile: m.sourceFile,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Register the read-only, metadata-only Great Expectations tools onto the given
|
|
43
|
+
* registrar. Shared between `server.ts` (live) and the contract test
|
|
44
|
+
* (introspection).
|
|
45
|
+
*/
|
|
46
|
+
export function registerGreatExpectationsTools(reg: ZodToolRegistrar): void {
|
|
47
|
+
reg(
|
|
48
|
+
"great_expectations_list",
|
|
49
|
+
"List recent Great Expectations validation results — METADATA ONLY. Reads the GX validation-result JSON artefacts under the configured results dir and returns one entry per (suite, batch, expectation) carrying suiteName, batchId, expectationType, column, success/failure, the aggregate observedValue, and element/unexpected counts. Never returns the failing-data sample lists (unexpected_list / partial_unexpected_list / …).",
|
|
50
|
+
z.object({
|
|
51
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
52
|
+
}),
|
|
53
|
+
async (p) => {
|
|
54
|
+
const all = await listAllExpectations();
|
|
55
|
+
const limit = p.limit ?? 100;
|
|
56
|
+
return jsonResult({ results: all.slice(0, limit).map(toEnvelope) });
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
reg(
|
|
61
|
+
"great_expectations_get",
|
|
62
|
+
"Fetch one Great Expectations validation result by its external id (`<suite>::<batch>::<expectationType>::<column>`) — METADATA ONLY. Returns the single expectation's metadata (success/failure, aggregate observedValue, element/unexpected counts). Never returns row/cell sample data.",
|
|
63
|
+
z.object({
|
|
64
|
+
externalId: z.string().min(1),
|
|
65
|
+
}),
|
|
66
|
+
async (p) => {
|
|
67
|
+
const all = await listAllExpectations();
|
|
68
|
+
const match = all.find((m) => m.externalId === p.externalId);
|
|
69
|
+
return jsonResult(match === undefined ? { match: null } : { match: toEnvelope(match) });
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
reg(
|
|
74
|
+
"great_expectations_search",
|
|
75
|
+
"Substring search over Great Expectations suite names and expectation types (case-insensitive) — METADATA ONLY. Returns a `{ matches: [...] }` envelope of metadata entries whose suiteName, expectationType, or column contains the query. Never returns row/cell sample data.",
|
|
76
|
+
z.object({
|
|
77
|
+
query: z.string().min(1),
|
|
78
|
+
limit: z.number().int().min(1).max(500).optional(),
|
|
79
|
+
}),
|
|
80
|
+
async (p) => {
|
|
81
|
+
const all = await listAllExpectations();
|
|
82
|
+
const q = p.query.toLowerCase();
|
|
83
|
+
const matches = all.filter((m) => {
|
|
84
|
+
const hay = `${m.suiteName} ${m.expectationType} ${m.column ?? ""}`.toLowerCase();
|
|
85
|
+
return hay.includes(q);
|
|
86
|
+
});
|
|
87
|
+
const limit = p.limit ?? 100;
|
|
88
|
+
return jsonResult({ matches: matches.slice(0, limit).map(toEnvelope) });
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.greenhouse",
|
|
3
|
+
"displayName": "Greenhouse",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Greenhouse connector (read-only). Surfaces the company's Greenhouse Harvest job openings as `greenhouse:job` items in the local index — the job name, the department + office names/locations, the workflow status, the requisition id, the confidential flag, and the open/close/created/updated timestamps. Useful for answering recruiting questions (\"which engineering jobs are open?\", \"find the job for the staff SRE requisition\") without leaving Nimbus. HTTP Basic auth where the Harvest API key is the username and the password is empty (`Authorization: Basic base64(<api_key>:)` — the trailing colon is the empty password; never logged). The SaaS host (harvest.greenhouse.io) is fixed. Greenhouse paginates `/v1/jobs` with `?per_page=100&page=N` returning a bare JSON array `[ ...jobs... ]` (NOT an envelope) — increment `page` while the array is a full page of 100, stop on a short/empty page. `created_at` / `updated_at` are ISO-8601 strings parsed to epoch MILLISECONDS. The canonical URL is null (the Harvest API exposes no per-job public URL without a board token). Candidates / applications are deliberately deferred (candidate PII; out of scope for v1) — v1 indexes job openings only.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["harvest.greenhouse.io"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": [],
|
|
13
|
+
"syncInterval": 600,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|