@nimbus-dev/connectors 0.2.3 → 0.3.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/connectors/apple/src/server.ts +5 -1
- package/connectors/aws/src/server.ts +5 -1
- package/connectors/azure/src/server.ts +5 -1
- package/connectors/bitbucket/src/server.ts +5 -1
- package/connectors/circleci/src/server.ts +5 -1
- package/connectors/confluence/src/server.ts +5 -1
- package/connectors/fastmail/src/server.ts +5 -1
- package/connectors/gcp/src/server.ts +5 -1
- package/connectors/github/src/server.ts +2 -0
- package/connectors/github-actions/src/server.ts +5 -1
- package/connectors/gitlab/src/server.ts +5 -1
- package/connectors/gmail/src/server.ts +5 -1
- package/connectors/google-drive/src/server.ts +5 -1
- package/connectors/iac/src/server.ts +5 -1
- package/connectors/iac/src/tools.ts +77 -20
- package/connectors/imap/src/server.ts +5 -1
- package/connectors/jenkins/src/server.ts +5 -1
- package/connectors/jira/src/server.ts +5 -1
- package/connectors/kubernetes/src/server.ts +5 -1
- package/connectors/linear/src/server.ts +5 -1
- package/connectors/notion/src/server.ts +5 -1
- package/connectors/obsidian/src/server.ts +5 -1
- package/connectors/onedrive/src/server.ts +5 -1
- package/connectors/outlook/src/server.ts +5 -1
- package/connectors/pagerduty/src/server.ts +5 -1
- package/connectors/protonmail/src/server.ts +5 -1
- package/connectors/readwise/nimbus.extension.json +9 -1
- package/connectors/readwise/src/book-mapping.ts +109 -0
- package/connectors/readwise/src/extension.ts +11 -0
- package/connectors/readwise/src/highlight-mapping.ts +88 -0
- package/connectors/readwise/src/readwise-base.ts +6 -0
- package/connectors/readwise/src/record-fields.ts +72 -0
- package/connectors/readwise/src/sync-tool.ts +56 -0
- package/connectors/readwise/src/sync.ts +348 -0
- package/connectors/readwise/src/tools.ts +2 -2
- package/connectors/slack/src/server.ts +5 -1
- package/connectors/teams/src/server.ts +5 -1
- package/package.json +2 -2
- package/shared/cli-json-kit.ts +1 -13
- package/shared/consent-kit.ts +39 -17
- package/shared/nimbus-spawn.ts +31 -1
- package/shared/run-read-only-mcp-connector.ts +7 -1
- package/shared/safe-cli-arg.ts +0 -20
- package/shared/windows-batch-args.ts +23 -9
|
@@ -6,15 +6,15 @@ import {
|
|
|
6
6
|
} from "../../../shared/collection-tool-kit.ts";
|
|
7
7
|
import { createJsonGetter, envAuthHeaders } from "../../../shared/env-json-api.ts";
|
|
8
8
|
import type { ZodToolRegistrar } from "../../../shared/run-read-only-mcp-connector.ts";
|
|
9
|
+
import { READWISE_BASE } from "./readwise-base.ts";
|
|
9
10
|
import { filterReadwiseBooks, filterReadwiseHighlights } from "./search-filter.ts";
|
|
10
11
|
|
|
11
|
-
const BASE = "https://readwise.io";
|
|
12
12
|
const HIGHLIGHTS = "/api/v2/highlights/?page_size=1000";
|
|
13
13
|
const BOOKS = "/api/v2/books/?page_size=1000";
|
|
14
14
|
|
|
15
15
|
/** Readwise authenticates with `Authorization: Token <key>`, not Bearer. */
|
|
16
16
|
const readwiseGet = createJsonGetter({
|
|
17
|
-
base:
|
|
17
|
+
base: READWISE_BASE,
|
|
18
18
|
label: "Readwise",
|
|
19
19
|
headers: envAuthHeaders({ env: "READWISE_TOKEN", scheme: "Token" }),
|
|
20
20
|
});
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CONSENT_SERVER_CAPABILITIES } from "../../../shared/consent-kit.ts";
|
|
3
4
|
import { registerSlackTools } from "./tools.ts";
|
|
4
5
|
|
|
5
|
-
const server = new McpServer(
|
|
6
|
+
const server = new McpServer(
|
|
7
|
+
{ name: "nimbus-slack", version: "0.1.0" },
|
|
8
|
+
{ capabilities: CONSENT_SERVER_CAPABILITIES },
|
|
9
|
+
);
|
|
6
10
|
|
|
7
11
|
registerSlackTools(server);
|
|
8
12
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CONSENT_SERVER_CAPABILITIES } from "../../../shared/consent-kit.ts";
|
|
3
4
|
import { registerTeamsTools } from "./tools.ts";
|
|
4
5
|
|
|
5
|
-
const server = new McpServer(
|
|
6
|
+
const server = new McpServer(
|
|
7
|
+
{ name: "nimbus-teams", version: "0.1.0" },
|
|
8
|
+
{ capabilities: CONSENT_SERVER_CAPABILITIES },
|
|
9
|
+
);
|
|
6
10
|
|
|
7
11
|
registerTeamsTools(server);
|
|
8
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbus-dev/connectors",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@modelcontextprotocol/sdk": "1.32.0",
|
|
29
|
-
"@nimbus-dev/sdk": "^2.
|
|
29
|
+
"@nimbus-dev/sdk": "^2.1.0",
|
|
30
30
|
"@types/nodemailer": "^8.0.2",
|
|
31
31
|
"zod": "^4.6.5"
|
|
32
32
|
},
|
package/shared/cli-json-kit.ts
CHANGED
|
@@ -29,12 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
import { z } from "zod";
|
|
31
31
|
import { optionalEnv } from "./env-json-api.ts";
|
|
32
|
-
import {
|
|
33
|
-
awsCliArgProblem,
|
|
34
|
-
awsCliDocumentProblem,
|
|
35
|
-
azCliArgProblem,
|
|
36
|
-
cliArgProblem,
|
|
37
|
-
} from "./safe-cli-arg.ts";
|
|
32
|
+
import { awsCliArgProblem, azCliArgProblem, cliArgProblem } from "./safe-cli-arg.ts";
|
|
38
33
|
|
|
39
34
|
/** Body-snippet length in the thrown error. The value every connector used. */
|
|
40
35
|
export const DEFAULT_STDERR_SNIPPET = 400;
|
|
@@ -68,13 +63,6 @@ export const cliArg = cliArgSchema(cliArgProblem);
|
|
|
68
63
|
*/
|
|
69
64
|
export const awsCliArg = cliArgSchema(awsCliArgProblem);
|
|
70
65
|
|
|
71
|
-
/**
|
|
72
|
-
* A DOCUMENT passed to the `aws` CLI as one argument — a CloudFormation template body. Unlike
|
|
73
|
-
* {@link awsCliArg} it may span lines and run past 1024 characters; it still may not start with `-`
|
|
74
|
-
* or with a prefix that makes the CLI read it from somewhere else.
|
|
75
|
-
*/
|
|
76
|
-
export const awsCliDocument = cliArgSchema(awsCliDocumentProblem);
|
|
77
|
-
|
|
78
66
|
/**
|
|
79
67
|
* A value passed to the `az` CLI as an argument: {@link cliArg}, and not one `az` would replace by
|
|
80
68
|
* a file's contents — a leading `@`, or `=@` anywhere.
|
package/shared/consent-kit.ts
CHANGED
|
@@ -59,6 +59,19 @@ export type ConsentServer = {
|
|
|
59
59
|
sendLoggingMessage(params: { level: "info" | "warning"; data: unknown }): Promise<void>;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* What a connector with write tools constructs its `McpServer` with:
|
|
64
|
+
* `new McpServer(info, { capabilities: CONSENT_SERVER_CAPABILITIES })`.
|
|
65
|
+
*
|
|
66
|
+
* The kit tells the client about every gated outcome with `notifications/message`, and the SDK
|
|
67
|
+
* sends that only for a server that declared the MCP `logging` capability. No connector did, so
|
|
68
|
+
* each notification was dropped without a word. It must be declared at construction: that is the
|
|
69
|
+
* only moment the SDK installs its `logging/setLevel` handler, and a server that declared the
|
|
70
|
+
* capability later would refuse a client setting the level. `scripts/connector-boot.test.ts`
|
|
71
|
+
* fails a connector whose manifest declares a write and whose booted server does not declare it.
|
|
72
|
+
*/
|
|
73
|
+
export const CONSENT_SERVER_CAPABILITIES = { logging: {} };
|
|
74
|
+
|
|
62
75
|
export type WriteToolConfig<T> = {
|
|
63
76
|
/** Action type this tool performs, e.g. "repo.branch.delete". Machine-readable, not prose. */
|
|
64
77
|
readonly mutates: string;
|
|
@@ -185,23 +198,32 @@ export function createWriteToolRegistrar(
|
|
|
185
198
|
outcome: AuditOutcome,
|
|
186
199
|
detail: Record<string, unknown>,
|
|
187
200
|
): Promise<void> {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
try {
|
|
202
|
+
// Durable channel first: only when the operator configured a path. An append that fails
|
|
203
|
+
// rejects, and every record before the mutation is awaited, so a write whose entry did not
|
|
204
|
+
// land never runs.
|
|
205
|
+
if (auditLog !== undefined && auditLog !== "") {
|
|
206
|
+
await appendAuditEntry(auditLog, {
|
|
207
|
+
ts: new Date().toISOString(),
|
|
208
|
+
connector: cfg.connector,
|
|
209
|
+
tool,
|
|
210
|
+
outcome,
|
|
211
|
+
detail,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
} finally {
|
|
215
|
+
// Then the client-visible channel, told of every record attempted, one whose append failed
|
|
216
|
+
// included. It reaches the client because the connector declared the MCP `logging`
|
|
217
|
+
// capability (CONSENT_SERVER_CAPABILITIES), and a client may filter it by level. Best
|
|
218
|
+
// effort, and after the durable log: a client that has gone away mid-write must not keep the
|
|
219
|
+
// `executed` or `failed` record of a write that ran out of the log, and since the capability
|
|
220
|
+
// made this channel live, a dead pipe is where a send now fails.
|
|
221
|
+
await server
|
|
222
|
+
.sendLoggingMessage({
|
|
223
|
+
level: outcome === "executed" ? "info" : "warning",
|
|
224
|
+
data: { connector: cfg.connector, tool, outcome },
|
|
225
|
+
})
|
|
226
|
+
.catch(() => undefined);
|
|
205
227
|
}
|
|
206
228
|
}
|
|
207
229
|
|
package/shared/nimbus-spawn.ts
CHANGED
|
@@ -16,6 +16,36 @@ import { batchArgumentRefusal } from "./windows-batch-args.ts";
|
|
|
16
16
|
|
|
17
17
|
export type SpawnResult = { code: number; stdout: string; stderr: string };
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The child's environment: `base` with `overrides` laid over it.
|
|
21
|
+
*
|
|
22
|
+
* On Windows a variable's name is case-insensitive, so an override replaces the base's variable
|
|
23
|
+
* whatever its spelling, instead of sitting beside it as a second one that the child may or may
|
|
24
|
+
* not see in its place. And the search path is spelled `PATH`, the only spelling Bun resolves a
|
|
25
|
+
* bare command through. A process started from PowerShell, cmd or Explorer has `Path`, and with it
|
|
26
|
+
* a bare `az` or `gcloud`, which are the batch files `az.cmd` and `gcloud.cmd` there, did not start
|
|
27
|
+
* at all: Bun 1.3.14 answered ENOENT, while an `.exe` such as `aws.exe` still started.
|
|
28
|
+
*
|
|
29
|
+
* Elsewhere names are case-sensitive, and the two are merged as written.
|
|
30
|
+
*/
|
|
31
|
+
export function childEnvironment(
|
|
32
|
+
base: Record<string, string | undefined>,
|
|
33
|
+
overrides: Record<string, string | undefined>,
|
|
34
|
+
platform: NodeJS.Platform = process.platform,
|
|
35
|
+
): Record<string, string | undefined> {
|
|
36
|
+
if (platform !== "win32") {
|
|
37
|
+
return { ...base, ...overrides };
|
|
38
|
+
}
|
|
39
|
+
const byName = new Map<string, readonly [string, string | undefined]>();
|
|
40
|
+
for (const source of [base, overrides]) {
|
|
41
|
+
for (const [key, value] of Object.entries(source)) {
|
|
42
|
+
const name = key.toUpperCase();
|
|
43
|
+
byName.set(name, [name === "PATH" ? "PATH" : key, value]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return Object.fromEntries(byName.values());
|
|
47
|
+
}
|
|
48
|
+
|
|
19
49
|
/**
|
|
20
50
|
* The child's environment for `env`, and why `command` must not be spawned with it, if it must
|
|
21
51
|
* not. Nothing is spawned for a refused command: it resolves as a failure whose stderr says so.
|
|
@@ -24,7 +54,7 @@ function prepareSpawn(
|
|
|
24
54
|
command: readonly string[],
|
|
25
55
|
env: Record<string, string | undefined>,
|
|
26
56
|
): { childEnv: Record<string, string | undefined>; refusal: SpawnResult | undefined } {
|
|
27
|
-
const childEnv =
|
|
57
|
+
const childEnv = childEnvironment(process.env, env);
|
|
28
58
|
const refusal = batchArgumentRefusal(command, childEnv);
|
|
29
59
|
return {
|
|
30
60
|
childEnv,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CONSENT_SERVER_CAPABILITIES } from "./consent-kit.ts";
|
|
3
4
|
import {
|
|
4
5
|
createRegisterSimpleTool,
|
|
5
6
|
createZodToolRegistrar,
|
|
@@ -41,7 +42,12 @@ export function buildReadOnlyMcpConnector(
|
|
|
41
42
|
register: (reg: ZodToolRegistrar, server: unknown) => void,
|
|
42
43
|
options?: BuildReadOnlyMcpConnectorOptions,
|
|
43
44
|
): unknown {
|
|
44
|
-
|
|
45
|
+
// Every server built here declares the MCP `logging` capability, since the consent kit's
|
|
46
|
+
// notifications reach a client only through it. A truly read-only connector sends no log
|
|
47
|
+
// message, so declaring it costs that connector nothing.
|
|
48
|
+
const make =
|
|
49
|
+
options?.createServer ??
|
|
50
|
+
((info) => new McpServer(info, { capabilities: CONSENT_SERVER_CAPABILITIES }));
|
|
45
51
|
const mcp = make({ name: serverName, version: "0.1.0" });
|
|
46
52
|
const reg = createZodToolRegistrar(createRegisterSimpleTool(mcp));
|
|
47
53
|
register(reg, mcp);
|
package/shared/safe-cli-arg.ts
CHANGED
|
@@ -124,26 +124,6 @@ export function awsCliArgProblem(value: unknown): string | undefined {
|
|
|
124
124
|
return cliArgProblem(value) ?? awsPrefixProblem(value) ?? awsShorthandProblem(value);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
/**
|
|
128
|
-
* Why `value` must not be passed to the AWS CLI as a DOCUMENT — a CloudFormation template body —
|
|
129
|
-
* or `undefined` when it may.
|
|
130
|
-
*
|
|
131
|
-
* A document legitimately spans lines and runs past the 1024 characters an argument is held to,
|
|
132
|
-
* so of the argument rules only the two about where the CLI looks remain: a leading `-`, which the
|
|
133
|
-
* CLI would parse as an option, and a loading prefix, which would make it read the "document" from
|
|
134
|
-
* a file. `@=` is not among them: shorthand syntax applies to structure parameters, and a template
|
|
135
|
-
* body is a string.
|
|
136
|
-
*/
|
|
137
|
-
export function awsCliDocumentProblem(value: unknown): string | undefined {
|
|
138
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
139
|
-
return "must be a non-empty string";
|
|
140
|
-
}
|
|
141
|
-
if (value.startsWith("-")) {
|
|
142
|
-
return 'must not start with "-" (argv flag smuggling is not allowed)';
|
|
143
|
-
}
|
|
144
|
-
return awsPrefixProblem(value);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
127
|
// ---------------------------------------------------------------------------
|
|
148
128
|
// az
|
|
149
129
|
// ---------------------------------------------------------------------------
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
* AWS resource name, an Azure app, cluster or node pool name, or a subscription id. An Azure
|
|
29
29
|
* resource group may hold `(` and `)`, but never a space, so the runtime never quotes it and
|
|
30
30
|
* `az.cmd` already failed on it as above. What did work and is now refused: an Azure subscription
|
|
31
|
-
* passed by a display name holding one of these characters and a space — its id still works
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* passed by a display name holding one of these characters and a space — its id still works. A
|
|
32
|
+
* CloudFormation template, full of quotes, was refused too whenever the `aws` that runs is a batch
|
|
33
|
+
* file, until it began to reach `aws` as a file instead of an argument.
|
|
34
34
|
*
|
|
35
35
|
* Which file runs is decided the way the runtime decides it — see {@link mayRunAsBatchFile} — so
|
|
36
36
|
* an `aws.exe` found first is never refused, whatever batch file of that name comes later.
|
|
@@ -77,12 +77,25 @@ export function cmdMetacharacterIn(value: string): string | undefined {
|
|
|
77
77
|
return undefined;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* `name` without the dots and spaces at its end. A loop rather than `/[. ]+$/`, whose engine
|
|
82
|
+
* retries the run from every start position and so takes quadratic time on a long run of dots
|
|
83
|
+
* and spaces that something else follows.
|
|
84
|
+
*/
|
|
85
|
+
function withoutTrailingDotsAndSpaces(name: string): string {
|
|
86
|
+
let end = name.length;
|
|
87
|
+
while (end > 0 && (name[end - 1] === "." || name[end - 1] === " ")) {
|
|
88
|
+
end -= 1;
|
|
89
|
+
}
|
|
90
|
+
return name.slice(0, end);
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
/**
|
|
81
94
|
* Whether Windows runs a file of this name through cmd.exe: a `.cmd` or a `.bat`, in any case,
|
|
82
95
|
* after the trailing dots and spaces Windows drops from a file name — `az.cmd.` opens `az.cmd`.
|
|
83
96
|
*/
|
|
84
97
|
export function isBatchFileName(name: string): boolean {
|
|
85
|
-
const trimmed = name
|
|
98
|
+
const trimmed = withoutTrailingDotsAndSpaces(name).toLowerCase();
|
|
86
99
|
return BATCH_EXTENSIONS.some((ext) => trimmed.endsWith(ext));
|
|
87
100
|
}
|
|
88
101
|
|
|
@@ -183,11 +196,12 @@ function firstHolderIsBatch(bases: readonly string[], exists: (path: string) =>
|
|
|
183
196
|
* and libuv starts only a `.com` or an `.exe`: never a batch file.
|
|
184
197
|
*
|
|
185
198
|
* It says yes where Bun could change without notice, and nowhere else: a PATH variable counts in
|
|
186
|
-
* any spelling, not only `PATH` —
|
|
187
|
-
*
|
|
188
|
-
* stripped from its entries; a name written with a
|
|
189
|
-
* a `\` is; and the first place holding the name
|
|
190
|
-
* that name, even beside an `.exe` Bun would try
|
|
199
|
+
* any spelling, not only `PATH` — Bun reads no other, though `nimbus-spawn.ts` respells the
|
|
200
|
+
* variable `PATH` before every spawn, so a bare `az` under `Path` starts too — and each is searched
|
|
201
|
+
* both as written and with whitespace and quotes stripped from its entries; a name written with a
|
|
202
|
+
* `/` is looked up in its directory as one with a `\` is; and the first place holding the name
|
|
203
|
+
* answers yes if it holds a `.cmd` or `.bat` by that name, even beside an `.exe` Bun would try
|
|
204
|
+
* first.
|
|
191
205
|
*/
|
|
192
206
|
export function mayRunAsBatchFile(
|
|
193
207
|
bin: string,
|