This commit is contained in:
2026-07-09 00:33:51 +03:00
parent bbe1ec4b23
commit b1720b8a21
8 changed files with 480 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
+1
View File
@@ -0,0 +1 @@
/.pnp.* binary linguist-generated
+12
View File
@@ -0,0 +1,12 @@
.cache
.data
.DS_Store
.env
.git
.idea
.pnp.*
.target
.tmp
.vscode
.yarn
node_modules
+18
View File
@@ -0,0 +1,18 @@
{
"lineWidth": 80,
"indentWidth": 2,
"typescript": {
"module.sortImportDeclarations": "caseInsensitive",
"module.sortExportDeclarations": "caseInsensitive"
},
"excludes": [
"**/.git",
"**/.target"
],
"plugins": [
"https://plugins.dprint.dev/typescript-0.91.1.wasm",
"https://plugins.dprint.dev/json-0.17.4.wasm",
"https://plugins.dprint.dev/markdown-0.15.3.wasm",
"https://plugins.dprint.dev/dockerfile-0.3.0.wasm"
]
}
+148
View File
@@ -0,0 +1,148 @@
import { XMLParser } from "fast-xml-parser";
import { promisify } from "node:util";
import { env } from "node:process";
import { createRequire } from "node:module";
import https from "node:https";
const ntlm: any = createRequire(import.meta.url)("httpntlm");
const postAsync = promisify(ntlm.post.bind(ntlm));
const SSL_OP_LEGACY_SERVER_CONNECT = 0x00000004;
const AGENT = new https.Agent({
keepAlive: true,
rejectUnauthorized: false,
secureOptions: SSL_OP_LEGACY_SERVER_CONNECT,
});
const EWS_URL = "https://mail.b1.ru/EWS/Exchange.asmx";
const PARSER = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: "@_",
removeNSPrefix: true,
textNodeName: "#text",
});
function buildSoapEnvelope(body: string): string {
return `<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<s:Body>
${body}
</s:Body>
</s:Envelope>`;
}
async function ewsSoap(options: { body: string; soapAction: string }): Promise<any> {
const res = await postAsync({
url: EWS_URL,
username: env.EWS_USERNAME ?? "polina.litvinova",
domain: "corp",
password: env.EWS_PASSWORD,
agent: AGENT,
headers: {
"Content-Type": "text/xml; charset=utf-8",
SOAPAction: options.soapAction,
},
body: options.body,
});
if (typeof res.body !== "string") {
throw new Error(`NTLM request failed, status=${res.statusCode}`);
}
return PARSER.parse(res.body);
}
async function getFolder() {
const soap = buildSoapEnvelope(`\
<m:GetFolder>
<m:FolderShape>
<t:BaseShape>IdOnly</t:BaseShape>
<t:AdditionalProperties>
<t:FieldURI FieldURI="folder:DisplayName"/>
<t:FieldURI FieldURI="folder:TotalCount"/>
<t:FieldURI FieldURI="folder:UnreadCount"/>
</t:AdditionalProperties>
</m:FolderShape>
<m:FolderIds>
<t:DistinguishedFolderId Id="inbox"/>
</m:FolderIds>
</m:GetFolder>`);
const data = await ewsSoap({
body: soap,
soapAction: "http://schemas.microsoft.com/exchange/services/2006/messages/GetFolder",
});
const rm = data.Envelope?.Body?.GetFolderResponse?.ResponseMessages;
const msg = rm?.GetFolderResponseMessage;
const folder = msg?.Folders?.Folder;
console.log(`DisplayName: ${folder?.DisplayName}`);
console.log(`FolderId: ${folder?.FolderId?.["@_Id"]}`);
console.log(`Total items: ${folder?.TotalCount}`);
console.log(`Unread: ${folder?.UnreadCount}`);
}
async function getLastMessages() {
const soap = buildSoapEnvelope(`\
<m:FindItem Traversal="Shallow">
<m:ItemShape>
<t:BaseShape>AllProperties</t:BaseShape>
</m:ItemShape>
<m:IndexedPageItemView MaxEntriesReturned="3" Offset="0" BasePoint="Beginning"/>
<m:ParentFolderIds>
<t:DistinguishedFolderId Id="inbox"/>
</m:ParentFolderIds>
<m:SortOrder>
<t:FieldOrder Order="Descending">
<t:FieldURI FieldURI="item:DateTimeReceived"/>
</t:FieldOrder>
</m:SortOrder>
</m:FindItem>`);
const data = await ewsSoap({
body: soap,
soapAction: "http://schemas.microsoft.com/exchange/services/2006/messages/FindItem",
});
const rm = data.Envelope?.Body?.FindItemResponse?.ResponseMessages;
const msg = rm?.FindItemResponseMessage;
const rootFolder = msg?.RootFolder;
const items = rootFolder?.Items;
const messagesList: any[] = items?.Message ? (Array.isArray(items.Message) ? items.Message : [items.Message]) : [];
if (!messagesList.length) {
console.log("\n--- No messages found ---");
return;
}
console.log(`\n--- ${messagesList.length} message(s) ---`);
for (const m of messagesList) {
const mbox = m.From?.Mailbox;
const author = mbox ? `${mbox.Name ?? "(no name)"} <${mbox.EmailAddress ?? "(no email)"}>` : "(unknown)";
console.log(`\nSubject: ${m.Subject ?? "(no subject)"}`);
console.log(`From: ${author}`);
console.log(`Date: ${m.DateTimeReceived ?? "(unknown)"}`);
console.log(`Preview: ${(m.Preview ?? "(no preview)").slice(0, 200)}`);
}
}
async function main() {
if (!env.EWS_PASSWORD) {
console.error("Set EWS_PASSWORD environment variable");
process.exit(1);
}
try {
await getFolder();
await getLastMessages();
} catch (err) {
console.error("Error:", err);
process.exit(1);
}
}
main();
+238
View File
@@ -0,0 +1,238 @@
{
"name": "exchange-mcp",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "exchange-mcp",
"version": "0.0.0",
"dependencies": {
"fast-xml-parser": "^5.2.0",
"httpntlm": "^1.8.13"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.9.3"
}
},
"node_modules/@nodable/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/nodable"
}
],
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.20.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz",
"integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/anynum": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz",
"integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT"
},
"node_modules/des.js": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz",
"integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
}
},
"node_modules/fast-xml-builder": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.1.tgz",
"integrity": "sha512-tPb5TTWfgfVx5BNSi2xV0eLr89POeXXn0dXIsCJ9m1narrWxeIyx6je9d7Rce/3NyXLbvuQmLkxq+RuxMWejvw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"path-expression-matcher": "^1.5.0",
"xml-naming": "^0.1.0"
}
},
"node_modules/fast-xml-parser": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.9.3.tgz",
"integrity": "sha512-brCNCeScma/kqa54J4PIDriSSSLssRkuYaUCpvHJulGc3HGI/xxKUCTDcYkAdqJsyb//ydpbxecjC3hB9+tb/g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"@nodable/entities": "^2.2.0",
"fast-xml-builder": "^1.2.0",
"is-unsafe": "^1.0.1",
"path-expression-matcher": "^1.5.0",
"strnum": "^2.4.1",
"xml-naming": "^0.1.0"
},
"bin": {
"fxparser": "src/cli/cli.js"
}
},
"node_modules/httpntlm": {
"version": "1.8.13",
"resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz",
"integrity": "sha512-2F2FDPiWT4rewPzNMg3uPhNkP3NExENlUGADRUDPQvuftuUTGW98nLZtGemCIW3G40VhWZYgkIDcQFAwZ3mf2Q==",
"funding": [
{
"type": "paypal",
"url": "https://www.paypal.com/donate/?hosted_button_id=2CKNJLZJBW8ZC"
},
{
"type": "buymeacoffee",
"url": "https://www.buymeacoffee.com/samdecrock"
}
],
"dependencies": {
"des.js": "^1.0.1",
"httpreq": ">=0.4.22",
"js-md4": "^0.3.2",
"underscore": "~1.12.1"
},
"engines": {
"node": ">=10.4.0"
}
},
"node_modules/httpreq": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/httpreq/-/httpreq-1.1.1.tgz",
"integrity": "sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw==",
"license": "MIT",
"engines": {
"node": ">= 6.15.1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/is-unsafe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-1.0.1.tgz",
"integrity": "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT"
},
"node_modules/js-md4": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz",
"integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==",
"license": "MIT"
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC"
},
"node_modules/path-expression-matcher": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz",
"integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/strnum": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz",
"integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"anynum": "^1.0.1"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/underscore": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz",
"integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/xml-naming": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz",
"integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"engines": {
"node": ">=16.0.0"
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"name": "exchange-mcp",
"version": "0.0.0",
"type": "module",
"dependencies": {
"fast-xml-parser": "^5.2.0",
"httpntlm": "^1.8.13"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.9.3"
}
}
+42
View File
@@ -0,0 +1,42 @@
{
"compilerOptions": {
"allowImportingTsExtensions": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"checkJs": false,
"erasableSyntaxOnly": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"keyofStringsOnly": false,
"lib": [
"esnext"
],
"module": "nodenext",
"moduleResolution": "nodenext",
"noEmit": true,
"noErrorTruncation": false,
"noFallthroughCasesInSwitch": false,
"noImplicitAny": true,
"noImplicitOverride": true,
"noImplicitReturns": false,
"noImplicitThis": true,
"noStrictGenericChecks": false,
"noUncheckedIndexedAccess": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"skipLibCheck": true,
"strict": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"suppressExcessPropertyErrors": false,
"suppressImplicitAnyIndexErrors": false,
"target": "esnext",
"types": [
"node"
],
"useUnknownInCatchVariables": true,
"verbatimModuleSyntax": true
}
}