@nimbus-dev/connectors 0.6.0 → 0.7.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.
|
@@ -102,9 +102,17 @@ export const SLACK_TOOL_NAMES = [
|
|
|
102
102
|
"slack_message_post_dm",
|
|
103
103
|
"slack_user_info",
|
|
104
104
|
"slack_chat_post",
|
|
105
|
+
"slack_views_open",
|
|
105
106
|
"slack_socket_open",
|
|
106
107
|
] as const;
|
|
107
108
|
|
|
109
|
+
/** Largest serialised `blocks` array / `view` object Slack accepts from this connector (16 KiB). */
|
|
110
|
+
export const SLACK_PAYLOAD_MAX_BYTES = 16 * 1024;
|
|
111
|
+
|
|
112
|
+
function serialisedBytes(value: unknown): number {
|
|
113
|
+
return new TextEncoder().encode(JSON.stringify(value)).length;
|
|
114
|
+
}
|
|
115
|
+
|
|
108
116
|
export function registerSlackTools(
|
|
109
117
|
server: ConsentServer & { tool: (...args: never) => unknown },
|
|
110
118
|
): void {
|
|
@@ -122,7 +130,7 @@ export function registerSlackTools(
|
|
|
122
130
|
scopeEnv: "NIMBUS_MCP_SLACK_WRITE_SCOPE",
|
|
123
131
|
// `user` scopes a direct message by the recipient's user id, which is all a DM has before
|
|
124
132
|
// `conversations.open` creates its channel.
|
|
125
|
-
scopeKinds: ["channel", "user"],
|
|
133
|
+
scopeKinds: ["channel", "user", "view"],
|
|
126
134
|
});
|
|
127
135
|
|
|
128
136
|
const slackChannelListSchema = z.object({
|
|
@@ -341,7 +349,19 @@ export function registerSlackTools(
|
|
|
341
349
|
),
|
|
342
350
|
);
|
|
343
351
|
|
|
344
|
-
const slackChatPostSchema = z.object({
|
|
352
|
+
const slackChatPostSchema = z.object({
|
|
353
|
+
channel: z.string().min(1),
|
|
354
|
+
text: z.string().min(1),
|
|
355
|
+
// Block Kit blocks, sent to Slack verbatim beside `text` (which stays the notification fallback).
|
|
356
|
+
blocks: z
|
|
357
|
+
.array(z.looseObject({ type: z.string() }))
|
|
358
|
+
.min(1)
|
|
359
|
+
.max(50)
|
|
360
|
+
.refine((b) => serialisedBytes(b) <= SLACK_PAYLOAD_MAX_BYTES, {
|
|
361
|
+
message: `blocks must serialise to at most ${SLACK_PAYLOAD_MAX_BYTES} bytes`,
|
|
362
|
+
})
|
|
363
|
+
.optional(),
|
|
364
|
+
});
|
|
345
365
|
registerWriteTool(
|
|
346
366
|
"slack_chat_post",
|
|
347
367
|
{
|
|
@@ -351,12 +371,43 @@ export function registerSlackTools(
|
|
|
351
371
|
},
|
|
352
372
|
"Post an operational bot message to a channel (ChatOps reply surface; bot token).",
|
|
353
373
|
slackChatPostSchema,
|
|
354
|
-
async (parsed) =>
|
|
355
|
-
|
|
374
|
+
async (parsed) => {
|
|
375
|
+
const body: Record<string, unknown> = { channel: parsed.channel, text: parsed.text };
|
|
376
|
+
if (parsed.blocks !== undefined) {
|
|
377
|
+
body["blocks"] = parsed.blocks;
|
|
378
|
+
}
|
|
379
|
+
return slackInvokeJsonWithToken(
|
|
356
380
|
requireProcessEnv("SLACK_BOT_TOKEN"),
|
|
357
381
|
"chat.postMessage",
|
|
358
|
-
|
|
382
|
+
body,
|
|
359
383
|
"Slack chat.postMessage",
|
|
384
|
+
);
|
|
385
|
+
},
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const slackViewsOpenSchema = z.object({
|
|
389
|
+
trigger_id: z.string().min(1),
|
|
390
|
+
view: z
|
|
391
|
+
.looseObject({ type: z.literal("modal") })
|
|
392
|
+
.refine((v) => serialisedBytes(v) <= SLACK_PAYLOAD_MAX_BYTES, {
|
|
393
|
+
message: `view must serialise to at most ${SLACK_PAYLOAD_MAX_BYTES} bytes`,
|
|
394
|
+
}),
|
|
395
|
+
});
|
|
396
|
+
registerWriteTool(
|
|
397
|
+
"slack_views_open",
|
|
398
|
+
{
|
|
399
|
+
mutates: "slack.views.open",
|
|
400
|
+
recoverable: true,
|
|
401
|
+
scopeTargetOf: () => ({ kind: "view", value: "modal" }),
|
|
402
|
+
},
|
|
403
|
+
"Open a confirmation modal for a user who clicked a ChatOps card button (ChatOps operational surface; bot token).",
|
|
404
|
+
slackViewsOpenSchema,
|
|
405
|
+
async (parsed) =>
|
|
406
|
+
slackInvokeJsonWithToken(
|
|
407
|
+
requireProcessEnv("SLACK_BOT_TOKEN"),
|
|
408
|
+
"views.open",
|
|
409
|
+
{ trigger_id: parsed.trigger_id, view: parsed.view },
|
|
410
|
+
"Slack views.open",
|
|
360
411
|
),
|
|
361
412
|
);
|
|
362
413
|
|
|
@@ -55,10 +55,14 @@ function serviceUrlBase(): string {
|
|
|
55
55
|
return base.endsWith("/") ? base : `${base}/`;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Send a message activity to a Teams conversation via the Bot Framework. `attachments` (an Adaptive
|
|
60
|
+
* Card) goes into the activity body only when given.
|
|
61
|
+
*/
|
|
59
62
|
export async function teamsBotSendActivity(
|
|
60
63
|
conversationId: string,
|
|
61
64
|
text: string,
|
|
65
|
+
attachments?: readonly unknown[],
|
|
62
66
|
): Promise<GraphLikeResult> {
|
|
63
67
|
const appId = requireProcessEnv("TEAMS_BOT_APP_ID");
|
|
64
68
|
const appPassword = requireProcessEnv("TEAMS_BOT_APP_PASSWORD");
|
|
@@ -67,6 +71,10 @@ export async function teamsBotSendActivity(
|
|
|
67
71
|
return fetchBearerAuthorizedJson(url, token, {
|
|
68
72
|
method: "POST",
|
|
69
73
|
headers: { "Content-Type": "application/json" },
|
|
70
|
-
body: JSON.stringify(
|
|
74
|
+
body: JSON.stringify(
|
|
75
|
+
attachments === undefined
|
|
76
|
+
? { type: "message", text }
|
|
77
|
+
: { type: "message", text, attachments },
|
|
78
|
+
),
|
|
71
79
|
});
|
|
72
80
|
}
|
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
} from "../../../shared/mcp-tool-kit.ts";
|
|
15
15
|
import { teamsBotSendActivity } from "./bot-send.ts";
|
|
16
16
|
|
|
17
|
+
/** Largest serialised `attachments` array `teams_chat_post` accepts (28 KiB). */
|
|
18
|
+
export const TEAMS_ATTACHMENTS_MAX_BYTES = 28 * 1024;
|
|
19
|
+
|
|
17
20
|
const GRAPH = "https://graph.microsoft.com/v1.0";
|
|
18
21
|
|
|
19
22
|
async function graphRequest(
|
|
@@ -265,6 +268,20 @@ export function registerTeamsTools(
|
|
|
265
268
|
const teamsChatPostSchema = z.object({
|
|
266
269
|
conversationId: z.string().min(1),
|
|
267
270
|
text: z.string().min(1),
|
|
271
|
+
// Exactly one Adaptive Card, sent in the activity verbatim; at most 28 KiB serialised.
|
|
272
|
+
attachments: z
|
|
273
|
+
.array(
|
|
274
|
+
z.object({
|
|
275
|
+
contentType: z.literal("application/vnd.microsoft.card.adaptive"),
|
|
276
|
+
content: z.record(z.string(), z.unknown()),
|
|
277
|
+
}),
|
|
278
|
+
)
|
|
279
|
+
.length(1)
|
|
280
|
+
.refine(
|
|
281
|
+
(a) => new TextEncoder().encode(JSON.stringify(a)).length <= TEAMS_ATTACHMENTS_MAX_BYTES,
|
|
282
|
+
{ message: `attachments must serialise to at most ${TEAMS_ATTACHMENTS_MAX_BYTES} bytes` },
|
|
283
|
+
)
|
|
284
|
+
.optional(),
|
|
268
285
|
});
|
|
269
286
|
registerWriteTool(
|
|
270
287
|
"teams_chat_post",
|
|
@@ -276,7 +293,7 @@ export function registerTeamsTools(
|
|
|
276
293
|
"Post an operational bot message to a Teams conversation (ChatOps reply surface; bot app creds).",
|
|
277
294
|
teamsChatPostSchema,
|
|
278
295
|
async (parsed) => {
|
|
279
|
-
const r = await teamsBotSendActivity(parsed.conversationId, parsed.text);
|
|
296
|
+
const r = await teamsBotSendActivity(parsed.conversationId, parsed.text, parsed.attachments);
|
|
280
297
|
return graphListResult(r);
|
|
281
298
|
},
|
|
282
299
|
);
|