roboport 0.2.0 → 0.4.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Timur Badretdinov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,12 @@
1
+ import { Mcp as McpBase } from '../core';
2
+ type Options = {
3
+ token: string;
4
+ name?: string;
5
+ toolsets?: string[];
6
+ readOnly?: boolean;
7
+ deferred?: boolean;
8
+ };
9
+ declare class Mcp extends McpBase {
10
+ constructor(opts: Options);
11
+ }
12
+ export default Mcp;
@@ -0,0 +1,23 @@
1
+ import { Tool, type McpClient } from '../../core';
2
+ type Options = {
3
+ /**
4
+ * Slack bot token (`xoxb-…`). Requires these OAuth scopes:
5
+ * `chat:write` (post_message, reply_in_thread),
6
+ * `channels:read` (list_channels),
7
+ * `channels:history` (get_channel_history),
8
+ * `users:read` (list_users, get_user),
9
+ * `reactions:write` (add_reaction).
10
+ */
11
+ botToken: string;
12
+ name?: string;
13
+ deferred?: boolean;
14
+ };
15
+ declare class Mcp implements McpClient {
16
+ private auth;
17
+ private nameSpace;
18
+ private deferred;
19
+ constructor(opts: Options);
20
+ connect(): Promise<Tool[]>;
21
+ disconnect(): Promise<void>;
22
+ }
23
+ export default Mcp;
package/mcp/core.d.ts CHANGED
@@ -13,6 +13,7 @@ type HttpTransportConfig = {
13
13
  auth?: AuthProvider;
14
14
  };
15
15
  type McpTransportConfig = StdioTransportConfig | HttpTransportConfig;
16
+ declare function validateMcpName(name: string): void;
16
17
  declare class Mcp implements McpClient {
17
18
  name: string;
18
19
  transport: McpTransportConfig;
@@ -27,4 +28,4 @@ declare class Mcp implements McpClient {
27
28
  disconnect(): Promise<void>;
28
29
  private wrap;
29
30
  }
30
- export { Mcp, type McpTransportConfig, type StdioTransportConfig, type HttpTransportConfig, };
31
+ export { Mcp, validateMcpName, type McpTransportConfig, type StdioTransportConfig, type HttpTransportConfig, };
package/mcp/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { BearerAuth, OAuthAuth, type AuthProvider, type OAuthAuthOptions } from './auth';
2
+ import Github from './clients/github';
2
3
  import Grafana from './clients/grafana';
3
4
  import Linear from './clients/linear';
5
+ import Slack from './clients/slack';
4
6
  import Tenderly from './clients/tenderly';
5
7
  import { Mcp, type HttpTransportConfig, type McpTransportConfig, type StdioTransportConfig } from './core';
6
8
  import { FileStorage, MemoryStorage, type OAuthStorage, type TokenSet } from './storage';
7
- export { BearerAuth, FileStorage, Grafana, Linear, Mcp, MemoryStorage, OAuthAuth, Tenderly, type AuthProvider, type HttpTransportConfig, type McpTransportConfig, type OAuthAuthOptions, type OAuthStorage, type StdioTransportConfig, type TokenSet, };
9
+ export { BearerAuth, FileStorage, Github, Grafana, Linear, Mcp, MemoryStorage, OAuthAuth, Slack, Tenderly, type AuthProvider, type HttpTransportConfig, type McpTransportConfig, type OAuthAuthOptions, type OAuthStorage, type StdioTransportConfig, type TokenSet, };
package/mcp/index.js CHANGED
@@ -884,179 +884,6 @@ class Skill {
884
884
  }
885
885
  }
886
886
 
