@chrischall/office-outlook-mcp 0.5.2 → 0.6.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/mcp.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +23 -6
- package/dist/bundle.js +320 -63
- package/dist/tools/_paging.js +102 -0
- package/dist/tools/mail.js +72 -12
- package/dist/tools/writes.js +35 -25
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/server.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -248,6 +248,7 @@ __export(util_exports, {
|
|
|
248
248
|
required: () => required,
|
|
249
249
|
safeExtend: () => safeExtend,
|
|
250
250
|
shallowClone: () => shallowClone,
|
|
251
|
+
sizeCaps: () => sizeCaps,
|
|
251
252
|
slugify: () => slugify,
|
|
252
253
|
stringifyPrimitive: () => stringifyPrimitive,
|
|
253
254
|
toZod: () => toZod,
|
|
@@ -671,6 +672,18 @@ function prefixIssues(path, issues) {
|
|
|
671
672
|
function unwrapMessage(message) {
|
|
672
673
|
return typeof message === "string" ? message : message?.message;
|
|
673
674
|
}
|
|
675
|
+
function sizeCaps(defChecks) {
|
|
676
|
+
const caps = [];
|
|
677
|
+
for (const ch of defChecks ?? []) {
|
|
678
|
+
const d = ch._zod.def;
|
|
679
|
+
if (!/_(length|size)$|_equals$/.test(d.check))
|
|
680
|
+
break;
|
|
681
|
+
const max = d.maximum ?? d.length ?? d.size;
|
|
682
|
+
if (max >= 0)
|
|
683
|
+
caps.push([max, ch]);
|
|
684
|
+
}
|
|
685
|
+
return caps;
|
|
686
|
+
}
|
|
674
687
|
function attachSchema(issues, start, inst) {
|
|
675
688
|
var _a3;
|
|
676
689
|
for (let i = start; i < issues.length; i++) {
|
|
@@ -2234,7 +2247,7 @@ var init_versions = __esm({
|
|
|
2234
2247
|
version = {
|
|
2235
2248
|
major: 4,
|
|
2236
2249
|
minor: 6,
|
|
2237
|
-
patch:
|
|
2250
|
+
patch: 7
|
|
2238
2251
|
};
|
|
2239
2252
|
}
|
|
2240
2253
|
});
|
|
@@ -2396,6 +2409,28 @@ function isValidJWT(token, algorithm = null) {
|
|
|
2396
2409
|
return false;
|
|
2397
2410
|
}
|
|
2398
2411
|
}
|
|
2412
|
+
function capSize(inst, size) {
|
|
2413
|
+
const caps = sizeCaps(inst._zod.def.checks);
|
|
2414
|
+
if (!caps.length)
|
|
2415
|
+
return;
|
|
2416
|
+
const cap = Math.min(...caps.map((c) => c[0]));
|
|
2417
|
+
const parse3 = inst._zod.parse;
|
|
2418
|
+
inst._zod.parse = (payload, ctx) => {
|
|
2419
|
+
const n = size(payload.value);
|
|
2420
|
+
if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
|
|
2421
|
+
for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
|
|
2422
|
+
if (n > max && ch._zod.def.when(payload)) {
|
|
2423
|
+
const start = payload.issues.length;
|
|
2424
|
+
ch._zod.check(payload);
|
|
2425
|
+
payload.issues[start].continue = false;
|
|
2426
|
+
attachSchema(payload.issues, start, inst);
|
|
2427
|
+
return payload;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
return parse3(payload, ctx);
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2399
2434
|
function handleArrayResult(result, final, index) {
|
|
2400
2435
|
if (result.issues.length) {
|
|
2401
2436
|
final.issues.push(...prefixIssues(index, result.issues));
|
|
@@ -2896,7 +2931,7 @@ function handleRefineResult(result, payload, input2, inst) {
|
|
|
2896
2931
|
payload.issues.push(issue(_iss));
|
|
2897
2932
|
}
|
|
2898
2933
|
}
|
|
2899
|
-
var $ZodType, toStandardResult, $ZodString, $ZodStringFormat, $ZodGUID, $ZodUUID, $ZodEmail, URL_BAD_FORMAT, URL_UNPARSEABLE, asciiTabOrNewline, $ZodURL, $ZodEmoji, $ZodNanoID, $ZodCUID, $ZodCUID2, $ZodULID, $ZodXID, $ZodKSUID, $ZodISODateTime, $ZodISODate, $ZodISOTime, $ZodISODuration, $ZodIPv4, ipv6Alphabet, $ZodIPv6, $ZodMAC, $ZodCIDRv4, $ZodCIDRv6, base64Charset, $ZodBase64, base64urlCharset, $ZodBase64URL, $ZodE164, CC_SANITIZE, $ZodCreditCard, $ZodIBAN, $ZodJWT, $ZodCustomStringFormat, $ZodNumber, $ZodNumberFormat, $ZodBoolean, $ZodBigInt, $ZodBigIntFormat, $ZodSymbol, $ZodUndefined, $ZodNull, $ZodAny, $ZodUnknown, $ZodNever, $ZodVoid, $ZodDate, $ZodArray, NO_SYMBOL_KEYS, $ZodObject, $ZodObjectJIT, $ZodUnion, $ZodXor, $ZodDiscriminatedUnion, $ZodIntersection, $ZodTuple, $ZodRecord, $ZodMap, $ZodSet, $ZodEnum, $ZodLiteral, $ZodFile, $ZodTransform, $ZodOptional, $ZodExactOptional, $ZodNullable, $ZodDefault, $ZodPrefault, $ZodNonOptional, $ZodSuccess, $ZodCatch, $ZodNaN, $ZodPipe, $ZodCodec, $ZodPreprocess, $ZodReadonly, $ZodTemplateLiteral, $ZodFunction, $ZodPromise, $ZodLazy, $ZodCustom;
|
|
2934
|
+
var $ZodType, toStandardResult, $ZodString, $ZodStringFormat, $ZodGUID, $ZodUUID, $ZodEmail, URL_BAD_FORMAT, URL_UNPARSEABLE, asciiTabOrNewline, $ZodURL, $ZodEmoji, $ZodNanoID, $ZodCUID, $ZodCUID2, $ZodULID, $ZodXID, $ZodKSUID, $ZodISODateTime, $ZodISODate, $ZodISOTime, $ZodISODuration, $ZodIPv4, ipv6Alphabet, $ZodIPv6, $ZodMAC, $ZodCIDRv4, $ZodCIDRv6, base64Charset, $ZodBase64, base64urlCharset, $ZodBase64URL, $ZodE164, CC_SANITIZE, $ZodCreditCard, $ZodIBAN, $ZodJWT, $ZodCustomStringFormat, $ZodNumber, $ZodNumberFormat, $ZodBoolean, $ZodBigInt, $ZodBigIntFormat, $ZodSymbol, $ZodUndefined, $ZodNull, $ZodAny, $ZodUnknown, $ZodNever, $ZodVoid, $ZodDate, arraySize, mapSize, setSize, $ZodArray, NO_SYMBOL_KEYS, $ZodObject, $ZodObjectJIT, $ZodUnion, $ZodXor, $ZodDiscriminatedUnion, $ZodIntersection, $ZodTuple, $ZodRecord, $ZodMap, $ZodSet, $ZodEnum, $ZodLiteral, $ZodFile, $ZodTransform, $ZodOptional, $ZodExactOptional, $ZodNullable, $ZodDefault, $ZodPrefault, $ZodNonOptional, $ZodSuccess, $ZodCatch, $ZodNaN, $ZodPipe, $ZodCodec, $ZodPreprocess, $ZodReadonly, $ZodTemplateLiteral, $ZodFunction, $ZodPromise, $ZodLazy, $ZodCustom;
|
|
2900
2935
|
var init_schemas = __esm({
|
|
2901
2936
|
"node_modules/zod/v4/core/schemas.js"() {
|
|
2902
2937
|
init_checks();
|
|
@@ -3503,6 +3538,9 @@ var init_schemas = __esm({
|
|
|
3503
3538
|
return payload;
|
|
3504
3539
|
};
|
|
3505
3540
|
});
|
|
3541
|
+
arraySize = (input2) => Array.isArray(input2) ? input2.length : -1;
|
|
3542
|
+
mapSize = (input2) => input2 instanceof Map ? input2.size : -1;
|
|
3543
|
+
setSize = (input2) => input2 instanceof Set ? input2.size : -1;
|
|
3506
3544
|
$ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
|
|
3507
3545
|
$ZodType.init(inst, def);
|
|
3508
3546
|
const memo3 = globalConfig.memoizer;
|
|
@@ -3540,6 +3578,7 @@ var init_schemas = __esm({
|
|
|
3540
3578
|
}
|
|
3541
3579
|
return payload;
|
|
3542
3580
|
};
|
|
3581
|
+
capSize(inst, arraySize);
|
|
3543
3582
|
});
|
|
3544
3583
|
NO_SYMBOL_KEYS = [];
|
|
3545
3584
|
$ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
@@ -4178,6 +4217,7 @@ var init_schemas = __esm({
|
|
|
4178
4217
|
return Promise.all(proms).then(() => payload);
|
|
4179
4218
|
return payload;
|
|
4180
4219
|
};
|
|
4220
|
+
capSize(inst, mapSize);
|
|
4181
4221
|
});
|
|
4182
4222
|
$ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
|
|
4183
4223
|
$ZodType.init(inst, def);
|
|
@@ -4214,6 +4254,7 @@ var init_schemas = __esm({
|
|
|
4214
4254
|
return Promise.all(proms).then(() => payload);
|
|
4215
4255
|
return payload;
|
|
4216
4256
|
};
|
|
4257
|
+
capSize(inst, setSize);
|
|
4217
4258
|
});
|
|
4218
4259
|
$ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
4219
4260
|
$ZodType.init(inst, def);
|
|
@@ -14003,9 +14044,15 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
14003
14044
|
doc.write(`}`);
|
|
14004
14045
|
return outputVar;
|
|
14005
14046
|
}
|
|
14047
|
+
function generateSizeGate(doc, schema, size) {
|
|
14048
|
+
for (const [max, check2] of sizeCaps(schema._zod.def.checks)) {
|
|
14049
|
+
doc.write(`if (${size} > ${numericOperand(max, check2._zod.def.check)}) return INVALID;`);
|
|
14050
|
+
}
|
|
14051
|
+
}
|
|
14006
14052
|
function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
14007
14053
|
const def = schema._zod.def;
|
|
14008
14054
|
doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
|
|
14055
|
+
generateSizeGate(doc, schema, `${accessor}.length`);
|
|
14009
14056
|
const outputVar = buildsValue ? newVar(ctx) : null;
|
|
14010
14057
|
const iVar = newVar(ctx);
|
|
14011
14058
|
const elemVar = newVar(ctx);
|
|
@@ -14405,6 +14452,7 @@ function literalPropertyKey(ctx, key) {
|
|
|
14405
14452
|
function generateMapCheck(doc, ctx, schema, accessor) {
|
|
14406
14453
|
const def = schema._zod.def;
|
|
14407
14454
|
doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
|
|
14455
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
14408
14456
|
const outputVar = newVar(ctx);
|
|
14409
14457
|
const kVar = newVar(ctx);
|
|
14410
14458
|
const valVar = newVar(ctx);
|
|
@@ -14421,6 +14469,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
|
|
|
14421
14469
|
function generateSetCheck(doc, ctx, schema, accessor) {
|
|
14422
14470
|
const def = schema._zod.def;
|
|
14423
14471
|
doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
|
|
14472
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
14424
14473
|
const outputVar = newVar(ctx);
|
|
14425
14474
|
const valVar = newVar(ctx);
|
|
14426
14475
|
doc.write(`const ${outputVar} = new Set();`);
|
|
@@ -35482,7 +35531,7 @@ var init_errors3 = __esm({
|
|
|
35482
35531
|
// webhook signing secret (Stripe-style)
|
|
35483
35532
|
].map((p) => `\\b${p}`).join("|"), "g");
|
|
35484
35533
|
GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
|
|
35485
|
-
QUERY_SECRET_RE = new RegExp(`([
|
|
35534
|
+
QUERY_SECRET_RE = new RegExp(`([?&#;](?:(?:access|refresh|id|auth|session|csrf|xsrf|api)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|secret|token|auth|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
|
|
35486
35535
|
OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
|
|
35487
35536
|
FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
|
|
35488
35537
|
HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
|
|
@@ -35493,6 +35542,8 @@ var init_errors3 = __esm({
|
|
|
35493
35542
|
"api[_-]?secret",
|
|
35494
35543
|
"(?:x[_-])?api[_-]?key",
|
|
35495
35544
|
"private[_-]?key",
|
|
35545
|
+
"aws[_-]?secret[_-]?access[_-]?key",
|
|
35546
|
+
// fleet-audit#1297
|
|
35496
35547
|
"code[_-]?verifier",
|
|
35497
35548
|
// PKCE; a bare `verifier` key is too generic for JSON
|
|
35498
35549
|
`x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
|
|
@@ -36651,6 +36702,9 @@ var init_headers = __esm({
|
|
|
36651
36702
|
});
|
|
36652
36703
|
|
|
36653
36704
|
// node_modules/@chrischall/mcp-utils/dist/internal/ascii.js
|
|
36705
|
+
function asciiLower(s) {
|
|
36706
|
+
return s.replace(/[A-Z]+/g, (m) => m.toLowerCase());
|
|
36707
|
+
}
|
|
36654
36708
|
var init_ascii = __esm({
|
|
36655
36709
|
"node_modules/@chrischall/mcp-utils/dist/internal/ascii.js"() {
|
|
36656
36710
|
}
|
|
@@ -36662,16 +36716,42 @@ var init_code_point = __esm({
|
|
|
36662
36716
|
}
|
|
36663
36717
|
});
|
|
36664
36718
|
|
|
36719
|
+
// node_modules/@chrischall/mcp-utils/dist/internal/tags.js
|
|
36720
|
+
var init_tags = __esm({
|
|
36721
|
+
"node_modules/@chrischall/mcp-utils/dist/internal/tags.js"() {
|
|
36722
|
+
}
|
|
36723
|
+
});
|
|
36724
|
+
|
|
36665
36725
|
// node_modules/@chrischall/mcp-utils/dist/scrape/index.js
|
|
36666
36726
|
function isCloudflareChallenge(html) {
|
|
36667
|
-
return
|
|
36727
|
+
return html.includes("_cf_chl_opt") || hasJustAMomentTitle(html);
|
|
36728
|
+
}
|
|
36729
|
+
function hasJustAMomentTitle(html) {
|
|
36730
|
+
const lower2 = asciiLower(html);
|
|
36731
|
+
let i = 0;
|
|
36732
|
+
for (; ; ) {
|
|
36733
|
+
const open2 = lower2.indexOf("<title", i);
|
|
36734
|
+
if (open2 < 0)
|
|
36735
|
+
return false;
|
|
36736
|
+
const gt = lower2.indexOf(">", open2 + 6);
|
|
36737
|
+
if (gt < 0)
|
|
36738
|
+
return false;
|
|
36739
|
+
let j = gt + 1;
|
|
36740
|
+
while (j < lower2.length && /\s/.test(lower2[j]))
|
|
36741
|
+
j++;
|
|
36742
|
+
if (lower2.startsWith(JUST_A_MOMENT, j))
|
|
36743
|
+
return true;
|
|
36744
|
+
i = gt + 1;
|
|
36745
|
+
}
|
|
36668
36746
|
}
|
|
36669
|
-
var NEXT_DATA_MAX_CHARS;
|
|
36747
|
+
var NEXT_DATA_MAX_CHARS, JUST_A_MOMENT;
|
|
36670
36748
|
var init_scrape = __esm({
|
|
36671
36749
|
"node_modules/@chrischall/mcp-utils/dist/scrape/index.js"() {
|
|
36672
36750
|
init_ascii();
|
|
36673
36751
|
init_code_point();
|
|
36752
|
+
init_tags();
|
|
36674
36753
|
NEXT_DATA_MAX_CHARS = 16 * 1024 * 1024;
|
|
36754
|
+
JUST_A_MOMENT = "just a moment";
|
|
36675
36755
|
}
|
|
36676
36756
|
});
|
|
36677
36757
|
|
|
@@ -36707,7 +36787,7 @@ var init_net_atoms = __esm({
|
|
|
36707
36787
|
// node_modules/@chrischall/mcp-utils/dist/http/body-limit.js
|
|
36708
36788
|
async function readBytesCapped(res, maxBytes, service, bounded) {
|
|
36709
36789
|
if (maxBytes !== void 0) {
|
|
36710
|
-
const declared = Number(res
|
|
36790
|
+
const declared = Number(responseHeader(res, "content-length") ?? NaN);
|
|
36711
36791
|
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
36712
36792
|
cancelStream(res.body);
|
|
36713
36793
|
throw new ResponseTooLargeError(service, maxBytes);
|
|
@@ -36752,6 +36832,7 @@ var ResponseTooLargeError;
|
|
|
36752
36832
|
var init_body_limit = __esm({
|
|
36753
36833
|
"node_modules/@chrischall/mcp-utils/dist/http/body-limit.js"() {
|
|
36754
36834
|
init_errors3();
|
|
36835
|
+
init_headers();
|
|
36755
36836
|
ResponseTooLargeError = class extends McpToolError {
|
|
36756
36837
|
maxBytes;
|
|
36757
36838
|
constructor(service, maxBytes) {
|
|
@@ -36831,6 +36912,27 @@ var init_retry_after = __esm({
|
|
|
36831
36912
|
});
|
|
36832
36913
|
|
|
36833
36914
|
// node_modules/@chrischall/mcp-utils/dist/http/index.js
|
|
36915
|
+
function withoutCredentials(headers, tokenHeader) {
|
|
36916
|
+
const drop = new Set(CROSS_ORIGIN_DROPPED_HEADERS);
|
|
36917
|
+
if (tokenHeader !== void 0)
|
|
36918
|
+
drop.add(tokenHeader.toLowerCase());
|
|
36919
|
+
const out = {};
|
|
36920
|
+
for (const [k, v] of Object.entries(headers))
|
|
36921
|
+
if (!drop.has(k.toLowerCase()))
|
|
36922
|
+
out[k] = v;
|
|
36923
|
+
return out;
|
|
36924
|
+
}
|
|
36925
|
+
function joinQuery(path, query) {
|
|
36926
|
+
if (!query)
|
|
36927
|
+
return path;
|
|
36928
|
+
const hashAt = path.indexOf("#");
|
|
36929
|
+
const head = hashAt < 0 ? path : path.slice(0, hashAt);
|
|
36930
|
+
const fragment = hashAt < 0 ? "" : path.slice(hashAt);
|
|
36931
|
+
if (!head.includes("?"))
|
|
36932
|
+
return `${head}${query}${fragment}`;
|
|
36933
|
+
const sep = head.endsWith("?") || head.endsWith("&") ? "" : "&";
|
|
36934
|
+
return `${head}${sep}${query.slice(1)}${fragment}`;
|
|
36935
|
+
}
|
|
36834
36936
|
function cancelBody(res) {
|
|
36835
36937
|
try {
|
|
36836
36938
|
const body = res.body;
|
|
@@ -36959,7 +37061,9 @@ function createApiClient(opts) {
|
|
|
36959
37061
|
const retryStatuses = retry.statuses ?? [429];
|
|
36960
37062
|
const timeoutMs = opts.timeout === false ? 0 : opts.timeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
36961
37063
|
const redirectMode = opts.redirect;
|
|
36962
|
-
const
|
|
37064
|
+
const stripCrossOrigin = redirectMode === void 0 && opts.tokenHeader !== void 0;
|
|
37065
|
+
const manualRedirects = redirectMode === "same-origin" || stripCrossOrigin;
|
|
37066
|
+
const maxRedirects = opts.maxRedirects ?? (stripCrossOrigin ? FETCH_MAX_REDIRECTS : DEFAULT_MAX_REDIRECTS);
|
|
36963
37067
|
const reportWriteOutcome = opts.writeOutcomeUnknown !== false;
|
|
36964
37068
|
const defaultMaxBytes = checkMaxBytes("maxResponseBytes", opts.maxResponseBytes);
|
|
36965
37069
|
if (!Number.isSafeInteger(maxRedirects) || maxRedirects < 0) {
|
|
@@ -37061,7 +37165,7 @@ function createApiClient(opts) {
|
|
|
37061
37165
|
`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.`
|
|
37062
37166
|
);
|
|
37063
37167
|
}
|
|
37064
|
-
const url2 = `${base}${path
|
|
37168
|
+
const url2 = `${base}${joinQuery(path, query)}`;
|
|
37065
37169
|
if (baseOrigin === void 0)
|
|
37066
37170
|
return url2;
|
|
37067
37171
|
let parsed;
|
|
@@ -37075,14 +37179,21 @@ function createApiClient(opts) {
|
|
|
37075
37179
|
}
|
|
37076
37180
|
return url2;
|
|
37077
37181
|
}
|
|
37078
|
-
function
|
|
37079
|
-
let next;
|
|
37182
|
+
function resolveLocation(location, from) {
|
|
37080
37183
|
try {
|
|
37081
|
-
|
|
37184
|
+
return new URL(location, from).href;
|
|
37082
37185
|
} catch {
|
|
37083
37186
|
throw new RedirectRefusedError(`Refusing redirect from ${service}: the Location header is not a valid URL.`);
|
|
37084
37187
|
}
|
|
37085
|
-
|
|
37188
|
+
}
|
|
37189
|
+
function onBaseOrigin(href) {
|
|
37190
|
+
const next = new URL(href);
|
|
37191
|
+
return baseOrigin !== void 0 && next.origin === baseOrigin && next.username === baseParts?.username && next.password === baseParts?.password;
|
|
37192
|
+
}
|
|
37193
|
+
function sameOriginHop(location, from) {
|
|
37194
|
+
const href = resolveLocation(location, from);
|
|
37195
|
+
const next = new URL(href);
|
|
37196
|
+
if (!onBaseOrigin(href)) {
|
|
37086
37197
|
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.`);
|
|
37087
37198
|
}
|
|
37088
37199
|
return next.href;
|
|
@@ -37092,20 +37203,21 @@ function createApiClient(opts) {
|
|
|
37092
37203
|
const query = opt.query ? buildQueryString(opt.query) : "";
|
|
37093
37204
|
const url2 = resolveUrl(path, query);
|
|
37094
37205
|
const bodyInit = reqBody !== void 0 ? { body: reqBody } : {};
|
|
37095
|
-
const fetchHop = (hopUrl, hopMethod, withBody, token, signal) => {
|
|
37206
|
+
const fetchHop = (hopUrl, hopMethod, withBody, token, signal, credentialed = true) => {
|
|
37096
37207
|
if (!signal?.aborted)
|
|
37097
37208
|
dispatch.sent = true;
|
|
37209
|
+
const headers = {
|
|
37210
|
+
Accept: "application/json",
|
|
37211
|
+
...withBody && contentType !== void 0 ? { "Content-Type": contentType } : {},
|
|
37212
|
+
...opts.baseHeaders,
|
|
37213
|
+
...authHeader(token || void 0),
|
|
37214
|
+
...opt.headers
|
|
37215
|
+
};
|
|
37098
37216
|
return doFetch(hopUrl, {
|
|
37099
37217
|
method: hopMethod,
|
|
37100
|
-
headers:
|
|
37101
|
-
Accept: "application/json",
|
|
37102
|
-
...withBody && contentType !== void 0 ? { "Content-Type": contentType } : {},
|
|
37103
|
-
...opts.baseHeaders,
|
|
37104
|
-
...authHeader(token || void 0),
|
|
37105
|
-
...opt.headers
|
|
37106
|
-
},
|
|
37218
|
+
headers: credentialed ? headers : withoutCredentials(headers, opts.tokenHeader),
|
|
37107
37219
|
...signal ? { signal } : {},
|
|
37108
|
-
...
|
|
37220
|
+
...manualRedirects ? { redirect: "manual" } : redirectMode !== void 0 ? { redirect: redirectMode } : {},
|
|
37109
37221
|
...withBody ? bodyInit : {}
|
|
37110
37222
|
}).catch((err) => {
|
|
37111
37223
|
markTransportFailure(err);
|
|
@@ -37113,13 +37225,14 @@ function createApiClient(opts) {
|
|
|
37113
37225
|
});
|
|
37114
37226
|
};
|
|
37115
37227
|
const fetchWith = async (token, signal) => {
|
|
37116
|
-
if (
|
|
37228
|
+
if (!manualRedirects)
|
|
37117
37229
|
return fetchHop(url2, method, true, token, signal);
|
|
37118
37230
|
let hopUrl = url2;
|
|
37119
37231
|
let hopMethod = method;
|
|
37120
37232
|
let withBody = true;
|
|
37233
|
+
let credentialed = true;
|
|
37121
37234
|
for (let hops = 0; ; hops += 1) {
|
|
37122
|
-
const res = await fetchHop(hopUrl, hopMethod, withBody, token, signal);
|
|
37235
|
+
const res = await fetchHop(hopUrl, hopMethod, withBody, token, signal, credentialed);
|
|
37123
37236
|
const location = REDIRECT_STATUSES.has(res.status) ? responseHeader(res, "location") : void 0;
|
|
37124
37237
|
if (location === void 0 || location === null)
|
|
37125
37238
|
return res;
|
|
@@ -37127,7 +37240,13 @@ function createApiClient(opts) {
|
|
|
37127
37240
|
if (hops >= maxRedirects) {
|
|
37128
37241
|
throw new RedirectRefusedError(`Refusing request to ${service}: too many redirects (more than ${maxRedirects}).`);
|
|
37129
37242
|
}
|
|
37130
|
-
|
|
37243
|
+
if (stripCrossOrigin) {
|
|
37244
|
+
hopUrl = resolveLocation(location, hopUrl);
|
|
37245
|
+
if (credentialed && !onBaseOrigin(hopUrl))
|
|
37246
|
+
credentialed = false;
|
|
37247
|
+
} else {
|
|
37248
|
+
hopUrl = sameOriginHop(location, hopUrl);
|
|
37249
|
+
}
|
|
37131
37250
|
if (res.status === 303 || (res.status === 301 || res.status === 302) && hopMethod === "POST") {
|
|
37132
37251
|
if (hopMethod !== "HEAD")
|
|
37133
37252
|
hopMethod = "GET";
|
|
@@ -37331,7 +37450,7 @@ function decodeJwtExp(token) {
|
|
|
37331
37450
|
}
|
|
37332
37451
|
return exp;
|
|
37333
37452
|
}
|
|
37334
|
-
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;
|
|
37453
|
+
var DEFAULT_RETRY, defaultSleep, DEFAULT_REQUEST_TIMEOUT_MS, DEFAULT_MAX_REDIRECTS, FETCH_MAX_REDIRECTS, CROSS_ORIGIN_DROPPED_HEADERS, REDIRECT_STATUSES, UnauthorizedError, RateLimitedError, RequestTimeoutError, DEFAULT_WRITE_OUTCOME_HINT, WriteOutcomeUnknownError, SAFE_METHODS, ApiError, EdgeBlockedError, EDGE_BLOCK_SCAN_MAX, EDGE_BLOCK_MARKERS;
|
|
37335
37454
|
var init_http = __esm({
|
|
37336
37455
|
"node_modules/@chrischall/mcp-utils/dist/http/index.js"() {
|
|
37337
37456
|
init_headers();
|
|
@@ -37351,6 +37470,8 @@ var init_http = __esm({
|
|
|
37351
37470
|
defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
37352
37471
|
DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
37353
37472
|
DEFAULT_MAX_REDIRECTS = 5;
|
|
37473
|
+
FETCH_MAX_REDIRECTS = 20;
|
|
37474
|
+
CROSS_ORIGIN_DROPPED_HEADERS = ["authorization", "cookie", "proxy-authorization"];
|
|
37354
37475
|
REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
37355
37476
|
UnauthorizedError = class extends Error {
|
|
37356
37477
|
status = 401;
|
|
@@ -37588,7 +37709,7 @@ var init_version = __esm({
|
|
|
37588
37709
|
"src/version.ts"() {
|
|
37589
37710
|
"use strict";
|
|
37590
37711
|
PACKAGE_NAME = "office-outlook-mcp";
|
|
37591
|
-
VERSION = "0.
|
|
37712
|
+
VERSION = "0.6.1";
|
|
37592
37713
|
}
|
|
37593
37714
|
});
|
|
37594
37715
|
|
|
@@ -38143,6 +38264,21 @@ var init_public_suffix = __esm({
|
|
|
38143
38264
|
}
|
|
38144
38265
|
});
|
|
38145
38266
|
|
|
38267
|
+
// node_modules/@fetchproxy/protocol/dist/ip-literal.js
|
|
38268
|
+
function isIpLiteralHost(host) {
|
|
38269
|
+
if (host.includes(":") || host.startsWith("["))
|
|
38270
|
+
return true;
|
|
38271
|
+
const labels = host.split(".");
|
|
38272
|
+
if (labels.length > 1 && labels[labels.length - 1] === "")
|
|
38273
|
+
labels.pop();
|
|
38274
|
+
const last = labels[labels.length - 1] ?? "";
|
|
38275
|
+
return /^[0-9]+$/.test(last) || /^0x[0-9a-f]*$/i.test(last);
|
|
38276
|
+
}
|
|
38277
|
+
var init_ip_literal = __esm({
|
|
38278
|
+
"node_modules/@fetchproxy/protocol/dist/ip-literal.js"() {
|
|
38279
|
+
}
|
|
38280
|
+
});
|
|
38281
|
+
|
|
38146
38282
|
// node_modules/@fetchproxy/protocol/dist/validate.js
|
|
38147
38283
|
function assertObject(x, label) {
|
|
38148
38284
|
if (typeof x !== "object" || x === null || Array.isArray(x)) {
|
|
@@ -38592,6 +38728,9 @@ function validateHello(raw) {
|
|
|
38592
38728
|
if (!HOSTNAME_RE.test(d)) {
|
|
38593
38729
|
throw new ProtocolError3(`hello.domains: invalid hostname ${JSON.stringify(d)}`);
|
|
38594
38730
|
}
|
|
38731
|
+
if (isIpLiteralHost(d)) {
|
|
38732
|
+
throw new ProtocolError3(`hello.domains: ${JSON.stringify(d)} is an IP address \u2014 declare the site's DNS hostname; IP-literal origins (LAN, loopback, link-local) are not reachable through the bridge`);
|
|
38733
|
+
}
|
|
38595
38734
|
if (isPublicSuffix(d)) {
|
|
38596
38735
|
throw new ProtocolError3(`hello.domains: ${JSON.stringify(d)} is a public suffix \u2014 declare a registrable domain (e.g. "example.${d}"), not the suffix every site under it shares`);
|
|
38597
38736
|
}
|
|
@@ -39365,6 +39504,7 @@ var init_validate = __esm({
|
|
|
39365
39504
|
init_account_attest();
|
|
39366
39505
|
init_json_pointer();
|
|
39367
39506
|
init_public_suffix();
|
|
39507
|
+
init_ip_literal();
|
|
39368
39508
|
ProtocolError3 = class extends Error {
|
|
39369
39509
|
constructor(message) {
|
|
39370
39510
|
super(message);
|
|
@@ -39544,6 +39684,7 @@ var init_dist3 = __esm({
|
|
|
39544
39684
|
init_seal();
|
|
39545
39685
|
init_encoding();
|
|
39546
39686
|
init_public_suffix();
|
|
39687
|
+
init_ip_literal();
|
|
39547
39688
|
init_json_pointer();
|
|
39548
39689
|
}
|
|
39549
39690
|
});
|
|
@@ -44514,7 +44655,7 @@ async function startHost(opts) {
|
|
|
44514
44655
|
if (ownRetryableRefusal)
|
|
44515
44656
|
resetSessionPromise();
|
|
44516
44657
|
resolveOwnSession(ownSession);
|
|
44517
|
-
} else {
|
|
44658
|
+
} else if (identified === "extension" && extensionWs === ws) {
|
|
44518
44659
|
const slot = peers.get(frame.mcpId);
|
|
44519
44660
|
if (slot)
|
|
44520
44661
|
slot.ws.send(JSON.stringify(frame));
|
|
@@ -45602,6 +45743,9 @@ var init_ws_server = __esm({
|
|
|
45602
45743
|
throw new Error("FetchproxyServer: opts.domains must be a non-empty array of hostnames");
|
|
45603
45744
|
}
|
|
45604
45745
|
for (const d of opts.domains) {
|
|
45746
|
+
if (typeof d === "string" && isIpLiteralHost(d)) {
|
|
45747
|
+
throw new Error(`FetchproxyServer: opts.domains entry ${JSON.stringify(d)} is an IP address \u2014 declare the site's DNS hostname; IP-literal origins (LAN, loopback, link-local) are not reachable through the bridge`);
|
|
45748
|
+
}
|
|
45605
45749
|
if (typeof d === "string" && isPublicSuffix(d)) {
|
|
45606
45750
|
throw new Error(`FetchproxyServer: opts.domains entry ${JSON.stringify(d)} is a public suffix \u2014 declare a registrable domain (e.g. "example.${d}"), not the suffix every site under it shares`);
|
|
45607
45751
|
}
|
|
@@ -47547,8 +47691,10 @@ var init_bulk = __esm({
|
|
|
47547
47691
|
});
|
|
47548
47692
|
|
|
47549
47693
|
// node_modules/@fetchproxy/server/dist/bot-wall.js
|
|
47694
|
+
var CLOUDFLARE_TITLE_SCAN_MAX;
|
|
47550
47695
|
var init_bot_wall = __esm({
|
|
47551
47696
|
"node_modules/@fetchproxy/server/dist/bot-wall.js"() {
|
|
47697
|
+
CLOUDFLARE_TITLE_SCAN_MAX = 64 * 1024;
|
|
47552
47698
|
}
|
|
47553
47699
|
});
|
|
47554
47700
|
|
|
@@ -49028,6 +49174,7 @@ function isMeetingMessage(m) {
|
|
|
49028
49174
|
|
|
49029
49175
|
// src/tools/_paging.ts
|
|
49030
49176
|
init_zod();
|
|
49177
|
+
init_dist2();
|
|
49031
49178
|
var nextLinkParam = external_exports.string().min(1).optional().describe(
|
|
49032
49179
|
"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."
|
|
49033
49180
|
);
|
|
@@ -49043,6 +49190,71 @@ function plainCollection(data) {
|
|
|
49043
49190
|
if (next) out.nextLink = next;
|
|
49044
49191
|
return out;
|
|
49045
49192
|
}
|
|
49193
|
+
var fromParam = external_exports.string().min(1).optional().describe(
|
|
49194
|
+
'Page oldest first from this point: an ISO 8601 UTC time (e.g. "2026-10-01T00:00:00Z"; one with no zone is read as UTC), or a previous result\'s `next_from` passed unchanged. The result always carries `next_from` and `has_more`.'
|
|
49195
|
+
);
|
|
49196
|
+
var MAX_TOP = 1e3;
|
|
49197
|
+
var FROM_HINT = "Pass a previous next_from unchanged, or an ISO 8601 time such as 2026-10-01T00:00:00Z (one with no zone is read as UTC).";
|
|
49198
|
+
var ISO_DATE_TIME = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?)?$/i;
|
|
49199
|
+
function utcLiteral(ms) {
|
|
49200
|
+
return new Date(ms).toISOString().replace(/\.000Z$/, "Z");
|
|
49201
|
+
}
|
|
49202
|
+
function parseTime(raw) {
|
|
49203
|
+
if (!ISO_DATE_TIME.test(raw)) return void 0;
|
|
49204
|
+
const t = /T[\d:.]+$/.test(raw) ? `${raw}Z` : raw;
|
|
49205
|
+
const ms = Date.parse(t);
|
|
49206
|
+
if (Number.isNaN(ms)) return void 0;
|
|
49207
|
+
if (new Date(ms + offsetMs(t)).toISOString().slice(0, 10) !== t.slice(0, 10)) return void 0;
|
|
49208
|
+
return ms;
|
|
49209
|
+
}
|
|
49210
|
+
function offsetMs(t) {
|
|
49211
|
+
const m = /([+-])(\d{2}):?(\d{2})$/.exec(t);
|
|
49212
|
+
if (!m || /Z$/i.test(t)) return 0;
|
|
49213
|
+
const mins = Number(m[2]) * 60 + Number(m[3]);
|
|
49214
|
+
return (m[1] === "-" ? -1 : 1) * mins * 6e4;
|
|
49215
|
+
}
|
|
49216
|
+
function encodeFrom(pos) {
|
|
49217
|
+
return Buffer.from(JSON.stringify({ t: pos.t, ids: pos.ids }), "utf8").toString("base64url");
|
|
49218
|
+
}
|
|
49219
|
+
function decodeFrom(from) {
|
|
49220
|
+
if (/^\d{4}-/.test(from)) {
|
|
49221
|
+
const ms2 = parseTime(from);
|
|
49222
|
+
if (ms2 === void 0) {
|
|
49223
|
+
throw new McpToolError(`\`from\` is not a valid ISO 8601 UTC date-time: ${from}`, { hint: FROM_HINT });
|
|
49224
|
+
}
|
|
49225
|
+
return { t: utcLiteral(ms2), ids: [] };
|
|
49226
|
+
}
|
|
49227
|
+
let parsed;
|
|
49228
|
+
try {
|
|
49229
|
+
parsed = /^[A-Za-z0-9_-]+$/.test(from) ? JSON.parse(Buffer.from(from, "base64url").toString("utf8")) : void 0;
|
|
49230
|
+
} catch {
|
|
49231
|
+
parsed = void 0;
|
|
49232
|
+
}
|
|
49233
|
+
const p = parsed;
|
|
49234
|
+
const ms = typeof p?.t === "string" ? parseTime(p.t) : void 0;
|
|
49235
|
+
if (ms === void 0 || !Array.isArray(p?.ids) || !p.ids.every((id) => typeof id === "string")) {
|
|
49236
|
+
throw new McpToolError("`from` is neither an ISO 8601 time nor a next_from token.", { hint: FROM_HINT });
|
|
49237
|
+
}
|
|
49238
|
+
return { t: utcLiteral(ms), ids: p.ids };
|
|
49239
|
+
}
|
|
49240
|
+
function fromQuery(pos, limit) {
|
|
49241
|
+
return { bound: `ReceivedDateTime ge ${pos.t}`, top: Math.min(limit + pos.ids.length, MAX_TOP) };
|
|
49242
|
+
}
|
|
49243
|
+
function fromPage(rows, pos, limit, apiHasMore) {
|
|
49244
|
+
const seen = new Set(pos.ids);
|
|
49245
|
+
const fresh = rows.filter((r) => r.Id === void 0 || !seen.has(r.Id));
|
|
49246
|
+
const kept = fresh.slice(0, limit);
|
|
49247
|
+
const next = nextPosition(kept, pos);
|
|
49248
|
+
return { rows: kept, next_from: encodeFrom(next), has_more: fresh.length > limit || apiHasMore };
|
|
49249
|
+
}
|
|
49250
|
+
function nextPosition(rows, pos) {
|
|
49251
|
+
const dated = rows.map((r) => ({ id: r.Id, ms: r.ReceivedDateTime ? Date.parse(r.ReceivedDateTime) : NaN })).filter((r) => r.id !== void 0 && !Number.isNaN(r.ms));
|
|
49252
|
+
if (dated.length === 0) return pos;
|
|
49253
|
+
const newest = Math.max(...dated.map((r) => r.ms));
|
|
49254
|
+
const tied = dated.filter((r) => r.ms === newest).map((r) => r.id);
|
|
49255
|
+
const carried = newest === Date.parse(pos.t) ? pos.ids.filter((id) => !tied.includes(id)) : [];
|
|
49256
|
+
return { t: utcLiteral(newest), ids: [...carried, ...tied] };
|
|
49257
|
+
}
|
|
49046
49258
|
|
|
49047
49259
|
// src/tools/_untrusted.ts
|
|
49048
49260
|
init_dist2();
|
|
@@ -49152,6 +49364,37 @@ function utf8Boundary(buf, max) {
|
|
|
49152
49364
|
function sinceLiteral(hours) {
|
|
49153
49365
|
return new Date(Date.now() - hours * 36e5).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
49154
49366
|
}
|
|
49367
|
+
function fromPosition(from, conflicts) {
|
|
49368
|
+
if (from === void 0) return void 0;
|
|
49369
|
+
for (const [name, value] of Object.entries(conflicts)) {
|
|
49370
|
+
if (value === void 0) continue;
|
|
49371
|
+
throw new McpToolError(`\`from\` and \`${name}\` cannot be combined.`, {
|
|
49372
|
+
hint: name === "nextLink" ? "To continue a `from` listing, pass its `next_from` as `from`; `nextLink` belongs to the newest-first listing." : `\`from\` pages oldest first by time; drop \`${name}\`, or omit \`from\` for the newest-first listing.`
|
|
49373
|
+
});
|
|
49374
|
+
}
|
|
49375
|
+
return decodeFrom(from);
|
|
49376
|
+
}
|
|
49377
|
+
var FROM_DESCRIPTION = "Pass `from` (an ISO 8601 UTC time, or a previous `next_from`) to page OLDEST first from that time instead: the result carries `next_from` (pass it back unchanged as `from` for the next page; on an empty page it is the same position, so it can be polled for new arrivals) and `has_more` (more already waiting), and no nextLink. Backfill: pass an old `from` and keep passing `next_from` until `has_more` is false; then keep polling with the last `next_from` for new mail.";
|
|
49378
|
+
async function listFrom(client2, pos, opts) {
|
|
49379
|
+
const { bound, top } = fromQuery(pos, opts.limit);
|
|
49380
|
+
const data = await client2.get(
|
|
49381
|
+
`/me/mailfolders/${folderSegment(opts.folder ?? "inbox")}/messages`,
|
|
49382
|
+
{
|
|
49383
|
+
query: {
|
|
49384
|
+
$top: top,
|
|
49385
|
+
$select: LIST_SELECT,
|
|
49386
|
+
// The date bound leads: Outlook rejects a $filter that does not start with the $orderby property.
|
|
49387
|
+
$filter: opts.unreadOnly === true ? `${bound} and IsRead eq false` : bound,
|
|
49388
|
+
$orderby: "ReceivedDateTime asc"
|
|
49389
|
+
}
|
|
49390
|
+
}
|
|
49391
|
+
);
|
|
49392
|
+
const listed = Array.isArray(data?.value) ? data.value : [];
|
|
49393
|
+
const page = fromPage(listed, pos, opts.limit, Boolean(data?.["@odata.nextLink"]));
|
|
49394
|
+
const { "@odata.nextLink": _superseded, ...rest } = data ?? {};
|
|
49395
|
+
const body = opts.view === "raw" ? { ...rest, value: page.rows } : projectCollection({ value: page.rows }, opts.view === "full" ? fullMessage : compactMessage, "message");
|
|
49396
|
+
return mailboxUntrusted({ ...body, next_from: page.next_from, has_more: page.has_more });
|
|
49397
|
+
}
|
|
49155
49398
|
function registerMailTools(server, client2) {
|
|
49156
49399
|
server.registerTool(
|
|
49157
49400
|
"outlook_list_folders",
|
|
@@ -49180,7 +49423,7 @@ function registerMailTools(server, client2) {
|
|
|
49180
49423
|
server.registerTool(
|
|
49181
49424
|
"outlook_list_messages",
|
|
49182
49425
|
{
|
|
49183
|
-
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. Each message\'s To list (and Cc, in the full view) holds at most 20 addresses; a longer list adds ToCount/ToTruncated (CcCount/CcTruncated) with the full count \u2014 pass view:"raw" for every address. ' + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
49426
|
+
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. Each message\'s To list (and Cc, in the full view) holds at most 20 addresses; a longer list adds ToCount/ToTruncated (CcCount/CcTruncated) with the full count \u2014 pass view:"raw" for every address. ' + FROM_DESCRIPTION + " Cannot combine with `search`, `skip` or `nextLink`. " + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
49184
49427
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
49185
49428
|
inputSchema: external_exports.object({
|
|
49186
49429
|
view: viewParam(VIEWS2),
|
|
@@ -49189,10 +49432,15 @@ function registerMailTools(server, client2) {
|
|
|
49189
49432
|
skip: external_exports.number().int().min(0).optional().describe("Offset for paging"),
|
|
49190
49433
|
unreadOnly: external_exports.boolean().optional().describe("Only unread messages"),
|
|
49191
49434
|
search: external_exports.string().optional().describe("Full-text search across the whole mailbox; cannot combine with unreadOnly on a first page (ignored with nextLink)"),
|
|
49435
|
+
from: fromParam,
|
|
49192
49436
|
nextLink: nextLinkParam
|
|
49193
49437
|
})
|
|
49194
49438
|
},
|
|
49195
|
-
async ({ view, folder, limit, skip, unreadOnly, search, nextLink }) => {
|
|
49439
|
+
async ({ view, folder, limit, skip, unreadOnly, search, from, nextLink }) => {
|
|
49440
|
+
const pos = fromPosition(from, { nextLink, search, skip });
|
|
49441
|
+
if (pos !== void 0) {
|
|
49442
|
+
return listFrom(client2, pos, { view: resolveView(view, VIEWS2), folder, limit: limit ?? 25, unreadOnly });
|
|
49443
|
+
}
|
|
49196
49444
|
if (nextLink === void 0 && search !== void 0 && unreadOnly === true) {
|
|
49197
49445
|
throw new McpToolError("`search` and `unreadOnly` cannot be combined.", {
|
|
49198
49446
|
hint: "Outlook rejects $search together with $filter. Search first, then filter the results."
|
|
@@ -49246,19 +49494,24 @@ function registerMailTools(server, client2) {
|
|
|
49246
49494
|
server.registerTool(
|
|
49247
49495
|
"outlook_get_unread",
|
|
49248
49496
|
{
|
|
49249
|
-
description: 'Triage batch: the unread messages in a folder (default inbox), newest first, each WITH its plain-text body (truncated to `maxBodyChars`) in one call \u2014 no follow-up outlook_get_message needed. Every item has a `kind`: "mail", "meetingRequest", "meetingCancelled" or "meetingResponse" (a reply to an invite you sent). Requests and cancellations also carry `event` (id, time, organizer, your current `responseStatus`) so the invite can be answered by its event id (when the meeting is no longer on your calendar \u2014 already declined, cancelled or deleted \u2014 the item has a `hint` instead and there is nothing to respond to). `to`/`cc` list at most 20 addresses; a longer list adds `toCount`/`ccCount` and `toTruncated`/`ccTruncated`. This does NOT mark anything read. Processing loop: read the batch \u2192 act on each item (reply, respond to the invite, file, flag \u2014 or leave it) \u2192 call outlook_mark_read on the plain mail you handled, so the next call returns only what is still new. An invite you answer with outlook_respond_to_invite needs no mark-read: Outlook moves it to Deleted Items under a new id, so its old id would 404. Follow `nextLink` for more. ' + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
49497
|
+
description: 'Triage batch: the unread messages in a folder (default inbox), newest first, each WITH its plain-text body (truncated to `maxBodyChars`) in one call \u2014 no follow-up outlook_get_message needed. Every item has a `kind`: "mail", "meetingRequest", "meetingCancelled" or "meetingResponse" (a reply to an invite you sent). Requests and cancellations also carry `event` (id, time, organizer, your current `responseStatus`) so the invite can be answered by its event id (when the meeting is no longer on your calendar \u2014 already declined, cancelled or deleted \u2014 the item has a `hint` instead and there is nothing to respond to). `to`/`cc` list at most 20 addresses; a longer list adds `toCount`/`ccCount` and `toTruncated`/`ccTruncated`. This does NOT mark anything read. Processing loop: read the batch \u2192 act on each item (reply, respond to the invite, file, flag \u2014 or leave it) \u2192 call outlook_mark_read on the plain mail you handled, so the next call returns only what is still new. An invite you answer with outlook_respond_to_invite needs no mark-read: Outlook moves it to Deleted Items under a new id, so its old id would 404. Follow `nextLink` for more. ' + FROM_DESCRIPTION + " Cannot combine with `sinceHours` or `nextLink`. " + UNTRUSTED_DESCRIPTION_SUFFIX,
|
|
49250
49498
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
49251
49499
|
inputSchema: external_exports.object({
|
|
49252
49500
|
folder: external_exports.string().min(1).optional().describe('Folder id or well-known name (default "inbox")'),
|
|
49253
49501
|
limit: external_exports.number().int().min(1).max(50).optional().describe("Max messages (default 25, max 50)"),
|
|
49254
|
-
sinceHours: external_exports.number().positive().max(24 * 365).optional().describe("Only mail received in the last N hours"),
|
|
49502
|
+
sinceHours: external_exports.number().positive().max(24 * 365).optional().describe("Only mail received in the last N hours (not with `from`)"),
|
|
49255
49503
|
includeBody: external_exports.boolean().optional().describe("Fetch each plain-text body (default true). Invite events are resolved either way."),
|
|
49256
49504
|
maxBodyChars: external_exports.number().int().min(100).max(1e5).optional().describe("Truncate each body to this many characters (default 4000); `bodyTruncated` marks a cut"),
|
|
49505
|
+
from: fromParam,
|
|
49257
49506
|
nextLink: nextLinkParam
|
|
49258
49507
|
})
|
|
49259
49508
|
},
|
|
49260
|
-
async ({ folder, limit, sinceHours, includeBody, maxBodyChars, nextLink }) => {
|
|
49509
|
+
async ({ folder, limit, sinceHours, includeBody, maxBodyChars, from, nextLink }) => {
|
|
49510
|
+
const pos = fromPosition(from, { nextLink, sinceHours });
|
|
49511
|
+
const max = limit ?? 25;
|
|
49512
|
+
const keyset = pos !== void 0 ? fromQuery(pos, max) : void 0;
|
|
49261
49513
|
const filter = [
|
|
49514
|
+
keyset?.bound,
|
|
49262
49515
|
sinceHours !== void 0 ? `ReceivedDateTime ge ${sinceLiteral(sinceHours)}` : void 0,
|
|
49263
49516
|
"IsRead eq false"
|
|
49264
49517
|
].filter((c) => c !== void 0).join(" and ");
|
|
@@ -49268,14 +49521,16 @@ function registerMailTools(server, client2) {
|
|
|
49268
49521
|
`/me/mailfolders/${folderSegment(folder ?? "inbox")}/messages`,
|
|
49269
49522
|
{
|
|
49270
49523
|
query: {
|
|
49271
|
-
$top:
|
|
49524
|
+
$top: keyset?.top ?? max,
|
|
49272
49525
|
$select: UNREAD_SELECT,
|
|
49273
49526
|
$filter: filter,
|
|
49274
|
-
$orderby: "ReceivedDateTime desc"
|
|
49527
|
+
$orderby: keyset ? "ReceivedDateTime asc" : "ReceivedDateTime desc"
|
|
49275
49528
|
}
|
|
49276
49529
|
}
|
|
49277
49530
|
);
|
|
49278
|
-
const
|
|
49531
|
+
const listed = Array.isArray(data?.value) ? data.value : [];
|
|
49532
|
+
const page = pos !== void 0 ? fromPage(listed, pos, max, Boolean(data?.["@odata.nextLink"])) : void 0;
|
|
49533
|
+
const rows = page?.rows ?? listed;
|
|
49279
49534
|
const wantBody = includeBody !== false;
|
|
49280
49535
|
const cap = maxBodyChars ?? 4e3;
|
|
49281
49536
|
const items = await mapWithConcurrency(rows, BODY_CONCURRENCY, async (m) => {
|
|
@@ -49323,8 +49578,13 @@ function registerMailTools(server, client2) {
|
|
|
49323
49578
|
return item;
|
|
49324
49579
|
});
|
|
49325
49580
|
const out = { count: items.length, items };
|
|
49326
|
-
|
|
49327
|
-
|
|
49581
|
+
if (page !== void 0) {
|
|
49582
|
+
out.next_from = page.next_from;
|
|
49583
|
+
out.has_more = page.has_more;
|
|
49584
|
+
} else {
|
|
49585
|
+
const next = data?.["@odata.nextLink"];
|
|
49586
|
+
if (next) out.nextLink = next;
|
|
49587
|
+
}
|
|
49328
49588
|
return mailboxUntrusted(out);
|
|
49329
49589
|
}
|
|
49330
49590
|
);
|
|
@@ -49814,6 +50074,9 @@ function messageBody(subject, body, html) {
|
|
|
49814
50074
|
}
|
|
49815
50075
|
var TEAMS = { IsOnlineMeeting: true, OnlineMeetingProvider: "TeamsForBusiness" };
|
|
49816
50076
|
var teamsMeetingParam = external_exports.boolean().optional().describe("Attach a Microsoft Teams meeting (default true). false leaves it off.");
|
|
50077
|
+
var addTeamsMeetingParam = external_exports.boolean().optional().describe(
|
|
50078
|
+
"true adds a Microsoft Teams meeting to an event that lacks one (default false: the way people join is left as it is)."
|
|
50079
|
+
);
|
|
49817
50080
|
var EVENT_STATE_SELECT = "Id,Subject,IsOrganizer,Start,End,Location,Attendees,IsOnlineMeeting,OnlineMeetingProvider,OnlineMeeting";
|
|
49818
50081
|
function attendee(Address, Type) {
|
|
49819
50082
|
return { Type, EmailAddress: { Address } };
|
|
@@ -49963,39 +50226,30 @@ function registerWriteTools(server, client2) {
|
|
|
49963
50226
|
server.registerTool(
|
|
49964
50227
|
"outlook_create_draft",
|
|
49965
50228
|
{
|
|
49966
|
-
description: "Create a draft message in the Drafts folder without sending it. Returns the created draft, which can be reviewed and sent from Outlook. " +
|
|
49967
|
-
|
|
50229
|
+
description: "Create a draft message in the Drafts folder without sending it. Returns the created draft, which can be reviewed and sent from Outlook. It does not ask to confirm: a draft reaches nobody until it is sent from Outlook. Calling it twice leaves two drafts. " + TIMEOUT_SENTENCE,
|
|
50230
|
+
// Self-only and additive, like outlook_reply with draftOnly: nothing is
|
|
50231
|
+
// overwritten or sent. Not idempotent: a repeat creates a second draft.
|
|
50232
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
49968
50233
|
inputSchema: external_exports.object({
|
|
49969
50234
|
to: recipientList,
|
|
49970
50235
|
cc: recipientList,
|
|
49971
50236
|
subject: external_exports.string().describe("Subject line"),
|
|
49972
50237
|
body: external_exports.string().describe("Message body"),
|
|
49973
|
-
html: external_exports.boolean().optional().describe("Compose the body as HTML (default: plain text)")
|
|
49974
|
-
confirmToken: confirmTokenParam
|
|
50238
|
+
html: external_exports.boolean().optional().describe("Compose the body as HTML (default: plain text)")
|
|
49975
50239
|
})
|
|
49976
50240
|
},
|
|
49977
|
-
async ({ to, cc, subject, body, html
|
|
50241
|
+
async ({ to, cc, subject, body, html }) => {
|
|
49978
50242
|
const payload = {
|
|
49979
50243
|
...messageBody(subject, body, html),
|
|
49980
50244
|
ToRecipients: toRecipients(to),
|
|
49981
50245
|
...cc?.length ? { CcRecipients: toRecipients(cc) } : {}
|
|
49982
50246
|
};
|
|
49983
|
-
const
|
|
49984
|
-
|
|
49985
|
-
|
|
49986
|
-
message: "Review and confirm this draft:",
|
|
49987
|
-
summary: `Create draft "${subject}"`,
|
|
49988
|
-
// One signed-in mailbox per server process.
|
|
49989
|
-
account: void 0,
|
|
49990
|
-
request: { method: "POST", path: "/me/messages", body: payload },
|
|
49991
|
-
confirmToken
|
|
49992
|
-
});
|
|
49993
|
-
if (gate) return gate;
|
|
49994
|
-
const created = await client2.write(
|
|
49995
|
-
"POST",
|
|
49996
|
-
"/me/messages",
|
|
49997
|
-
payload
|
|
50247
|
+
const create = await writeOrUnknown(
|
|
50248
|
+
() => client2.write("POST", "/me/messages", payload),
|
|
50249
|
+
"Drafts"
|
|
49998
50250
|
);
|
|
50251
|
+
if (!create.ok) return create.result;
|
|
50252
|
+
const created = create.value;
|
|
49999
50253
|
return minifiedResult({ created: true, Id: created?.Id, WebLink: created?.WebLink });
|
|
50000
50254
|
}
|
|
50001
50255
|
);
|
|
@@ -50212,7 +50466,7 @@ function registerWriteTools(server, client2) {
|
|
|
50212
50466
|
server.registerTool(
|
|
50213
50467
|
"outlook_update_event",
|
|
50214
50468
|
{
|
|
50215
|
-
description: "Update a meeting you organize: retitle, move, relocate, edit the description, or add/remove attendees. Moving only `start` keeps the meeting's length.
|
|
50469
|
+
description: "Update a meeting you organize: retitle, move, relocate, edit the description, or add/remove attendees. Moving only `start` keeps the meeting's length. It does not add a Microsoft Teams meeting unless you pass `teamsMeeting: true` (on an event that lacks one). 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,
|
|
50216
50470
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
50217
50471
|
inputSchema: external_exports.object({
|
|
50218
50472
|
id: external_exports.string().min(1).describe("Event Id from outlook_list_events or outlook_get_event"),
|
|
@@ -50225,7 +50479,7 @@ function registerWriteTools(server, client2) {
|
|
|
50225
50479
|
addAttendees: recipientList.describe("Required attendees to add"),
|
|
50226
50480
|
addOptionalAttendees: recipientList.describe("Optional attendees to add"),
|
|
50227
50481
|
removeAttendees: recipientList.describe("Attendees to remove"),
|
|
50228
|
-
teamsMeeting:
|
|
50482
|
+
teamsMeeting: addTeamsMeetingParam,
|
|
50229
50483
|
confirmToken: confirmTokenParam
|
|
50230
50484
|
})
|
|
50231
50485
|
},
|
|
@@ -50278,18 +50532,21 @@ function registerWriteTools(server, client2) {
|
|
|
50278
50532
|
}
|
|
50279
50533
|
payload.Attendees = next;
|
|
50280
50534
|
}
|
|
50281
|
-
const addTeams = args.teamsMeeting
|
|
50535
|
+
const addTeams = args.teamsMeeting === true && !(current && isTeams(current));
|
|
50282
50536
|
if (addTeams) Object.assign(payload, TEAMS);
|
|
50283
50537
|
if (Object.keys(payload).length === 0) {
|
|
50284
50538
|
throw new McpToolError("Nothing to update.", {
|
|
50285
|
-
hint: "Pass at least one of subject, start, end, location, body, or
|
|
50539
|
+
hint: "Pass at least one of subject, start, end, location, body, attendee changes, or teamsMeeting: true."
|
|
50286
50540
|
});
|
|
50287
50541
|
}
|
|
50288
50542
|
const gate = await confirmWrite(ctx, {
|
|
50289
50543
|
tool: "outlook_update_event",
|
|
50290
50544
|
action: "calendar.update_event",
|
|
50291
|
-
message: "Review and confirm this change (attendees are sent an updated invitation):",
|
|
50292
|
-
summary: `Update "${current?.Subject ?? args.id}": ${
|
|
50545
|
+
message: addTeams ? "Review and confirm this change (attendees are sent an updated invitation). This will also make it a Microsoft Teams meeting: attendees get a Teams join link." : "Review and confirm this change (attendees are sent an updated invitation):",
|
|
50546
|
+
summary: `Update "${current?.Subject ?? args.id}": ${[
|
|
50547
|
+
Object.keys(payload).filter((k) => !(k in TEAMS)).join(", "),
|
|
50548
|
+
addTeams ? "this will also make it a Microsoft Teams meeting" : ""
|
|
50549
|
+
].filter((part) => part !== "").join("; ")}`,
|
|
50293
50550
|
// One signed-in mailbox per server process.
|
|
50294
50551
|
account: void 0,
|
|
50295
50552
|
target: args.id,
|