@softeria/ms-365-mcp-server 0.169.0 → 0.169.2
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/dist/attachment-route.js +25 -2
- package/dist/graph-tools.js +24 -3
- package/dist/lib/attachment-tickets.js +10 -4
- package/dist/lib/obo-access.js +1 -1
- package/package.json +1 -1
package/dist/attachment-route.js
CHANGED
|
@@ -46,6 +46,22 @@ function forceAttachment(header) {
|
|
|
46
46
|
if (filename) return `attachment; filename=${filename}`;
|
|
47
47
|
return "attachment";
|
|
48
48
|
}
|
|
49
|
+
const GENERIC_CONTENT_TYPES = /* @__PURE__ */ new Set(["application/octet-stream", "application/binary", ""]);
|
|
50
|
+
const HEADER_SAFE_CHAR = /^[\t\x20-\x7e\x80-\xff]*$/;
|
|
51
|
+
function dispositionForProbedName(name) {
|
|
52
|
+
const safe = name.replace(/[\r\n"\\]/g, "").replace(/[^\t\x20-\x7e\x80-\xff]/g, "").trim();
|
|
53
|
+
const extension = /(\.[A-Za-z0-9]{1,10})$/.exec(name)?.[1] ?? "";
|
|
54
|
+
const fallback = safe && safe !== extension ? safe : extension ? `attachment${extension}` : safe;
|
|
55
|
+
let value = `attachment; filename="${fallback || "attachment"}"`;
|
|
56
|
+
if (!HEADER_SAFE_CHAR.test(name)) {
|
|
57
|
+
const encoded = encodeURIComponent(name).replace(
|
|
58
|
+
/['()*]/g,
|
|
59
|
+
(char) => "%" + char.charCodeAt(0).toString(16).toUpperCase()
|
|
60
|
+
);
|
|
61
|
+
value += `; filename*=UTF-8''${encoded}`;
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
49
65
|
function refuse(res) {
|
|
50
66
|
res.status(404).type("text/plain").send(NOT_FOUND_BODY);
|
|
51
67
|
}
|
|
@@ -90,11 +106,18 @@ function createAttachmentHandler(deps) {
|
|
|
90
106
|
return;
|
|
91
107
|
}
|
|
92
108
|
res.status(200);
|
|
93
|
-
|
|
109
|
+
const probed = ticket.probedContentType && HEADER_SAFE_CHAR.test(ticket.probedContentType) ? ticket.probedContentType : void 0;
|
|
110
|
+
res.setHeader(
|
|
111
|
+
"content-type",
|
|
112
|
+
probed && GENERIC_CONTENT_TYPES.has(stream.contentType.split(";")[0].trim().toLowerCase()) ? probed : stream.contentType
|
|
113
|
+
);
|
|
94
114
|
if (stream.contentLength !== null) {
|
|
95
115
|
res.setHeader("content-length", String(stream.contentLength));
|
|
96
116
|
}
|
|
97
|
-
res.setHeader(
|
|
117
|
+
res.setHeader(
|
|
118
|
+
"content-disposition",
|
|
119
|
+
!stream.contentDisposition && ticket.probedName ? dispositionForProbedName(ticket.probedName) : forceAttachment(stream.contentDisposition)
|
|
120
|
+
);
|
|
98
121
|
res.setHeader("cache-control", "no-store");
|
|
99
122
|
res.setHeader("x-content-type-options", "nosniff");
|
|
100
123
|
try {
|
package/dist/graph-tools.js
CHANGED
|
@@ -688,7 +688,23 @@ async function mintUploadUrl(target, name, contentType, accountParam, authManage
|
|
|
688
688
|
]
|
|
689
689
|
};
|
|
690
690
|
}
|
|
691
|
-
|
|
691
|
+
const MAIL_EVENT_ATTACHMENT_VALUE = /^(?:\/me|\/users\/[^/]+|\/groups\/[^/]+)\/(?:messages|events)\/[^/]+\/attachments\/[^/]+\/\$value$/;
|
|
692
|
+
async function probeAttachment(target, graphClient, accessToken) {
|
|
693
|
+
if (!MAIL_EVENT_ATTACHMENT_VALUE.test(target)) return {};
|
|
694
|
+
try {
|
|
695
|
+
const meta = await graphClient.makeRequest(
|
|
696
|
+
`${target.slice(0, -"/$value".length)}?$select=name,contentType`,
|
|
697
|
+
{ accessToken }
|
|
698
|
+
);
|
|
699
|
+
return {
|
|
700
|
+
...typeof meta?.contentType === "string" && { probedContentType: meta.contentType },
|
|
701
|
+
...typeof meta?.name === "string" && { probedName: meta.name }
|
|
702
|
+
};
|
|
703
|
+
} catch {
|
|
704
|
+
return {};
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
async function mintDownloadUrl(target, accountParam, authManager, probeWith) {
|
|
692
708
|
const minting = getAttachmentMinting();
|
|
693
709
|
if (!minting) return null;
|
|
694
710
|
const identityFromRequest = Boolean(authManager?.isOAuthModeEnabled() || getRequestTokens());
|
|
@@ -726,9 +742,14 @@ async function mintDownloadUrl(target, accountParam, authManager) {
|
|
|
726
742
|
isError: true
|
|
727
743
|
};
|
|
728
744
|
}
|
|
745
|
+
const probe = probeWith ? await probeAttachment(
|
|
746
|
+
target,
|
|
747
|
+
probeWith,
|
|
748
|
+
requestToken ?? await authManager?.getTokenForAccount(accountParam).catch(() => void 0)
|
|
749
|
+
) : {};
|
|
729
750
|
let ticket;
|
|
730
751
|
try {
|
|
731
|
-
ticket = requestToken ? minting.store.mintWithToken(target, requestToken) : minting.store.mint(target, accountParam);
|
|
752
|
+
ticket = requestToken ? minting.store.mintWithToken(target, requestToken, void 0, probe) : minting.store.mint(target, accountParam, void 0, probe);
|
|
732
753
|
} catch (error) {
|
|
733
754
|
if (error instanceof TicketStoreFullError) {
|
|
734
755
|
return {
|
|
@@ -1380,7 +1401,7 @@ const UTILITY_TOOLS = [
|
|
|
1380
1401
|
isError: true
|
|
1381
1402
|
};
|
|
1382
1403
|
}
|
|
1383
|
-
const minted = await mintDownloadUrl(pathPart, accountParam, authManager);
|
|
1404
|
+
const minted = await mintDownloadUrl(pathPart, accountParam, authManager, graphClient);
|
|
1384
1405
|
if (minted) return minted;
|
|
1385
1406
|
return {
|
|
1386
1407
|
content: [
|
|
@@ -65,13 +65,19 @@ class AttachmentTicketStore {
|
|
|
65
65
|
return { id, expiresAtMs };
|
|
66
66
|
}
|
|
67
67
|
/** Mint a ticket redeemed with this server's own token for `accountName`. */
|
|
68
|
-
mint(target, accountName, nowMs = Date.now()) {
|
|
69
|
-
return this.add(target, { kind: "server-account", accountName }, nowMs
|
|
68
|
+
mint(target, accountName, nowMs = Date.now(), probe = {}) {
|
|
69
|
+
return this.add(target, { kind: "server-account", accountName }, nowMs, {
|
|
70
|
+
purpose: "download",
|
|
71
|
+
...probe
|
|
72
|
+
});
|
|
70
73
|
}
|
|
71
74
|
/** Mint a ticket redeemed with `accessToken` and nothing else. */
|
|
72
|
-
mintWithToken(target, accessToken, nowMs = Date.now()) {
|
|
75
|
+
mintWithToken(target, accessToken, nowMs = Date.now(), probe = {}) {
|
|
73
76
|
if (!accessToken) throw new Error("A request-token ticket needs a token");
|
|
74
|
-
return this.add(target, { kind: "request-token", accessToken }, nowMs
|
|
77
|
+
return this.add(target, { kind: "request-token", accessToken }, nowMs, {
|
|
78
|
+
purpose: "download",
|
|
79
|
+
...probe
|
|
80
|
+
});
|
|
75
81
|
}
|
|
76
82
|
/** Mint an upload ticket attached as this server's own token for `accountName`. */
|
|
77
83
|
mintUpload(target, accountName, upload, nowMs = Date.now()) {
|
package/dist/lib/obo-access.js
CHANGED
|
@@ -35,7 +35,7 @@ const ASSERTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
35
35
|
"consent_required",
|
|
36
36
|
"login_required"
|
|
37
37
|
]);
|
|
38
|
-
const ASSERTION_AADSTS = /\bAADSTS(50013|500133|50027|50173|700082|65001|50076|50079)\b/;
|
|
38
|
+
const ASSERTION_AADSTS = /\bAADSTS(50013|500133|50027|50173|700082|65001|50076|50079|5002723)\b/;
|
|
39
39
|
function isRejectedAssertion(error) {
|
|
40
40
|
if (!error || typeof error !== "object") return false;
|
|
41
41
|
const e = error;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
3
|
"mcpName": "io.github.Softeria/ms-365-mcp-server",
|
|
4
|
-
"version": "0.169.
|
|
4
|
+
"version": "0.169.2",
|
|
5
5
|
"description": " A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|