887
- // src/mcp/clients/grafana.ts
888
- var EMPTY_OBJECT = {
889
- type: "object",
890
- properties: {},
891
- additionalProperties: false
892
- };
893
- var TOOLS = [
894
- {
895
- name: "list_datasources",
896
- description: "List all configured Grafana datasources.",
897
- inputSchema: EMPTY_OBJECT,
898
- call: (_, ctx) => request(ctx, "GET", "/api/datasources")
899
- },
900
- {
901
- name: "get_datasource",
902
- description: "Fetch a single datasource by UID.",
903
- inputSchema: {
904
- type: "object",
905
- properties: {
906
- uid: { type: "string", description: "Datasource UID." }
907
- },
908
- required: ["uid"],
909
- additionalProperties: false
910
- },
911
- call: (args, ctx) => request(ctx, "GET", `/api/datasources/uid/${encodeURIComponent(String(args.uid))}`)
912
- },
913
- {
914
- name: "query",
915
- description: "Run one or more queries against Grafana datasources via /api/ds/query. Pass the query array as Grafana expects (each item needs refId and datasource.uid).",
916
- inputSchema: {
917
- type: "object",
918
- properties: {
919
- queries: {
920
- type: "array",
921
- description: "Array of query objects (refId, datasource, expr/range/etc.).",
922
- items: { type: "object" }
923
- },
924
- from: {
925
- type: "string",
926
- description: 'Range start, e.g. "now-1h" or epoch ms as string.'
927
- },
928
- to: {
929
- type: "string",
930
- description: 'Range end, e.g. "now" or epoch ms as string.'
931
- }
932
- },
933
- required: ["queries"],
934
- additionalProperties: false
935
- },
936
- call: (args, ctx) => request(ctx, "POST", "/api/ds/query", {
937
- queries: args.queries,
938
- from: args.from ?? "now-1h",
939
- to: args.to ?? "now"
940
- })
941
- },
942
- {
943
- name: "search_dashboards",
944
- description: "Search dashboards by name, tag, or folder.",
945
- inputSchema: {
946
- type: "object",
947
- properties: {
948
- query: {
949
- type: "string",
950
- description: "Substring match on dashboard title."
951
- },
952
- tag: {
953
- type: "array",
954
- items: { type: "string" },
955
- description: "Filter by tags."
956
- },
957
- folderUIDs: {
958
- type: "array",
959
- items: { type: "string" },
960
- description: "Limit to specific folder UIDs."
961
- },
962
- limit: { type: "number", description: "Max results (default 100)." }
963
- },
964
- additionalProperties: false
965
- },
966
- call: (args, ctx) => {
967
- const params = new URLSearchParams;
968
- params.set("type", "dash-db");
969
- if (typeof args.query === "string")
970
- params.set("query", args.query);
971
- if (typeof args.limit === "number")
972
- params.set("limit", String(args.limit));
973
- for (const tag of args.tag ?? [])
974
- params.append("tag", tag);
975
- for (const uid of args.folderUIDs ?? [])
976
- params.append("folderUIDs", uid);
977
- return request(ctx, "GET", `/api/search?${params.toString()}`);
978
- }
979
- },
980
- {
981
- name: "get_dashboard",
982
- description: "Fetch a dashboard JSON by UID.",
983
- inputSchema: {
984
- type: "object",
985
- properties: {
986
- uid: { type: "string", description: "Dashboard UID." }
987
- },
988
- required: ["uid"],
989
- additionalProperties: false
990
- },
991
- call: (args, ctx) => request(ctx, "GET", `/api/dashboards/uid/${encodeURIComponent(String(args.uid))}`)
992
- },
993
- {
994
- name: "list_folders",
995
- description: "List dashboard folders.",
996
- inputSchema: EMPTY_OBJECT,
997
- call: (_, ctx) => request(ctx, "GET", "/api/folders")
998
- },
999
- {
1000
- name: "list_alert_rules",
1001
- description: "List provisioned alert rules.",
1002
- inputSchema: EMPTY_OBJECT,
1003
- call: (_, ctx) => request(ctx, "GET", "/api/v1/provisioning/alert-rules")
1004
- }
1005
- ];
1006
- async function request(ctx, method, path, body) {
1007
- const headers = {
1008
- accept: "application/json",
1009
- authorization: await ctx.auth.getHeader()
1010
- };
1011
- if (body !== undefined)
1012
- headers["content-type"] = "application/json";
1013
- const res = await fetch(`${ctx.baseUrl}${path}`, {
1014
- method,
1015
- headers,
1016
- body: body !== undefined ? JSON.stringify(body) : undefined
1017
- });
1018
- const text = await res.text();
1019
- if (!res.ok) {
1020
- throw new Error(`Grafana ${method} ${path} failed: ${res.status} ${text}`);
1021
- }
1022
- if (!text)
1023
- return null;
1024
- try {
1025
- return JSON.parse(text);
1026
- } catch {
1027
- return text;
1028
- }
1029
- }
1030
-
1031
- class Mcp {
1032
- baseUrl;
1033
- auth;
1034
- nameSpace;
1035
- deferred;
1036
- constructor(opts) {
1037
- this.baseUrl = opts.url.replace(/\/$/, "");
1038
- this.auth = new BearerAuth(opts.serviceAccountToken);
1039
- this.nameSpace = opts.name ?? "grafana";
1040
- this.deferred = opts.deferred ?? true;
1041
- }
1042
- async connect() {
1043
- const ctx = { baseUrl: this.baseUrl, auth: this.auth };
1044
- return TOOLS.map((def) => new Tool({
1045
- name: `mcp__${this.nameSpace}__${def.name}`,
1046
- description: def.description,
1047
- jsonSchema: def.inputSchema,
1048
- deferred: this.deferred,
1049
- execute: async (input) => {
1050
- const args = input ?? {};
1051
- const result = await def.call(args, ctx);
1052
- return typeof result === "string" ? result : JSON.stringify(result);
1053
- }
1054
- }));
1055
- }
1056
- async disconnect() {}
1057
- }
1058
- var grafana_default = Mcp;
1059
-
1060
887
  // src/mcp/core.ts
1061
888
  var PROTOCOL_VERSION = "2024-11-05";
1062
889
 
@@ -1256,6 +1083,11 @@ class HttpTransport {
1256
1083
  await this.send(req, undefined);
1257
1084
  }
