@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,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.outlook",
|
|
3
|
+
"displayName": "Outlook",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Microsoft Graph Outlook connector (mail, calendar). Mutations require Gateway HITL.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["graph.microsoft.com", "login.microsoftonline.com"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": ["write", "delete"],
|
|
13
|
+
"syncInterval": 120,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nimbus-connector outlook — Microsoft Graph mail, calendar, contacts (read + guarded writes).
|
|
3
|
+
* Access token is injected as MICROSOFT_OAUTH_ACCESS_TOKEN (never logged).
|
|
4
|
+
* Optional `MICROSOFT_OAUTH_SCOPES` (space-separated) gates which tools register; the Gateway
|
|
5
|
+
* audit-ignore-next-line D11-vault-key (JSDoc reference, not vault-key construction)
|
|
6
|
+
* sets it from `microsoft.oauth` JSON when `scopes` is present.
|
|
7
|
+
* Send / calendar mutations require Gateway HITL (email.send, calendar.event.create | delete).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { headerLine } from "../../../shared/header-safe.ts";
|
|
14
|
+
import {
|
|
15
|
+
createRegisterSimpleTool,
|
|
16
|
+
createZodToolRegistrar,
|
|
17
|
+
mcpJsonResult,
|
|
18
|
+
mcpJsonResultIfOk,
|
|
19
|
+
requireProcessEnv,
|
|
20
|
+
type ZodObjectSchema,
|
|
21
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
22
|
+
import { makeRestFetcher, makeRestToolRegistrar } from "../../../shared/rest-tool-kit.ts";
|
|
23
|
+
import {
|
|
24
|
+
outlookToolShouldRegister,
|
|
25
|
+
parseMicrosoftOAuthScopesFromEnv,
|
|
26
|
+
} from "./tool-scope-policy.ts";
|
|
27
|
+
|
|
28
|
+
const GRAPH = "https://graph.microsoft.com/v1.0";
|
|
29
|
+
|
|
30
|
+
function graphRequest(
|
|
31
|
+
token: string,
|
|
32
|
+
pathOrUrl: string,
|
|
33
|
+
init?: RequestInit,
|
|
34
|
+
): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
|
|
35
|
+
return makeRestFetcher({ apiBase: GRAPH, token })(pathOrUrl, init);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const server = new McpServer({ name: "nimbus-outlook", version: "0.1.0" });
|
|
39
|
+
|
|
40
|
+
const reg = createZodToolRegistrar(createRegisterSimpleTool(server));
|
|
41
|
+
const grantedOutlookScopes = parseMicrosoftOAuthScopesFromEnv();
|
|
42
|
+
|
|
43
|
+
/** Standard Graph read tool: token → graphRequest(buildPath[, buildInit]) → mcpJsonResultIfOk("Graph", …, 200). */
|
|
44
|
+
import { createWriteToolRegistrar, type WriteToolConfig } from "../../../shared/consent-kit.ts";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Every MUTATING outlook tool goes through here. Outside the gateway this adds the
|
|
48
|
+
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
49
|
+
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
50
|
+
*/
|
|
51
|
+
const registerWriteTool = createWriteToolRegistrar(server, {
|
|
52
|
+
connector: "outlook",
|
|
53
|
+
scopeEnv: "NIMBUS_MCP_OUTLOOK_WRITE_SCOPE",
|
|
54
|
+
scopeKinds: ["recipient", "calendar"],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The write-tool equivalent of `registerOutlookTool`: identical fetch and result handling, routed
|
|
59
|
+
* through the write registrar.
|
|
60
|
+
*/
|
|
61
|
+
function registerOutlookWriteTool<T>(
|
|
62
|
+
name: string,
|
|
63
|
+
cfg: WriteToolConfig<T>,
|
|
64
|
+
description: string,
|
|
65
|
+
schema: ZodObjectSchema<T>,
|
|
66
|
+
buildPath: (p: T) => string,
|
|
67
|
+
buildInit?: (p: T) => RequestInit,
|
|
68
|
+
): void {
|
|
69
|
+
registerWriteTool(name, cfg, description, schema, async (parsed) => {
|
|
70
|
+
const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
|
|
71
|
+
const res = await graphRequest(token, buildPath(parsed), buildInit?.(parsed));
|
|
72
|
+
return mcpJsonResultIfOk("Graph", res, 200);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const registerOutlookTool = makeRestToolRegistrar({
|
|
77
|
+
registrar: reg,
|
|
78
|
+
tokenEnv: "MICROSOFT_OAUTH_ACCESS_TOKEN",
|
|
79
|
+
serviceLabel: "Graph",
|
|
80
|
+
fetch: graphRequest,
|
|
81
|
+
snippetMax: 200,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const outlookMailFoldersArgs = z.object({
|
|
85
|
+
top: z.number().int().min(1).max(200).optional(),
|
|
86
|
+
nextLink: z.url().optional(),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (outlookToolShouldRegister("outlook_mail_folders", grantedOutlookScopes)) {
|
|
90
|
+
registerOutlookTool(
|
|
91
|
+
"outlook_mail_folders",
|
|
92
|
+
"List mail folders (pagination via nextLink).",
|
|
93
|
+
outlookMailFoldersArgs,
|
|
94
|
+
(data) => {
|
|
95
|
+
if (data.nextLink !== undefined && data.nextLink !== "") {
|
|
96
|
+
return data.nextLink;
|
|
97
|
+
}
|
|
98
|
+
const top = data.top ?? 50;
|
|
99
|
+
return `/me/mailFolders?$top=${String(top)}`;
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const outlookMailListArgs = z.object({
|
|
105
|
+
folderId: z.string().min(1).optional(),
|
|
106
|
+
top: z.number().int().min(1).max(100).optional(),
|
|
107
|
+
skip: z.number().int().min(0).optional(),
|
|
108
|
+
nextLink: z.url().optional(),
|
|
109
|
+
filter: z.string().max(500).optional(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (outlookToolShouldRegister("outlook_mail_list", grantedOutlookScopes)) {
|
|
113
|
+
registerOutlookTool(
|
|
114
|
+
"outlook_mail_list",
|
|
115
|
+
"List mail messages (default folder inbox if folderId omitted). Pagination via nextLink.",
|
|
116
|
+
outlookMailListArgs,
|
|
117
|
+
(data) => {
|
|
118
|
+
if (data.nextLink !== undefined && data.nextLink !== "") {
|
|
119
|
+
return data.nextLink;
|
|
120
|
+
}
|
|
121
|
+
const top = data.top ?? 25;
|
|
122
|
+
const fid = data.folderId !== undefined && data.folderId !== "" ? data.folderId : "inbox";
|
|
123
|
+
const skip = data.skip ?? 0;
|
|
124
|
+
const u = new URL(`${GRAPH}/me/mailFolders/${encodeURIComponent(fid)}/messages`);
|
|
125
|
+
u.searchParams.set("$top", String(top));
|
|
126
|
+
u.searchParams.set("$skip", String(skip));
|
|
127
|
+
u.searchParams.set(
|
|
128
|
+
"$select",
|
|
129
|
+
"id,subject,bodyPreview,receivedDateTime,lastModifiedDateTime,hasAttachments,webLink",
|
|
130
|
+
);
|
|
131
|
+
if (data.filter !== undefined && data.filter !== "") {
|
|
132
|
+
u.searchParams.set("$filter", data.filter);
|
|
133
|
+
}
|
|
134
|
+
return `${u.pathname}${u.search}`;
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const outlookMailReadArgs = z.object({
|
|
140
|
+
messageId: z.string().min(1),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (outlookToolShouldRegister("outlook_mail_read", grantedOutlookScopes)) {
|
|
144
|
+
registerOutlookTool(
|
|
145
|
+
"outlook_mail_read",
|
|
146
|
+
"Read a single message (body, headers, attachments metadata).",
|
|
147
|
+
outlookMailReadArgs,
|
|
148
|
+
(data) => `/me/messages/${encodeURIComponent(data.messageId)}?$expand=attachments`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const outlookMailSendArgs = z.object({
|
|
153
|
+
to: headerLine({ min: 1, max: 2000 }),
|
|
154
|
+
subject: headerLine({ min: 1, max: 500 }),
|
|
155
|
+
body: z.string().min(1).max(1_000_000),
|
|
156
|
+
contentType: z.enum(["text", "html"]).optional(),
|
|
157
|
+
cc: headerLine({ max: 2000 }).optional(),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (outlookToolShouldRegister("outlook_mail_send", grantedOutlookScopes)) {
|
|
161
|
+
registerWriteTool(
|
|
162
|
+
"outlook_mail_send",
|
|
163
|
+
{
|
|
164
|
+
mutates: "outlook.mail.send",
|
|
165
|
+
recoverable: false,
|
|
166
|
+
capturePreState: (p) => Promise.resolve({ to: p.to, subject: p.subject }),
|
|
167
|
+
scopeTargetOf: (p) => ({ kind: "recipient", value: p.to }),
|
|
168
|
+
},
|
|
169
|
+
"Send an email via Microsoft Graph. Requires Gateway HITL email.send.",
|
|
170
|
+
outlookMailSendArgs,
|
|
171
|
+
async (data) => {
|
|
172
|
+
const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
|
|
173
|
+
const toList = data.to
|
|
174
|
+
.split(",")
|
|
175
|
+
.map((s) => s.trim())
|
|
176
|
+
.filter((s) => s.length > 0);
|
|
177
|
+
const message: Record<string, unknown> = {
|
|
178
|
+
subject: data.subject,
|
|
179
|
+
body: {
|
|
180
|
+
contentType: (data.contentType ?? "text") === "html" ? "HTML" : "Text",
|
|
181
|
+
content: data.body,
|
|
182
|
+
},
|
|
183
|
+
toRecipients: toList.map((addr) => ({
|
|
184
|
+
emailAddress: { address: addr },
|
|
185
|
+
})),
|
|
186
|
+
};
|
|
187
|
+
if (data.cc !== undefined && data.cc !== "") {
|
|
188
|
+
const ccList = data.cc
|
|
189
|
+
.split(",")
|
|
190
|
+
.map((s) => s.trim())
|
|
191
|
+
.filter((s) => s.length > 0);
|
|
192
|
+
message["ccRecipients"] = ccList.map((addr) => ({
|
|
193
|
+
emailAddress: { address: addr },
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
const r = await graphRequest(token, "/me/sendMail", {
|
|
197
|
+
method: "POST",
|
|
198
|
+
headers: { "Content-Type": "application/json" },
|
|
199
|
+
body: JSON.stringify({ message, saveToSentItems: true }),
|
|
200
|
+
});
|
|
201
|
+
if (!r.ok) {
|
|
202
|
+
throw new Error(`Graph ${String(r.status)}: ${r.text.slice(0, 200)}`);
|
|
203
|
+
}
|
|
204
|
+
return mcpJsonResult({ ok: true });
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const outlookCalendarListArgs = z.object({
|
|
210
|
+
startDateTime: z.string().min(1),
|
|
211
|
+
endDateTime: z.string().min(1),
|
|
212
|
+
top: z.number().int().min(1).max(200).optional(),
|
|
213
|
+
nextLink: z.url().optional(),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (outlookToolShouldRegister("outlook_calendar_list", grantedOutlookScopes)) {
|
|
217
|
+
registerOutlookTool(
|
|
218
|
+
"outlook_calendar_list",
|
|
219
|
+
"List calendar events in a time window (ISO 8601 startDateTime / endDateTime).",
|
|
220
|
+
outlookCalendarListArgs,
|
|
221
|
+
(data) => {
|
|
222
|
+
if (data.nextLink !== undefined && data.nextLink !== "") {
|
|
223
|
+
return data.nextLink;
|
|
224
|
+
}
|
|
225
|
+
const top = data.top ?? 50;
|
|
226
|
+
const s = encodeURIComponent(data.startDateTime);
|
|
227
|
+
const e = encodeURIComponent(data.endDateTime);
|
|
228
|
+
return `/me/calendarView?startDateTime=${s}&endDateTime=${e}&$top=${String(top)}`;
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const outlookCalendarGetArgs = z.object({
|
|
234
|
+
eventId: z.string().min(1),
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (outlookToolShouldRegister("outlook_calendar_get", grantedOutlookScopes)) {
|
|
238
|
+
registerOutlookTool(
|
|
239
|
+
"outlook_calendar_get",
|
|
240
|
+
"Get a single calendar event by id.",
|
|
241
|
+
outlookCalendarGetArgs,
|
|
242
|
+
(data) => `/me/events/${encodeURIComponent(data.eventId)}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const outlookCalendarCreateArgs = z.object({
|
|
247
|
+
subject: headerLine({ min: 1, max: 500 }),
|
|
248
|
+
startDateTime: z.string().min(1),
|
|
249
|
+
endDateTime: z.string().min(1),
|
|
250
|
+
timeZone: z.string().min(1).max(100).optional(),
|
|
251
|
+
body: z.string().max(50_000).optional(),
|
|
252
|
+
attendees: headerLine({ max: 4000 }).optional(),
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
if (outlookToolShouldRegister("outlook_calendar_create", grantedOutlookScopes)) {
|
|
256
|
+
registerOutlookWriteTool(
|
|
257
|
+
"outlook_calendar_create",
|
|
258
|
+
{
|
|
259
|
+
mutates: "outlook.calendar.create",
|
|
260
|
+
recoverable: true,
|
|
261
|
+
scopeTargetOf: (p) => ({ kind: "calendar", value: p.subject }),
|
|
262
|
+
},
|
|
263
|
+
"Create a calendar event. Requires Gateway HITL calendar.event.create.",
|
|
264
|
+
outlookCalendarCreateArgs,
|
|
265
|
+
() => "/me/events",
|
|
266
|
+
(data) => {
|
|
267
|
+
const tz = data.timeZone ?? "UTC";
|
|
268
|
+
const body: Record<string, unknown> = {
|
|
269
|
+
subject: data.subject,
|
|
270
|
+
start: { dateTime: data.startDateTime, timeZone: tz },
|
|
271
|
+
end: { dateTime: data.endDateTime, timeZone: tz },
|
|
272
|
+
};
|
|
273
|
+
if (data.body !== undefined && data.body !== "") {
|
|
274
|
+
body["body"] = { contentType: "Text", content: data.body };
|
|
275
|
+
}
|
|
276
|
+
if (data.attendees !== undefined && data.attendees !== "") {
|
|
277
|
+
const addrs = data.attendees
|
|
278
|
+
.split(",")
|
|
279
|
+
.map((s) => s.trim())
|
|
280
|
+
.filter((s) => s.length > 0);
|
|
281
|
+
body["attendees"] = addrs.map((a) => ({
|
|
282
|
+
emailAddress: { address: a },
|
|
283
|
+
type: "required",
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
method: "POST",
|
|
288
|
+
headers: { "Content-Type": "application/json" },
|
|
289
|
+
body: JSON.stringify(body),
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const outlookCalendarDeleteArgs = z.object({
|
|
296
|
+
eventId: z.string().min(1),
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (outlookToolShouldRegister("outlook_calendar_delete", grantedOutlookScopes)) {
|
|
300
|
+
registerWriteTool(
|
|
301
|
+
"outlook_calendar_delete",
|
|
302
|
+
{
|
|
303
|
+
mutates: "outlook.calendar.delete",
|
|
304
|
+
recoverable: false,
|
|
305
|
+
capturePreState: (p) => Promise.resolve({ eventId: p.eventId }),
|
|
306
|
+
scopeTargetOf: (p) => ({ kind: "calendar", value: p.eventId }),
|
|
307
|
+
},
|
|
308
|
+
"Delete a calendar event. Requires Gateway HITL calendar.event.delete.",
|
|
309
|
+
outlookCalendarDeleteArgs,
|
|
310
|
+
async (data) => {
|
|
311
|
+
const token = requireProcessEnv("MICROSOFT_OAUTH_ACCESS_TOKEN");
|
|
312
|
+
const r = await graphRequest(token, `/me/events/${encodeURIComponent(data.eventId)}`, {
|
|
313
|
+
method: "DELETE",
|
|
314
|
+
});
|
|
315
|
+
if (!r.ok && r.status !== 204) {
|
|
316
|
+
throw new Error(`Graph ${String(r.status)}: ${r.text.slice(0, 200)}`);
|
|
317
|
+
}
|
|
318
|
+
return mcpJsonResult({ ok: true });
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const outlookContactListArgs = z.object({
|
|
324
|
+
top: z.number().int().min(1).max(200).optional(),
|
|
325
|
+
skip: z.number().int().min(0).optional(),
|
|
326
|
+
nextLink: z.url().optional(),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (outlookToolShouldRegister("outlook_contact_list", grantedOutlookScopes)) {
|
|
330
|
+
registerOutlookTool(
|
|
331
|
+
"outlook_contact_list",
|
|
332
|
+
"List contacts from the default folder.",
|
|
333
|
+
outlookContactListArgs,
|
|
334
|
+
(data) => {
|
|
335
|
+
if (data.nextLink !== undefined && data.nextLink !== "") {
|
|
336
|
+
return data.nextLink;
|
|
337
|
+
}
|
|
338
|
+
const top = data.top ?? 50;
|
|
339
|
+
const skip = data.skip ?? 0;
|
|
340
|
+
return `/me/contacts?$top=${String(top)}&$skip=${String(skip)}`;
|
|
341
|
+
},
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const outlookContactGetArgs = z.object({
|
|
346
|
+
contactId: z.string().min(1),
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
if (outlookToolShouldRegister("outlook_contact_get", grantedOutlookScopes)) {
|
|
350
|
+
registerOutlookTool(
|
|
351
|
+
"outlook_contact_get",
|
|
352
|
+
"Get a single contact by id.",
|
|
353
|
+
outlookContactGetArgs,
|
|
354
|
+
(data) => `/me/contacts/${encodeURIComponent(data.contactId)}`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
await server.connect(new StdioServerTransport());
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const OUTLOOK_TOOL_MIN_SCOPES: Readonly<Record<string, readonly string[]>> = {
|
|
2
|
+
outlook_mail_folders: ["Mail.Read"],
|
|
3
|
+
outlook_mail_list: ["Mail.Read"],
|
|
4
|
+
outlook_mail_read: ["Mail.Read"],
|
|
5
|
+
outlook_mail_send: ["Mail.Send"],
|
|
6
|
+
outlook_calendar_list: ["Calendars.Read"],
|
|
7
|
+
outlook_calendar_get: ["Calendars.Read"],
|
|
8
|
+
outlook_calendar_create: ["Calendars.ReadWrite"],
|
|
9
|
+
outlook_calendar_delete: ["Calendars.ReadWrite"],
|
|
10
|
+
outlook_contact_list: ["Contacts.Read"],
|
|
11
|
+
outlook_contact_get: ["Contacts.Read"],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function scopeSatisfied(granted: ReadonlySet<string>, required: string): boolean {
|
|
15
|
+
if (granted.has(required)) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (required === "Calendars.Read" && granted.has("Calendars.ReadWrite")) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (required === "Mail.Read" && granted.has("Mail.ReadWrite")) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function outlookToolAllowed(toolId: string, grantedScopes: readonly string[]): boolean {
|
|
28
|
+
const reqs = OUTLOOK_TOOL_MIN_SCOPES[toolId];
|
|
29
|
+
if (reqs === undefined) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
const g = new Set(grantedScopes);
|
|
33
|
+
return reqs.every((r) => scopeSatisfied(g, r));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parseMicrosoftOAuthScopesFromEnv(): string[] | undefined {
|
|
37
|
+
const raw = process.env["MICROSOFT_OAUTH_SCOPES"];
|
|
38
|
+
if (raw === undefined) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const parts = raw.split(/\s+/).filter((s) => s.length > 0);
|
|
42
|
+
return parts.length === 0 ? undefined : parts;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function outlookToolShouldRegister(
|
|
46
|
+
toolId: string,
|
|
47
|
+
grantedFromEnv: string[] | undefined,
|
|
48
|
+
): boolean {
|
|
49
|
+
if (grantedFromEnv === undefined) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return outlookToolAllowed(toolId, grantedFromEnv);
|
|
53
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.pagerduty",
|
|
3
|
+
"displayName": "PagerDuty",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "PagerDuty REST + Events API connector (incidents, services, schedules). Mutations require Gateway HITL.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["api.pagerduty.com", "events.pagerduty.com"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": ["write", "delete"],
|
|
13
|
+
"syncInterval": 60,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
requireProcessEnv,
|
|
10
|
+
} from "../../../shared/mcp-tool-kit.ts";
|
|
11
|
+
import { makeRestToolRegistrar } from "../../../shared/rest-tool-kit.ts";
|
|
12
|
+
|
|
13
|
+
const API = "https://api.pagerduty.com";
|
|
14
|
+
|
|
15
|
+
async function pdFetch(
|
|
16
|
+
token: string,
|
|
17
|
+
path: string,
|
|
18
|
+
init?: RequestInit,
|
|
19
|
+
): Promise<{ ok: boolean; status: number; json: unknown; text: string }> {
|
|
20
|
+
const rel = path.startsWith("/") ? path : `/${path}`;
|
|
21
|
+
const url = path.startsWith("http") ? path : `${API}${rel}`;
|
|
22
|
+
const res = await fetch(url, {
|
|
23
|
+
...init,
|
|
24
|
+
headers: {
|
|
25
|
+
Accept: "application/vnd.pagerduty+json;version=2",
|
|
26
|
+
Authorization: `Token token=${token}`,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
...(init?.headers as Record<string, string> | undefined),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const text = await res.text();
|
|
32
|
+
let json: unknown;
|
|
33
|
+
try {
|
|
34
|
+
json = JSON.parse(text) as unknown;
|
|
35
|
+
} catch {
|
|
36
|
+
json = null;
|
|
37
|
+
}
|
|
38
|
+
return { ok: res.ok, status: res.status, json, text };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function pdIncidentPutMutation(actionLabel: string, pathSuffix: string, incidentId: string) {
|
|
42
|
+
const token = requireProcessEnv("PAGERDUTY_API_TOKEN");
|
|
43
|
+
const res = await pdFetch(token, `/incidents/${encodeURIComponent(incidentId)}/${pathSuffix}`, {
|
|
44
|
+
method: "PUT",
|
|
45
|
+
body: JSON.stringify({ incident: { type: "incident_reference", id: incidentId } }),
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
throw new Error(`PagerDuty ${actionLabel} ${String(res.status)}: ${res.text.slice(0, 400)}`);
|
|
49
|
+
}
|
|
50
|
+
return jsonResult(res.json ?? { ok: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const mcp = new McpServer({ name: "nimbus-pagerduty", version: "0.1.0" });
|
|
54
|
+
|
|
55
|
+
import { createWriteToolRegistrar } from "../../../shared/consent-kit.ts";
|
|
56
|
+
|
|
57
|
+
const reg = createZodToolRegistrar(createRegisterSimpleTool(mcp));
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Every MUTATING pagerduty tool goes through here. Outside the gateway this adds the
|
|
61
|
+
* consent gate, the write-scope allow-list, the mutation budget and the audit record; inside
|
|
62
|
+
* the gateway it is a pass-through, because executor.ts (I2) is the gate there.
|
|
63
|
+
*/
|
|
64
|
+
const registerWriteTool = createWriteToolRegistrar(mcp, {
|
|
65
|
+
connector: "pagerduty",
|
|
66
|
+
scopeEnv: "NIMBUS_MCP_PAGERDUTY_WRITE_SCOPE",
|
|
67
|
+
scopeKinds: ["incident"],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/** Standard PagerDuty read tool: token → pdFetch(buildPath) → mcpJsonResultIfOk("PagerDuty"). */
|
|
71
|
+
const registerPdTool = makeRestToolRegistrar({
|
|
72
|
+
registrar: reg,
|
|
73
|
+
tokenEnv: "PAGERDUTY_API_TOKEN",
|
|
74
|
+
serviceLabel: "PagerDuty",
|
|
75
|
+
fetch: pdFetch,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
registerPdTool(
|
|
79
|
+
"pd_incident_list",
|
|
80
|
+
"List PagerDuty incidents (open and recently resolved).",
|
|
81
|
+
z.object({
|
|
82
|
+
statuses: z.array(z.enum(["triggered", "acknowledged", "resolved"])).optional(),
|
|
83
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
84
|
+
}),
|
|
85
|
+
(parsed) => {
|
|
86
|
+
const u = new URL(`${API}/incidents`);
|
|
87
|
+
u.searchParams.set("limit", String(parsed.limit ?? 25));
|
|
88
|
+
const st = parsed.statuses ?? ["triggered", "acknowledged"];
|
|
89
|
+
for (const s of st) {
|
|
90
|
+
u.searchParams.append("statuses[]", s);
|
|
91
|
+
}
|
|
92
|
+
return `${u.pathname}${u.search}`;
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
registerPdTool(
|
|
97
|
+
"pd_incident_get",
|
|
98
|
+
"Get a single incident by id.",
|
|
99
|
+
z.object({ incidentId: z.string().min(1) }),
|
|
100
|
+
(parsed) => `/incidents/${encodeURIComponent(parsed.incidentId)}`,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
registerWriteTool(
|
|
104
|
+
"pd_incident_acknowledge",
|
|
105
|
+
{
|
|
106
|
+
mutates: "pagerduty.incident.acknowledge",
|
|
107
|
+
recoverable: true,
|
|
108
|
+
scopeTargetOf: (p) => ({ kind: "incident", value: p.incidentId }),
|
|
109
|
+
},
|
|
110
|
+
"Acknowledge an incident.",
|
|
111
|
+
z.object({ incidentId: z.string().min(1) }),
|
|
112
|
+
async (parsed) => pdIncidentPutMutation("acknowledge", "acknowledge", parsed.incidentId),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
registerWriteTool(
|
|
116
|
+
"pd_incident_resolve",
|
|
117
|
+
{
|
|
118
|
+
mutates: "pagerduty.incident.resolve",
|
|
119
|
+
recoverable: true,
|
|
120
|
+
scopeTargetOf: (p) => ({ kind: "incident", value: p.incidentId }),
|
|
121
|
+
},
|
|
122
|
+
"Resolve an incident.",
|
|
123
|
+
z.object({ incidentId: z.string().min(1) }),
|
|
124
|
+
async (parsed) => pdIncidentPutMutation("resolve", "resolve", parsed.incidentId),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
registerWriteTool(
|
|
128
|
+
"pd_incident_escalate",
|
|
129
|
+
{
|
|
130
|
+
mutates: "pagerduty.incident.escalate",
|
|
131
|
+
recoverable: true,
|
|
132
|
+
scopeTargetOf: (p) => ({ kind: "incident", value: p.incidentId }),
|
|
133
|
+
},
|
|
134
|
+
"Escalate an incident.",
|
|
135
|
+
z.object({ incidentId: z.string().min(1) }),
|
|
136
|
+
async (parsed) => pdIncidentPutMutation("escalate", "escalate", parsed.incidentId),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const transport = new StdioServerTransport();
|
|
140
|
+
await mcp.connect(transport);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.nimbus.pipedrive",
|
|
3
|
+
"displayName": "Pipedrive",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Pipedrive connector (read-only). Surfaces the user's Pipedrive CRM deals as `pipedrive:deal` items in the local index — the deal title, value + currency, status (open/won/lost), stage and pipeline ids, the linked person/organization names, the owner, probability, label, expected close date, and won/close times. Useful for answering sales questions (\"what deals are closing this month?\", \"find the Acme renewal deal\") without leaving Nimbus. Auth is via the API token IN THE QUERY STRING (`?api_token=<token>`) — there is NO Authorization header, so the request URL itself carries the secret and is never logged or surfaced in errors. The SaaS host (api.pipedrive.com) is fixed. Pipedrive paginates `/v1/deals` by `start`/`next_start` offsets (page_size=100), returning the `{ success, data, additional_data: { pagination: { more_items_in_collection, next_start } } }` envelope; `data` may be null. Pipedrive's `add_time` / `update_time` are non-ISO `\"YYYY-MM-DD HH:MM:SS\"` UTC strings (converted to epoch-ms on index). Persons / organizations / activities / notes are deferred follow-ups; v1 indexes deals only.",
|
|
6
|
+
"author": "Nimbus",
|
|
7
|
+
"entrypoint": "dist/server.js",
|
|
8
|
+
"runtime": "bun",
|
|
9
|
+
"permissions": {
|
|
10
|
+
"network": ["api.pipedrive.com"]
|
|
11
|
+
},
|
|
12
|
+
"hitlRequired": [],
|
|
13
|
+
"syncInterval": 600,
|
|
14
|
+
"minNimbusVersion": "0.2.0"
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fieldsFromKeys,
|
|
3
|
+
makeQueryFilter,
|
|
4
|
+
type SearchMatchOptions,
|
|
5
|
+
} from "../../../shared/search-filter.ts";
|
|
6
|
+
|
|
7
|
+
export type PipedriveSearchMatchOptions = SearchMatchOptions;
|
|
8
|
+
|
|
9
|
+
export const filterPipedriveDeals = makeQueryFilter(
|
|
10
|
+
fieldsFromKeys(["title", "status", "org_name", "person_name", "owner_name", "label"]),
|
|
11
|
+
);
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
import { runReadOnlyMcpConnector } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
5
|
+
import { filterPipedriveDeals } from "./search-filter.ts";
|
|
6
|
+
|
|
7
|
+
const BASE = "https://api.pipedrive.com";
|
|
8
|
+
|
|
9
|
+
function apiToken(): string {
|
|
10
|
+
const t = process.env["PIPEDRIVE_TOKEN"]?.trim();
|
|
11
|
+
if (t === undefined || t === "") {
|
|
12
|
+
throw new Error("PIPEDRIVE_TOKEN is not set");
|
|
13
|
+
}
|
|
14
|
+
return t;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function withToken(path: string): string {
|
|
18
|
+
const sep = path.includes("?") ? "&" : "?";
|
|
19
|
+
return `${BASE}${path}${sep}api_token=${encodeURIComponent(apiToken())}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function pipedriveGet(path: string, resourceLabel: string): Promise<unknown> {
|
|
23
|
+
const res = await fetch(withToken(path), { headers: { Accept: "application/json" } });
|
|
24
|
+
const text = await res.text();
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
throw new Error(`Pipedrive ${resourceLabel} ${String(res.status)}: ${text.slice(0, 400)}`);
|
|
27
|
+
}
|
|
28
|
+
return JSON.parse(text) as unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await runReadOnlyMcpConnector("nimbus-pipedrive", (reg) => {
|
|
32
|
+
reg(
|
|
33
|
+
"pipedrive_list",
|
|
34
|
+
"List the user's Pipedrive deals (`GET /v1/deals?limit=100`). Returns the `{ success, data: [...], additional_data: { pagination } }` envelope — `data` holds the deal objects (and may be null when there are no deals).",
|
|
35
|
+
z.object({}),
|
|
36
|
+
async () => {
|
|
37
|
+
return jsonResult(await pipedriveGet(`/v1/deals?limit=100`, "deals"));
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
reg(
|
|
42
|
+
"pipedrive_get",
|
|
43
|
+
"Fetch one Pipedrive deal by its id (`GET /v1/deals/{id}`). Returns the `{ success, data: {...} }` envelope. Throws when no match is found.",
|
|
44
|
+
z.object({
|
|
45
|
+
id: z.string().min(1),
|
|
46
|
+
}),
|
|
47
|
+
async (p) => {
|
|
48
|
+
return jsonResult(await pipedriveGet(`/v1/deals/${encodeURIComponent(p.id)}`, "deal"));
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
reg(
|
|
53
|
+
"pipedrive_search",
|
|
54
|
+
"Substring search across the user's Pipedrive deals (first page only). Matches the query against the deal title, status, organization name, person name, owner name, and label (case-insensitive). Returns a `{ matches: [...] }` envelope.",
|
|
55
|
+
searchToolInputSchema(100),
|
|
56
|
+
async (p) => {
|
|
57
|
+
const root = await pipedriveGet(`/v1/deals?limit=100`, "deals");
|
|
58
|
+
const data = (root as { data?: unknown } | null)?.data;
|
|
59
|
+
return matchesResult(data, filterPipedriveDeals, p);
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
});
|