@arbium/enterprise-agent-connect 0.0.0-stage → 0.1.1
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 +202 -0
- package/README.md +117 -2
- package/dist/connectors/dell-techdirect/connector.json +39 -0
- package/dist/connectors/dell-techdirect/index.js +7 -0
- package/dist/connectors/dell-techdirect/index.js.map +1 -0
- package/dist/connectors/dell-techdirect/tools.js +49 -0
- package/dist/connectors/dell-techdirect/tools.js.map +1 -0
- package/dist/connectors/dell-techdirect/vendor.js +57 -0
- package/dist/connectors/dell-techdirect/vendor.js.map +1 -0
- package/dist/connectors/entra/connector.json +85 -0
- package/dist/connectors/entra/index.js +4 -0
- package/dist/connectors/entra/index.js.map +1 -0
- package/dist/connectors/entra/tools.js +262 -0
- package/dist/connectors/entra/tools.js.map +1 -0
- package/dist/connectors/fedex/connector.json +118 -0
- package/dist/connectors/fedex/index.js +7 -0
- package/dist/connectors/fedex/index.js.map +1 -0
- package/dist/connectors/fedex/tools.js +152 -0
- package/dist/connectors/fedex/tools.js.map +1 -0
- package/dist/connectors/fedex/vendor.js +98 -0
- package/dist/connectors/fedex/vendor.js.map +1 -0
- package/dist/connectors/http/index.js +235 -0
- package/dist/connectors/http/index.js.map +1 -0
- package/dist/connectors/intune/connector.json +106 -0
- package/dist/connectors/intune/index.js +4 -0
- package/dist/connectors/intune/index.js.map +1 -0
- package/dist/connectors/intune/tools.js +416 -0
- package/dist/connectors/intune/tools.js.map +1 -0
- package/dist/connectors/jira/connector.json +129 -0
- package/dist/connectors/jira/index.js +7 -0
- package/dist/connectors/jira/index.js.map +1 -0
- package/dist/connectors/jira/jql.js +67 -0
- package/dist/connectors/jira/jql.js.map +1 -0
- package/dist/connectors/jira/tools.js +464 -0
- package/dist/connectors/jira/tools.js.map +1 -0
- package/dist/connectors/jira/vendor.js +94 -0
- package/dist/connectors/jira/vendor.js.map +1 -0
- package/dist/connectors/microsoft/client.js +434 -0
- package/dist/connectors/microsoft/client.js.map +1 -0
- package/dist/connectors/microsoft/provider.json +118 -0
- package/dist/connectors/outlook-calendar/connector.json +136 -0
- package/dist/connectors/outlook-calendar/index.js +4 -0
- package/dist/connectors/outlook-calendar/index.js.map +1 -0
- package/dist/connectors/outlook-calendar/tools.js +398 -0
- package/dist/connectors/outlook-calendar/tools.js.map +1 -0
- package/dist/connectors/outlook-mail/connector.json +154 -0
- package/dist/connectors/outlook-mail/index.js +4 -0
- package/dist/connectors/outlook-mail/index.js.map +1 -0
- package/dist/connectors/outlook-mail/tools.js +236 -0
- package/dist/connectors/outlook-mail/tools.js.map +1 -0
- package/dist/connectors/salesforce/connector.json +122 -0
- package/dist/connectors/salesforce/index.js +7 -0
- package/dist/connectors/salesforce/index.js.map +1 -0
- package/dist/connectors/salesforce/soql.js +60 -0
- package/dist/connectors/salesforce/soql.js.map +1 -0
- package/dist/connectors/salesforce/tools.js +234 -0
- package/dist/connectors/salesforce/tools.js.map +1 -0
- package/dist/connectors/salesforce/vendor.js +107 -0
- package/dist/connectors/salesforce/vendor.js.map +1 -0
- package/dist/connectors/sharepoint-files/connector.json +194 -0
- package/dist/connectors/sharepoint-files/excel.js +300 -0
- package/dist/connectors/sharepoint-files/excel.js.map +1 -0
- package/dist/connectors/sharepoint-files/index.js +12 -0
- package/dist/connectors/sharepoint-files/index.js.map +1 -0
- package/dist/connectors/sharepoint-files/tools.js +365 -0
- package/dist/connectors/sharepoint-files/tools.js.map +1 -0
- package/dist/connectors/sharepoint-files/vendor.js +167 -0
- package/dist/connectors/sharepoint-files/vendor.js.map +1 -0
- package/dist/connectors/teams/connector.json +298 -0
- package/dist/connectors/teams/index.js +5 -0
- package/dist/connectors/teams/index.js.map +1 -0
- package/dist/connectors/teams/tools.js +391 -0
- package/dist/connectors/teams/tools.js.map +1 -0
- package/dist/packages/connector-kit/src/branding.js +102 -0
- package/dist/packages/connector-kit/src/branding.js.map +1 -0
- package/dist/packages/connector-kit/src/certificate.js +84 -0
- package/dist/packages/connector-kit/src/certificate.js.map +1 -0
- package/dist/packages/connector-kit/src/common.js +107 -0
- package/dist/packages/connector-kit/src/common.js.map +1 -0
- package/dist/packages/connector-kit/src/connector.js +492 -0
- package/dist/packages/connector-kit/src/connector.js.map +1 -0
- package/dist/packages/connector-kit/src/oauth.js +79 -0
- package/dist/packages/connector-kit/src/oauth.js.map +1 -0
- package/dist/packages/connector-kit/src/types.js +20 -0
- package/dist/packages/connector-kit/src/types.js.map +1 -0
- package/dist/packages/server/migrations/0001_tables.sql +268 -0
- package/dist/packages/server/migrations/0002_organizations.sql +72 -0
- package/dist/packages/server/migrations/0003_drop_unused.sql +3 -0
- package/dist/packages/server/migrations/0004_trigger_runners.sql +15 -0
- package/dist/packages/server/migrations/0005_run_runner.sql +3 -0
- package/dist/packages/server/migrations/0006_approvals.sql +36 -0
- package/dist/packages/server/migrations/rollback/to-legacy.sql +87 -0
- package/dist/packages/server/src/api.js +684 -0
- package/dist/packages/server/src/api.js.map +1 -0
- package/dist/packages/server/src/app.js +444 -0
- package/dist/packages/server/src/app.js.map +1 -0
- package/dist/packages/server/src/builtin.js +24 -0
- package/dist/packages/server/src/builtin.js.map +1 -0
- package/dist/packages/server/src/consent.js +36 -0
- package/dist/packages/server/src/consent.js.map +1 -0
- package/dist/packages/server/src/index.js +129 -0
- package/dist/packages/server/src/index.js.map +1 -0
- package/dist/packages/server/src/migrate.js +134 -0
- package/dist/packages/server/src/migrate.js.map +1 -0
- package/dist/packages/server/src/openapi.js +101 -0
- package/dist/packages/server/src/openapi.js.map +1 -0
- package/dist/packages/server/src/secrets.js +76 -0
- package/dist/packages/server/src/secrets.js.map +1 -0
- package/dist/packages/server/src/server.js +69 -0
- package/dist/packages/server/src/server.js.map +1 -0
- package/dist/packages/server/src/service.js +908 -0
- package/dist/packages/server/src/service.js.map +1 -0
- package/dist/packages/server/src/store.js +411 -0
- package/dist/packages/server/src/store.js.map +1 -0
- package/dist/packages/server/src/teams/activity.js +187 -0
- package/dist/packages/server/src/teams/activity.js.map +1 -0
- package/dist/packages/server/src/teams/admin.js +699 -0
- package/dist/packages/server/src/teams/admin.js.map +1 -0
- package/dist/packages/server/src/teams/approval.js +118 -0
- package/dist/packages/server/src/teams/approval.js.map +1 -0
- package/dist/packages/server/src/teams/bot.js +124 -0
- package/dist/packages/server/src/teams/bot.js.map +1 -0
- package/dist/packages/server/src/teams/claude.js +165 -0
- package/dist/packages/server/src/teams/claude.js.map +1 -0
- package/dist/packages/server/src/teams/jwt.js +121 -0
- package/dist/packages/server/src/teams/jwt.js.map +1 -0
- package/dist/packages/server/src/teams/package.js +319 -0
- package/dist/packages/server/src/teams/package.js.map +1 -0
- package/dist/packages/server/src/teams/provision.js +505 -0
- package/dist/packages/server/src/teams/provision.js.map +1 -0
- package/dist/packages/server/src/teams/runner.js +143 -0
- package/dist/packages/server/src/teams/runner.js.map +1 -0
- package/dist/packages/server/src/teams/runtime.js +701 -0
- package/dist/packages/server/src/teams/runtime.js.map +1 -0
- package/dist/packages/server/src/teams/store.js +507 -0
- package/dist/packages/server/src/teams/store.js.map +1 -0
- package/package.json +42 -4
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { clientCredentials, vendorError } from "../../packages/connector-kit/src/oauth.js";
|
|
3
|
+
import { ConnectError, } from "../../packages/connector-kit/src/types.js";
|
|
4
|
+
// A FedEx Developer Portal project: API key and secret key, OAuth client credentials at
|
|
5
|
+
// /oauth/token (form-encoded, expires_in 3600), then JSON calls with the bearer token. Hosts
|
|
6
|
+
// and request shapes are FedEx's published OpenAPI files (developer.fedex.com/api/en-us/catalog/
|
|
7
|
+
// {authorization,track,rate,ship}/v1/docs.html, retrieved October 21, 2026).
|
|
8
|
+
export const FEDEX_HOSTS = {
|
|
9
|
+
sandbox: "https://apis-sandbox.fedex.com",
|
|
10
|
+
production: "https://apis.fedex.com",
|
|
11
|
+
};
|
|
12
|
+
export const ACCOUNT_NUMBER = /^[0-9]{9,12}$/;
|
|
13
|
+
const MAX_ACCOUNTS = 50;
|
|
14
|
+
// FedEx documents x-locale; errors and status text come back in English.
|
|
15
|
+
const HEADERS = { "x-locale": "en_US" };
|
|
16
|
+
// A number no shipment has: the Track API answers it (with a not-found result), which proves
|
|
17
|
+
// the token reaches the Track API without reading anyone's shipment.
|
|
18
|
+
export const PROBE_TRACKING_NUMBER = "000000000000";
|
|
19
|
+
export function parseAccounts(raw) {
|
|
20
|
+
const accounts = [...new Set(raw.split(/[\s,;]+/).filter(Boolean))];
|
|
21
|
+
if (!accounts.length)
|
|
22
|
+
throw new ConnectError("Enter at least one FedEx account number");
|
|
23
|
+
const bad = accounts.find((a) => !ACCOUNT_NUMBER.test(a));
|
|
24
|
+
if (bad)
|
|
25
|
+
throw new ConnectError(`${bad} is not a FedEx account number (9 to 12 digits)`);
|
|
26
|
+
if (accounts.length > MAX_ACCOUNTS)
|
|
27
|
+
throw new ConnectError(`Enter at most ${MAX_ACCOUNTS} account numbers`);
|
|
28
|
+
return accounts;
|
|
29
|
+
}
|
|
30
|
+
export function connectedAccounts(config) {
|
|
31
|
+
return (config.accounts ?? "").split(",").filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
function host(config) {
|
|
34
|
+
const base = Object.hasOwn(FEDEX_HOSTS, config.environment)
|
|
35
|
+
? FEDEX_HOSTS[config.environment]
|
|
36
|
+
: null;
|
|
37
|
+
if (!base)
|
|
38
|
+
throw new Error(`Unknown FedEx environment ${config.environment}`);
|
|
39
|
+
return base;
|
|
40
|
+
}
|
|
41
|
+
export function fedexVendor(fetchImpl = fetch) {
|
|
42
|
+
const oauth = clientCredentials("FedEx", fetchImpl);
|
|
43
|
+
const creds = (c) => ({
|
|
44
|
+
tokenUrl: `${host(c.config)}/oauth/token`,
|
|
45
|
+
clientId: c.secret.api_key,
|
|
46
|
+
clientSecret: c.secret.secret_key,
|
|
47
|
+
});
|
|
48
|
+
const call = (c, request) => oauth.call(creds(c), host(c.config), request, HEADERS);
|
|
49
|
+
return {
|
|
50
|
+
async connect(fields) {
|
|
51
|
+
const environment = fields.environment ?? "";
|
|
52
|
+
if (!Object.hasOwn(FEDEX_HOSTS, environment))
|
|
53
|
+
throw new ConnectError("Choose Sandbox or Production");
|
|
54
|
+
const apiKey = fields.api_key?.trim();
|
|
55
|
+
const secretKey = fields.secret_key?.trim();
|
|
56
|
+
if (!apiKey || !secretKey)
|
|
57
|
+
throw new ConnectError("Enter the API key and secret key from your FedEx project");
|
|
58
|
+
const accounts = parseAccounts(fields.account_numbers ?? "");
|
|
59
|
+
const conn = {
|
|
60
|
+
config: {
|
|
61
|
+
environment,
|
|
62
|
+
accounts: accounts.join(","),
|
|
63
|
+
// Names the project reached without keeping the key in plain config.
|
|
64
|
+
project: createHash("sha256").update(apiKey).digest("hex").slice(0, 12),
|
|
65
|
+
},
|
|
66
|
+
secret: { api_key: apiKey, secret_key: secretKey },
|
|
67
|
+
};
|
|
68
|
+
await oauth.signIn(creds(conn));
|
|
69
|
+
return {
|
|
70
|
+
...conn,
|
|
71
|
+
accountLabel: `${environment === "production" ? "Production" : "Sandbox"} · ${accounts.length} account${accounts.length === 1 ? "" : "s"}`,
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
call,
|
|
75
|
+
checks: {
|
|
76
|
+
// A Track API answer about a number no shipment has.
|
|
77
|
+
async track(connection) {
|
|
78
|
+
const res = await call(connection, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
path: "/track/v1/trackingnumbers",
|
|
81
|
+
body: {
|
|
82
|
+
includeDetailedScans: false,
|
|
83
|
+
trackingInfo: [{ trackingNumberInfo: { trackingNumber: PROBE_TRACKING_NUMBER } }],
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
if (res.status === 401 || res.status === 403)
|
|
87
|
+
throw new ConnectError(`FedEx refused the Track API for this project. Add the Track API to the project, then check again. (${vendorError(res.data) || res.status})`);
|
|
88
|
+
if (res.status < 200 || res.status >= 300)
|
|
89
|
+
throw new ConnectError(`FedEx Track API answered ${res.status}: ${vendorError(res.data) || "no reason given"}`);
|
|
90
|
+
return { values: [new URL(host(connection.config)).host] };
|
|
91
|
+
},
|
|
92
|
+
async accounts(connection) {
|
|
93
|
+
return { values: connectedAccounts(connection.config) };
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=vendor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vendor.js","sourceRoot":"","sources":["../../../connectors/fedex/vendor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { audited, Denied, json, listLimit, VendorFailed, } from "../../packages/connector-kit/src/common.js";
|
|
3
|
+
import { clientCredentials, vendorError } from "../../packages/connector-kit/src/oauth.js";
|
|
4
|
+
import { ConnectError, } from "../../packages/connector-kit/src/types.js";
|
|
5
|
+
// The generic module behind every connector.json with an `http` block: an app described as
|
|
6
|
+
// data, e.g. a company's internal REST API. Calls reach only baseUrl's origin under its path.
|
|
7
|
+
export const MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
8
|
+
export const MAX_BODY_BYTES = 256 * 1024;
|
|
9
|
+
export const MAX_TARGET_BYTES = 8 * 1024;
|
|
10
|
+
const TIMEOUT_MS = 30_000;
|
|
11
|
+
// fetch that reaches only `allowed` origins, follows no redirect, gives up after TIMEOUT_MS and
|
|
12
|
+
// reads at most MAX_RESPONSE_BYTES of JSON or text. Its refusals are ConnectError.
|
|
13
|
+
export function guardedFetch(allowed, fetchImpl = fetch) {
|
|
14
|
+
return (async (input, init) => {
|
|
15
|
+
const url = new URL(input instanceof Request ? input.url : input);
|
|
16
|
+
if (!allowed.includes(url.origin))
|
|
17
|
+
throw new ConnectError(`${url.origin} is not this app's address`);
|
|
18
|
+
const res = await fetchImpl(url, {
|
|
19
|
+
...init,
|
|
20
|
+
redirect: "manual",
|
|
21
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
22
|
+
});
|
|
23
|
+
if (res.status >= 300 && res.status < 400)
|
|
24
|
+
throw new ConnectError(`The app answered with a redirect (${res.status}), which is not followed`);
|
|
25
|
+
const type = res.headers.get("content-type") ?? "";
|
|
26
|
+
if (res.body &&
|
|
27
|
+
!/^(application\/([\w.+-]*\+)?json|text\/)/i.test(type) &&
|
|
28
|
+
res.headers.get("content-length") !== "0") {
|
|
29
|
+
await res.body.cancel();
|
|
30
|
+
throw new ConnectError(`The app answered ${type || "without a content type"}; only JSON or text is passed on`);
|
|
31
|
+
}
|
|
32
|
+
const chunks = [];
|
|
33
|
+
let size = 0;
|
|
34
|
+
const reader = res.body?.getReader();
|
|
35
|
+
for (let r = await reader?.read(); r && !r.done; r = await reader.read()) {
|
|
36
|
+
size += r.value.byteLength;
|
|
37
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
38
|
+
await reader.cancel();
|
|
39
|
+
throw new ConnectError(`The app's answer is larger than ${MAX_RESPONSE_BYTES / 1024} KB`);
|
|
40
|
+
}
|
|
41
|
+
chunks.push(r.value);
|
|
42
|
+
}
|
|
43
|
+
// These statuses may not carry a body, even an empty one.
|
|
44
|
+
const body = [204, 205, 304].includes(res.status) ? null : Buffer.concat(chunks);
|
|
45
|
+
return new Response(body, { status: res.status, headers: res.headers });
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function parse(raw) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(raw);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return raw;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function httpVendor(connector, fetchImpl = fetch) {
|
|
57
|
+
const http = connector.http;
|
|
58
|
+
const base = new URL(http.baseUrl);
|
|
59
|
+
const prefix = base.pathname.replace(/\/+$/, "");
|
|
60
|
+
const auth = http.auth;
|
|
61
|
+
const safe = guardedFetch([base.origin, ...(auth.type === "oauth2" ? [new URL(auth.tokenUrl).origin] : [])], fetchImpl);
|
|
62
|
+
const oauth = auth.type === "oauth2" ? clientCredentials(connector.name, safe) : null;
|
|
63
|
+
const oauthCreds = (c) => auth.type === "oauth2"
|
|
64
|
+
? {
|
|
65
|
+
tokenUrl: auth.tokenUrl,
|
|
66
|
+
clientId: c.config[auth.clientId],
|
|
67
|
+
clientSecret: c.secret[auth.clientSecret],
|
|
68
|
+
}
|
|
69
|
+
: null;
|
|
70
|
+
function headersFor(c) {
|
|
71
|
+
if (auth.type === "header")
|
|
72
|
+
return { [auth.header]: c.secret[auth.value] };
|
|
73
|
+
if (auth.type === "bearer")
|
|
74
|
+
return { authorization: `Bearer ${c.secret[auth.token]}` };
|
|
75
|
+
if (auth.type === "basic")
|
|
76
|
+
return {
|
|
77
|
+
authorization: `Basic ${Buffer.from(`${c.config[auth.username]}:${c.secret[auth.password]}`).toString("base64")}`,
|
|
78
|
+
};
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
const call = async (c, request) => {
|
|
82
|
+
// The path is the tool's, already encoded; it must stay under baseUrl's path.
|
|
83
|
+
const url = new URL(`${base.origin}${prefix}${request.path}`);
|
|
84
|
+
if (url.origin !== base.origin || !`${url.pathname}/`.startsWith(`${prefix}/`))
|
|
85
|
+
throw new ConnectError("The call leaves this app's address");
|
|
86
|
+
const req = {
|
|
87
|
+
method: request.method,
|
|
88
|
+
path: `${url.pathname}${url.search}`,
|
|
89
|
+
body: request.body,
|
|
90
|
+
};
|
|
91
|
+
if (oauth)
|
|
92
|
+
return oauth.call(oauthCreds(c), base.origin, req);
|
|
93
|
+
const res = await safe(`${base.origin}${req.path}`, {
|
|
94
|
+
method: req.method,
|
|
95
|
+
headers: {
|
|
96
|
+
accept: "application/json",
|
|
97
|
+
...headersFor(c),
|
|
98
|
+
...(req.body === undefined ? {} : { "content-type": "application/json" }),
|
|
99
|
+
},
|
|
100
|
+
body: req.body === undefined ? undefined : JSON.stringify(req.body),
|
|
101
|
+
});
|
|
102
|
+
const raw = await res.text();
|
|
103
|
+
return { status: res.status, data: raw ? parse(raw) : null };
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
async connect(fields) {
|
|
107
|
+
const config = { host: base.host };
|
|
108
|
+
const secret = {};
|
|
109
|
+
for (const f of connector.fields) {
|
|
110
|
+
const value = fields[f.id]?.trim() ?? "";
|
|
111
|
+
if (!value)
|
|
112
|
+
throw new ConnectError(`Enter ${f.label}`);
|
|
113
|
+
(f.secret ? secret : config)[f.id] = value;
|
|
114
|
+
}
|
|
115
|
+
const conn = { config, secret };
|
|
116
|
+
if (oauth)
|
|
117
|
+
await oauth.signIn(oauthCreds(conn));
|
|
118
|
+
return { ...conn, accountLabel: config[connector.accountKey] ?? base.host };
|
|
119
|
+
},
|
|
120
|
+
call,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Whether every item of the answer (or the answer itself) has `path` among `values`.
|
|
124
|
+
function answerWithin(data, path, values) {
|
|
125
|
+
const items = Array.isArray(data) ? data : [data];
|
|
126
|
+
return items.every((item) => {
|
|
127
|
+
let v = item;
|
|
128
|
+
for (const key of path.split(".")) {
|
|
129
|
+
if (v === null || typeof v !== "object" || !Object.hasOwn(v, key))
|
|
130
|
+
return false;
|
|
131
|
+
v = v[key];
|
|
132
|
+
}
|
|
133
|
+
return (typeof v === "string" || typeof v === "number") && values.includes(String(v));
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const zodType = {
|
|
137
|
+
string: z.string(),
|
|
138
|
+
number: z.number(),
|
|
139
|
+
integer: z.number().int(),
|
|
140
|
+
boolean: z.boolean(),
|
|
141
|
+
};
|
|
142
|
+
export function httpTools(http) {
|
|
143
|
+
return {
|
|
144
|
+
names: http.tools.map((t) => t.name),
|
|
145
|
+
register(server, ctx, allowed) {
|
|
146
|
+
for (const tool of http.tools) {
|
|
147
|
+
if (!allowed.has(tool.name))
|
|
148
|
+
continue;
|
|
149
|
+
const shape = Object.fromEntries(Object.entries(tool.params).map(([name, p]) => {
|
|
150
|
+
const type = zodType[p.type].describe(p.description);
|
|
151
|
+
return [name, p.required ? type : type.optional()];
|
|
152
|
+
}));
|
|
153
|
+
const destructive = tool.destructive || tool.method === "DELETE";
|
|
154
|
+
server.registerTool(tool.name, {
|
|
155
|
+
description: tool.description,
|
|
156
|
+
inputSchema: z.object(shape).strict(),
|
|
157
|
+
annotations: {
|
|
158
|
+
readOnlyHint: tool.method === "GET",
|
|
159
|
+
destructiveHint: destructive,
|
|
160
|
+
openWorldHint: true,
|
|
161
|
+
},
|
|
162
|
+
}, (args) => audited(ctx, tool.name, JSON.stringify(args).slice(0, 300), async (mark) => {
|
|
163
|
+
for (const [name, p] of Object.entries(tool.params)) {
|
|
164
|
+
const allowedValues = p.limit ? listLimit(ctx.limits, p.limit) : null;
|
|
165
|
+
if (!allowedValues)
|
|
166
|
+
continue;
|
|
167
|
+
// Left out, an optional param would mean "all of them".
|
|
168
|
+
if (args[name] === undefined)
|
|
169
|
+
throw new Denied(`${name} is required: this agent is limited to some values`);
|
|
170
|
+
if (!allowedValues.includes(String(args[name])))
|
|
171
|
+
throw new Denied(`${name} ${String(args[name])} is outside this agent's limits`);
|
|
172
|
+
}
|
|
173
|
+
const path = tool.path.replace(/\{([a-z][a-z0-9_]*)\}/g, (_, name) => {
|
|
174
|
+
const value = String(args[name]);
|
|
175
|
+
// URLs resolve . and .. segments even when percent-encoded.
|
|
176
|
+
if (value === "." || value === ".." || value === "")
|
|
177
|
+
throw new Denied(`${name} is not a valid value`);
|
|
178
|
+
return encodeURIComponent(value);
|
|
179
|
+
});
|
|
180
|
+
const query = new URLSearchParams();
|
|
181
|
+
const body = {};
|
|
182
|
+
for (const [name, p] of Object.entries(tool.params)) {
|
|
183
|
+
if (args[name] === undefined)
|
|
184
|
+
continue;
|
|
185
|
+
if (p.in === "query")
|
|
186
|
+
query.set(name, String(args[name]));
|
|
187
|
+
if (p.in === "body")
|
|
188
|
+
body[name] = args[name];
|
|
189
|
+
}
|
|
190
|
+
const target = query.size ? `${path}?${query}` : path;
|
|
191
|
+
if (Buffer.byteLength(target) > MAX_TARGET_BYTES)
|
|
192
|
+
throw new Denied(`The address is longer than ${MAX_TARGET_BYTES / 1024} KB`);
|
|
193
|
+
const hasBody = Object.values(tool.params).some((p) => p.in === "body");
|
|
194
|
+
if (hasBody && Buffer.byteLength(JSON.stringify(body)) > MAX_BODY_BYTES)
|
|
195
|
+
throw new Denied(`The request is larger than ${MAX_BODY_BYTES / 1024} KB`);
|
|
196
|
+
let res;
|
|
197
|
+
try {
|
|
198
|
+
res = await ctx.call({
|
|
199
|
+
method: tool.method,
|
|
200
|
+
path: target,
|
|
201
|
+
...(hasBody ? { body } : {}),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
if (error instanceof ConnectError)
|
|
206
|
+
throw new VendorFailed(502, error.message);
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
mark(res.status);
|
|
210
|
+
if (res.status < 200 || res.status >= 300) {
|
|
211
|
+
const detail = vendorError(res.data);
|
|
212
|
+
throw new VendorFailed(res.status, `The app answered ${res.status}${detail ? `: ${detail}` : ""}`);
|
|
213
|
+
}
|
|
214
|
+
for (const a of tool.answerLimits) {
|
|
215
|
+
const values = listLimit(ctx.limits, a.limit);
|
|
216
|
+
if (values && !answerWithin(res.data, a.field, values))
|
|
217
|
+
throw new Denied(`The answer's ${a.field} is outside this agent's limits`);
|
|
218
|
+
}
|
|
219
|
+
return typeof res.data === "string"
|
|
220
|
+
? { content: [{ type: "text", text: res.data }] }
|
|
221
|
+
: json(res.data);
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** Binds to any connector with an `http` block; createAgentConnect uses it for those. */
|
|
228
|
+
export default {
|
|
229
|
+
id: "http",
|
|
230
|
+
create: ({ connector }) => ({
|
|
231
|
+
vendor: httpVendor(connector),
|
|
232
|
+
toolset: httpTools(connector.http),
|
|
233
|
+
}),
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../connectors/http/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "intune",
|
|
3
|
+
"name": "Microsoft Intune",
|
|
4
|
+
"description": "Let agents look up the devices Intune manages: who uses them, their system and version, compliance, encryption, last check-in and the apps found on them. Read-only.",
|
|
5
|
+
"logo": "/brand/registry/intune.svg",
|
|
6
|
+
"identity": "Your Microsoft app registration, through the Microsoft connection. It can read your managed devices and, if you allow it, their Autopilot status, and change nothing: it cannot wipe, lock, retire or sync a device, or change an Autopilot profile. Each API key can be kept to the devices of people in chosen groups.",
|
|
7
|
+
"provider": "microsoft",
|
|
8
|
+
"order": 6,
|
|
9
|
+
"accountKey": "tenantId",
|
|
10
|
+
"connectionKind": "guided",
|
|
11
|
+
"setup": [
|
|
12
|
+
{
|
|
13
|
+
"title": "Add these Microsoft Graph permissions to the app",
|
|
14
|
+
"body": "In the app, open API permissions, select Add a permission, then Microsoft Graph, then Application permissions, and add exactly these. They only read. Do not add DeviceManagementManagedDevices.ReadWrite.All, DeviceManagementManagedDevices.PrivilegedOperations.All or DeviceManagementServiceConfig.ReadWrite.All: they let the app change, wipe or retire devices or change Autopilot. Your organization needs an Intune license.",
|
|
15
|
+
"copy": [
|
|
16
|
+
"az ad app permission add --id {clientId} --api 00000003-0000-0000-c000-000000000000 --api-permissions \"$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query \"appRoles[?value=='{scope}'].id\" -o tsv)=Role\""
|
|
17
|
+
],
|
|
18
|
+
"click": [
|
|
19
|
+
"In the Microsoft Entra admin center, go to Entra ID, then App registrations, and open {brand.microsoft.appName}.",
|
|
20
|
+
"Select API permissions, then Add a permission, then Microsoft Graph, then Application permissions.",
|
|
21
|
+
"Tick {scopes} and select Add permissions."
|
|
22
|
+
],
|
|
23
|
+
"shell": "bash"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"title": "Grant admin consent",
|
|
27
|
+
"body": "Still under API permissions, select \"Grant admin consent\". This needs a Global Administrator or Privileged Role Administrator. Each permission should then show Granted. With Commands, a permission that was granted already is answered with \"Permission being assigned already exists\", which is fine.",
|
|
28
|
+
"click": [
|
|
29
|
+
"Still under API permissions, select Grant admin consent for your organization, then Yes.",
|
|
30
|
+
"Each permission should show Granted under Status."
|
|
31
|
+
],
|
|
32
|
+
"copy": [
|
|
33
|
+
"SP=$(az ad sp show --id {clientId} --query id -o tsv)",
|
|
34
|
+
"GRAPH=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query id -o tsv)",
|
|
35
|
+
"az rest --method post --url \"https://graph.microsoft.com/v1.0/servicePrincipals/$SP/appRoleAssignments\" --body \"{\\\"principalId\\\": \\\"$SP\\\", \\\"resourceId\\\": \\\"$GRAPH\\\", \\\"appRoleId\\\": \\\"$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query \"appRoles[?value=='{scope}'].id\" -o tsv)\\\"}\""
|
|
36
|
+
],
|
|
37
|
+
"shell": "bash"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"afterConnect": [],
|
|
41
|
+
"fields": [
|
|
42
|
+
{
|
|
43
|
+
"id": "people_groups",
|
|
44
|
+
"label": "Keeping keys to groups of people",
|
|
45
|
+
"secret": false,
|
|
46
|
+
"options": [
|
|
47
|
+
{
|
|
48
|
+
"value": "off",
|
|
49
|
+
"label": "Not used",
|
|
50
|
+
"description": "Each API key reaches every managed device."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"value": "groups",
|
|
54
|
+
"label": "Keep keys to people's groups",
|
|
55
|
+
"description": "Lets you keep an API key to the devices whose primary user is in groups you choose. The app then also reads your organization's people and group memberships.",
|
|
56
|
+
"scopes": ["User.ReadBasic.All", "GroupMember.Read.All"]
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"capabilities": [
|
|
62
|
+
{
|
|
63
|
+
"id": "read",
|
|
64
|
+
"label": "Look up managed devices",
|
|
65
|
+
"description": "List and search managed devices, read one device's details and the apps found on it, a person's devices, and compliance across devices.",
|
|
66
|
+
"required": true,
|
|
67
|
+
"note": "Reaches every managed device unless you keep a key to chosen groups under API keys. Read-only.",
|
|
68
|
+
"scopes": ["DeviceManagementManagedDevices.Read.All"],
|
|
69
|
+
"permissions": [],
|
|
70
|
+
"tools": [
|
|
71
|
+
"intune_list_devices",
|
|
72
|
+
"intune_get_device",
|
|
73
|
+
"intune_device_apps",
|
|
74
|
+
"intune_person_devices",
|
|
75
|
+
"intune_compliance_summary"
|
|
76
|
+
],
|
|
77
|
+
"forbidden": "Microsoft refused this. The app needs {scopes} with admin consent (the Microsoft Intune page in {brand.productName} shows how), and your organization needs an Intune license."
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "autopilot",
|
|
81
|
+
"label": "Read Autopilot status",
|
|
82
|
+
"description": "Look up a Windows Autopilot device by serial number: its enrollment state, profile, group tag, last contact with Intune and, once managed, its compliance and primary user.",
|
|
83
|
+
"note": "Reads the Autopilot device list of your whole organization. Read-only. The answer also reads the managed device, which \"Look up managed devices\" (always on) allows.",
|
|
84
|
+
"scopes": ["DeviceManagementServiceConfig.Read.All"],
|
|
85
|
+
"permissions": [],
|
|
86
|
+
"tools": ["intune_autopilot_status"],
|
|
87
|
+
"forbidden": "Microsoft refused this. The app needs {scopes} with admin consent (the Microsoft Intune page in {brand.productName} shows how), and your organization needs an Intune license."
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"limits": [
|
|
91
|
+
{
|
|
92
|
+
"id": "groups",
|
|
93
|
+
"label": "Groups",
|
|
94
|
+
"help": "Optional: only devices whose primary user is in these groups (their object IDs, from Groups in the Microsoft Entra admin center). Needs \"Keep keys to people's groups\" on the Microsoft Intune page. At most 20. Leave empty for every device. Autopilot status (if allowed) also needs any user assigned on the Autopilot record to be in these groups; for a device no person is tied to yet (a spare), it answers only the serial number, the Autopilot enrollment state and whether Intune manages it and its compliance: no device id, names or group tag.",
|
|
95
|
+
"kind": "list",
|
|
96
|
+
"required": false,
|
|
97
|
+
"pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"verify": {
|
|
101
|
+
"label": "Microsoft permissions the app has",
|
|
102
|
+
"check": "permissions",
|
|
103
|
+
"ifEmpty": "The app has no Microsoft Graph application permissions yet. Add the permissions above and grant admin consent."
|
|
104
|
+
},
|
|
105
|
+
"revoke": "Remove DeviceManagementManagedDevices.Read.All and DeviceManagementServiceConfig.Read.All (and User.ReadBasic.All and GroupMember.Read.All, unless another app here needs them) from the app under API permissions. To stop every Microsoft app at once, remove the app registration (see the Microsoft connection)."
|
|
106
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../connectors/intune/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,eAAe,YAAY,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC"}
|