@chrischall/office-outlook-mcp 0.3.5 → 0.4.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +10 -2
- package/dist/bundle.js +938 -182
- package/dist/client.js +26 -8
- package/dist/timezone.js +32 -3
- package/dist/tools/_paging.js +9 -2
- package/dist/tools/calendar.js +177 -22
- package/dist/tools/directory.js +27 -31
- package/dist/tools/healthcheck.js +4 -3
- package/dist/tools/mail.js +16 -22
- package/dist/tools/writes.js +274 -23
- package/dist/version.js +1 -1
- package/dist/view.js +9 -1
- package/package.json +2 -2
- package/server.json +14 -2
- package/skills/outlook-fpx/references/outlook-env.sh +2 -2
package/dist/bundle.js
CHANGED
|
@@ -23737,10 +23737,10 @@ function scanXMcpHeaderDeclarations(inputSchema) {
|
|
|
23737
23737
|
if (!RFC9110_TOKEN.test(raw)) return `${pathName(path)}: x-mcp-header '${raw}' is not a valid RFC 9110 token (no spaces, control characters or HTTP delimiters)`;
|
|
23738
23738
|
const type = typeof schema.type === "string" ? schema.type : void 0;
|
|
23739
23739
|
if (type === void 0 || !PERMITTED_X_MCP_HEADER_TYPES.has(type)) return `${pathName(path)}: x-mcp-header is only permitted on primitive-typed properties (string, integer, boolean); got ${type ?? "<none>"}`;
|
|
23740
|
-
const
|
|
23741
|
-
const prior = seenLower.get(
|
|
23740
|
+
const lower2 = raw.toLowerCase();
|
|
23741
|
+
const prior = seenLower.get(lower2);
|
|
23742
23742
|
if (prior !== void 0) return `x-mcp-header '${raw}' is not case-insensitively unique (also declared as '${prior}')`;
|
|
23743
|
-
seenLower.set(
|
|
23743
|
+
seenLower.set(lower2, raw);
|
|
23744
23744
|
declarations.push({
|
|
23745
23745
|
path,
|
|
23746
23746
|
headerName: raw,
|
|
@@ -35334,6 +35334,30 @@ var init_dist = __esm({
|
|
|
35334
35334
|
});
|
|
35335
35335
|
|
|
35336
35336
|
// node_modules/@chrischall/mcp-utils/dist/errors/index.js
|
|
35337
|
+
function firstDeclared(err, read) {
|
|
35338
|
+
let cur = err;
|
|
35339
|
+
for (let depth = 0; depth < DECLARATION_CAUSE_DEPTH && cur !== null && typeof cur === "object"; depth++) {
|
|
35340
|
+
const found = read(cur);
|
|
35341
|
+
if (found !== void 0)
|
|
35342
|
+
return found;
|
|
35343
|
+
cur = cur.cause;
|
|
35344
|
+
}
|
|
35345
|
+
return void 0;
|
|
35346
|
+
}
|
|
35347
|
+
function errorStatusOf(err) {
|
|
35348
|
+
return firstDeclared(err, (link2) => {
|
|
35349
|
+
const e = link2;
|
|
35350
|
+
if (typeof e.status === "number")
|
|
35351
|
+
return e.status;
|
|
35352
|
+
return typeof e.statusCode === "number" ? e.statusCode : void 0;
|
|
35353
|
+
});
|
|
35354
|
+
}
|
|
35355
|
+
function errorKindOf(err) {
|
|
35356
|
+
return firstDeclared(err, (link2) => {
|
|
35357
|
+
const k = link2.kind;
|
|
35358
|
+
return typeof k === "string" && MCP_TOOL_ERROR_KINDS.has(k) ? k : void 0;
|
|
35359
|
+
});
|
|
35360
|
+
}
|
|
35337
35361
|
function redactJsonCookie(quote) {
|
|
35338
35362
|
const stringRe = quote === '"' ? DQ_STRING_RE : SQ_STRING_RE;
|
|
35339
35363
|
return (_m, prefix, key, value) => {
|
|
@@ -35361,21 +35385,82 @@ function messageOf(err) {
|
|
|
35361
35385
|
return err.message;
|
|
35362
35386
|
return String(err);
|
|
35363
35387
|
}
|
|
35364
|
-
var DEFAULT_ERROR_MESSAGE_MAX, McpToolError, HEADER_NAME_RUN, BEARER_RE, JWT_RE, BASIC_AUTH_RE, SET_COOKIE_RE, COOKIE_HEADER_RE, API_KEY_RE, GOOGLE_OAUTH_TOKEN_RE, QUERY_SECRET_RE, OAUTH_CODE_RE, FORM_PASSWORD_RE, HEADER_SECRET_RE, AWS_SIGV4_RE, JSON_SECRET_KEYS, JSON_SECRET_DQ_RE, JSON_SECRET_SQ_RE, JSON_SECRET_NUM_RE, JSON_COOKIE_DQ_RE, JSON_COOKIE_SQ_RE, DQ_STRING_RE, SQ_STRING_RE, ERROR_REDACTION_INPUT_MAX;
|
|
35388
|
+
var DEFAULT_ERROR_MESSAGE_MAX, MCP_TOOL_ERROR_KINDS, McpToolError, DECLARATION_CAUSE_DEPTH, UPSTREAM_BODY_PHRASE, UpstreamFormatError, HEADER_NAME_RUN, BEARER_RE, JWT_RE, BASIC_AUTH_RE, SET_COOKIE_RE, COOKIE_HEADER_RE, API_KEY_RE, GOOGLE_OAUTH_TOKEN_RE, QUERY_SECRET_RE, OAUTH_CODE_RE, FORM_PASSWORD_RE, HEADER_SECRET_RE, AWS_SIGV4_RE, JSON_SECRET_KEYS, JSON_SECRET_DQ_RE, JSON_SECRET_SQ_RE, JSON_SECRET_NUM_RE, JSON_COOKIE_DQ_RE, JSON_COOKIE_SQ_RE, DQ_STRING_RE, SQ_STRING_RE, ERROR_REDACTION_INPUT_MAX;
|
|
35365
35389
|
var init_errors3 = __esm({
|
|
35366
35390
|
"node_modules/@chrischall/mcp-utils/dist/errors/index.js"() {
|
|
35367
35391
|
DEFAULT_ERROR_MESSAGE_MAX = 500;
|
|
35392
|
+
MCP_TOOL_ERROR_KINDS = /* @__PURE__ */ new Set([
|
|
35393
|
+
"no_credential",
|
|
35394
|
+
"credential_rejected",
|
|
35395
|
+
"edge_blocked",
|
|
35396
|
+
"session_expired",
|
|
35397
|
+
"verification_pending",
|
|
35398
|
+
"timeout",
|
|
35399
|
+
"http",
|
|
35400
|
+
"transport",
|
|
35401
|
+
"too_large",
|
|
35402
|
+
"unknown"
|
|
35403
|
+
]);
|
|
35368
35404
|
McpToolError = class extends Error {
|
|
35369
35405
|
/** Actionable remediation text, when one applies. */
|
|
35370
35406
|
hint;
|
|
35407
|
+
/** The upstream HTTP status this failure came from, when known. */
|
|
35408
|
+
status;
|
|
35409
|
+
/** What kind of failure this is, when the thrower knows. */
|
|
35410
|
+
kind;
|
|
35371
35411
|
constructor(message, opts) {
|
|
35372
35412
|
super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
35373
35413
|
this.name = "McpToolError";
|
|
35374
35414
|
if (opts?.hint !== void 0)
|
|
35375
35415
|
this.hint = opts.hint;
|
|
35416
|
+
if (opts?.status !== void 0)
|
|
35417
|
+
this.status = opts.status;
|
|
35418
|
+
if (opts?.kind !== void 0)
|
|
35419
|
+
this.kind = opts.kind;
|
|
35376
35420
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
35377
35421
|
}
|
|
35378
35422
|
};
|
|
35423
|
+
DECLARATION_CAUSE_DEPTH = 4;
|
|
35424
|
+
UPSTREAM_BODY_PHRASE = {
|
|
35425
|
+
"non-json": "a non-JSON response",
|
|
35426
|
+
empty: "an empty body",
|
|
35427
|
+
null: "null",
|
|
35428
|
+
array: "an array",
|
|
35429
|
+
object: "an object",
|
|
35430
|
+
string: "a string",
|
|
35431
|
+
number: "a number",
|
|
35432
|
+
boolean: "a boolean"
|
|
35433
|
+
};
|
|
35434
|
+
UpstreamFormatError = class extends McpToolError {
|
|
35435
|
+
/** What the body turned out to be. */
|
|
35436
|
+
received;
|
|
35437
|
+
/** The shape the caller required, when it required one. */
|
|
35438
|
+
expected;
|
|
35439
|
+
/** The response `Content-Type`, when known. (`status` is the base class's.) */
|
|
35440
|
+
contentType;
|
|
35441
|
+
constructor(details) {
|
|
35442
|
+
const service = details.service ?? "The upstream service";
|
|
35443
|
+
const request = details.method !== void 0 && details.path !== void 0 ? `${details.method.toUpperCase()} ${details.path}` : void 0;
|
|
35444
|
+
const meta3 = [
|
|
35445
|
+
details.status !== void 0 ? `HTTP ${details.status}` : void 0,
|
|
35446
|
+
details.contentType || void 0
|
|
35447
|
+
].filter((p) => p !== void 0);
|
|
35448
|
+
const what = details.expected !== void 0 ? `${UPSTREAM_BODY_PHRASE[details.received]} where a JSON ${details.expected} was expected` + (request ? ` for ${request}` : "") : `${UPSTREAM_BODY_PHRASE[details.received]}` + (request ? ` to ${request}` : "");
|
|
35449
|
+
const message = `${service} returned ${what}${meta3.length > 0 ? ` (${meta3.join(", ")})` : ""}.`;
|
|
35450
|
+
const hint = details.received === "non-json" ? "The service answered with something other than JSON, usually a sign-in page or interstitial, or its API has changed. Check the session, then retry." : "The service answered with a body of the wrong shape: the resource may be missing or its API may have changed.";
|
|
35451
|
+
super(truncateErrorMessage(message), {
|
|
35452
|
+
hint,
|
|
35453
|
+
cause: details.cause,
|
|
35454
|
+
...details.status !== void 0 ? { status: details.status } : {}
|
|
35455
|
+
});
|
|
35456
|
+
this.name = "UpstreamFormatError";
|
|
35457
|
+
this.received = details.received;
|
|
35458
|
+
if (details.expected !== void 0)
|
|
35459
|
+
this.expected = details.expected;
|
|
35460
|
+
if (details.contentType)
|
|
35461
|
+
this.contentType = details.contentType;
|
|
35462
|
+
}
|
|
35463
|
+
};
|
|
35379
35464
|
HEADER_NAME_RUN = "[a-z0-9_-]{0,40}";
|
|
35380
35465
|
BEARER_RE = /(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
35381
35466
|
JWT_RE = /(?<![A-Za-z0-9_-])[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{8,}(?![A-Za-z0-9_-])/g;
|
|
@@ -35397,7 +35482,7 @@ var init_errors3 = __esm({
|
|
|
35397
35482
|
// webhook signing secret (Stripe-style)
|
|
35398
35483
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
35399
35484
|
GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
|
|
35400
|
-
QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
35485
|
+
QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
35401
35486
|
OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
35402
35487
|
FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
35403
35488
|
HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
|
|
@@ -35408,6 +35493,8 @@ var init_errors3 = __esm({
|
|
|
35408
35493
|
"api[_-]?secret",
|
|
35409
35494
|
"(?:x[_-])?api[_-]?key",
|
|
35410
35495
|
"private[_-]?key",
|
|
35496
|
+
"code[_-]?verifier",
|
|
35497
|
+
// PKCE; a bare `verifier` key is too generic for JSON
|
|
35411
35498
|
`x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
|
|
35412
35499
|
// bounded like HEADER_SECRET_RE
|
|
35413
35500
|
"(?:proxy-)?authorization",
|
|
@@ -35904,7 +35991,7 @@ function readEnvVar(key, opts = {}) {
|
|
|
35904
35991
|
if (typeof raw === "string") {
|
|
35905
35992
|
const trimmed = raw.trim();
|
|
35906
35993
|
if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
|
|
35907
|
-
return trimmed;
|
|
35994
|
+
return opts.trim === false ? raw : trimmed;
|
|
35908
35995
|
}
|
|
35909
35996
|
}
|
|
35910
35997
|
return opts.default;
|
|
@@ -36141,12 +36228,19 @@ function boundArgs(args) {
|
|
|
36141
36228
|
}
|
|
36142
36229
|
function confirmationFromEnv(options) {
|
|
36143
36230
|
const { tool, account, confirmToken, subject, instruction, args, env = process.env, spent: callerSpent, ...rest } = options;
|
|
36231
|
+
if (args === void 0 || args === null) {
|
|
36232
|
+
throw new TypeError(`confirmationFromEnv: ${tool} passed no args; pass the tool's validated arguments ({} for a tool with none).`);
|
|
36233
|
+
}
|
|
36144
36234
|
const spent = callerSpent ?? spentTokenStoreFromEnv(env);
|
|
36145
36235
|
const mode = readConfirmMode(env);
|
|
36146
36236
|
const ttlSeconds = readConfirmTtl(env);
|
|
36147
36237
|
const elicitationOff = readConfirmElicitation(env) === "off";
|
|
36148
|
-
const bound =
|
|
36149
|
-
const
|
|
36238
|
+
const bound = boundArgs(args);
|
|
36239
|
+
const elicitationBound = account === void 0 ? { args: bound } : { account, args: bound };
|
|
36240
|
+
const bindingAdded = rest.binding ? rest : {
|
|
36241
|
+
...rest,
|
|
36242
|
+
binding: { key: confirmKeyFromEnv(env), args: elicitationBound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 }
|
|
36243
|
+
};
|
|
36150
36244
|
const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
|
|
36151
36245
|
if (mode === "refuse" || ttlSeconds === void 0) {
|
|
36152
36246
|
const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
|
|
@@ -36156,7 +36250,7 @@ function confirmationFromEnv(options) {
|
|
|
36156
36250
|
unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
|
|
36157
36251
|
};
|
|
36158
36252
|
}
|
|
36159
|
-
const boundSubject =
|
|
36253
|
+
const boundSubject = async () => {
|
|
36160
36254
|
const read = await subject();
|
|
36161
36255
|
if ("content" in read)
|
|
36162
36256
|
return read;
|
|
@@ -36244,7 +36338,7 @@ async function confirmWrite(ctx, options) {
|
|
|
36244
36338
|
...options.unsupportedNote === void 0 ? {} : { unsupportedNote: options.unsupportedNote },
|
|
36245
36339
|
...options.confirmationLabel === void 0 ? {} : { confirmationLabel: options.confirmationLabel },
|
|
36246
36340
|
tool,
|
|
36247
|
-
|
|
36341
|
+
account,
|
|
36248
36342
|
confirmToken,
|
|
36249
36343
|
args: bound,
|
|
36250
36344
|
subject: () => ({ target, ...revision === void 0 ? {} : { revision }, payload: bound, preview }),
|
|
@@ -36272,12 +36366,14 @@ var init_merge_write = __esm({
|
|
|
36272
36366
|
|
|
36273
36367
|
// node_modules/@chrischall/mcp-utils/dist/server/index.js
|
|
36274
36368
|
function hintResultOrRethrow(err) {
|
|
36369
|
+
if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
|
|
36370
|
+
throw err;
|
|
36275
36371
|
if (err instanceof McpToolError && err.hint) {
|
|
36276
36372
|
return errorResult(`${err.message}
|
|
36277
36373
|
|
|
36278
36374
|
Hint: ${err.hint}`);
|
|
36279
36375
|
}
|
|
36280
|
-
|
|
36376
|
+
return errorResult(messageOf(err));
|
|
36281
36377
|
}
|
|
36282
36378
|
function mcpRequestFrom(args) {
|
|
36283
36379
|
const ctx = args.at(-1);
|
|
@@ -36496,6 +36592,7 @@ var init_write = __esm({
|
|
|
36496
36592
|
var init_output = __esm({
|
|
36497
36593
|
"node_modules/@chrischall/mcp-utils/dist/fs/output.js"() {
|
|
36498
36594
|
init_config();
|
|
36595
|
+
init_errors3();
|
|
36499
36596
|
init_confine();
|
|
36500
36597
|
init_write();
|
|
36501
36598
|
}
|
|
@@ -36576,6 +36673,7 @@ var init_scrape = __esm({
|
|
|
36576
36673
|
// node_modules/@chrischall/mcp-utils/dist/http/throttle.js
|
|
36577
36674
|
var init_throttle = __esm({
|
|
36578
36675
|
"node_modules/@chrischall/mcp-utils/dist/http/throttle.js"() {
|
|
36676
|
+
init_cancel();
|
|
36579
36677
|
}
|
|
36580
36678
|
});
|
|
36581
36679
|
|
|
@@ -36601,6 +36699,113 @@ var init_net_atoms = __esm({
|
|
|
36601
36699
|
}
|
|
36602
36700
|
});
|
|
36603
36701
|
|
|
36702
|
+
// node_modules/@chrischall/mcp-utils/dist/http/body-limit.js
|
|
36703
|
+
async function readBytesCapped(res, maxBytes, service, bounded) {
|
|
36704
|
+
if (maxBytes !== void 0) {
|
|
36705
|
+
const declared = Number(res.headers.get("content-length") ?? NaN);
|
|
36706
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
36707
|
+
cancelStream(res.body);
|
|
36708
|
+
throw new ResponseTooLargeError(service, maxBytes);
|
|
36709
|
+
}
|
|
36710
|
+
}
|
|
36711
|
+
if (!res.body)
|
|
36712
|
+
return new Uint8Array(0);
|
|
36713
|
+
const reader = res.body.getReader();
|
|
36714
|
+
const chunks = [];
|
|
36715
|
+
let total = 0;
|
|
36716
|
+
try {
|
|
36717
|
+
for (; ; ) {
|
|
36718
|
+
const { done, value } = await bounded(reader.read());
|
|
36719
|
+
if (done)
|
|
36720
|
+
break;
|
|
36721
|
+
total += value.byteLength;
|
|
36722
|
+
if (maxBytes !== void 0 && total > maxBytes)
|
|
36723
|
+
throw new ResponseTooLargeError(service, maxBytes);
|
|
36724
|
+
chunks.push(value);
|
|
36725
|
+
}
|
|
36726
|
+
} catch (err) {
|
|
36727
|
+
reader.cancel().catch(() => {
|
|
36728
|
+
});
|
|
36729
|
+
throw err;
|
|
36730
|
+
}
|
|
36731
|
+
const out = new Uint8Array(total);
|
|
36732
|
+
let offset = 0;
|
|
36733
|
+
for (const c of chunks) {
|
|
36734
|
+
out.set(c, offset);
|
|
36735
|
+
offset += c.byteLength;
|
|
36736
|
+
}
|
|
36737
|
+
return out;
|
|
36738
|
+
}
|
|
36739
|
+
function cancelStream(body) {
|
|
36740
|
+
try {
|
|
36741
|
+
body?.cancel().catch(() => {
|
|
36742
|
+
});
|
|
36743
|
+
} catch {
|
|
36744
|
+
}
|
|
36745
|
+
}
|
|
36746
|
+
var ResponseTooLargeError;
|
|
36747
|
+
var init_body_limit = __esm({
|
|
36748
|
+
"node_modules/@chrischall/mcp-utils/dist/http/body-limit.js"() {
|
|
36749
|
+
init_errors3();
|
|
36750
|
+
ResponseTooLargeError = class extends McpToolError {
|
|
36751
|
+
maxBytes;
|
|
36752
|
+
constructor(service, maxBytes) {
|
|
36753
|
+
super(`Response from ${service} is larger than the ${maxBytes}-byte limit.`, {
|
|
36754
|
+
hint: "Raise maxBytes (or the client's maxResponseBytes) if a response this large is expected.",
|
|
36755
|
+
kind: "too_large"
|
|
36756
|
+
});
|
|
36757
|
+
this.name = "ResponseTooLargeError";
|
|
36758
|
+
this.maxBytes = maxBytes;
|
|
36759
|
+
}
|
|
36760
|
+
};
|
|
36761
|
+
}
|
|
36762
|
+
});
|
|
36763
|
+
|
|
36764
|
+
// node_modules/@chrischall/mcp-utils/dist/http/fetch-bounded.js
|
|
36765
|
+
var init_fetch_bounded = __esm({
|
|
36766
|
+
"node_modules/@chrischall/mcp-utils/dist/http/fetch-bounded.js"() {
|
|
36767
|
+
init_cancel();
|
|
36768
|
+
init_errors3();
|
|
36769
|
+
init_body_limit();
|
|
36770
|
+
init_http();
|
|
36771
|
+
}
|
|
36772
|
+
});
|
|
36773
|
+
|
|
36774
|
+
// node_modules/@chrischall/mcp-utils/dist/http/url-safety.js
|
|
36775
|
+
function isDotSegment(segment) {
|
|
36776
|
+
const s = segment.replace(/%2e/gi, ".");
|
|
36777
|
+
return s === "." || s === "..";
|
|
36778
|
+
}
|
|
36779
|
+
function pathPartOf(path) {
|
|
36780
|
+
const end = path.search(/[?#]/);
|
|
36781
|
+
return end === -1 ? path : path.slice(0, end);
|
|
36782
|
+
}
|
|
36783
|
+
function findPathHazard(path) {
|
|
36784
|
+
const pathPart = pathPartOf(path);
|
|
36785
|
+
if (/[\x00-\x1f\x7f]/.test(pathPart))
|
|
36786
|
+
return "control character";
|
|
36787
|
+
if (pathPart.endsWith(" "))
|
|
36788
|
+
return "trailing space";
|
|
36789
|
+
if (pathPart.includes("\\"))
|
|
36790
|
+
return "backslash";
|
|
36791
|
+
if (pathPart.split("/").some(isDotSegment))
|
|
36792
|
+
return "dot segment";
|
|
36793
|
+
return void 0;
|
|
36794
|
+
}
|
|
36795
|
+
var RedirectRefusedError;
|
|
36796
|
+
var init_url_safety = __esm({
|
|
36797
|
+
"node_modules/@chrischall/mcp-utils/dist/http/url-safety.js"() {
|
|
36798
|
+
init_errors3();
|
|
36799
|
+
init_config();
|
|
36800
|
+
RedirectRefusedError = class extends McpToolError {
|
|
36801
|
+
constructor(message) {
|
|
36802
|
+
super(message);
|
|
36803
|
+
this.name = "RedirectRefusedError";
|
|
36804
|
+
}
|
|
36805
|
+
};
|
|
36806
|
+
}
|
|
36807
|
+
});
|
|
36808
|
+
|
|
36604
36809
|
// node_modules/@chrischall/mcp-utils/dist/internal/retry-after.js
|
|
36605
36810
|
function retryAfterToMs(header, now = Date.now()) {
|
|
36606
36811
|
if (header == null)
|
|
@@ -36621,6 +36826,14 @@ var init_retry_after = __esm({
|
|
|
36621
36826
|
});
|
|
36622
36827
|
|
|
36623
36828
|
// node_modules/@chrischall/mcp-utils/dist/http/index.js
|
|
36829
|
+
function cancelBody(res) {
|
|
36830
|
+
try {
|
|
36831
|
+
const body = res.body;
|
|
36832
|
+
body?.cancel?.()?.catch(() => {
|
|
36833
|
+
});
|
|
36834
|
+
} catch {
|
|
36835
|
+
}
|
|
36836
|
+
}
|
|
36624
36837
|
function headerOf(headers, name) {
|
|
36625
36838
|
if (headers === void 0)
|
|
36626
36839
|
return void 0;
|
|
@@ -36646,6 +36859,49 @@ function detectEdgeBlock(input2) {
|
|
|
36646
36859
|
}
|
|
36647
36860
|
return null;
|
|
36648
36861
|
}
|
|
36862
|
+
function jsonKind(value) {
|
|
36863
|
+
if (value === null)
|
|
36864
|
+
return "null";
|
|
36865
|
+
if (Array.isArray(value))
|
|
36866
|
+
return "array";
|
|
36867
|
+
return typeof value;
|
|
36868
|
+
}
|
|
36869
|
+
function parseJsonBody(text, opts = {}) {
|
|
36870
|
+
const contentType = opts.contentType ?? headerOf(opts.headers, "content-type");
|
|
36871
|
+
const details = {
|
|
36872
|
+
service: opts.service,
|
|
36873
|
+
method: opts.method,
|
|
36874
|
+
path: opts.path,
|
|
36875
|
+
status: opts.status,
|
|
36876
|
+
contentType,
|
|
36877
|
+
expected: opts.expect
|
|
36878
|
+
};
|
|
36879
|
+
if (text.trim().length === 0) {
|
|
36880
|
+
if (opts.expect === void 0)
|
|
36881
|
+
return void 0;
|
|
36882
|
+
throw new UpstreamFormatError({ ...details, received: "empty" });
|
|
36883
|
+
}
|
|
36884
|
+
let value;
|
|
36885
|
+
try {
|
|
36886
|
+
value = JSON.parse(text);
|
|
36887
|
+
} catch (cause) {
|
|
36888
|
+
const edge = detectEdgeBlock({ body: text, headers: opts.headers, status: opts.status });
|
|
36889
|
+
if (edge) {
|
|
36890
|
+
throw new EdgeBlockedError(opts.status ?? 200, edge.vendor, {
|
|
36891
|
+
service: opts.service ?? "the upstream service",
|
|
36892
|
+
method: opts.method,
|
|
36893
|
+
path: opts.path
|
|
36894
|
+
});
|
|
36895
|
+
}
|
|
36896
|
+
throw new UpstreamFormatError({ ...details, expected: void 0, received: "non-json", cause });
|
|
36897
|
+
}
|
|
36898
|
+
if (opts.expect !== void 0) {
|
|
36899
|
+
const kind = jsonKind(value);
|
|
36900
|
+
if (kind !== opts.expect)
|
|
36901
|
+
throw new UpstreamFormatError({ ...details, received: kind });
|
|
36902
|
+
}
|
|
36903
|
+
return value;
|
|
36904
|
+
}
|
|
36649
36905
|
function httpError(res, text, method, path, service) {
|
|
36650
36906
|
const edge = detectEdgeBlock({ body: text, headers: res.headers, status: res.status });
|
|
36651
36907
|
if (edge)
|
|
@@ -36696,13 +36952,26 @@ function createApiClient(opts) {
|
|
|
36696
36952
|
const sleep3 = opts.sleep ?? defaultSleep;
|
|
36697
36953
|
const unauthorized = () => opts.onUnauthorized ? opts.onUnauthorized() : new UnauthorizedError(service);
|
|
36698
36954
|
const retryStatuses = retry.statuses ?? [429];
|
|
36699
|
-
const timeoutMs = opts.timeout;
|
|
36955
|
+
const timeoutMs = opts.timeout === false ? 0 : opts.timeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
36956
|
+
const redirectMode = opts.redirect;
|
|
36957
|
+
const maxRedirects = opts.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
|
36958
|
+
const reportWriteOutcome = opts.writeOutcomeUnknown !== false;
|
|
36959
|
+
const defaultMaxBytes = checkMaxBytes("maxResponseBytes", opts.maxResponseBytes);
|
|
36960
|
+
if (!Number.isSafeInteger(maxRedirects) || maxRedirects < 0) {
|
|
36961
|
+
throw new TypeError(`createApiClient: maxRedirects must be a non-negative integer, got ${String(maxRedirects)}.`);
|
|
36962
|
+
}
|
|
36700
36963
|
const authHeader = (token) => !token ? {} : opts.tokenHeader ? { [opts.tokenHeader]: token } : { Authorization: `Bearer ${token}` };
|
|
36964
|
+
const transportFailures = /* @__PURE__ */ new WeakSet();
|
|
36965
|
+
const markTransportFailure = (err) => {
|
|
36966
|
+
if (typeof err === "object" && err !== null && err.name !== "AbortError") {
|
|
36967
|
+
transportFailures.add(err);
|
|
36968
|
+
}
|
|
36969
|
+
};
|
|
36701
36970
|
async function withTimeout(run) {
|
|
36702
|
-
if (timeoutMs
|
|
36971
|
+
if (timeoutMs <= 0) {
|
|
36703
36972
|
const res2 = await run(withAmbientCancellation(void 0));
|
|
36704
36973
|
return { res: res2, done: () => {
|
|
36705
|
-
}, expired: void 0 };
|
|
36974
|
+
}, expired: void 0, timedOut: () => false };
|
|
36706
36975
|
}
|
|
36707
36976
|
const controller = new AbortController();
|
|
36708
36977
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -36723,16 +36992,11 @@ function createApiClient(opts) {
|
|
|
36723
36992
|
});
|
|
36724
36993
|
expired.catch(() => {
|
|
36725
36994
|
});
|
|
36726
|
-
return { res, done: () => clearTimeout(timer), expired };
|
|
36995
|
+
return { res, done: () => clearTimeout(timer), expired, timedOut: () => controller.signal.aborted };
|
|
36727
36996
|
}
|
|
36728
36997
|
function discard(attempt) {
|
|
36729
36998
|
attempt.done();
|
|
36730
|
-
|
|
36731
|
-
const body = attempt.res.body;
|
|
36732
|
-
body?.cancel?.()?.catch(() => {
|
|
36733
|
-
});
|
|
36734
|
-
} catch {
|
|
36735
|
-
}
|
|
36999
|
+
cancelBody(attempt.res);
|
|
36736
37000
|
}
|
|
36737
37001
|
async function readBody(attempt, read) {
|
|
36738
37002
|
try {
|
|
@@ -36740,15 +37004,40 @@ function createApiClient(opts) {
|
|
|
36740
37004
|
return await read(attempt.res);
|
|
36741
37005
|
return await Promise.race([read(attempt.res), attempt.expired]);
|
|
36742
37006
|
} catch (err) {
|
|
36743
|
-
|
|
36744
|
-
|
|
36745
|
-
|
|
37007
|
+
const timeout = err instanceof RequestTimeoutError || err instanceof Error && err.name === "AbortError" && attempt.timedOut();
|
|
37008
|
+
if (timeout) {
|
|
37009
|
+
cancelBody(attempt.res);
|
|
37010
|
+
throw err instanceof RequestTimeoutError ? err : new RequestTimeoutError(service, timeoutMs);
|
|
36746
37011
|
}
|
|
37012
|
+
if (!(err instanceof ResponseTooLargeError))
|
|
37013
|
+
markTransportFailure(err);
|
|
36747
37014
|
throw err;
|
|
36748
37015
|
} finally {
|
|
36749
37016
|
attempt.done();
|
|
36750
37017
|
}
|
|
36751
37018
|
}
|
|
37019
|
+
function readBytes(attempt, maxBytes) {
|
|
37020
|
+
if (maxBytes === void 0)
|
|
37021
|
+
return readBody(attempt, async (r) => new Uint8Array(await r.arrayBuffer()));
|
|
37022
|
+
const race = (p) => attempt.expired ? Promise.race([p, attempt.expired]) : p;
|
|
37023
|
+
return readBody(attempt, (r) => readBytesCapped(r, maxBytes, service, race));
|
|
37024
|
+
}
|
|
37025
|
+
async function readText(attempt, maxBytes) {
|
|
37026
|
+
if (maxBytes === void 0)
|
|
37027
|
+
return readBody(attempt, (r) => r.text());
|
|
37028
|
+
return new TextDecoder().decode(await readBytes(attempt, maxBytes));
|
|
37029
|
+
}
|
|
37030
|
+
function readErrorText(attempt, maxBytes) {
|
|
37031
|
+
return readText(attempt, maxBytes).catch((err) => {
|
|
37032
|
+
if (err instanceof ResponseTooLargeError)
|
|
37033
|
+
return "";
|
|
37034
|
+
throw err;
|
|
37035
|
+
});
|
|
37036
|
+
}
|
|
37037
|
+
function scanText(attempt, maxBytes) {
|
|
37038
|
+
return readText(attempt, maxBytes).catch(() => "");
|
|
37039
|
+
}
|
|
37040
|
+
const capOf = (opt) => opt.maxBytes !== void 0 ? checkMaxBytes("maxBytes", opt.maxBytes) : defaultMaxBytes;
|
|
36752
37041
|
const baseParts = (() => {
|
|
36753
37042
|
try {
|
|
36754
37043
|
const u = new URL(base);
|
|
@@ -36759,6 +37048,14 @@ function createApiClient(opts) {
|
|
|
36759
37048
|
})();
|
|
36760
37049
|
const baseOrigin = baseParts?.origin;
|
|
36761
37050
|
function resolveUrl(path, query) {
|
|
37051
|
+
const hazard = findPathHazard(path);
|
|
37052
|
+
if (hazard !== void 0) {
|
|
37053
|
+
throw new Error(
|
|
37054
|
+
// Quote only the path part: this fires on hostile input, and the
|
|
37055
|
+
// query may carry values that do not belong in a log line.
|
|
37056
|
+
`Refusing request to ${service}: path ${JSON.stringify(path.split(/[?#]/, 1)[0])} contains a ${hazard}, which URL normalisation would rewrite. Build paths with apiPath\`...\` so each value is one encoded segment.`
|
|
37057
|
+
);
|
|
37058
|
+
}
|
|
36762
37059
|
const url2 = `${base}${path}${query}`;
|
|
36763
37060
|
if (baseOrigin === void 0)
|
|
36764
37061
|
return url2;
|
|
@@ -36773,23 +37070,66 @@ function createApiClient(opts) {
|
|
|
36773
37070
|
}
|
|
36774
37071
|
return url2;
|
|
36775
37072
|
}
|
|
36776
|
-
|
|
37073
|
+
function sameOriginHop(location, from) {
|
|
37074
|
+
let next;
|
|
37075
|
+
try {
|
|
37076
|
+
next = new URL(location, from);
|
|
37077
|
+
} catch {
|
|
37078
|
+
throw new RedirectRefusedError(`Refusing redirect from ${service}: the Location header is not a valid URL.`);
|
|
37079
|
+
}
|
|
37080
|
+
if (baseOrigin === void 0 || next.origin !== baseOrigin || next.username !== baseParts?.username || next.password !== baseParts?.password) {
|
|
37081
|
+
throw new RedirectRefusedError(`Refusing redirect from ${service} to ${next.origin}: it leaves the base origin ${baseOrigin ?? "(unparseable)"}, and following it would re-send the credential there.`);
|
|
37082
|
+
}
|
|
37083
|
+
return next.href;
|
|
37084
|
+
}
|
|
37085
|
+
async function send(method, path, opt, dispatch) {
|
|
36777
37086
|
const { body: reqBody, contentType } = encodeBody(opt);
|
|
36778
37087
|
const query = opt.query ? buildQueryString(opt.query) : "";
|
|
36779
37088
|
const url2 = resolveUrl(path, query);
|
|
36780
37089
|
const bodyInit = reqBody !== void 0 ? { body: reqBody } : {};
|
|
36781
|
-
const
|
|
36782
|
-
|
|
36783
|
-
|
|
36784
|
-
|
|
36785
|
-
|
|
36786
|
-
|
|
36787
|
-
|
|
36788
|
-
|
|
36789
|
-
|
|
36790
|
-
|
|
36791
|
-
|
|
36792
|
-
|
|
37090
|
+
const fetchHop = (hopUrl, hopMethod, withBody, token, signal) => {
|
|
37091
|
+
if (!signal?.aborted)
|
|
37092
|
+
dispatch.sent = true;
|
|
37093
|
+
return doFetch(hopUrl, {
|
|
37094
|
+
method: hopMethod,
|
|
37095
|
+
headers: {
|
|
37096
|
+
Accept: "application/json",
|
|
37097
|
+
...withBody && contentType !== void 0 ? { "Content-Type": contentType } : {},
|
|
37098
|
+
...opts.baseHeaders,
|
|
37099
|
+
...authHeader(token || void 0),
|
|
37100
|
+
...opt.headers
|
|
37101
|
+
},
|
|
37102
|
+
...signal ? { signal } : {},
|
|
37103
|
+
...redirectMode !== void 0 ? { redirect: redirectMode === "same-origin" ? "manual" : redirectMode } : {},
|
|
37104
|
+
...withBody ? bodyInit : {}
|
|
37105
|
+
}).catch((err) => {
|
|
37106
|
+
markTransportFailure(err);
|
|
37107
|
+
throw err;
|
|
37108
|
+
});
|
|
37109
|
+
};
|
|
37110
|
+
const fetchWith = async (token, signal) => {
|
|
37111
|
+
if (redirectMode !== "same-origin")
|
|
37112
|
+
return fetchHop(url2, method, true, token, signal);
|
|
37113
|
+
let hopUrl = url2;
|
|
37114
|
+
let hopMethod = method;
|
|
37115
|
+
let withBody = true;
|
|
37116
|
+
for (let hops = 0; ; hops += 1) {
|
|
37117
|
+
const res = await fetchHop(hopUrl, hopMethod, withBody, token, signal);
|
|
37118
|
+
const location = REDIRECT_STATUSES.has(res.status) ? responseHeader(res, "location") : void 0;
|
|
37119
|
+
if (location === void 0 || location === null)
|
|
37120
|
+
return res;
|
|
37121
|
+
cancelBody(res);
|
|
37122
|
+
if (hops >= maxRedirects) {
|
|
37123
|
+
throw new RedirectRefusedError(`Refusing request to ${service}: too many redirects (more than ${maxRedirects}).`);
|
|
37124
|
+
}
|
|
37125
|
+
hopUrl = sameOriginHop(location, hopUrl);
|
|
37126
|
+
if (res.status === 303 || (res.status === 301 || res.status === 302) && hopMethod === "POST") {
|
|
37127
|
+
if (hopMethod !== "HEAD")
|
|
37128
|
+
hopMethod = "GET";
|
|
37129
|
+
withBody = false;
|
|
37130
|
+
}
|
|
37131
|
+
}
|
|
37132
|
+
};
|
|
36793
37133
|
const once = () => withTimeout((signal) => opts.tokenManager ? opts.tokenManager.withAuth((token) => fetchWith(token, signal)) : (async () => fetchWith(await opts.getToken?.() || void 0, signal))());
|
|
36794
37134
|
let attempt = 0;
|
|
36795
37135
|
for (; ; ) {
|
|
@@ -36808,20 +37148,20 @@ function createApiClient(opts) {
|
|
|
36808
37148
|
return current;
|
|
36809
37149
|
}
|
|
36810
37150
|
}
|
|
36811
|
-
async function unauthorizedOrEdge(attempt, method, path) {
|
|
37151
|
+
async function unauthorizedOrEdge(attempt, method, path, maxBytes) {
|
|
36812
37152
|
const res = attempt.res;
|
|
36813
37153
|
let text = "";
|
|
36814
37154
|
if (/json/i.test(responseHeader(res, "content-type") ?? "")) {
|
|
36815
37155
|
discard(attempt);
|
|
36816
37156
|
} else {
|
|
36817
|
-
text = await
|
|
37157
|
+
text = await scanText(attempt, maxBytes);
|
|
36818
37158
|
}
|
|
36819
37159
|
const edge = detectEdgeBlock({ body: text, headers: res.headers, status: res.status });
|
|
36820
37160
|
if (edge)
|
|
36821
37161
|
return new EdgeBlockedError(res.status, edge.vendor, { service, method, path });
|
|
36822
37162
|
return unauthorized();
|
|
36823
37163
|
}
|
|
36824
|
-
async function rateLimitedError(attempt, method, path) {
|
|
37164
|
+
async function rateLimitedError(attempt, method, path, maxBytes) {
|
|
36825
37165
|
const res = attempt.res;
|
|
36826
37166
|
const retryAfter = responseHeader(res, "retry-after");
|
|
36827
37167
|
const retryAfterMs = retryAfterToMs(retryAfter);
|
|
@@ -36833,61 +37173,92 @@ function createApiClient(opts) {
|
|
|
36833
37173
|
if (/json/i.test(responseHeader(res, "content-type") ?? "")) {
|
|
36834
37174
|
discard(attempt);
|
|
36835
37175
|
} else {
|
|
36836
|
-
text = await
|
|
37176
|
+
text = await scanText(attempt, maxBytes);
|
|
36837
37177
|
}
|
|
36838
37178
|
const edgeBlock = detectEdgeBlock({ body: text, headers: res.headers, status: res.status });
|
|
36839
37179
|
return opts.onRateLimited({ status: res.status, retryAfter, retryAfterMs, edgeBlock, method, path });
|
|
36840
37180
|
}
|
|
36841
|
-
async function
|
|
36842
|
-
const
|
|
37181
|
+
async function guardWrite(method, opt, call) {
|
|
37182
|
+
const dispatch = { sent: false };
|
|
37183
|
+
try {
|
|
37184
|
+
return await call(dispatch);
|
|
37185
|
+
} catch (err) {
|
|
37186
|
+
const write = reportWriteOutcome && opt.idempotent !== true && !SAFE_METHODS.has(method.toUpperCase());
|
|
37187
|
+
if (!write || !dispatch.sent)
|
|
37188
|
+
throw err;
|
|
37189
|
+
if (err instanceof RequestTimeoutError) {
|
|
37190
|
+
throw new WriteOutcomeUnknownError(service, method, {
|
|
37191
|
+
timeoutMs: err.timeoutMs,
|
|
37192
|
+
cause: err,
|
|
37193
|
+
hint: opts.writeOutcomeHint
|
|
37194
|
+
});
|
|
37195
|
+
}
|
|
37196
|
+
if (typeof err === "object" && err !== null && transportFailures.has(err)) {
|
|
37197
|
+
throw new WriteOutcomeUnknownError(service, method, { cause: err, hint: opts.writeOutcomeHint });
|
|
37198
|
+
}
|
|
37199
|
+
throw err;
|
|
37200
|
+
}
|
|
37201
|
+
}
|
|
37202
|
+
const fetchJson = (method, path, opt = {}) => guardWrite(method, opt, (dispatch) => fetchJsonOnce(method, path, opt, dispatch));
|
|
37203
|
+
const fetchHtml = (method, path, opt = {}) => guardWrite(method, opt, (dispatch) => fetchHtmlOnce(method, path, opt, dispatch));
|
|
37204
|
+
const fetchRaw = (method, path, opt = {}) => guardWrite(method, opt, (dispatch) => fetchRawOnce(method, path, opt, dispatch));
|
|
37205
|
+
async function fetchJsonOnce(method, path, opt, dispatch) {
|
|
37206
|
+
const maxBytes = capOf(opt);
|
|
37207
|
+
const attempt = await send(method, path, opt, dispatch);
|
|
36843
37208
|
const res = attempt.res;
|
|
36844
37209
|
if (res.status === 401)
|
|
36845
|
-
throw await unauthorizedOrEdge(attempt, method, path);
|
|
37210
|
+
throw await unauthorizedOrEdge(attempt, method, path, maxBytes);
|
|
36846
37211
|
if (res.status === 429)
|
|
36847
|
-
throw await rateLimitedError(attempt, method, path);
|
|
37212
|
+
throw await rateLimitedError(attempt, method, path, maxBytes);
|
|
36848
37213
|
if (res.status === 204) {
|
|
36849
37214
|
discard(attempt);
|
|
36850
|
-
return
|
|
37215
|
+
return parseJsonBody("", { expect: opt.expect, service, method, path, status: 204 });
|
|
36851
37216
|
}
|
|
36852
|
-
const text = await readBody(attempt, (r) => r.text());
|
|
36853
37217
|
if (!res.ok) {
|
|
36854
|
-
throw httpError(res,
|
|
37218
|
+
throw httpError(res, await readErrorText(attempt, maxBytes), method, path, service);
|
|
36855
37219
|
}
|
|
36856
|
-
|
|
36857
|
-
|
|
36858
|
-
|
|
37220
|
+
const text = await readText(attempt, maxBytes);
|
|
37221
|
+
return parseJsonBody(text, {
|
|
37222
|
+
expect: opt.expect,
|
|
37223
|
+
service,
|
|
37224
|
+
method,
|
|
37225
|
+
path,
|
|
37226
|
+
status: res.status,
|
|
37227
|
+
headers: res.headers
|
|
37228
|
+
});
|
|
36859
37229
|
}
|
|
36860
|
-
async function
|
|
37230
|
+
async function fetchHtmlOnce(method, path, opt, dispatch) {
|
|
37231
|
+
const maxBytes = capOf(opt);
|
|
36861
37232
|
const headers = { Accept: "text/html,*/*", ...opt.headers };
|
|
36862
|
-
const attempt = await send(method, path, { ...opt, headers });
|
|
37233
|
+
const attempt = await send(method, path, { ...opt, headers }, dispatch);
|
|
36863
37234
|
const res = attempt.res;
|
|
36864
37235
|
if (res.status === 401)
|
|
36865
|
-
throw await unauthorizedOrEdge(attempt, method, path);
|
|
37236
|
+
throw await unauthorizedOrEdge(attempt, method, path, maxBytes);
|
|
36866
37237
|
if (res.status === 429)
|
|
36867
|
-
throw await rateLimitedError(attempt, method, path);
|
|
36868
|
-
const text = await readBody(attempt, (r) => r.text());
|
|
37238
|
+
throw await rateLimitedError(attempt, method, path, maxBytes);
|
|
36869
37239
|
if (!res.ok) {
|
|
36870
|
-
throw httpError(res,
|
|
37240
|
+
throw httpError(res, await readErrorText(attempt, maxBytes), method, path, service);
|
|
36871
37241
|
}
|
|
36872
|
-
return
|
|
37242
|
+
return readText(attempt, maxBytes);
|
|
36873
37243
|
}
|
|
36874
|
-
async function
|
|
37244
|
+
async function fetchRawOnce(method, path, opt, dispatch) {
|
|
37245
|
+
const maxBytes = capOf(opt);
|
|
36875
37246
|
const headers = { Accept: "*/*", ...opt.headers };
|
|
36876
|
-
const attempt = await send(method, path, { ...opt, headers });
|
|
37247
|
+
const attempt = await send(method, path, { ...opt, headers }, dispatch);
|
|
36877
37248
|
const res = attempt.res;
|
|
36878
37249
|
if (res.status === 401)
|
|
36879
|
-
throw await unauthorizedOrEdge(attempt, method, path);
|
|
37250
|
+
throw await unauthorizedOrEdge(attempt, method, path, maxBytes);
|
|
36880
37251
|
if (res.status === 429)
|
|
36881
|
-
throw await rateLimitedError(attempt, method, path);
|
|
37252
|
+
throw await rateLimitedError(attempt, method, path, maxBytes);
|
|
36882
37253
|
if (!res.ok) {
|
|
36883
|
-
const text = await
|
|
37254
|
+
const text = await readText(attempt, maxBytes).catch((err) => {
|
|
36884
37255
|
if (err instanceof RequestTimeoutError)
|
|
36885
37256
|
throw err;
|
|
36886
37257
|
return "";
|
|
36887
37258
|
});
|
|
36888
37259
|
throw httpError(res, text, method, path, service);
|
|
36889
37260
|
}
|
|
36890
|
-
const bytes =
|
|
37261
|
+
const bytes = await readBytes(attempt, maxBytes);
|
|
36891
37262
|
return {
|
|
36892
37263
|
status: res.status,
|
|
36893
37264
|
contentType: responseHeader(res, "content-type"),
|
|
@@ -36897,6 +37268,14 @@ function createApiClient(opts) {
|
|
|
36897
37268
|
}
|
|
36898
37269
|
return { fetchJson, fetchHtml, fetchRaw };
|
|
36899
37270
|
}
|
|
37271
|
+
function checkMaxBytes(name, value) {
|
|
37272
|
+
if (value === void 0)
|
|
37273
|
+
return void 0;
|
|
37274
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
37275
|
+
throw new TypeError(`createApiClient: ${name} must be a non-negative number, got ${String(value)}.`);
|
|
37276
|
+
}
|
|
37277
|
+
return value === Infinity ? void 0 : value;
|
|
37278
|
+
}
|
|
36900
37279
|
function buildQueryString(params) {
|
|
36901
37280
|
const parts = [];
|
|
36902
37281
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -36947,7 +37326,7 @@ function decodeJwtExp(token) {
|
|
|
36947
37326
|
}
|
|
36948
37327
|
return exp;
|
|
36949
37328
|
}
|
|
36950
|
-
var DEFAULT_RETRY, defaultSleep, UnauthorizedError, RateLimitedError, RequestTimeoutError, ApiError, EdgeBlockedError, EDGE_BLOCK_SCAN_MAX, EDGE_BLOCK_MARKERS;
|
|
37329
|
+
var DEFAULT_RETRY, defaultSleep, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_MAX_REDIRECTS, REDIRECT_STATUSES, UnauthorizedError, RateLimitedError, RequestTimeoutError, DEFAULT_WRITE_OUTCOME_HINT, WriteOutcomeUnknownError, SAFE_METHODS, ApiError, EdgeBlockedError, EDGE_BLOCK_SCAN_MAX, EDGE_BLOCK_MARKERS;
|
|
36951
37330
|
var init_http = __esm({
|
|
36952
37331
|
"node_modules/@chrischall/mcp-utils/dist/http/index.js"() {
|
|
36953
37332
|
init_headers();
|
|
@@ -36957,12 +37336,21 @@ var init_http = __esm({
|
|
|
36957
37336
|
init_throttle();
|
|
36958
37337
|
init_response_cache();
|
|
36959
37338
|
init_net_atoms();
|
|
37339
|
+
init_fetch_bounded();
|
|
37340
|
+
init_url_safety();
|
|
37341
|
+
init_body_limit();
|
|
36960
37342
|
init_net_atoms();
|
|
37343
|
+
init_url_safety();
|
|
36961
37344
|
init_retry_after();
|
|
36962
37345
|
DEFAULT_RETRY = { count: 1, delayMs: 2e3 };
|
|
36963
37346
|
defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
37347
|
+
DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
37348
|
+
DEFAULT_MAX_REDIRECTS = 5;
|
|
37349
|
+
REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
36964
37350
|
UnauthorizedError = class extends Error {
|
|
36965
37351
|
status = 401;
|
|
37352
|
+
/** Always `'credential_rejected'` unless a subclass says otherwise. */
|
|
37353
|
+
kind = "credential_rejected";
|
|
36966
37354
|
constructor(service) {
|
|
36967
37355
|
super(`Unauthorized (401) from ${service} \u2014 the token is missing, invalid, or expired.`);
|
|
36968
37356
|
this.name = "UnauthorizedError";
|
|
@@ -36971,6 +37359,8 @@ var init_http = __esm({
|
|
|
36971
37359
|
};
|
|
36972
37360
|
RateLimitedError = class extends Error {
|
|
36973
37361
|
status = 429;
|
|
37362
|
+
/** `'http'`: the far side answered, with a 429 (see `status`). */
|
|
37363
|
+
kind = "http";
|
|
36974
37364
|
retryAfterMs;
|
|
36975
37365
|
constructor(service, retryAfterMs) {
|
|
36976
37366
|
super(`Rate limited (429) by ${service} after retries.`);
|
|
@@ -36981,6 +37371,8 @@ var init_http = __esm({
|
|
|
36981
37371
|
};
|
|
36982
37372
|
RequestTimeoutError = class extends Error {
|
|
36983
37373
|
timeoutMs;
|
|
37374
|
+
/** Always `'timeout'` unless a subclass says otherwise. */
|
|
37375
|
+
kind = "timeout";
|
|
36984
37376
|
constructor(service, timeoutMs) {
|
|
36985
37377
|
super(`Request to ${service} timed out after ${timeoutMs}ms.`);
|
|
36986
37378
|
this.name = "RequestTimeoutError";
|
|
@@ -36988,6 +37380,33 @@ var init_http = __esm({
|
|
|
36988
37380
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
36989
37381
|
}
|
|
36990
37382
|
};
|
|
37383
|
+
DEFAULT_WRITE_OUTCOME_HINT = "The write may have happened \u2014 check before retrying; do not resend blindly.";
|
|
37384
|
+
WriteOutcomeUnknownError = class extends McpToolError {
|
|
37385
|
+
/** Always `true`: whether the write happened is not known. */
|
|
37386
|
+
outcomeUnknown = true;
|
|
37387
|
+
/** Always `false`: never replay this request without checking first. */
|
|
37388
|
+
retrySafe = false;
|
|
37389
|
+
/** `true` when the request's timeout expired; `false` for a dropped connection or broken body. */
|
|
37390
|
+
timedOut;
|
|
37391
|
+
/** The timeout that expired, when {@link timedOut}. */
|
|
37392
|
+
timeoutMs;
|
|
37393
|
+
/** The request method, upper-cased. */
|
|
37394
|
+
method;
|
|
37395
|
+
constructor(service, method, opts = {}) {
|
|
37396
|
+
const verb = method.toUpperCase();
|
|
37397
|
+
const what = opts.timeoutMs !== void 0 ? `${verb} to ${service} timed out after ${opts.timeoutMs}ms` : `${verb} to ${service} failed: ${truncateErrorMessage(messageOf(opts.cause), 200)}`;
|
|
37398
|
+
super(`${what} \u2014 the write may already have been applied (outcome is unknown).`, {
|
|
37399
|
+
hint: opts.hint ?? DEFAULT_WRITE_OUTCOME_HINT,
|
|
37400
|
+
cause: opts.cause,
|
|
37401
|
+
kind: opts.timeoutMs !== void 0 ? "timeout" : "transport"
|
|
37402
|
+
});
|
|
37403
|
+
this.name = "WriteOutcomeUnknownError";
|
|
37404
|
+
this.timedOut = opts.timeoutMs !== void 0;
|
|
37405
|
+
this.timeoutMs = opts.timeoutMs;
|
|
37406
|
+
this.method = verb;
|
|
37407
|
+
}
|
|
37408
|
+
};
|
|
37409
|
+
SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS", "TRACE"]);
|
|
36991
37410
|
ApiError = class extends Error {
|
|
36992
37411
|
status;
|
|
36993
37412
|
constructor(status, message) {
|
|
@@ -37000,6 +37419,8 @@ var init_http = __esm({
|
|
|
37000
37419
|
EdgeBlockedError = class extends ApiError {
|
|
37001
37420
|
/** Which edge refused the request, e.g. `'CloudFront'` or `'Cloudflare'`. */
|
|
37002
37421
|
vendor;
|
|
37422
|
+
/** Always `'edge_blocked'`: the credential was never judged. */
|
|
37423
|
+
kind = "edge_blocked";
|
|
37003
37424
|
/**
|
|
37004
37425
|
* @param where The service the request was for, and — when the thrower knows
|
|
37005
37426
|
* it — the method and path, which the message names.
|
|
@@ -37063,6 +37484,7 @@ var init_concurrency = __esm({
|
|
|
37063
37484
|
// node_modules/@chrischall/mcp-utils/dist/dates/index.js
|
|
37064
37485
|
var init_dates = __esm({
|
|
37065
37486
|
"node_modules/@chrischall/mcp-utils/dist/dates/index.js"() {
|
|
37487
|
+
init_config();
|
|
37066
37488
|
}
|
|
37067
37489
|
});
|
|
37068
37490
|
|
|
@@ -37083,7 +37505,7 @@ var init_zod2 = __esm({
|
|
|
37083
37505
|
NonNegInt = external_exports.number().int().nonnegative();
|
|
37084
37506
|
NonEmptyString = external_exports.string().min(1);
|
|
37085
37507
|
IsoDate = external_exports.iso.date();
|
|
37086
|
-
IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
|
|
37508
|
+
IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30").overwrite((v) => v.padStart(5, "0"));
|
|
37087
37509
|
NumericIdString = external_exports.string().regex(/^\d+$/, "must be a numeric id (digits only)").describe("A numeric id string (digits only), safe to interpolate into a URL path.");
|
|
37088
37510
|
SafePathSegment = external_exports.string().min(1).regex(/^[^/?#\s]+$/, 'must not contain "/", "?", "#", or whitespace').refine((v) => !v.includes(".."), 'must not contain ".."').describe('A single URL path segment, safe to interpolate: no "/", "..", "?", "#", or whitespace.');
|
|
37089
37511
|
schemaOrigin = external_exports.string().optional().describe("Portal origin (e.g. https://<vendor>.example.co) selecting which active session to use. Optional when only one session is active.");
|
|
@@ -37118,6 +37540,7 @@ var init_auth = __esm({
|
|
|
37118
37540
|
init_config();
|
|
37119
37541
|
init_errors3();
|
|
37120
37542
|
init_http();
|
|
37543
|
+
init_cancel();
|
|
37121
37544
|
init_cached_token();
|
|
37122
37545
|
init_es256();
|
|
37123
37546
|
}
|
|
@@ -37148,7 +37571,7 @@ var init_version = __esm({
|
|
|
37148
37571
|
"src/version.ts"() {
|
|
37149
37572
|
"use strict";
|
|
37150
37573
|
PACKAGE_NAME = "office-outlook-mcp";
|
|
37151
|
-
VERSION = "0.
|
|
37574
|
+
VERSION = "0.4.1";
|
|
37152
37575
|
}
|
|
37153
37576
|
});
|
|
37154
37577
|
|
|
@@ -44875,7 +45298,7 @@ function assertUrlInDomains(field, url2, domains) {
|
|
|
44875
45298
|
const declared = domains.map((d) => JSON.stringify(d)).join(", ");
|
|
44876
45299
|
throw new Error(`FetchproxyServer: ${field} host "${host}" is outside declared domains [${declared}] \u2014 must be one of them or a subdomain`);
|
|
44877
45300
|
}
|
|
44878
|
-
function
|
|
45301
|
+
function parseJsonBody2(response) {
|
|
44879
45302
|
if (response.status === 204 || response.body.trim() === "")
|
|
44880
45303
|
return null;
|
|
44881
45304
|
return JSON.parse(response.body);
|
|
@@ -45944,7 +46367,7 @@ var init_ws_server = __esm({
|
|
|
45944
46367
|
*/
|
|
45945
46368
|
async getJson(path, opts = {}) {
|
|
45946
46369
|
const response = await this.get(path, this.applyJsonDefaults(opts));
|
|
45947
|
-
return
|
|
46370
|
+
return parseJsonBody2(response);
|
|
45948
46371
|
}
|
|
45949
46372
|
/**
|
|
45950
46373
|
* POST a JSON body and parse the response body as JSON. The body is
|
|
@@ -45963,7 +46386,7 @@ var init_ws_server = __esm({
|
|
|
45963
46386
|
headers,
|
|
45964
46387
|
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
45965
46388
|
});
|
|
45966
|
-
return
|
|
46389
|
+
return parseJsonBody2(response);
|
|
45967
46390
|
}
|
|
45968
46391
|
/**
|
|
45969
46392
|
* GET a path and return the response body as a string. Throws
|
|
@@ -46028,7 +46451,7 @@ var init_ws_server = __esm({
|
|
|
46028
46451
|
};
|
|
46029
46452
|
let data;
|
|
46030
46453
|
try {
|
|
46031
|
-
data =
|
|
46454
|
+
data = parseJsonBody2(response);
|
|
46032
46455
|
} catch (e) {
|
|
46033
46456
|
throw new Error(`fetchproxy ${method} ${path} \u2014 response was not JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
46034
46457
|
}
|
|
@@ -47164,15 +47587,12 @@ var init_dist4 = __esm({
|
|
|
47164
47587
|
});
|
|
47165
47588
|
|
|
47166
47589
|
// node_modules/@chrischall/mcp-utils/dist/healthcheck/index.js
|
|
47167
|
-
function statusOf2(err) {
|
|
47168
|
-
if (typeof err !== "object" || err === null)
|
|
47169
|
-
return void 0;
|
|
47170
|
-
const s = err.status ?? err.statusCode;
|
|
47171
|
-
return typeof s === "number" ? s : void 0;
|
|
47172
|
-
}
|
|
47173
47590
|
function isArm(kind) {
|
|
47174
47591
|
return kind !== void 0 && CREDENTIAL_ARMS.has(kind);
|
|
47175
47592
|
}
|
|
47593
|
+
function fromSource(source) {
|
|
47594
|
+
return source !== null ? ` from '${source}'` : "";
|
|
47595
|
+
}
|
|
47176
47596
|
function credentialHint(arm, prefix, hostLabel, source) {
|
|
47177
47597
|
switch (arm) {
|
|
47178
47598
|
case "ok":
|
|
@@ -47180,15 +47600,15 @@ function credentialHint(arm, prefix, hostLabel, source) {
|
|
|
47180
47600
|
case "no_credential":
|
|
47181
47601
|
return `No credential resolved. Nothing was available to authenticate with \u2014 sign in and reconnect the connector so ${prefix} receives a token, or set the documented environment variable.`;
|
|
47182
47602
|
case "credential_rejected":
|
|
47183
|
-
return `${hostLabel} rejected the credential
|
|
47603
|
+
return `${hostLabel} rejected the credential${fromSource(source)}. It is present but no longer valid \u2014 most often expired or revoked upstream. Re-authenticate and reconnect; retrying will not fix it.`;
|
|
47184
47604
|
case "edge_blocked":
|
|
47185
47605
|
return `${hostLabel} refused the request at its CDN/WAF before it reached the API, so the credential${source !== null ? ` from '${source}'` : ""} was never judged \u2014 re-signing in or rotating it will not help. This is usually a block on this host's IP address or request fingerprint: route through the ContextMint Bridge if this connector supports it, or retry later from a different network.`;
|
|
47186
47606
|
case "session_expired":
|
|
47187
|
-
return `The credential
|
|
47607
|
+
return `The credential${fromSource(source)} is configured, but no session is live \u2014 ${hostLabel} served a sign-in page rather than the data. Sign in again; a cookie-session portal expires these on its own, so this recurs between uses.`;
|
|
47188
47608
|
case "verification_pending":
|
|
47189
47609
|
return `${hostLabel} is holding the sign-in on a second factor rather than refusing it. Supply the verification code the ACCOUNT HOLDER received \u2014 the credential itself is not the problem, so changing it will not help.`;
|
|
47190
47610
|
case "timeout":
|
|
47191
|
-
return `The credential from '${source}' resolved, but ${hostLabel} did not answer in time. Usually transient \u2014 retry. If it persists, ${hostLabel} is slow or unreachable from here.`;
|
|
47611
|
+
return `${source !== null ? `The credential from '${source}' resolved, but ` : ""}${hostLabel} did not answer in time. Usually transient \u2014 retry. If it persists, ${hostLabel} is slow or unreachable from here.`;
|
|
47192
47612
|
case "http":
|
|
47193
47613
|
return `${hostLabel} answered with an error status that is not an auth rejection. That is USUALLY a ${hostLabel}-side problem rather than an auth one \u2014 but a 404 here more often means the probe path is wrong than that ${hostLabel} is broken, so check error.message and probe.url before concluding anything about the credential.`;
|
|
47194
47614
|
case "transport":
|
|
@@ -47209,7 +47629,8 @@ async function runCredentialHealthcheck(args) {
|
|
|
47209
47629
|
state = await resolveCredential();
|
|
47210
47630
|
} catch (e) {
|
|
47211
47631
|
const edge = edgeBlockOf(e);
|
|
47212
|
-
const
|
|
47632
|
+
const declared = errorKindOf(e);
|
|
47633
|
+
const classified = classifyThrown?.(e) ?? (edge !== null ? { kind: "edge_blocked", detail: { vendor: edge.vendor } } : declared !== void 0 ? { kind: declared } : void 0);
|
|
47213
47634
|
const result2 = {
|
|
47214
47635
|
ok: false,
|
|
47215
47636
|
// Still false, and still no source: a classification explains WHY
|
|
@@ -47256,10 +47677,11 @@ async function runCredentialHealthcheck(args) {
|
|
|
47256
47677
|
try {
|
|
47257
47678
|
await probeFn();
|
|
47258
47679
|
} catch (e) {
|
|
47259
|
-
status =
|
|
47680
|
+
status = errorStatusOf(e);
|
|
47260
47681
|
const aborted2 = e instanceof Error && e.name === "AbortError";
|
|
47261
47682
|
const edge = edgeBlockOf(e);
|
|
47262
|
-
|
|
47683
|
+
const declared = errorKindOf(e);
|
|
47684
|
+
arm = edge !== null ? "edge_blocked" : declared !== void 0 ? declared : status === 401 || status === 403 ? "credential_rejected" : status !== void 0 ? "http" : aborted2 || /timeout|timed out|ETIMEDOUT/i.test(messageOf(e)) ? "timeout" : /fetch failed|ENOTFOUND|ECONNREFUSED|ECONNRESET|network/i.test(messageOf(e)) ? "transport" : "unknown";
|
|
47263
47685
|
let kind = arm;
|
|
47264
47686
|
let detail = edge !== null ? { vendor: edge.vendor } : void 0;
|
|
47265
47687
|
const custom2 = classifyThrown?.(e);
|
|
@@ -47618,6 +48040,7 @@ init_dist2();
|
|
|
47618
48040
|
// node_modules/@chrischall/mcp-utils/dist/session/index.js
|
|
47619
48041
|
init_response();
|
|
47620
48042
|
init_config();
|
|
48043
|
+
init_errors3();
|
|
47621
48044
|
init_http();
|
|
47622
48045
|
init_edge_block();
|
|
47623
48046
|
import { existsSync as existsSync3, readFileSync as readFileSync3, mkdirSync as mkdirSync4, chmodSync as chmodSync2, renameSync as renameSync2, unlinkSync as unlinkSync4 } from "node:fs";
|
|
@@ -48099,7 +48522,9 @@ var TokenManager = class {
|
|
|
48099
48522
|
/** Spend `rt`, then install and persist what comes back. */
|
|
48100
48523
|
async exchange(rt) {
|
|
48101
48524
|
if (rt === void 0) {
|
|
48102
|
-
throw new
|
|
48525
|
+
throw new McpToolError("TokenManager: cannot refresh \u2014 no refresh token is available.", {
|
|
48526
|
+
kind: "no_credential"
|
|
48527
|
+
});
|
|
48103
48528
|
}
|
|
48104
48529
|
const tok = await this.refreshFn(rt);
|
|
48105
48530
|
this.tokens = {
|
|
@@ -48347,7 +48772,8 @@ var OutlookClient = class {
|
|
|
48347
48772
|
* — measured 9.4x smaller on a real message, and it also normalises a mixed
|
|
48348
48773
|
* HTML/Text collection to one type. `prefer` sets the `Prefer` header
|
|
48349
48774
|
* directly, for anything else the API takes there (notably
|
|
48350
|
-
* `outlook.timezone="…"`).
|
|
48775
|
+
* `outlook.timezone="…"`). `query` is serialised by mcp-utils'
|
|
48776
|
+
* `buildQueryString`, which drops `undefined`/empty values.
|
|
48351
48777
|
*/
|
|
48352
48778
|
async get(path, opts = {}) {
|
|
48353
48779
|
this.#requireConfigured();
|
|
@@ -48356,7 +48782,8 @@ var OutlookClient = class {
|
|
|
48356
48782
|
opts.prefer
|
|
48357
48783
|
].filter((p) => p !== void 0);
|
|
48358
48784
|
return this.#client().fetchJson("GET", path, {
|
|
48359
|
-
headers: prefer.length ? { Prefer: prefer.join(", ") } : void 0
|
|
48785
|
+
headers: prefer.length ? { Prefer: prefer.join(", ") } : void 0,
|
|
48786
|
+
...opts.query ? { query: opts.query } : {}
|
|
48360
48787
|
});
|
|
48361
48788
|
}
|
|
48362
48789
|
/**
|
|
@@ -48387,6 +48814,22 @@ var OutlookClient = class {
|
|
|
48387
48814
|
}
|
|
48388
48815
|
return this.get(target.pathname.slice(prefix.length) + target.search, opts);
|
|
48389
48816
|
}
|
|
48817
|
+
/**
|
|
48818
|
+
* POST a read-only query — `findmeetingtimes`, `getschedule` — that changes
|
|
48819
|
+
* nothing in the mailbox. Kept apart from `write` so a reader can tell the
|
|
48820
|
+
* two apart at the call site, and because these take `Prefer` (notably
|
|
48821
|
+
* `outlook.timezone`, without which every returned slot is UTC).
|
|
48822
|
+
*/
|
|
48823
|
+
async post(path, body, opts = {}) {
|
|
48824
|
+
this.#requireConfigured();
|
|
48825
|
+
return this.#client().fetchJson("POST", path, {
|
|
48826
|
+
body,
|
|
48827
|
+
headers: opts.prefer ? { Prefer: opts.prefer } : void 0,
|
|
48828
|
+
// Read-only: a timeout or dropped connection is safe to retry, so keep
|
|
48829
|
+
// the plain error rather than mcp-utils' WriteOutcomeUnknownError.
|
|
48830
|
+
idempotent: true
|
|
48831
|
+
});
|
|
48832
|
+
}
|
|
48390
48833
|
/**
|
|
48391
48834
|
* The single write path. Everything mutating goes through here so auth and
|
|
48392
48835
|
* error shaping stay in one place.
|
|
@@ -48429,6 +48872,7 @@ init_dist2();
|
|
|
48429
48872
|
|
|
48430
48873
|
// src/view.ts
|
|
48431
48874
|
init_dist2();
|
|
48875
|
+
var VIEWS2 = ["compact", "full", "raw"];
|
|
48432
48876
|
function addr(r) {
|
|
48433
48877
|
const e = r?.EmailAddress;
|
|
48434
48878
|
if (!e) return void 0;
|
|
@@ -48487,7 +48931,11 @@ function fullEvent(e) {
|
|
|
48487
48931
|
Attendees: e.Attendees?.map(
|
|
48488
48932
|
(a) => pruned({ Who: addr(a), Response: a.Status?.Response })
|
|
48489
48933
|
),
|
|
48490
|
-
OnlineMeetingUrl
|
|
48934
|
+
// The Teams link lives under OnlineMeeting; OnlineMeetingUrl is empty on
|
|
48935
|
+
// every live Teams meeting checked.
|
|
48936
|
+
JoinUrl: e.OnlineMeeting?.JoinUrl || e.OnlineMeetingUrl || void 0,
|
|
48937
|
+
// Kept beside JoinUrl so a consumer of the old field still finds it.
|
|
48938
|
+
OnlineMeetingUrl: e.OnlineMeetingUrl || void 0,
|
|
48491
48939
|
Preview: e.BodyPreview?.trim() || void 0,
|
|
48492
48940
|
WebLink: e.WebLink
|
|
48493
48941
|
});
|
|
@@ -48497,7 +48945,9 @@ function compactFolder(f) {
|
|
|
48497
48945
|
Id: f.Id,
|
|
48498
48946
|
Name: f.DisplayName,
|
|
48499
48947
|
Unread: f.UnreadItemCount,
|
|
48500
|
-
Total: f.TotalItemCount
|
|
48948
|
+
Total: f.TotalItemCount,
|
|
48949
|
+
// Listings are one level deep; a non-zero count says there is more below.
|
|
48950
|
+
Children: f.ChildFolderCount
|
|
48501
48951
|
});
|
|
48502
48952
|
}
|
|
48503
48953
|
function stripOData(o) {
|
|
@@ -48531,7 +48981,9 @@ var nextLinkParam = external_exports.string().min(1).optional().describe(
|
|
|
48531
48981
|
"Continue a previous listing: pass its `nextLink` value unchanged to fetch the next page. When set, the paging/filter arguments are taken from the link and ignored here."
|
|
48532
48982
|
);
|
|
48533
48983
|
function fetchPage(client2, nextLink, path, opts = {}) {
|
|
48534
|
-
|
|
48984
|
+
if (nextLink === void 0) return client2.get(path, opts);
|
|
48985
|
+
const { query: _ignored, ...headerOpts } = opts;
|
|
48986
|
+
return client2.getAbsolute(nextLink, headerOpts);
|
|
48535
48987
|
}
|
|
48536
48988
|
function plainCollection(data) {
|
|
48537
48989
|
const items = Array.isArray(data?.value) ? data.value : [];
|
|
@@ -48550,7 +49002,6 @@ function mailboxUntrusted(payload) {
|
|
|
48550
49002
|
}
|
|
48551
49003
|
|
|
48552
49004
|
// src/tools/mail.ts
|
|
48553
|
-
var VIEWS2 = ["compact", "full", "raw"];
|
|
48554
49005
|
var WELL_KNOWN = [
|
|
48555
49006
|
"inbox",
|
|
48556
49007
|
"drafts",
|
|
@@ -48562,14 +49013,6 @@ var WELL_KNOWN = [
|
|
|
48562
49013
|
"clutter"
|
|
48563
49014
|
];
|
|
48564
49015
|
var LIST_SELECT = "Id,Subject,From,ToRecipients,ReceivedDateTime,IsRead,HasAttachments,BodyPreview";
|
|
48565
|
-
function qs(params) {
|
|
48566
|
-
const parts = [];
|
|
48567
|
-
for (const [k, v] of Object.entries(params)) {
|
|
48568
|
-
if (v === void 0 || v === "") continue;
|
|
48569
|
-
parts.push(`${k}=${encodeURIComponent(String(v))}`);
|
|
48570
|
-
}
|
|
48571
|
-
return parts.length ? `?${parts.join("&")}` : "";
|
|
48572
|
-
}
|
|
48573
49016
|
function folderSegment(folder) {
|
|
48574
49017
|
return encodeURIComponent(folder);
|
|
48575
49018
|
}
|
|
@@ -48577,19 +49020,22 @@ function registerMailTools(server, client2) {
|
|
|
48577
49020
|
server.registerTool(
|
|
48578
49021
|
"outlook_list_folders",
|
|
48579
49022
|
{
|
|
48580
|
-
description: "List mail folders with unread and total counts. Use this to discover folder ids before listing messages; the well-known names (" + WELL_KNOWN.join(", ") + ") can be used directly without a lookup.",
|
|
48581
|
-
annotations: { readOnlyHint: true },
|
|
49023
|
+
description: "List mail folders with unread and total counts. Use this to discover folder ids before listing messages; the well-known names (" + WELL_KNOWN.join(", ") + ") can be used directly without a lookup. Lists ONE level: top-level folders by default, or the child folders of `parent`. A folder with `Children` > 0 has subfolders \u2014 list them with `parent` set to its Id.",
|
|
49024
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48582
49025
|
inputSchema: external_exports.object({
|
|
48583
49026
|
view: viewParam(VIEWS2),
|
|
49027
|
+
parent: external_exports.string().min(1).optional().describe("List the child folders of this folder (id or well-known name). Omit for the top level."),
|
|
48584
49028
|
limit: external_exports.number().int().min(1).max(200).optional().describe("Max folders (default 50)"),
|
|
48585
49029
|
nextLink: nextLinkParam
|
|
48586
49030
|
})
|
|
48587
49031
|
},
|
|
48588
|
-
async ({ view, limit, nextLink }) => {
|
|
49032
|
+
async ({ view, parent, limit, nextLink }) => {
|
|
49033
|
+
const base = parent !== void 0 ? `/me/mailfolders/${folderSegment(parent)}/childfolders` : "/me/mailfolders";
|
|
48589
49034
|
const data = await fetchPage(
|
|
48590
49035
|
client2,
|
|
48591
49036
|
nextLink,
|
|
48592
|
-
|
|
49037
|
+
base,
|
|
49038
|
+
{ query: { $top: limit ?? 50 } }
|
|
48593
49039
|
);
|
|
48594
49040
|
if (resolveView(view, VIEWS2) === "raw") return minifiedResult(data);
|
|
48595
49041
|
return minifiedResult(projectCollection(data, compactFolder, "mail folder"));
|
|
@@ -48599,7 +49045,7 @@ function registerMailTools(server, client2) {
|
|
|
48599
49045
|
"outlook_list_messages",
|
|
48600
49046
|
{
|
|
48601
49047
|
description: "List messages in a folder, newest first. Defaults to the inbox. Use `unreadOnly` for a triage view. Bodies are NOT included \u2014 call outlook_get_message for one. Note `search` and `unreadOnly` cannot be combined on a first-page request (the API rejects $search with $filter); pass `search` alone to search the whole mailbox. When following a `nextLink`, both are ignored, so echoing them back is harmless. " + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
48602
|
-
annotations: { readOnlyHint: true },
|
|
49048
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48603
49049
|
inputSchema: external_exports.object({
|
|
48604
49050
|
view: viewParam(VIEWS2),
|
|
48605
49051
|
folder: external_exports.string().optional().describe('Folder id or well-known name (default "inbox")'),
|
|
@@ -48631,7 +49077,8 @@ function registerMailTools(server, client2) {
|
|
|
48631
49077
|
const data = await fetchPage(
|
|
48632
49078
|
client2,
|
|
48633
49079
|
nextLink,
|
|
48634
|
-
|
|
49080
|
+
base,
|
|
49081
|
+
{ query: params }
|
|
48635
49082
|
);
|
|
48636
49083
|
const v = resolveView(view, VIEWS2);
|
|
48637
49084
|
if (v === "raw") return mailboxUntrusted(data);
|
|
@@ -48644,7 +49091,7 @@ function registerMailTools(server, client2) {
|
|
|
48644
49091
|
"outlook_get_message",
|
|
48645
49092
|
{
|
|
48646
49093
|
description: `Get one message including its body. The body is requested as plain text rather than HTML (measured ~9x smaller and far easier to read); pass view:"raw" to get Outlook's untouched record. ` + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
48647
|
-
annotations: { readOnlyHint: true },
|
|
49094
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48648
49095
|
inputSchema: external_exports.object({
|
|
48649
49096
|
view: viewParam(VIEWS2),
|
|
48650
49097
|
id: external_exports.string().min(1).describe("Message Id from outlook_list_messages"),
|
|
@@ -48664,7 +49111,7 @@ function registerMailTools(server, client2) {
|
|
|
48664
49111
|
"outlook_list_attachments",
|
|
48665
49112
|
{
|
|
48666
49113
|
description: "List a message's attachments (name, size, content type). Metadata only \u2014 this deliberately does not download bytes.",
|
|
48667
|
-
annotations: { readOnlyHint: true },
|
|
49114
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48668
49115
|
inputSchema: external_exports.object({
|
|
48669
49116
|
id: external_exports.string().min(1).describe("Message Id"),
|
|
48670
49117
|
nextLink: nextLinkParam
|
|
@@ -48674,9 +49121,8 @@ function registerMailTools(server, client2) {
|
|
|
48674
49121
|
const data = await fetchPage(
|
|
48675
49122
|
client2,
|
|
48676
49123
|
nextLink,
|
|
48677
|
-
`/me/messages/${encodeURIComponent(id)}/attachments
|
|
48678
|
-
|
|
48679
|
-
})}`
|
|
49124
|
+
`/me/messages/${encodeURIComponent(id)}/attachments`,
|
|
49125
|
+
{ query: { $select: "Id,Name,Size,ContentType" } }
|
|
48680
49126
|
);
|
|
48681
49127
|
return minifiedResult(plainCollection(data));
|
|
48682
49128
|
}
|
|
@@ -48688,8 +49134,9 @@ init_zod();
|
|
|
48688
49134
|
init_dist2();
|
|
48689
49135
|
|
|
48690
49136
|
// src/timezone.ts
|
|
49137
|
+
init_dist2();
|
|
48691
49138
|
var zoneByClient = /* @__PURE__ */ new WeakMap();
|
|
48692
|
-
async function mailboxTimeZone(client2) {
|
|
49139
|
+
async function mailboxTimeZone(client2, opts = {}) {
|
|
48693
49140
|
const cached2 = zoneByClient.get(client2);
|
|
48694
49141
|
if (cached2) return cached2;
|
|
48695
49142
|
try {
|
|
@@ -48697,27 +49144,44 @@ async function mailboxTimeZone(client2) {
|
|
|
48697
49144
|
const zone = settings?.TimeZone?.trim() || void 0;
|
|
48698
49145
|
if (zone) zoneByClient.set(client2, zone);
|
|
48699
49146
|
return zone;
|
|
48700
|
-
} catch {
|
|
48701
|
-
return void 0;
|
|
49147
|
+
} catch (e) {
|
|
49148
|
+
if (!opts.required) return void 0;
|
|
49149
|
+
throw new McpToolError("Could not read the mailbox's time zone, so the event time is ambiguous.", {
|
|
49150
|
+
hint: `Retry, or pass \`timeZone\` explicitly (a Windows zone name such as "Eastern Standard Time"). Cause: ${e instanceof Error ? e.message : String(e)}`
|
|
49151
|
+
});
|
|
48702
49152
|
}
|
|
48703
49153
|
}
|
|
48704
49154
|
|
|
48705
49155
|
// src/tools/calendar.ts
|
|
48706
|
-
var EVENT_SELECT = "Id,Subject,Start,End,Location,Organizer,IsAllDay,IsCancelled,ShowAs,OnlineMeetingUrl,BodyPreview";
|
|
48707
|
-
function
|
|
48708
|
-
|
|
48709
|
-
|
|
48710
|
-
if (
|
|
48711
|
-
|
|
49156
|
+
var EVENT_SELECT = "Id,Subject,Start,End,Location,Organizer,IsAllDay,IsCancelled,ShowAs,OnlineMeetingUrl,OnlineMeeting,BodyPreview";
|
|
49157
|
+
function dateTimeTimeZone(value, zone) {
|
|
49158
|
+
if (/(Z|[+-]\d{2}:?\d{2})$/i.test(value)) {
|
|
49159
|
+
const d = new Date(value);
|
|
49160
|
+
if (!Number.isNaN(d.getTime())) {
|
|
49161
|
+
return { DateTime: d.toISOString().replace(/\.\d{3}Z$/, ""), TimeZone: "UTC" };
|
|
49162
|
+
}
|
|
49163
|
+
}
|
|
49164
|
+
return { DateTime: value, TimeZone: zone };
|
|
49165
|
+
}
|
|
49166
|
+
function assertWindow(start, end) {
|
|
49167
|
+
const a = Date.parse(start);
|
|
49168
|
+
const b = Date.parse(end);
|
|
49169
|
+
if (!Number.isNaN(a) && !Number.isNaN(b) && b <= a) {
|
|
49170
|
+
throw new McpToolError(`The window ends (${end}) before it starts (${start}).`, {
|
|
49171
|
+
hint: "`end` must be later than `start`."
|
|
49172
|
+
});
|
|
48712
49173
|
}
|
|
48713
|
-
return parts.length ? `?${parts.join("&")}` : "";
|
|
48714
49174
|
}
|
|
49175
|
+
function trimTime(dt) {
|
|
49176
|
+
return dt?.replace(/\.0+$/, "");
|
|
49177
|
+
}
|
|
49178
|
+
var attendeeList = (what) => external_exports.array(external_exports.string().min(3)).describe(`Email addresses of ${what}`);
|
|
48715
49179
|
function registerCalendarTools(server, client2) {
|
|
48716
49180
|
server.registerTool(
|
|
48717
49181
|
"outlook_list_events",
|
|
48718
49182
|
{
|
|
48719
49183
|
description: 'List calendar events in a date window. This uses the calendar VIEW, which expands recurring series into their individual occurrences \u2014 the right tool for "what is on my schedule". Times are returned in `timeZone` when given. ' + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
48720
|
-
annotations: { readOnlyHint: true },
|
|
49184
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48721
49185
|
inputSchema: external_exports.object({
|
|
48722
49186
|
view: viewParam(VIEWS2),
|
|
48723
49187
|
start: external_exports.string().min(1).describe("Window start, ISO 8601 (e.g. 2026-09-20T00:00:00Z)"),
|
|
@@ -48734,14 +49198,17 @@ function registerCalendarTools(server, client2) {
|
|
|
48734
49198
|
const data = await fetchPage(
|
|
48735
49199
|
client2,
|
|
48736
49200
|
nextLink,
|
|
48737
|
-
|
|
48738
|
-
|
|
48739
|
-
|
|
48740
|
-
|
|
48741
|
-
|
|
48742
|
-
|
|
48743
|
-
|
|
48744
|
-
|
|
49201
|
+
"/me/calendarview",
|
|
49202
|
+
{
|
|
49203
|
+
query: {
|
|
49204
|
+
startDateTime: start,
|
|
49205
|
+
endDateTime: end,
|
|
49206
|
+
$select: EVENT_SELECT,
|
|
49207
|
+
$orderby: "Start/DateTime",
|
|
49208
|
+
$top: limit ?? 50
|
|
49209
|
+
},
|
|
49210
|
+
...zone ? { prefer: `outlook.timezone="${zone}"` } : {}
|
|
49211
|
+
}
|
|
48745
49212
|
);
|
|
48746
49213
|
const v = resolveView(view, VIEWS2);
|
|
48747
49214
|
if (v === "raw") return mailboxUntrusted(data);
|
|
@@ -48754,7 +49221,7 @@ function registerCalendarTools(server, client2) {
|
|
|
48754
49221
|
"outlook_get_event",
|
|
48755
49222
|
{
|
|
48756
49223
|
description: "Get one calendar event in full, including attendees and their responses. " + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
48757
|
-
annotations: { readOnlyHint: true },
|
|
49224
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48758
49225
|
inputSchema: external_exports.object({
|
|
48759
49226
|
view: viewParam(VIEWS2),
|
|
48760
49227
|
id: external_exports.string().min(1).describe("Event Id from outlook_list_events"),
|
|
@@ -48776,31 +49243,136 @@ function registerCalendarTools(server, client2) {
|
|
|
48776
49243
|
"outlook_list_calendars",
|
|
48777
49244
|
{
|
|
48778
49245
|
description: "List the calendars in the mailbox, for picking one to read or write against.",
|
|
48779
|
-
annotations: { readOnlyHint: true },
|
|
49246
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48780
49247
|
inputSchema: external_exports.object({
|
|
48781
49248
|
view: viewParam(VIEWS2),
|
|
48782
49249
|
nextLink: nextLinkParam
|
|
48783
49250
|
})
|
|
48784
49251
|
},
|
|
48785
49252
|
async ({ view, nextLink }) => {
|
|
48786
|
-
const data = await fetchPage(client2, nextLink,
|
|
49253
|
+
const data = await fetchPage(client2, nextLink, "/me/calendars", { query: { $select: "Id,Name,Color,CanEdit,Owner" } });
|
|
48787
49254
|
if (resolveView(view, VIEWS2) === "raw") return minifiedResult(data);
|
|
48788
49255
|
return minifiedResult(plainCollection(data));
|
|
48789
49256
|
}
|
|
48790
49257
|
);
|
|
49258
|
+
server.registerTool(
|
|
49259
|
+
"outlook_find_meeting_times",
|
|
49260
|
+
{
|
|
49261
|
+
description: "Find times when people can meet \u2014 Outlook's Scheduling Assistant. Give the attendees, a search window and a duration; Outlook checks everyone's free/busy (and the signed-in user's, as organizer) and returns ranked candidate slots with each attendee's availability. By default only working hours are searched. Use this to answer \"when can X and Y meet?\"; then book with outlook_create_event. `start`/`end` are local wall-clock times in `timeZone` (default: the mailbox zone), and returned slots are in that zone too. " + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
49262
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
49263
|
+
inputSchema: external_exports.object({
|
|
49264
|
+
view: viewParam(VIEWS2),
|
|
49265
|
+
attendees: attendeeList("required attendees").min(1),
|
|
49266
|
+
optionalAttendees: attendeeList("optional attendees").optional(),
|
|
49267
|
+
start: external_exports.string().min(1).describe("Earliest the meeting may start, e.g. 2026-10-12T09:00:00"),
|
|
49268
|
+
end: external_exports.string().min(1).describe("Latest the meeting may end, e.g. 2026-10-16T17:00:00"),
|
|
49269
|
+
durationMinutes: external_exports.number().int().min(5).max(1440).describe("Meeting length in minutes"),
|
|
49270
|
+
timeZone: external_exports.string().optional().describe('Windows time-zone name, e.g. "Eastern Standard Time". Defaults to the MAILBOX time zone.'),
|
|
49271
|
+
maxCandidates: external_exports.number().int().min(1).max(50).optional().describe("Max suggestions (default 10)"),
|
|
49272
|
+
workingHoursOnly: external_exports.boolean().optional().describe("Only suggest times inside working hours (default true). false searches around the clock."),
|
|
49273
|
+
minimumAttendeePercentage: external_exports.number().min(0).max(100).optional().describe("Lowest confidence (0-100) a slot needs to be suggested. Lower it to see slots where not everyone is free."),
|
|
49274
|
+
isOrganizerOptional: external_exports.boolean().optional().describe("true when the signed-in user need not attend, so their own calendar is ignored")
|
|
49275
|
+
})
|
|
49276
|
+
},
|
|
49277
|
+
async (args) => {
|
|
49278
|
+
assertWindow(args.start, args.end);
|
|
49279
|
+
const zone = args.timeZone ?? await mailboxTimeZone(client2) ?? "UTC";
|
|
49280
|
+
const body = {
|
|
49281
|
+
Attendees: [
|
|
49282
|
+
...args.attendees.map((Address) => ({ Type: "Required", EmailAddress: { Address } })),
|
|
49283
|
+
...(args.optionalAttendees ?? []).map((Address) => ({
|
|
49284
|
+
Type: "Optional",
|
|
49285
|
+
EmailAddress: { Address }
|
|
49286
|
+
}))
|
|
49287
|
+
],
|
|
49288
|
+
TimeConstraint: {
|
|
49289
|
+
ActivityDomain: args.workingHoursOnly === false ? "Unrestricted" : "Work",
|
|
49290
|
+
Timeslots: [
|
|
49291
|
+
{ Start: dateTimeTimeZone(args.start, zone), End: dateTimeTimeZone(args.end, zone) }
|
|
49292
|
+
]
|
|
49293
|
+
},
|
|
49294
|
+
MeetingDuration: `PT${args.durationMinutes}M`,
|
|
49295
|
+
MaxCandidates: args.maxCandidates ?? 10,
|
|
49296
|
+
ReturnSuggestionReasons: true,
|
|
49297
|
+
...args.minimumAttendeePercentage !== void 0 ? { MinimumAttendeePercentage: args.minimumAttendeePercentage } : {},
|
|
49298
|
+
...args.isOrganizerOptional !== void 0 ? { IsOrganizerOptional: args.isOrganizerOptional } : {}
|
|
49299
|
+
};
|
|
49300
|
+
const data = await client2.post("/me/findmeetingtimes", body, { prefer: `outlook.timezone="${zone}"` });
|
|
49301
|
+
if (resolveView(args.view, VIEWS2) === "raw") return mailboxUntrusted(data);
|
|
49302
|
+
const suggestions = (data?.MeetingTimeSuggestions ?? []).map((s) => ({
|
|
49303
|
+
Start: trimTime(s.MeetingTimeSlot?.Start?.DateTime),
|
|
49304
|
+
End: trimTime(s.MeetingTimeSlot?.End?.DateTime),
|
|
49305
|
+
Confidence: s.Confidence,
|
|
49306
|
+
Organizer: s.OrganizerAvailability,
|
|
49307
|
+
Attendees: Object.fromEntries(
|
|
49308
|
+
(s.AttendeeAvailability ?? []).map((a) => [
|
|
49309
|
+
a.Attendee?.EmailAddress?.Address ?? a.Attendee?.EmailAddress?.Name ?? "?",
|
|
49310
|
+
a.Availability
|
|
49311
|
+
])
|
|
49312
|
+
),
|
|
49313
|
+
Reason: s.SuggestionReason
|
|
49314
|
+
}));
|
|
49315
|
+
return mailboxUntrusted({
|
|
49316
|
+
timeZone: zone,
|
|
49317
|
+
count: suggestions.length,
|
|
49318
|
+
suggestions,
|
|
49319
|
+
// Outlook explains an empty result ("AttendeesUnavailable", ...); an
|
|
49320
|
+
// empty list without it reads as "nobody is ever free".
|
|
49321
|
+
...data?.EmptySuggestionsReason ? { emptySuggestionsReason: data.EmptySuggestionsReason } : {}
|
|
49322
|
+
});
|
|
49323
|
+
}
|
|
49324
|
+
);
|
|
49325
|
+
server.registerTool(
|
|
49326
|
+
"outlook_get_schedule",
|
|
49327
|
+
{
|
|
49328
|
+
description: "Get people's free/busy blocks for a time window \u2014 the grid view of Outlook's Scheduling Assistant. Returns each person's busy, tentative and out-of-office items (with subject and location where their calendar shares them). Use outlook_find_meeting_times to have Outlook pick slots instead. `start`/`end` are local wall-clock times in `timeZone` (default: the mailbox zone). " + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
49329
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
49330
|
+
inputSchema: external_exports.object({
|
|
49331
|
+
view: viewParam(VIEWS2),
|
|
49332
|
+
people: attendeeList("people (or rooms) to look up").min(1).max(100),
|
|
49333
|
+
start: external_exports.string().min(1).describe("Window start, e.g. 2026-10-12T09:00:00"),
|
|
49334
|
+
end: external_exports.string().min(1).describe("Window end, e.g. 2026-10-12T17:00:00"),
|
|
49335
|
+
timeZone: external_exports.string().optional().describe('Windows time-zone name, e.g. "Eastern Standard Time". Defaults to the MAILBOX time zone.'),
|
|
49336
|
+
intervalMinutes: external_exports.number().int().min(5).max(1440).optional().describe("Slot size for the raw view's AvailabilityView string (default 30)")
|
|
49337
|
+
})
|
|
49338
|
+
},
|
|
49339
|
+
async ({ view, people, start, end, timeZone, intervalMinutes }) => {
|
|
49340
|
+
assertWindow(start, end);
|
|
49341
|
+
const zone = timeZone ?? await mailboxTimeZone(client2) ?? "UTC";
|
|
49342
|
+
const data = await client2.post(
|
|
49343
|
+
"/me/calendar/getschedule",
|
|
49344
|
+
{
|
|
49345
|
+
Schedules: people,
|
|
49346
|
+
StartTime: dateTimeTimeZone(start, zone),
|
|
49347
|
+
EndTime: dateTimeTimeZone(end, zone),
|
|
49348
|
+
AvailabilityViewInterval: intervalMinutes ?? 30
|
|
49349
|
+
},
|
|
49350
|
+
{ prefer: `outlook.timezone="${zone}"` }
|
|
49351
|
+
);
|
|
49352
|
+
if (resolveView(view, VIEWS2) === "raw") return mailboxUntrusted(data);
|
|
49353
|
+
const schedules = (data?.value ?? []).map(
|
|
49354
|
+
(p) => (
|
|
49355
|
+
// An unresolvable address must not read as "free all day".
|
|
49356
|
+
p.Error !== void 0 && p.Error !== null ? { Who: p.ScheduleId, Error: p.Error } : {
|
|
49357
|
+
Who: p.ScheduleId,
|
|
49358
|
+
Busy: (p.ScheduleItems ?? []).map((i) => ({
|
|
49359
|
+
Start: trimTime(i.Start?.DateTime),
|
|
49360
|
+
End: trimTime(i.End?.DateTime),
|
|
49361
|
+
Status: i.Status,
|
|
49362
|
+
...i.Subject ? { Subject: i.Subject } : {},
|
|
49363
|
+
...i.Location ? { Location: i.Location } : {}
|
|
49364
|
+
}))
|
|
49365
|
+
}
|
|
49366
|
+
)
|
|
49367
|
+
);
|
|
49368
|
+
return mailboxUntrusted({ timeZone: zone, schedules });
|
|
49369
|
+
}
|
|
49370
|
+
);
|
|
48791
49371
|
}
|
|
48792
49372
|
|
|
48793
49373
|
// src/tools/directory.ts
|
|
48794
49374
|
init_zod();
|
|
48795
49375
|
init_dist2();
|
|
48796
|
-
function qs3(params) {
|
|
48797
|
-
const parts = [];
|
|
48798
|
-
for (const [k, v] of Object.entries(params)) {
|
|
48799
|
-
if (v === void 0 || v === "") continue;
|
|
48800
|
-
parts.push(`${k}=${encodeURIComponent(String(v))}`);
|
|
48801
|
-
}
|
|
48802
|
-
return parts.length ? `?${parts.join("&")}` : "";
|
|
48803
|
-
}
|
|
48804
49376
|
function collection(data, raw) {
|
|
48805
49377
|
if (raw) return data;
|
|
48806
49378
|
return plainCollection(data);
|
|
@@ -48810,7 +49382,7 @@ function registerDirectoryTools(server, client2) {
|
|
|
48810
49382
|
"outlook_get_profile",
|
|
48811
49383
|
{
|
|
48812
49384
|
description: "Get the signed-in mailbox's identity (email address, display name, alias). The cheapest call that proves the credential works.",
|
|
48813
|
-
annotations: { readOnlyHint: true },
|
|
49385
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48814
49386
|
inputSchema: external_exports.object({})
|
|
48815
49387
|
},
|
|
48816
49388
|
async () => minifiedResult(stripOData(await client2.get("/me")))
|
|
@@ -48819,7 +49391,7 @@ function registerDirectoryTools(server, client2) {
|
|
|
48819
49391
|
"outlook_get_mailbox_settings",
|
|
48820
49392
|
{
|
|
48821
49393
|
description: "Get mailbox settings: time zone, working hours, language, and automatic-replies (out-of-office) configuration.",
|
|
48822
|
-
annotations: { readOnlyHint: true },
|
|
49394
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48823
49395
|
inputSchema: external_exports.object({})
|
|
48824
49396
|
},
|
|
48825
49397
|
async () => minifiedResult(stripOData(await client2.get("/me/MailboxSettings")))
|
|
@@ -48828,7 +49400,7 @@ function registerDirectoryTools(server, client2) {
|
|
|
48828
49400
|
"outlook_list_contacts",
|
|
48829
49401
|
{
|
|
48830
49402
|
description: "List saved contacts from the mailbox address book.",
|
|
48831
|
-
annotations: { readOnlyHint: true },
|
|
49403
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48832
49404
|
inputSchema: external_exports.object({
|
|
48833
49405
|
view: viewParam(VIEWS2),
|
|
48834
49406
|
limit: external_exports.number().int().min(1).max(200).optional().describe("Max contacts (default 50)"),
|
|
@@ -48840,13 +49412,16 @@ function registerDirectoryTools(server, client2) {
|
|
|
48840
49412
|
const data = await fetchPage(
|
|
48841
49413
|
client2,
|
|
48842
49414
|
nextLink,
|
|
48843
|
-
|
|
48844
|
-
|
|
48845
|
-
|
|
48846
|
-
|
|
48847
|
-
|
|
48848
|
-
|
|
48849
|
-
|
|
49415
|
+
"/me/contacts",
|
|
49416
|
+
{
|
|
49417
|
+
query: {
|
|
49418
|
+
$top: limit ?? 50,
|
|
49419
|
+
$skip: skip,
|
|
49420
|
+
// `MobilePhone1`, not `MobilePhone`: the latter is the Graph name and
|
|
49421
|
+
// the v2.0 Contact type rejects it outright with a 400.
|
|
49422
|
+
$select: "Id,DisplayName,EmailAddresses,CompanyName,JobTitle,MobilePhone1"
|
|
49423
|
+
}
|
|
49424
|
+
}
|
|
48850
49425
|
);
|
|
48851
49426
|
return minifiedResult(collection(data, resolveView(view, VIEWS2) === "raw"));
|
|
48852
49427
|
}
|
|
@@ -48855,7 +49430,7 @@ function registerDirectoryTools(server, client2) {
|
|
|
48855
49430
|
"outlook_list_people",
|
|
48856
49431
|
{
|
|
48857
49432
|
description: 'List people ranked by relevance to the user \u2014 colleagues and frequent correspondents, drawn from mail traffic rather than the saved address book. Better than outlook_list_contacts for resolving "who is X" in a work mailbox.',
|
|
48858
|
-
annotations: { readOnlyHint: true },
|
|
49433
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48859
49434
|
inputSchema: external_exports.object({
|
|
48860
49435
|
view: viewParam(VIEWS2),
|
|
48861
49436
|
limit: external_exports.number().int().min(1).max(100).optional().describe("Max people (default 25)"),
|
|
@@ -48866,10 +49441,13 @@ function registerDirectoryTools(server, client2) {
|
|
|
48866
49441
|
const data = await fetchPage(
|
|
48867
49442
|
client2,
|
|
48868
49443
|
nextLink,
|
|
48869
|
-
|
|
48870
|
-
|
|
48871
|
-
|
|
48872
|
-
|
|
49444
|
+
"/me/people",
|
|
49445
|
+
{
|
|
49446
|
+
query: {
|
|
49447
|
+
$top: limit ?? 25,
|
|
49448
|
+
$select: "Id,DisplayName,ScoredEmailAddresses,JobTitle,CompanyName"
|
|
49449
|
+
}
|
|
49450
|
+
}
|
|
48873
49451
|
);
|
|
48874
49452
|
return minifiedResult(collection(data, resolveView(view, VIEWS2) === "raw"));
|
|
48875
49453
|
}
|
|
@@ -48878,7 +49456,7 @@ function registerDirectoryTools(server, client2) {
|
|
|
48878
49456
|
"outlook_list_tasks",
|
|
48879
49457
|
{
|
|
48880
49458
|
description: "List Outlook tasks with status and due dates.",
|
|
48881
|
-
annotations: { readOnlyHint: true },
|
|
49459
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48882
49460
|
inputSchema: external_exports.object({
|
|
48883
49461
|
view: viewParam(VIEWS2),
|
|
48884
49462
|
limit: external_exports.number().int().min(1).max(200).optional().describe("Max tasks (default 50)"),
|
|
@@ -48889,10 +49467,13 @@ function registerDirectoryTools(server, client2) {
|
|
|
48889
49467
|
const data = await fetchPage(
|
|
48890
49468
|
client2,
|
|
48891
49469
|
nextLink,
|
|
48892
|
-
|
|
48893
|
-
|
|
48894
|
-
|
|
48895
|
-
|
|
49470
|
+
"/me/tasks",
|
|
49471
|
+
{
|
|
49472
|
+
query: {
|
|
49473
|
+
$top: limit ?? 50,
|
|
49474
|
+
$select: "Id,Subject,Status,Importance,DueDateTime,CompletedDateTime"
|
|
49475
|
+
}
|
|
49476
|
+
}
|
|
48896
49477
|
);
|
|
48897
49478
|
return minifiedResult(collection(data, resolveView(view, VIEWS2) === "raw"));
|
|
48898
49479
|
}
|
|
@@ -48912,12 +49493,52 @@ function messageBody(subject, body, html) {
|
|
|
48912
49493
|
Body: { ContentType: html === true ? "HTML" : "Text", Content: body }
|
|
48913
49494
|
};
|
|
48914
49495
|
}
|
|
49496
|
+
var TEAMS = { IsOnlineMeeting: true, OnlineMeetingProvider: "TeamsForBusiness" };
|
|
49497
|
+
var teamsMeetingParam = external_exports.boolean().optional().describe("Attach a Microsoft Teams meeting (default true). false leaves it off.");
|
|
49498
|
+
var EVENT_STATE_SELECT = "Id,Subject,IsOrganizer,Start,End,Location,Attendees,IsOnlineMeeting,OnlineMeetingProvider,OnlineMeeting";
|
|
49499
|
+
function attendee(Address, Type) {
|
|
49500
|
+
return { Type, EmailAddress: { Address } };
|
|
49501
|
+
}
|
|
49502
|
+
var lower = (a) => a.EmailAddress?.Address?.toLowerCase();
|
|
49503
|
+
function joinUrlOf(e) {
|
|
49504
|
+
return e?.OnlineMeeting?.JoinUrl || void 0;
|
|
49505
|
+
}
|
|
49506
|
+
function isTeams(e) {
|
|
49507
|
+
return e.IsOnlineMeeting === true && e.OnlineMeetingProvider === "TeamsForBusiness";
|
|
49508
|
+
}
|
|
49509
|
+
function dateTimeTimeZone2(value, zone) {
|
|
49510
|
+
if (/(Z|[+-]\d{2}:?\d{2})$/i.test(value)) {
|
|
49511
|
+
const d = new Date(value);
|
|
49512
|
+
if (!Number.isNaN(d.getTime())) {
|
|
49513
|
+
return { DateTime: d.toISOString().replace(/\.\d{3}Z$/, ""), TimeZone: "UTC" };
|
|
49514
|
+
}
|
|
49515
|
+
}
|
|
49516
|
+
return { DateTime: value, TimeZone: zone };
|
|
49517
|
+
}
|
|
49518
|
+
var TIMEOUT_SENTENCE = 'If Outlook does not answer in time (or the connection drops) the result has status "unknown": the write may well have gone through, so check before retrying \u2014 never resend blindly.';
|
|
49519
|
+
async function writeOrUnknown(run, checkWhere) {
|
|
49520
|
+
try {
|
|
49521
|
+
return { ok: true, value: await run() };
|
|
49522
|
+
} catch (e) {
|
|
49523
|
+
if (!(e instanceof WriteOutcomeUnknownError)) throw e;
|
|
49524
|
+
return {
|
|
49525
|
+
ok: false,
|
|
49526
|
+
result: minifiedResult({
|
|
49527
|
+
status: "unknown",
|
|
49528
|
+
warning: `Outlook did not confirm this write (${e.timedOut ? "it did not answer in time" : "the connection dropped"}), so it may already have happened. Check ${checkWhere} before retrying; do not resend blindly.`
|
|
49529
|
+
})
|
|
49530
|
+
};
|
|
49531
|
+
}
|
|
49532
|
+
}
|
|
49533
|
+
var naiveMs = (dt) => Date.parse(`${dt.replace(/\.\d+$/, "")}Z`);
|
|
49534
|
+
var fromNaiveMs = (ms) => new Date(ms).toISOString().replace(/\.\d{3}Z$/, "");
|
|
49535
|
+
var trimTime2 = (dt) => dt?.replace(/\.\d+$/, "");
|
|
48915
49536
|
function registerWriteTools(server, client2) {
|
|
48916
49537
|
server.registerTool(
|
|
48917
49538
|
"outlook_send_mail",
|
|
48918
49539
|
{
|
|
48919
|
-
description: "Send an email from the signed-in mailbox. " + CONFIRM_FLOW_SENTENCE + " The preview shows exactly what would be sent. " + CONFIRM_INJECTION_RULE,
|
|
48920
|
-
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
49540
|
+
description: "Send an email from the signed-in mailbox. " + CONFIRM_FLOW_SENTENCE + " The preview shows exactly what would be sent. " + TIMEOUT_SENTENCE + " " + CONFIRM_INJECTION_RULE,
|
|
49541
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
48921
49542
|
inputSchema: external_exports.object({
|
|
48922
49543
|
to: recipientList,
|
|
48923
49544
|
cc: recipientList,
|
|
@@ -48951,7 +49572,8 @@ function registerWriteTools(server, client2) {
|
|
|
48951
49572
|
confirmToken
|
|
48952
49573
|
});
|
|
48953
49574
|
if (gate) return gate;
|
|
48954
|
-
await client2.write("POST", "/me/sendmail", payload);
|
|
49575
|
+
const sent = await writeOrUnknown(() => client2.write("POST", "/me/sendmail", payload), "Sent Items");
|
|
49576
|
+
if (!sent.ok) return sent.result;
|
|
48955
49577
|
return minifiedResult({ sent: true, subject, recipients: recipients || null });
|
|
48956
49578
|
}
|
|
48957
49579
|
);
|
|
@@ -48959,7 +49581,7 @@ function registerWriteTools(server, client2) {
|
|
|
48959
49581
|
"outlook_create_draft",
|
|
48960
49582
|
{
|
|
48961
49583
|
description: "Create a draft message in the Drafts folder without sending it. Returns the created draft, which can be reviewed and sent from Outlook. " + CONFIRM_FLOW_SENTENCE,
|
|
48962
|
-
annotations: { readOnlyHint: false, destructiveHint:
|
|
49584
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
48963
49585
|
inputSchema: external_exports.object({
|
|
48964
49586
|
to: recipientList,
|
|
48965
49587
|
cc: recipientList,
|
|
@@ -48998,7 +49620,7 @@ function registerWriteTools(server, client2) {
|
|
|
48998
49620
|
"outlook_mark_read",
|
|
48999
49621
|
{
|
|
49000
49622
|
description: "Mark a message read or unread. The result is verified by re-reading the message \u2014 a 2xx alone is not proof it persisted. " + CONFIRM_FLOW_SENTENCE,
|
|
49001
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
49623
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
49002
49624
|
inputSchema: external_exports.object({
|
|
49003
49625
|
id: external_exports.string().min(1).describe("Message Id"),
|
|
49004
49626
|
isRead: external_exports.boolean().describe("true to mark read, false to mark unread"),
|
|
@@ -49033,7 +49655,7 @@ function registerWriteTools(server, client2) {
|
|
|
49033
49655
|
"outlook_move_message",
|
|
49034
49656
|
{
|
|
49035
49657
|
description: 'Move a message to another folder (e.g. "archive", "deleteditems", or a folder id from outlook_list_folders). Moving assigns a NEW message id, which is returned. ' + CONFIRM_FLOW_SENTENCE,
|
|
49036
|
-
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
49658
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
49037
49659
|
inputSchema: external_exports.object({
|
|
49038
49660
|
id: external_exports.string().min(1).describe("Message Id"),
|
|
49039
49661
|
destination: external_exports.string().min(1).describe('Destination folder id or well-known name (e.g. "archive")'),
|
|
@@ -49071,8 +49693,8 @@ function registerWriteTools(server, client2) {
|
|
|
49071
49693
|
server.registerTool(
|
|
49072
49694
|
"outlook_create_event",
|
|
49073
49695
|
{
|
|
49074
|
-
description: 'Create a calendar event. `timeZone` takes a WINDOWS zone name such as "Eastern Standard Time", not an IANA name. Attendees are emailed an invitation. ' + CONFIRM_FLOW_SENTENCE + " " + CONFIRM_INJECTION_RULE,
|
|
49075
|
-
annotations: { readOnlyHint: false, destructiveHint:
|
|
49696
|
+
description: 'Create a calendar event or meeting. A Microsoft Teams meeting is attached by default (`teamsMeeting: false` to skip) and its join link is returned. `timeZone` takes a WINDOWS zone name such as "Eastern Standard Time", not an IANA name. Attendees are emailed an invitation. To pick a time first, use outlook_find_meeting_times. ' + CONFIRM_FLOW_SENTENCE + " " + TIMEOUT_SENTENCE + " " + CONFIRM_INJECTION_RULE,
|
|
49697
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
49076
49698
|
inputSchema: external_exports.object({
|
|
49077
49699
|
subject: external_exports.string().describe("Event title"),
|
|
49078
49700
|
start: external_exports.string().min(1).describe("Start, ISO 8601 local time e.g. 2026-09-22T15:00:00"),
|
|
@@ -49080,42 +49702,176 @@ function registerWriteTools(server, client2) {
|
|
|
49080
49702
|
timeZone: external_exports.string().optional().describe("Windows time-zone name. Defaults to the MAILBOX time zone."),
|
|
49081
49703
|
location: external_exports.string().optional().describe("Location display name"),
|
|
49082
49704
|
body: external_exports.string().optional().describe("Event description"),
|
|
49083
|
-
attendees: recipientList,
|
|
49705
|
+
attendees: recipientList.describe("Required attendees' email addresses"),
|
|
49706
|
+
optionalAttendees: recipientList.describe("Optional attendees' email addresses"),
|
|
49707
|
+
teamsMeeting: teamsMeetingParam,
|
|
49084
49708
|
confirmToken: confirmTokenParam
|
|
49085
49709
|
})
|
|
49086
49710
|
},
|
|
49087
|
-
async ({ subject, start, end, timeZone, location, body, attendees, confirmToken }, ctx) => {
|
|
49088
|
-
const
|
|
49711
|
+
async ({ subject, start, end, timeZone, location, body, attendees, optionalAttendees, teamsMeeting, confirmToken }, ctx) => {
|
|
49712
|
+
const wantTeams = teamsMeeting !== false;
|
|
49713
|
+
const allAttendees = [
|
|
49714
|
+
...(attendees ?? []).map((a) => attendee(a, "Required")),
|
|
49715
|
+
...(optionalAttendees ?? []).map((a) => attendee(a, "Optional"))
|
|
49716
|
+
];
|
|
49717
|
+
const tz = timeZone ?? await mailboxTimeZone(client2, { required: true }) ?? "UTC";
|
|
49089
49718
|
const payload = {
|
|
49090
49719
|
Subject: subject,
|
|
49091
49720
|
Start: { DateTime: start, TimeZone: tz },
|
|
49092
49721
|
End: { DateTime: end, TimeZone: tz },
|
|
49093
49722
|
...location ? { Location: { DisplayName: location } } : {},
|
|
49094
49723
|
...body ? { Body: { ContentType: "Text", Content: body } } : {},
|
|
49095
|
-
...
|
|
49096
|
-
|
|
49097
|
-
EmailAddress: { Address },
|
|
49098
|
-
Type: "Required"
|
|
49099
|
-
}))
|
|
49100
|
-
} : {}
|
|
49724
|
+
...allAttendees.length ? { Attendees: allAttendees } : {},
|
|
49725
|
+
...wantTeams ? TEAMS : {}
|
|
49101
49726
|
};
|
|
49102
49727
|
const gate = await confirmWrite(ctx, {
|
|
49103
49728
|
tool: "outlook_create_event",
|
|
49104
49729
|
action: "calendar.create_event",
|
|
49105
49730
|
message: "Review and confirm this event (attendees are emailed an invitation):",
|
|
49106
|
-
summary: `Create event "${subject}" ${start} to ${end} (${tz})`,
|
|
49731
|
+
summary: `Create ${wantTeams ? "Teams meeting" : "event"} "${subject}" ${start} to ${end} (${tz})`,
|
|
49107
49732
|
// One signed-in mailbox per server process.
|
|
49108
49733
|
account: void 0,
|
|
49109
49734
|
request: { method: "POST", path: "/me/events", body: payload },
|
|
49110
49735
|
confirmToken
|
|
49111
49736
|
});
|
|
49112
49737
|
if (gate) return gate;
|
|
49113
|
-
const
|
|
49114
|
-
"POST",
|
|
49115
|
-
"
|
|
49116
|
-
payload
|
|
49738
|
+
const write = await writeOrUnknown(
|
|
49739
|
+
() => client2.write("POST", "/me/events", payload),
|
|
49740
|
+
"the calendar (outlook_list_events)"
|
|
49117
49741
|
);
|
|
49118
|
-
|
|
49742
|
+
if (!write.ok) return write.result;
|
|
49743
|
+
const created = write.value;
|
|
49744
|
+
let joinUrl = joinUrlOf(created);
|
|
49745
|
+
if (wantTeams && !joinUrl && created?.Id) {
|
|
49746
|
+
const after = await client2.get(
|
|
49747
|
+
`/me/events/${encodeURIComponent(created.Id)}?$select=OnlineMeeting`
|
|
49748
|
+
);
|
|
49749
|
+
joinUrl = joinUrlOf(after);
|
|
49750
|
+
}
|
|
49751
|
+
return minifiedResult({
|
|
49752
|
+
created: true,
|
|
49753
|
+
Id: created?.Id,
|
|
49754
|
+
WebLink: created?.WebLink,
|
|
49755
|
+
TimeZone: tz,
|
|
49756
|
+
...joinUrl ? { JoinUrl: joinUrl } : {},
|
|
49757
|
+
...wantTeams && !joinUrl ? { warning: "The event was created, but Outlook returned no Teams join link." } : {}
|
|
49758
|
+
});
|
|
49759
|
+
}
|
|
49760
|
+
);
|
|
49761
|
+
server.registerTool(
|
|
49762
|
+
"outlook_update_event",
|
|
49763
|
+
{
|
|
49764
|
+
description: "Update a meeting you organize: retitle, move, relocate, edit the description, or add/remove attendees. Moving only `start` keeps the meeting's length. A Microsoft Teams meeting is added if the event lacks one (`teamsMeeting: false` to skip). Attendees are sent an updated invitation. Only the organizer can update a meeting \u2014 an attendee's edit would change their own copy alone, so it is refused. The result is verified by re-reading the event. " + CONFIRM_FLOW_SENTENCE + " " + CONFIRM_INJECTION_RULE,
|
|
49765
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
49766
|
+
inputSchema: external_exports.object({
|
|
49767
|
+
id: external_exports.string().min(1).describe("Event Id from outlook_list_events or outlook_get_event"),
|
|
49768
|
+
subject: external_exports.string().optional().describe("New title"),
|
|
49769
|
+
start: external_exports.string().optional().describe("New start, ISO 8601 local time e.g. 2026-09-22T15:00:00"),
|
|
49770
|
+
end: external_exports.string().optional().describe("New end. Omit when moving `start` to keep the current length."),
|
|
49771
|
+
timeZone: external_exports.string().optional().describe("Windows time-zone name for start/end. Defaults to the MAILBOX time zone."),
|
|
49772
|
+
location: external_exports.string().optional().describe("New location display name"),
|
|
49773
|
+
body: external_exports.string().optional().describe("New description (replaces the existing one)"),
|
|
49774
|
+
addAttendees: recipientList.describe("Required attendees to add"),
|
|
49775
|
+
addOptionalAttendees: recipientList.describe("Optional attendees to add"),
|
|
49776
|
+
removeAttendees: recipientList.describe("Attendees to remove"),
|
|
49777
|
+
teamsMeeting: teamsMeetingParam,
|
|
49778
|
+
confirmToken: confirmTokenParam
|
|
49779
|
+
})
|
|
49780
|
+
},
|
|
49781
|
+
async (args, ctx) => {
|
|
49782
|
+
const tz = args.timeZone ?? await mailboxTimeZone(client2, { required: true }) ?? "UTC";
|
|
49783
|
+
const path = `/me/events/${encodeURIComponent(args.id)}`;
|
|
49784
|
+
const readEvent = (zone) => client2.get(`${path}?$select=${EVENT_STATE_SELECT}`, {
|
|
49785
|
+
prefer: `outlook.timezone="${zone}"`
|
|
49786
|
+
});
|
|
49787
|
+
const current = await readEvent(tz);
|
|
49788
|
+
if (current?.IsOrganizer === false) {
|
|
49789
|
+
throw new McpToolError("Only the organizer can update this meeting.", {
|
|
49790
|
+
hint: "You are an attendee. Changing your copy would not reach the organizer or anyone else; ask the organizer, or propose a new time from Outlook."
|
|
49791
|
+
});
|
|
49792
|
+
}
|
|
49793
|
+
const payload = {};
|
|
49794
|
+
if (args.subject !== void 0) payload.Subject = args.subject;
|
|
49795
|
+
if (args.location !== void 0) payload.Location = { DisplayName: args.location };
|
|
49796
|
+
if (args.body !== void 0) payload.Body = { ContentType: "Text", Content: args.body };
|
|
49797
|
+
if (args.start !== void 0 || args.end !== void 0) {
|
|
49798
|
+
const start = args.start !== void 0 ? dateTimeTimeZone2(args.start, tz) : void 0;
|
|
49799
|
+
let end = args.end !== void 0 ? dateTimeTimeZone2(args.end, tz) : void 0;
|
|
49800
|
+
if (start && !end && current?.Start?.DateTime && current?.End?.DateTime) {
|
|
49801
|
+
const length = naiveMs(current.End.DateTime) - naiveMs(current.Start.DateTime);
|
|
49802
|
+
end = { DateTime: fromNaiveMs(naiveMs(start.DateTime) + length), TimeZone: start.TimeZone };
|
|
49803
|
+
}
|
|
49804
|
+
if (args.start !== void 0 && args.end !== void 0 && start?.TimeZone !== end?.TimeZone) {
|
|
49805
|
+
throw new McpToolError("`start` and `end` are in different zones.", {
|
|
49806
|
+
hint: "Give both the same way: both local wall-clock times, or both with Z or an offset."
|
|
49807
|
+
});
|
|
49808
|
+
}
|
|
49809
|
+
const startIn = start ?? (end && end.TimeZone !== tz ? (await readEvent(end.TimeZone))?.Start : current?.Start);
|
|
49810
|
+
if (end && startIn?.DateTime && naiveMs(end.DateTime) <= naiveMs(startIn.DateTime)) {
|
|
49811
|
+
throw new McpToolError("The meeting would end before it starts.", {
|
|
49812
|
+
hint: "`end` must be later than `start`."
|
|
49813
|
+
});
|
|
49814
|
+
}
|
|
49815
|
+
if (start) payload.Start = start;
|
|
49816
|
+
if (end) payload.End = end;
|
|
49817
|
+
}
|
|
49818
|
+
const adding = [
|
|
49819
|
+
...(args.addAttendees ?? []).map((a) => attendee(a, "Required")),
|
|
49820
|
+
...(args.addOptionalAttendees ?? []).map((a) => attendee(a, "Optional"))
|
|
49821
|
+
];
|
|
49822
|
+
const removing = new Set((args.removeAttendees ?? []).map((a) => a.toLowerCase()));
|
|
49823
|
+
if (adding.length || removing.size) {
|
|
49824
|
+
const next = (current?.Attendees ?? []).filter((a) => !removing.has(lower(a) ?? "")).map(({ Type, EmailAddress }) => ({ Type, EmailAddress }));
|
|
49825
|
+
for (const a of adding) {
|
|
49826
|
+
if (!next.some((n) => lower(n) === lower(a))) next.push(a);
|
|
49827
|
+
}
|
|
49828
|
+
payload.Attendees = next;
|
|
49829
|
+
}
|
|
49830
|
+
const addTeams = args.teamsMeeting !== false && !(current && isTeams(current));
|
|
49831
|
+
if (addTeams) Object.assign(payload, TEAMS);
|
|
49832
|
+
if (Object.keys(payload).length === 0) {
|
|
49833
|
+
throw new McpToolError("Nothing to update.", {
|
|
49834
|
+
hint: "Pass at least one of subject, start, end, location, body, or attendee changes."
|
|
49835
|
+
});
|
|
49836
|
+
}
|
|
49837
|
+
const gate = await confirmWrite(ctx, {
|
|
49838
|
+
tool: "outlook_update_event",
|
|
49839
|
+
action: "calendar.update_event",
|
|
49840
|
+
message: "Review and confirm this change (attendees are sent an updated invitation):",
|
|
49841
|
+
summary: `Update "${current?.Subject ?? args.id}": ${Object.keys(payload).join(", ")}`,
|
|
49842
|
+
// One signed-in mailbox per server process.
|
|
49843
|
+
account: void 0,
|
|
49844
|
+
target: args.id,
|
|
49845
|
+
request: { method: "PATCH", path, body: payload },
|
|
49846
|
+
confirmToken: args.confirmToken
|
|
49847
|
+
});
|
|
49848
|
+
if (gate) return gate;
|
|
49849
|
+
await client2.write("PATCH", path, payload);
|
|
49850
|
+
const writtenZone = (payload.Start ?? payload.End)?.TimeZone ?? tz;
|
|
49851
|
+
const after = await readEvent(writtenZone);
|
|
49852
|
+
const unchanged = [];
|
|
49853
|
+
if (payload.Subject !== void 0 && after?.Subject !== payload.Subject) unchanged.push("Subject");
|
|
49854
|
+
for (const key of ["Start", "End"]) {
|
|
49855
|
+
const want = payload[key];
|
|
49856
|
+
if (want && trimTime2(after?.[key]?.DateTime) !== trimTime2(want.DateTime)) unchanged.push(key);
|
|
49857
|
+
}
|
|
49858
|
+
if (payload.Location !== void 0 && after?.Location?.DisplayName !== args.location) {
|
|
49859
|
+
unchanged.push("Location");
|
|
49860
|
+
}
|
|
49861
|
+
if (payload.Attendees !== void 0) {
|
|
49862
|
+
const want = payload.Attendees.map(lower).sort().join();
|
|
49863
|
+
const got = (after?.Attendees ?? []).map(lower).sort().join();
|
|
49864
|
+
if (want !== got) unchanged.push("Attendees");
|
|
49865
|
+
}
|
|
49866
|
+
const joinUrl = joinUrlOf(after);
|
|
49867
|
+
if (addTeams && !joinUrl) unchanged.push("Teams meeting");
|
|
49868
|
+
return minifiedResult({
|
|
49869
|
+
updated: unchanged.length === 0,
|
|
49870
|
+
Id: after?.Id ?? args.id,
|
|
49871
|
+
changed: Object.keys(payload),
|
|
49872
|
+
...joinUrl ? { JoinUrl: joinUrl } : {},
|
|
49873
|
+
...unchanged.length ? { warning: `Outlook accepted the update but these did not change: ${unchanged.join(", ")}.` } : {}
|
|
49874
|
+
});
|
|
49119
49875
|
}
|
|
49120
49876
|
);
|
|
49121
49877
|
}
|