1258
1085
  }
1086
+ function validateMcpName(name) {
1087
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
1088
+ throw new Error(`Invalid MCP name "${name}": use only letters, digits, underscores, and hyphens.`);
1089
+ }
1090
+ }
1259
1091
  function formatContent(content) {
1260
1092
  if (content.length === 1 && content[0]?.type === "text") {
1261
1093
  return content[0].text ?? "";
@@ -1264,7 +1096,7 @@ function formatContent(content) {
1264
1096
  `);
1265
1097
  }
1266
1098
 
1267
- class Mcp2 {
1099
+ class Mcp {
1268
1100
  name;
1269
1101
  transport;
1270
1102
  deferred;
@@ -1274,9 +1106,7 @@ class Mcp2 {
1274
1106
  transport,
1275
1107
  deferred
1276
1108
  }) {
1277
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
1278
- throw new Error(`Invalid MCP name "${name}": use only letters, digits, underscores, and hyphens.`);
1279
- }
1109
+ validateMcpName(name);
1280
1110
  this.name = name;
1281
1111
  this.transport = transport;
1282
1112
  this.deferred = deferred ?? true;
@@ -1324,10 +1154,210 @@ class Mcp2 {
1324
1154
  }
1325
1155
  }
1326
1156
 
1157
+ // src/mcp/clients/github.ts
1158
+ var GITHUB_MCP_URL = "https://api.githubcopilot.com/mcp/";
1159
+
1160
+ class Mcp2 extends Mcp {
1161
+ constructor(opts) {
1162
+ const headers = {};
1163
+ if (opts.toolsets?.length) {
1164
+ headers["X-MCP-Toolsets"] = opts.toolsets.join(",");
1165
+ }
1166
+ if (opts.readOnly) {
1167
+ headers["X-MCP-Readonly"] = "true";
1168
+ }
1169
+ super({
1170
+ name: opts.name ?? "github",
1171
+ deferred: opts.deferred,
1172
+ transport: {
1173
+ type: "http",
1174
+ url: GITHUB_MCP_URL,
1175
+ headers,
1176
+ auth: new BearerAuth(opts.token)
1177
+ }
1178
+ });
1179
+ }
1180
+ }
1181
+ var github_default = Mcp2;
1182
+
1183
+ // src/mcp/clients/grafana.ts
1184
+ var EMPTY_OBJECT = {
1185
+ type: "object",
1186
+ properties: {},
1187
+ additionalProperties: false
1188
+ };
1189
+ var TOOLS = [
1190
+ {
1191
+ name: "list_datasources",
1192
+ description: "List all configured Grafana datasources.",
1193
+ inputSchema: EMPTY_OBJECT,
1194
+ call: (_, ctx) => request(ctx, "GET", "/api/datasources")
1195
+ },
1196
+ {
1197
+ name: "get_datasource",
1198
+ description: "Fetch a single datasource by UID.",
1199
+ inputSchema: {
1200
+ type: "object",
1201
+ properties: {
1202
+ uid: { type: "string", description: "Datasource UID." }
1203
+ },
1204
+ required: ["uid"],
1205
+ additionalProperties: false
1206
+ },
1207
+ call: (args, ctx) => request(ctx, "GET", `/api/datasources/uid/${encodeURIComponent(String(args.uid))}`)
1208
+ },
1209
+ {
1210
+ name: "query",
1211
+ description: "Run one or more queries against Grafana datasources via /api/ds/query. Pass the query array as Grafana expects (each item needs refId and datasource.uid).",
1212
+ inputSchema: {
1213
+ type: "object",
1214
+ properties: {
1215
+ queries: {
1216
+ type: "array",
1217
+ description: "Array of query objects (refId, datasource, expr/range/etc.).",
1218
+ items: { type: "object" }
1219
+ },
1220
+ from: {
1221
+ type: "string",
1222
+ description: 'Range start, e.g. "now-1h" or epoch ms as string.'
1223
+ },
1224
+ to: {
1225
+ type: "string",
1226
+ description: 'Range end, e.g. "now" or epoch ms as string.'
1227
+ }
1228
+ },
1229
+ required: ["queries"],
1230
+ additionalProperties: false
1231
+ },
1232
+ call: (args, ctx) => request(ctx, "POST", "/api/ds/query", {
1233
+ queries: args.queries,
1234
+ from: args.from ?? "now-1h",
1235
+ to: args.to ?? "now"
1236
+ })
1237
+ },
1238
+ {
1239
+ name: "search_dashboards",
1240
+ description: "Search dashboards by name, tag, or folder.",
1241
+ inputSchema: {
1242
+ type: "object",
1243
+ properties: {
1244
+ query: {
1245
+ type: "string",
1246
+ description: "Substring match on dashboard title."
1247
+ },
1248
+ tag: {
1249
+ type: "array",
1250
+ items: { type: "string" },
1251
+ description: "Filter by tags."
1252
+ },
1253
+ folderUIDs: {
1254
+ type: "array",
1255
+ items: { type: "string" },
1256
+ description: "Limit to specific folder UIDs."
1257
+ },
1258
+ limit: { type: "number", description: "Max results (default 100)." }
1259
+ },
1260
+ additionalProperties: false
1261
+ },
1262
+ call: (args, ctx) => {
1263
+ const params = new URLSearchParams;
1264
+ params.set("type", "dash-db");
1265
+ if (typeof args.query === "string")
1266
+ params.set("query", args.query);
1267
+ if (typeof args.limit === "number")
1268
+ params.set("limit", String(args.limit));
1269
+ for (const tag of args.tag ?? [])
1270
+ params.append("tag", tag);
1271
+ for (const uid of args.folderUIDs ?? [])
1272
+ params.append("folderUIDs", uid);
1273
+ return request(ctx, "GET", `/api/search?${params.toString()}`);
1274
+ }
1275
+ },
1276
+ {
1277
+ name: "get_dashboard",
1278
+ description: "Fetch a dashboard JSON by UID.",
1279
+ inputSchema: {
1280
+ type: "object",
1281
+ properties: {
1282
+ uid: { type: "string", description: "Dashboard UID." }
1283
+ },
1284
+ required: ["uid"],
1285
+ additionalProperties: false
1286
+ },
1287
+ call: (args, ctx) => request(ctx, "GET", `/api/dashboards/uid/${encodeURIComponent(String(args.uid))}`)
1288
+ },
1289
+ {
1290
+ name: "list_folders",
1291
+ description: "List dashboard folders.",
1292
+ inputSchema: EMPTY_OBJECT,
1293
+ call: (_, ctx) => request(ctx, "GET", "/api/folders")
1294
+ },
1295
+ {
1296
+ name: "list_alert_rules",
1297
+ description: "List provisioned alert rules.",
1298
+ inputSchema: EMPTY_OBJECT,
1299
+ call: (_, ctx) => request(ctx, "GET", "/api/v1/provisioning/alert-rules")
1300
+ }
1301
+ ];
1302
+ async function request(ctx, method, path, body) {
1303
+ const headers = {
1304
+ accept: "application/json",
1305
+ authorization: await ctx.auth.getHeader()
1306
+ };
1307
+ if (body !== undefined)
1308
+ headers["content-type"] = "application/json";
1309
+ const res = await fetch(`${ctx.baseUrl}${path}`, {
1310
+ method,
1311
+ headers,
1312
+ body: body !== undefined ? JSON.stringify(body) : undefined
1313
+ });
1314
+ const text = await res.text();
1315
+ if (!res.ok) {
1316
+ throw new Error(`Grafana ${method} ${path} failed: ${res.status} ${text}`);
1317
+ }
1318
+ if (!text)
1319
+ return null;
1320
+ try {
1321
+ return JSON.parse(text);
1322
+ } catch {
1323
+ return text;
1324
+ }
1325
+ }
1326
+
1327
+ class Mcp3 {
1328
+ baseUrl;
1329
+ auth;
1330
+ nameSpace;
1331
+ deferred;
1332
+ constructor(opts) {
1333
+ this.baseUrl = opts.url.replace(/\/$/, "");
1334
+ this.auth = new BearerAuth(opts.serviceAccountToken);
1335
+ this.nameSpace = opts.name ?? "grafana";
1336
+ validateMcpName(this.nameSpace);
1337
+ this.deferred = opts.deferred ?? true;
1338
+ }
1339
+ async connect() {
1340
+ const ctx = { baseUrl: this.baseUrl, auth: this.auth };
1341
+ return TOOLS.map((def) => new Tool({
1342
+ name: `mcp__${this.nameSpace}__${def.name}`,
1343
+ description: def.description,
1344
+ jsonSchema: def.inputSchema,
1345
+ deferred: this.deferred,
1346
+ execute: async (input) => {
1347
+ const args = input ?? {};
1348
+ const result = await def.call(args, ctx);
1349
+ return typeof result === "string" ? result : JSON.stringify(result);
1350
+ }
1351
+ }));
1352
+ }
1353
+ async disconnect() {}
1354
+ }
1355
+ var grafana_default = Mcp3;
1356
+
1327
1357
  // src/mcp/clients/linear.ts
1328
1358
  var LINEAR_MCP_URL = "https://mcp.linear.app/mcp";
1329
1359
 
1330
- class Mcp3 extends Mcp2 {
1360
+ class Mcp4 extends Mcp {
1331
1361
  constructor(opts) {
1332
1362
  super({
1333
1363
  name: opts.name ?? "linear",
@@ -1339,12 +1369,217 @@ class Mcp3 extends Mcp2 {
1339
1369
  });
1340
1370
  }
1341
1371
  }
1342
- var linear_default = Mcp3;
1372
+ var linear_default = Mcp4;
1373
+
1374
+ // src/mcp/clients/slack.ts
1375
+ var SLACK_API_URL = "https://slack.com/api";
1376
+ var TOOLS2 = [
1377
+ {
1378
+ name: "post_message",
1379
+ description: "Post a message to a Slack channel.",
1380
+ inputSchema: {
1381
+ type: "object",
1382
+ properties: {
1383
+ channel: {
1384
+ type: "string",
1385
+ description: "Channel ID (e.g. C012AB3CD) or name (e.g. #general)."
1386
+ },
1387
+ text: { type: "string", description: "Message text." }
1388
+ },
1389
+ required: ["channel", "text"],
1390
+ additionalProperties: false
1391
+ },
1392
+ call: (args, ctx) => request2(ctx, "chat.postMessage", {
1393
+ channel: args.channel,
1394
+ text: args.text
1395
+ })
1396
+ },
1397
+ {
1398
+ name: "reply_in_thread",
1399
+ description: "Reply to an existing message thread in a Slack channel.",
1400
+ inputSchema: {
1401
+ type: "object",
1402
+ properties: {
1403
+ channel: { type: "string", description: "Channel ID or name." },
1404
+ thread_ts: {
1405
+ type: "string",
1406
+ description: "Timestamp (ts) of the parent message to reply to."
1407
+ },
1408
+ text: { type: "string", description: "Reply text." }
1409
+ },
1410
+ required: ["channel", "thread_ts", "text"],
1411
+ additionalProperties: false
1412
+ },
1413
+ call: (args, ctx) => request2(ctx, "chat.postMessage", {
1414
+ channel: args.channel,
1415
+ thread_ts: args.thread_ts,
1416
+ text: args.text
1417
+ })
1418
+ },
1419
+ {
1420
+ name: "list_channels",
1421
+ description: "List channels in the workspace.",
1422
+ inputSchema: {
1423
+ type: "object",
1424
+ properties: {
1425
+ types: {
1426
+ type: "string",
1427
+ description: 'Comma-separated channel types (default "public_channel").'
1428
+ },
1429
+ limit: { type: "number", description: "Max results (default 100)." },
1430
+ cursor: { type: "string", description: "Pagination cursor." },
1431
+ exclude_archived: {
1432
+ type: "boolean",
1433
+ description: "Omit archived channels (default true)."
1434
+ }
1435
+ },
1436
+ additionalProperties: false
1437
+ },
1438
+ call: (args, ctx) => request2(ctx, "conversations.list", {
1439
+ types: args.types ?? "public_channel",
1440
+ limit: args.limit ?? 100,
1441
+ cursor: args.cursor,
1442
+ exclude_archived: args.exclude_archived ?? true
1443
+ })
1444
+ },
1445
+ {
1446
+ name: "get_channel_history",
1447
+ description: "Fetch recent messages from a channel.",
1448
+ inputSchema: {
1449
+ type: "object",
1450
+ properties: {
1451
+ channel: { type: "string", description: "Channel ID." },
1452
+ limit: { type: "number", description: "Max messages (default 100)." },
1453
+ cursor: { type: "string", description: "Pagination cursor." },
1454
+ oldest: {
1455
+ type: "string",
1456
+ description: "Only messages after this ts."
1457
+ },
1458
+ latest: {
1459
+ type: "string",
1460
+ description: "Only messages before this ts."
1461
+ }
1462
+ },
1463
+ required: ["channel"],
1464
+ additionalProperties: false
1465
+ },
1466
+ call: (args, ctx) => request2(ctx, "conversations.history", {
1467
+ channel: args.channel,
1468
+ limit: args.limit ?? 100,
1469
+ cursor: args.cursor,
1470
+ oldest: args.oldest,
1471
+ latest: args.latest
1472
+ })
1473
+ },
1474
+ {
1475
+ name: "list_users",
1476
+ description: "List users in the workspace.",
1477
+ inputSchema: {
1478
+ type: "object",
1479
+ properties: {
1480
+ limit: { type: "number", description: "Max results (default 100)." },
1481
+ cursor: { type: "string", description: "Pagination cursor." }
1482
+ },
1483
+ additionalProperties: false
1484
+ },
1485
+ call: (args, ctx) => request2(ctx, "users.list", {
1486
+ limit: args.limit ?? 100,
1487
+ cursor: args.cursor
1488
+ })
1489
+ },
1490
+ {
1491
+ name: "get_user",
1492
+ description: "Fetch a single user by ID.",
1493
+ inputSchema: {
1494
+ type: "object",
1495
+ properties: {
1496
+ user: { type: "string", description: "User ID (e.g. U012AB3CD)." }
1497
+ },
1498
+ required: ["user"],
1499
+ additionalProperties: false
1500
+ },
1501
+ call: (args, ctx) => request2(ctx, "users.info", { user: args.user })
1502
+ },
1503
+ {
1504
+ name: "add_reaction",
1505
+ description: "Add an emoji reaction to a message.",
1506
+ inputSchema: {
1507
+ type: "object",
1508
+ properties: {
1509
+ channel: { type: "string", description: "Channel ID." },
1510
+ timestamp: {
1511
+ type: "string",
1512
+ description: "Timestamp (ts) of the target message."
1513
+ },
1514
+ name: {
1515
+ type: "string",
1516
+ description: 'Emoji name without colons (e.g. "thumbsup").'
1517
+ }
1518
+ },
1519
+ required: ["channel", "timestamp", "name"],
1520
+ additionalProperties: false
1521
+ },
1522
+ call: (args, ctx) => request2(ctx, "reactions.add", {
1523
+ channel: args.channel,
1524
+ timestamp: args.timestamp,
1525
+ name: args.name
1526
+ })
1527
+ }
1528
+ ];
1529
+ async function request2(ctx, method, params) {
1530
+ const body = new URLSearchParams;
1531
+ for (const [key, value] of Object.entries(params)) {
1532
+ if (value === undefined || value === null)
1533
+ continue;
1534
+ body.set(key, String(value));
1535
+ }
1536
+ const res = await fetch(`${SLACK_API_URL}/${method}`, {
1537
+ method: "POST",
1538
+ headers: {
1539
+ authorization: await ctx.auth.getHeader(),
1540
+ "content-type": "application/x-www-form-urlencoded"
1541
+ },
1542
+ body
1543
+ });
1544
+ const json = await res.json();
1545
+ if (!json.ok) {
1546
+ throw new Error(`Slack ${method} failed: ${json.error ?? res.status}`);
1547
+ }
1548
+ return json;
1549
+ }
1550
+
1551
+ class Mcp5 {
1552
+ auth;
1553
+ nameSpace;
1554
+ deferred;
1555
+ constructor(opts) {
1556
+ this.auth = new BearerAuth(opts.botToken);
1557
+ this.nameSpace = opts.name ?? "slack";
1558
+ validateMcpName(this.nameSpace);
1559
+ this.deferred = opts.deferred ?? true;
1560
+ }
1561
+ async connect() {
1562
+ const ctx = { auth: this.auth };
1563
+ return TOOLS2.map((def) => new Tool({
1564
+ name: `mcp__${this.nameSpace}__${def.name}`,
1565
+ description: def.description,
1566
+ jsonSchema: def.inputSchema,
1567
+ deferred: this.deferred,
1568
+ execute: async (input) => {
1569
+ const args = input ?? {};
1570
+ const result = await def.call(args, ctx);
1571
+ return typeof result === "string" ? result : JSON.stringify(result);
1572
+ }
1573
+ }));
1574
+ }
1575
+ async disconnect() {}
1576
+ }
1577
+ var slack_default = Mcp5;
1343
1578
 
1344
1579
  // src/mcp/clients/tenderly.ts
1345
1580
  var TENDERLY_MCP_URL = "https://mcp.tenderly.co/mcp";
1346
1581
 
1347
- class Mcp4 extends Mcp2 {
1582
+ class Mcp6 extends Mcp {
1348
1583
  constructor(opts) {
1349
1584
  const auth = new OAuthAuth({
1350
1585
  serverUrl: TENDERLY_MCP_URL,
@@ -1362,14 +1597,16 @@ class Mcp4 extends Mcp2 {
1362
1597
  });
1363
1598
  }
1364
1599
  }
1365
- var tenderly_default = Mcp4;
1600
+ var tenderly_default = Mcp6;
1366
1601
  export {
1367
1602
  tenderly_default as Tenderly,
1603
+ slack_default as Slack,
1368
1604
  OAuthAuth,
1369
1605
  MemoryStorage,
1370
- Mcp2 as Mcp,
1606
+ Mcp,
1371
1607
  linear_default as Linear,
1372
1608
  grafana_default as Grafana,
1609
+ github_default as Github,
1373
1610
  FileStorage,
1374
1611
  BearerAuth
1375
1612
  };
@@ -1,7 +1,7 @@
1
1
  import { Model, type CreateMessageParams, type LiteralUnion, type ModelStreamEvent, type SearchHit, type SearchOptions, type ThinkingLevel } from '../core';
2
2
  declare const ANTHROPIC_MODELS: readonly ["claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-4-6", "claude-sonnet-4-5", "claude-haiku-4-5"];
3
3
  type AnthropicModelName = LiteralUnion<(typeof ANTHROPIC_MODELS)[number]>;
4
- declare class AnthropicModel extends Model {
4
+ declare class Anthropic extends Model {
5
5
  modelName: string;
6
6
  apiKey: string;
7
7
  thinking: ThinkingLevel;
@@ -12,4 +12,4 @@ declare class AnthropicModel extends Model {
12
12
  streamMessage(params: CreateMessageParams): AsyncIterable<ModelStreamEvent>;
13
13
  searchWeb(query: string, opts?: SearchOptions): Promise<SearchHit[]>;
14
14
  }
15
- export { AnthropicModel, ANTHROPIC_MODELS };
15
+ export { Anthropic, ANTHROPIC_MODELS };
@@ -1,8 +1,8 @@
1
1
  import type { LiteralUnion, SearchHit, SearchOptions, ThinkingLevel } from '../core';
2
- import { OpenAICompatibleModel } from './openai-compatible';
2
+ import { OpenAICompatible } from './openai-compatible';
3
3
  declare const GEMINI_MODELS: readonly ["gemini-3.5-flash", "gemini-3.1-pro", "gemini-3.1-flash-lite", "gemini-3-flash"];
4
4
  type GeminiModelName = LiteralUnion<(typeof GEMINI_MODELS)[number]>;
5
- declare class GeminiModel extends OpenAICompatibleModel {
5
+ declare class Gemini extends OpenAICompatible {
6
6
  constructor(modelName: GeminiModelName, options?: {
7
7
  apiKey?: string;
8
8
  baseUrl?: string;
@@ -11,4 +11,4 @@ declare class GeminiModel extends OpenAICompatibleModel {
11
11
  protected applyThinking(body: Record<string, unknown>): void;
12
12
  searchWeb(query: string, opts?: SearchOptions): Promise<SearchHit[]>;
13
13
  }
14
- export { GeminiModel, GEMINI_MODELS };
14
+ export { Gemini, GEMINI_MODELS };
package/models/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { ANTHROPIC_MODELS, AnthropicModel } from './anthropic';
2
- import { GEMINI_MODELS, GeminiModel } from './google';
3
- import { MOONSHOT_MODELS, MoonshotModel } from './moonshot';
4
- import { OPENAI_MODELS, OpenAIModel } from './openai';
5
- import { OpenAICompatibleModel } from './openai-compatible';
6
- export { AnthropicModel, GeminiModel, MoonshotModel, OpenAIModel, OpenAICompatibleModel, ANTHROPIC_MODELS, GEMINI_MODELS, MOONSHOT_MODELS, OPENAI_MODELS, };
1
+ import { ANTHROPIC_MODELS, Anthropic } from './anthropic';
2
+ import { GEMINI_MODELS, Gemini } from './google';
3
+ import { MOONSHOT_MODELS, Moonshot } from './moonshot';
4
+ import { OPENAI_MODELS, OpenAI } from './openai';
5
+ import { OpenAICompatible } from './openai-compatible';
6
+ export { Anthropic, Gemini, Moonshot, OpenAI, OpenAICompatible, ANTHROPIC_MODELS, GEMINI_MODELS, MOONSHOT_MODELS, OPENAI_MODELS, };
package/models/index.js CHANGED
@@ -742,7 +742,7 @@ function toWire(messages) {
742
742
  return { system, wireMessages };
743
743
  }
744
744
 
745
- class AnthropicModel extends Model {
745
+ class Anthropic extends Model {
746
746
  modelName;
747
747
  apiKey;
748
748
  thinking;
@@ -1009,7 +1009,7 @@ function parseToolArguments(raw) {
1009
1009
  }
1010
1010
  }
1011
1011
 
1012
- class OpenAICompatibleModel extends Model {
1012
+ class OpenAICompatible extends Model {
1013
1013
  modelName;
1014
1014
  apiKey;
1015
1015
  baseUrl;
@@ -1220,7 +1220,7 @@ var GEMINI_MODELS = [
1220
1220
  "gemini-3-flash"
1221
1221
  ];
1222
1222
 
1223
- class GeminiModel extends OpenAICompatibleModel {
1223
+ class Gemini extends OpenAICompatible {
1224
1224
  constructor(modelName, options) {
1225
1225
  const key = options?.apiKey ?? env.geminiApiKey;
1226
1226
  if (!key) {
@@ -1276,7 +1276,7 @@ class GeminiModel extends OpenAICompatibleModel {
1276
1276
  // src/models/moonshot.ts
1277
1277
  var MOONSHOT_MODELS = ["kimi-k2.6", "kimi-k2.5"];
1278
1278
 
1279
- class MoonshotModel extends OpenAICompatibleModel {
1279
+ class Moonshot extends OpenAICompatible {
1280
1280
  constructor(modelName, options) {
1281
1281
  const key = options?.apiKey ?? env.moonshotApiKey;
1282
1282
  if (!key) {
@@ -1672,7 +1672,7 @@ function mapResponsesStatus(status) {
1672
1672
  }
1673
1673
  }
1674
1674
 
1675
- class OpenAIModel extends OpenAICompatibleModel {
1675
+ class OpenAI extends OpenAICompatible {
1676
1676
  codexAuth;
1677
1677
  constructor(modelName, options) {
1678
1678
  const auth = options?.auth ?? { type: "apiKey" };
@@ -2036,13 +2036,13 @@ function extractSearchHits(json) {
2036
2036
  return hits;
2037
2037
  }
2038
2038
  export {
2039
- OpenAIModel,
2040
- OpenAICompatibleModel,
2039
+ OpenAICompatible,
2040
+ OpenAI,
2041
2041
  OPENAI_MODELS,
2042
- MoonshotModel,
2042
+ Moonshot,
2043
2043
  MOONSHOT_MODELS,
2044
- GeminiModel,
2044
+ Gemini,
2045
2045
  GEMINI_MODELS,
2046
- AnthropicModel,
2046
+ Anthropic,
2047
2047
  ANTHROPIC_MODELS
2048
2048
  };
@@ -1,8 +1,8 @@
1
1
  import type { LiteralUnion, SearchHit, SearchOptions, ThinkingLevel } from '../core';
2
- import { OpenAICompatibleModel, type OpenAIAssistantWireMessage } from './openai-compatible';
2
+ import { OpenAICompatible, type OpenAIAssistantWireMessage } from './openai-compatible';
3
3
  declare const MOONSHOT_MODELS: readonly ["kimi-k2.6", "kimi-k2.5"];
4
4
  type MoonshotModelName = LiteralUnion<(typeof MOONSHOT_MODELS)[number]>;
5
- declare class MoonshotModel extends OpenAICompatibleModel {
5
+ declare class Moonshot extends OpenAICompatible {
6
6
  constructor(modelName: MoonshotModelName, options?: {
7
7
  apiKey?: string;
8
8
  baseUrl?: string;
@@ -13,4 +13,4 @@ declare class MoonshotModel extends OpenAICompatibleModel {
13
13
  searchWeb(query: string, opts?: SearchOptions): Promise<SearchHit[]>;
14
14
  private chat;
15
15
  }
16
- export { MoonshotModel, MOONSHOT_MODELS };
16
+ export { Moonshot, MOONSHOT_MODELS };
@@ -23,7 +23,7 @@ type OpenAIWireMessage = {
23
23
  tool_call_id: string;
24
24
  content: string;
25
25
  };
26
- declare abstract class OpenAICompatibleModel extends Model {
26
+ declare abstract class OpenAICompatible extends Model {
27
27
  modelName: string;
28
28
  apiKey: string;
29
29
  baseUrl: string;
@@ -38,4 +38,4 @@ declare abstract class OpenAICompatibleModel extends Model {
38
38
  protected adaptAssistantWire(msg: OpenAIAssistantWireMessage): OpenAIAssistantWireMessage;
39
39
  protected applyThinking(body: Record<string, unknown>): void;
40
40
  }
41
- export { OpenAICompatibleModel, type OpenAIAssistantWireMessage };
41
+ export { OpenAICompatible, type OpenAIAssistantWireMessage };
@@ -1,24 +1,24 @@
1
1
  import type { CreateMessageParams, LiteralUnion, ModelStreamEvent, SearchHit, SearchOptions, ThinkingLevel } from '../core';
2
- import { OpenAICompatibleModel } from './openai-compatible';
2
+ import { OpenAICompatible } from './openai-compatible';
3
3
  declare const OPENAI_MODELS: readonly ["gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-5.3-codex"];
4
4
  type OpenAIModelName = LiteralUnion<(typeof OPENAI_MODELS)[number]>;
5
5
  type OpenAIApiKeyAuthOptions = {
6
6
  type: 'apiKey';
7
7
  apiKey?: string;
8
8
  };
9
- type OpenAICodexAuthModelOptions = {
9
+ type OpenAICodexAuthOptions = {
10
10
  type: 'codex';
11
11
  authFile?: string;
12
12
  };
13
- type OpenAIAuthOptions = OpenAIApiKeyAuthOptions | OpenAICodexAuthModelOptions;
14
- type OpenAIModelOptions = {
13
+ type OpenAIAuthOptions = OpenAIApiKeyAuthOptions | OpenAICodexAuthOptions;
14
+ type OpenAIOptions = {
15
15
  auth?: OpenAIAuthOptions;
16
16
  baseUrl?: string;
17
17
  thinking?: ThinkingLevel;
18
18
  };
19
- declare class OpenAIModel extends OpenAICompatibleModel {
19
+ declare class OpenAI extends OpenAICompatible {
20
20
  private codexAuth?;
21
- constructor(modelName: OpenAIModelName, options?: OpenAIModelOptions);
21
+ constructor(modelName: OpenAIModelName, options?: OpenAIOptions);
22
22
  streamMessage(params: CreateMessageParams): AsyncIterable<ModelStreamEvent>;
23
23
  protected applyThinking(body: Record<string, unknown>): void;
24
24
  searchWeb(query: string, opts?: SearchOptions): Promise<SearchHit[]>;
@@ -26,4 +26,4 @@ declare class OpenAIModel extends OpenAICompatibleModel {
26
26
  private fetchResponsesBuffered;
27
27
  private responsesEndpoint;
28
28
  }
29
- export { OpenAIModel, OPENAI_MODELS, type OpenAIAuthOptions, type OpenAIModelOptions, };
29
+ export { OpenAI, OPENAI_MODELS, type OpenAIAuthOptions, type OpenAIOptions };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "roboport",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Minimal TypeScript framework for building LLM agents.",
5
5
  "author": {
6
6
  "name": "Timur Badretdinov",
7
7
  "url": "https://github.com/Destiner"
8
8
  },
9
- "license": "UNLICENSED",
9
+ "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "git+https://github.com/Destiner/roboport.git",