From b1720b8a2104b78f8f0e4b59e5c17c47e2f32ec8 Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 00:33:51 +0300 Subject: [PATCH 01/13] w --- .editorconfig | 8 ++ .gitattributes | 1 + .gitignore | 12 +++ dprint.json | 18 ++++ main.ts | 148 ++++++++++++++++++++++++++++ package-lock.json | 238 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 13 +++ tsconfig.json | 42 ++++++++ 8 files changed, 480 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 dprint.json create mode 100644 main.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b261f16 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e51fdcc --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +/.pnp.* binary linguist-generated \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c46c3e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.cache +.data +.DS_Store +.env +.git +.idea +.pnp.* +.target +.tmp +.vscode +.yarn +node_modules \ No newline at end of file diff --git a/dprint.json b/dprint.json new file mode 100644 index 0000000..b9bf997 --- /dev/null +++ b/dprint.json @@ -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" + ] +} diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..e4704d1 --- /dev/null +++ b/main.ts @@ -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 ` + + +${body} + +`; +} + +async function ewsSoap(options: { body: string; soapAction: string }): Promise { + 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(`\ + + + IdOnly + + + + + + + + + + `); + + 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(`\ + + + AllProperties + + + + + + + + + + + `); + + 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(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2bf61c1 --- /dev/null +++ b/package-lock.json @@ -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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1f68d12 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..424bf37 --- /dev/null +++ b/tsconfig.json @@ -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 + } +} \ No newline at end of file -- 2.52.0 From 8d5f254728397cc1ae6da56e0b89a597abfa665e Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 00:52:48 +0300 Subject: [PATCH 02/13] w --- ews_client.ts | 472 +++++++++++++++++++++++++++++++++++++++ main.ts | 158 ++------------ models.ts | 103 +++++++++ package-lock.json | 546 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 11 +- tools/auth.ts | 93 ++++++++ tools/email.ts | 228 +++++++++++++++++++ tools/folders.ts | 30 +++ 8 files changed, 1494 insertions(+), 147 deletions(-) create mode 100644 ews_client.ts create mode 100644 models.ts create mode 100644 tools/auth.ts create mode 100644 tools/email.ts create mode 100644 tools/folders.ts diff --git a/ews_client.ts b/ews_client.ts new file mode 100644 index 0000000..f0f6620 --- /dev/null +++ b/ews_client.ts @@ -0,0 +1,472 @@ +import { XMLParser } from "fast-xml-parser"; +import { promisify } from "node:util"; +import { createRequire } from "node:module"; +import https from "node:https"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import type { Email, Folder, LoginConfig } from "./models.ts"; + +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 PARSER = new XMLParser({ + ignoreAttributes: false, + attributeNamePrefix: "@_", + removeNSPrefix: true, + textNodeName: "#text", +}); + +const DISTINGUISHED_FOLDERS: Record = { + inbox: "inbox", + "входящие": "inbox", + sent: "sentitems", + "отправленные": "sentitems", + drafts: "drafts", + "черновики": "drafts", + deleted: "deleteditems", + "удаленные": "deleteditems", + junk: "junkemail", + "нежелательная почта": "junkemail", + outbox: "outbox", + "исходящие": "outbox", + calendar: "calendar", + "календарь": "calendar", +}; + +function buildSoapEnvelope(body: string): string { + return ` + + +${body} + +`; +} + +function configFilePath(): string { + const dir = path.dirname(fileURLToPath(import.meta.url)); + return path.join(dir, "config.json"); +} + +export function loadConfig(): LoginConfig { + const filePath = configFilePath(); + if (!fs.existsSync(filePath)) { + throw new Error("Not logged in. Use the login tool first."); + } + return JSON.parse(fs.readFileSync(filePath, "utf-8")) as LoginConfig; +} + +export function saveConfig(config: LoginConfig): void { + const filePath = configFilePath(); + fs.writeFileSync(filePath, JSON.stringify(config, null, 2), "utf-8"); +} + +export function clearConfig(): void { + const filePath = configFilePath(); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + } +} + +export class EwsClient { + private config: LoginConfig; + private ewsUrl: string; + + constructor(config: LoginConfig) { + this.config = config; + this.ewsUrl = `${config.serverUrl.replace(/\/+$/, "")}/EWS/Exchange.asmx`; + } + + private get domain(): string { + return this.config.domain ?? "corp"; + } + + private async soapRequest(body: string, soapAction: string): Promise { + const envelope = buildSoapEnvelope(body); + + const res = await postAsync({ + url: this.ewsUrl, + username: this.config.username, + domain: this.domain, + password: this.config.password, + agent: AGENT, + headers: { + "Content-Type": "text/xml; charset=utf-8", + SOAPAction: soapAction, + }, + body: envelope, + }); + + if (typeof res.body !== "string") { + throw new Error(`NTLM request failed, status=${res.statusCode}`); + } + + return PARSER.parse(res.body); + } + + private extractResponseMessages(data: any): any[] { + const body = data?.Envelope?.Body; + if (!body) return []; + + const firstKey = Object.keys(body).find((k) => k.endsWith("Response")); + if (!firstKey) return []; + + const rm = body[firstKey]?.ResponseMessages; + if (!rm) return []; + + const msgKey = Object.keys(rm).find((k) => k.endsWith("ResponseMessage")); + if (!msgKey) return []; + + const msgs = rm[msgKey]; + return Array.isArray(msgs) ? msgs : [msgs]; + } + + async verifyConnection(): Promise { + try { + await this.getFolderId("inbox"); + return true; + } catch { + return false; + } + } + + async getFolderId(folderName: string): Promise { + const lower = folderName.toLowerCase(); + const distinguished = DISTINGUISHED_FOLDERS[lower]; + + if (distinguished) { + const soap = buildSoapEnvelope(`\ + + + IdOnly + + + + + `); + + const data = await this.soapRequest( + soap, + "http://schemas.microsoft.com/exchange/services/2006/messages/GetFolder", + ); + + for (const msg of this.extractResponseMessages(data)) { + const folder = msg?.Folders?.Folder; + if (folder?.FolderId?.["@_Id"]) { + return folder.FolderId["@_Id"]; + } + } + } + + const soap = buildSoapEnvelope(`\ + + + Default + + + + + + `); + + const data = await this.soapRequest( + soap, + "http://schemas.microsoft.com/exchange/services/2006/messages/FindFolder", + ); + + for (const msg of this.extractResponseMessages(data)) { + const folders = msg?.RootFolder?.Folders?.Folder; + if (!folders) continue; + const list = Array.isArray(folders) ? folders : [folders]; + for (const f of list) { + if (f.DisplayName?.toLowerCase() === lower) { + return f.FolderId?.["@_Id"] ?? ""; + } + } + } + + throw new Error(`Folder '${folderName}' not found`); + } + + async findItems( + folderId: string, + options: { + limit?: number; + offset?: number; + baseShape?: string; + restriction?: string; + traversal?: string; + } = {}, + ): Promise { + const { + limit = 10, + offset = 0, + baseShape = "AllProperties", + restriction = "", + traversal = "Shallow", + } = options; + + const soap = buildSoapEnvelope(`\ + + + ${baseShape} + + + + + + + + + + +${restriction} + `); + + const data = await this.soapRequest( + soap, + "http://schemas.microsoft.com/exchange/services/2006/messages/FindItem", + ); + + for (const msg of this.extractResponseMessages(data)) { + const items = msg?.RootFolder?.Items?.Message ?? msg?.RootFolder?.Items?.CalendarItem; + if (!items) continue; + return Array.isArray(items) ? items : [items]; + } + + return []; + } + + async getItem(itemId: string): Promise { + const soap = buildSoapEnvelope(`\ + + + AllProperties + HTML + + + + + `); + + const data = await this.soapRequest( + soap, + "http://schemas.microsoft.com/exchange/services/2006/messages/GetItem", + ); + + for (const msg of this.extractResponseMessages(data)) { + const items = msg?.Items; + if (!items) continue; + + const itemKey = Object.keys(items).find((k) => + ["Message", "CalendarItem", "MeetingRequest", "MeetingResponse", "MeetingCancellation"].includes(k) + ); + if (itemKey) { + return items[itemKey]; + } + } + + throw new Error(`Item '${itemId}' not found`); + } + + async findFolders( + parentFolderId: string, + recursive: boolean = false, + ): Promise { + const traversal = recursive ? "Deep" : "Shallow"; + const isDistinguished = DISTINGUISHED_FOLDERS[parentFolderId.toLowerCase()] !== undefined + || ["msgfolderroot"].includes(parentFolderId.toLowerCase()); + + const folderIdXml = isDistinguished + ? `` + : ``; + + const soap = buildSoapEnvelope(`\ + + + Default + + + ${folderIdXml} + + + `); + + const data = await this.soapRequest( + soap, + "http://schemas.microsoft.com/exchange/services/2006/messages/FindFolder", + ); + + const folders: Folder[] = []; + + for (const msg of this.extractResponseMessages(data)) { + const folderList = msg?.RootFolder?.Folders?.Folder; + if (!folderList) continue; + const list = Array.isArray(folderList) ? folderList : [folderList]; + + for (const f of list) { + folders.push({ + name: f.DisplayName ?? "Unknown", + id: f.FolderId?.["@_Id"] ?? "", + totalCount: f.TotalCount ?? 0, + unreadCount: f.UnreadCount ?? 0, + childFolderCount: f.ChildFolderCount ?? 0, + }); + } + } + + return folders; + } + + extractEmailSummary(item: any): Email { + const itemType = item["@_xsi_type"] ?? item.__type ?? ""; + const isMeeting = /MeetingRequest|MeetingResponse|MeetingCancellation|CalendarItem/i.test(itemType); + + const fromMailbox = item.From?.Mailbox + ?? item.Organizer?.Mailbox + ?? item.Sender?.Mailbox + ?? {}; + + const email: Email = { + subject: item.Subject ?? "(No subject)", + from: fromMailbox.EmailAddress ?? "", + fromName: fromMailbox.Name ?? "", + date: item.DateTimeSent ?? item.DateTimeReceived ?? item.DateTimeCreated ?? "", + isRead: item.IsRead === "true" || item.IsRead === true, + hasAttachments: item.HasAttachments === "true" || item.HasAttachments === true, + hasLinks: false, + itemId: item.ItemId?.["@_Id"] ?? "", + size: item.Size ? Number(item.Size) : 0, + isMeeting, + itemType: itemType || "Message", + to: [], + cc: [], + body: "", + bodyType: "Text", + attachments: [], + preview: item.Preview ?? "", + }; + + if (item.DisplayTo) { + email.to = item.DisplayTo.split(";").map((t: string) => t.trim()).filter(Boolean); + } + if (item.DisplayCc) { + email.cc = item.DisplayCc.split(";").map((c: string) => c.trim()).filter(Boolean); + } + + if (isMeeting) { + email.location = item.Location ?? ""; + email.start = item.Start ?? item.StartWallClock ?? item.ReminderDueBy ?? ""; + email.end = item.End ?? item.EndWallClock ?? ""; + } + + return email; + } + + extractEmailDetails(item: any): Email { + const email = this.extractEmailSummary(item); + + const bodyVal = item.Body?.Value ?? item.Body?.["#text"] ?? ""; + const bodyType = item.Body?.["@_BodyType"] ?? item.Body?.BodyType ?? "Text"; + + if (bodyType === "HTML") { + email.hasLinks = / { + const name = r.Name ?? ""; + const addr = r.EmailAddress ?? ""; + return name && addr ? `${name} <${addr}>` : addr; + }) + .filter(Boolean); + + const ccRecipients = item.CcRecipients?.Mailbox ?? []; + email.cc = (Array.isArray(ccRecipients) ? ccRecipients : [ccRecipients]) + .map((r: any) => { + const name = r.Name ?? ""; + const addr = r.EmailAddress ?? ""; + return name && addr ? `${name} <${addr}>` : addr; + }) + .filter(Boolean); + + const attachments = item.Attachments?.FileAttachment ?? []; + const attList = Array.isArray(attachments) ? attachments : [attachments]; + email.attachments = attList + .filter((a: any) => a) + .map((a: any) => ({ + name: a.Name ?? "", + size: a.Size ? Number(a.Size) : 0, + contentType: a.ContentType ?? "", + attachmentId: a.AttachmentId?.["@_Id"] ?? "", + isInline: a.IsInline === "true" || a.IsInline === true, + })); + + const isMeeting = /MeetingRequest|MeetingResponse|MeetingCancellation|CalendarItem/i.test( + item["@_xsi_type"] ?? "", + ); + if (isMeeting) { + email.location = item.Location ?? item.EnhancedLocation?.DisplayName ?? ""; + email.start = item.Start ?? ""; + email.end = item.End ?? ""; + + const reqAtt = item.RequiredAttendees?.Attendee ?? []; + email.requiredAttendees = (Array.isArray(reqAtt) ? reqAtt : [reqAtt]) + .map((a: any) => { + const mb = a.Mailbox ?? {}; + const name = mb.Name ?? ""; + const addr = mb.EmailAddress ?? ""; + return name && addr ? `${name} <${addr}>` : addr; + }) + .filter(Boolean); + + const optAtt = item.OptionalAttendees?.Attendee ?? []; + email.optionalAttendees = (Array.isArray(optAtt) ? optAtt : [optAtt]) + .map((a: any) => { + const mb = a.Mailbox ?? {}; + const name = mb.Name ?? ""; + const addr = mb.EmailAddress ?? ""; + return name && addr ? `${name} <${addr}>` : addr; + }) + .filter(Boolean); + } + + return email; + } + + private htmlToText(html: string): string { + if (!html) return ""; + let text = html.replace(/]*>.*?<\/script>/gis, ""); + text = text.replace(/]*>.*?<\/style>/gis, ""); + text = text.replace(//gi, "\n"); + text = text.replace(/]*>/gi, "\n"); + text = text.replace(/<\/p>/gi, ""); + text = text.replace(/]*>/gi, "\n"); + text = text.replace(/<\/div>/gi, ""); + text = text.replace(/<[^>]+>/g, ""); + text = text.replace(/ /g, " "); + text = text.replace(/&/g, "&"); + text = text.replace(/</g, "<"); + text = text.replace(/>/g, ">"); + text = text.replace(/"/g, '"'); + text = text.replace(/'/g, "'"); + text = text.replace(/\n\s*\n/g, "\n\n"); + text = text.replace(/[ \t]+/g, " "); + return text.trim(); + } +} \ No newline at end of file diff --git a/main.ts b/main.ts index e4704d1..f225d16 100644 --- a/main.ts +++ b/main.ts @@ -1,148 +1,20 @@ -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"; +import { McpServer } from "@modelcontextprotocol/server"; +import { serveStdio } from "@modelcontextprotocol/server/stdio"; +import { registerAuthTools } from "./tools/auth.ts"; +import { registerEmailTools } from "./tools/email.ts"; +import { registerFolderTools } from "./tools/folders.ts"; -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 ` - - -${body} - -`; -} - -async function ewsSoap(options: { body: string; soapAction: string }): Promise { - 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, +function buildServer(): McpServer { + const server = new McpServer({ + name: "exchange-mcp", + version: "1.0.0", }); - if (typeof res.body !== "string") { - throw new Error(`NTLM request failed, status=${res.statusCode}`); - } - return PARSER.parse(res.body); + registerAuthTools(server); + registerEmailTools(server); + registerFolderTools(server); + + return server; } -async function getFolder() { - const soap = buildSoapEnvelope(`\ - - - IdOnly - - - - - - - - - - `); - - 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(`\ - - - AllProperties - - - - - - - - - - - `); - - 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(); \ No newline at end of file +serveStdio(buildServer); \ No newline at end of file diff --git a/models.ts b/models.ts new file mode 100644 index 0000000..4d9575d --- /dev/null +++ b/models.ts @@ -0,0 +1,103 @@ +export interface LoginConfig { + serverUrl: string; + email: string; + username: string; + password: string; + domain?: string; +} + +export interface Email { + subject: string; + from: string; + fromName: string; + date: string; + isRead: boolean; + hasAttachments: boolean; + hasLinks: boolean; + itemId: string; + size: number; + isMeeting: boolean; + itemType: string; + to: string[]; + cc: string[]; + body: string; + bodyType: string; + attachments: Attachment[]; + preview: string; + location?: string; + start?: string; + end?: string; + requiredAttendees?: string[]; + optionalAttendees?: string[]; +} + +export interface Attachment { + name: string; + size: number; + contentType: string; + attachmentId: string; + isInline: boolean; +} + +export interface Folder { + name: string; + id: string; + totalCount: number; + unreadCount: number; + childFolderCount: number; +} + +export interface Person { + name: string; + email: string; + mailboxType: string; + firstName: string; + lastName: string; + jobTitle: string; + department: string; + company: string; + office: string; + alias: string; + manager: string; + managerEmail: string; + phones: Record; + address: string; + directReports: Array<{ name: string; email: string }>; +} + +export interface CalendarEvent { + subject: string; + start: string; + end: string; + location: string; + isAllDay: boolean; + isCancelled: boolean; + isMeeting: boolean; + isRecurring: boolean; + organizer: string; + organizerEmail: string; + myResponse: string; + itemId: string; + body: string; + requiredAttendees: string[]; + optionalAttendees: string[]; +} + +export interface FreeSlot { + date: string; + start: string; + end: string; + durationMinutes: number; +} + +export interface MeetingResult { + success: boolean; + subject: string; + date: string; + startTime: string; + endTime: string; + location: string; + requiredAttendees: string[]; + optionalAttendees: string[]; + error: string; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2bf61c1..742b892 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,14 +8,471 @@ "name": "exchange-mcp", "version": "0.0.0", "dependencies": { + "@modelcontextprotocol/server": "^2.0.0-beta.2", "fast-xml-parser": "^5.2.0", - "httpntlm": "^1.8.13" + "httpntlm": "^1.8.13", + "zod": "^4.4.3" }, "devDependencies": { - "@types/node": "^22.0.0", + "@types/node": "^22.20.1", + "tsx": "^4.23.0", "typescript": "^5.9.3" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0-beta.2.tgz", + "integrity": "sha512-XK+sgFntT5ZzeqtTGpT9uti+Sqmq7YJZdx/N76eLJv9UA9vKvP04Qh9Hc45LCeIHGfTKRwDG6eh4C1Hs4omyIg==", + "license": "MIT", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@nodable/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", @@ -60,6 +517,48 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, "node_modules/fast-xml-builder": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.1.tgz", @@ -99,6 +598,21 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/httpntlm": { "version": "1.8.13", "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz", @@ -192,6 +706,25 @@ "anynum": "^1.0.1" } }, + "node_modules/tsx": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.0.tgz", + "integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -233,6 +766,15 @@ "engines": { "node": ">=16.0.0" } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 1f68d12..a39c2e4 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,19 @@ "name": "exchange-mcp", "version": "0.0.0", "type": "module", + "scripts": { + "start": "tsx main.ts", + "format": "dprint fmt" + }, "dependencies": { + "@modelcontextprotocol/server": "^2.0.0-beta.2", "fast-xml-parser": "^5.2.0", - "httpntlm": "^1.8.13" + "httpntlm": "^1.8.13", + "zod": "^4.4.3" }, "devDependencies": { - "@types/node": "^22.0.0", + "@types/node": "^22.20.1", + "tsx": "^4.23.0", "typescript": "^5.9.3" } } diff --git a/tools/auth.ts b/tools/auth.ts new file mode 100644 index 0000000..ca481ad --- /dev/null +++ b/tools/auth.ts @@ -0,0 +1,93 @@ +import { z } from "zod/v4"; +import { EwsClient, loadConfig, saveConfig, clearConfig } from "../ews_client.ts"; +import { type McpServer } from "@modelcontextprotocol/server"; + +export function registerAuthTools(server: McpServer): void { + server.registerTool( + "login", + { + description: "Authenticate to Exchange EWS using NTLM credentials", + inputSchema: z.object({ + serverUrl: z.string().describe("EWS server URL (e.g. https://mail.example.com)"), + email: z.string().describe("Email address"), + username: z.string().describe("NTLM username"), + password: z.string().describe("NTLM password"), + domain: z.string().optional().describe("NTLM domain (default: corp)"), + }), + }, + async ({ serverUrl, email, username, password, domain }) => { + try { + const config = { + serverUrl: serverUrl.replace(/\/+$/, ""), + email, + username, + password, + domain: domain ?? "corp", + }; + + const client = new EwsClient(config); + const ok = await client.verifyConnection(); + + if (!ok) { + return { + content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: "Connection verification failed. Check credentials and server URL." }) }], + }; + } + + saveConfig(config); + + return { + content: [{ type: "text" as const, text: JSON.stringify({ success: true, message: `Logged in as ${email} to ${serverUrl}` }) }], + }; + } catch (error: any) { + return { + content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: error.message ?? String(error) }) }], + }; + } + }, + ); + + server.registerTool( + "check_session", + { + description: "Check whether the current EWS session is authenticated", + inputSchema: z.object({}), + }, + async () => { + try { + const config = loadConfig(); + const client = new EwsClient(config); + const ok = await client.verifyConnection(); + + return { + content: [{ + type: "text" as const, + text: JSON.stringify({ + authenticated: ok, + email: config.email, + serverUrl: config.serverUrl, + }), + }], + }; + } catch (error: any) { + return { + content: [{ type: "text" as const, text: JSON.stringify({ authenticated: false, error: error.message ?? String(error) }) }], + }; + } + }, + ); + + server.registerTool( + "logout", + { + description: "Clear stored credentials", + inputSchema: z.object({}), + }, + async () => { + clearConfig(); + return { + content: [{ type: "text" as const, text: JSON.stringify({ success: true, message: "Logged out. Credentials cleared." }) }], + }; + }, + ); +} \ No newline at end of file diff --git a/tools/email.ts b/tools/email.ts new file mode 100644 index 0000000..78a650f --- /dev/null +++ b/tools/email.ts @@ -0,0 +1,228 @@ +import { z } from "zod/v4"; +import { EwsClient, loadConfig } from "../ews_client.ts"; +import { type McpServer } from "@modelcontextprotocol/server"; + +function getClient(): EwsClient { + return new EwsClient(loadConfig()); +} + +export function registerEmailTools(server: McpServer): void { + server.registerTool( + "get_emails", + { + description: "Get emails from a mailbox folder", + inputSchema: z.object({ + folder: z.string().default("Inbox").describe("Folder name (Inbox, Sent, Drafts, Deleted, Junk, or custom)"), + limit: z.number().default(10).describe("Maximum number of emails to return (default 10, max 50)"), + offset: z.number().default(0).describe("Number of emails to skip for pagination"), + includeBody: z.boolean().default(false).describe("If True, fetch full body for each email (slower)"), + unreadOnly: z.boolean().default(false).describe("If True, only return unread emails"), + idsOnly: z.boolean().default(false).describe("If True, return only item IDs and dates (max limit 500)"), + }), + }, + async ({ folder, limit, offset, includeBody, unreadOnly, idsOnly }) => { + try { + const client = getClient(); + const maxLimit = idsOnly ? 500 : 50; + if (limit > maxLimit) limit = maxLimit; + + const folderId = await client.getFolderId(folder); + const baseShape = idsOnly ? "IdOnly" : "AllProperties"; + + let restriction = ""; + if (unreadOnly) { + restriction = `\ + + + + + + + + `; + } + + const items = await client.findItems(folderId, { limit, offset, baseShape, restriction }); + + if (idsOnly) { + const result = items.map((item: any) => ({ + itemId: item.ItemId?.["@_Id"] ?? "", + date: item.DateTimeReceived ?? "", + subject: item.Subject ?? "", + })); + return { + content: [{ type: "text" as const, text: JSON.stringify({ itemIds: result, count: result.length }) }], + }; + } + + const emails = []; + for (const item of items) { + const email = client.extractEmailSummary(item); + + if (includeBody && email.itemId) { + const details = client.extractEmailDetails(item); + email.to = details.to; + email.cc = details.cc; + email.body = details.body; + email.hasLinks = details.hasLinks; + } + + emails.push(email); + } + + return { + content: [{ type: "text" as const, text: JSON.stringify({ emails, count: emails.length }) }], + }; + } catch (error: any) { + return { + content: [{ type: "text" as const, text: JSON.stringify({ error: error.message ?? String(error) }) }], + }; + } + }, + ); + + server.registerTool( + "get_email", + { + description: "Get a single email with full body and details", + inputSchema: z.object({ + itemId: z.string().describe("The Exchange ItemId of the email to retrieve"), + }), + }, + async ({ itemId }) => { + try { + const client = getClient(); + const item = await client.getItem(itemId); + const email = client.extractEmailDetails(item); + + return { + content: [{ type: "text" as const, text: JSON.stringify(email) }], + }; + } catch (error: any) { + return { + content: [{ type: "text" as const, text: JSON.stringify({ error: error.message ?? String(error) }) }], + }; + } + }, + ); + + server.registerTool( + "search_emails", + { + description: "Search emails by text across one or all folders", + inputSchema: z.object({ + query: z.string().describe("The text to search for"), + folderId: z.string().optional().describe("Optional folder ID to limit the search. When omitted, searches all mail folders"), + maxResults: z.number().default(20).describe("Maximum number of results (default 20, max 100)"), + searchScope: z.enum(["all", "subject", "body", "from"]).default("all").describe("Where to search"), + }), + }, + async ({ query, folderId, maxResults, searchScope }) => { + try { + const client = getClient(); + + if (!query.trim()) { + return { + content: [{ type: "text" as const, text: JSON.stringify({ error: "query must not be empty", results: [] }) }], + }; + } + + maxResults = Math.max(1, Math.min(maxResults, 100)); + + const fieldUriMap: Record = { + subject: "item:Subject", + body: "item:Body", + from: "message:From", + }; + + function containsExpression(fieldUri: string, value: string): string { + return `\ + + + /g, ">").replace(/"/g, """)}"/> + `; + } + + let restriction: string; + if (searchScope === "all") { + restriction = `\ + + + ${containsExpression("item:Subject", query)} + ${containsExpression("item:Body", query)} + + `; + } else { + const fieldUri = fieldUriMap[searchScope]; + if (!fieldUri) { + return { + content: [{ type: "text" as const, text: JSON.stringify({ error: `unsupported search_scope: ${searchScope}`, results: [] }) }], + }; + } + restriction = `\ + + ${containsExpression(fieldUri, query)} + `; + } + + const traversal = folderId ? "Shallow" : "Deep"; + + if (folderId) { + const items = await client.findItems(folderId, { limit: maxResults, restriction, traversal }); + const results = formatSearchResults(items, client, maxResults); + + return { + content: [{ type: "text" as const, text: JSON.stringify({ query, searchScope, folderId, totalResults: results.length, results }) }], + }; + } else { + const folders = await client.findFolders("msgfolderroot", true); + const allResults = []; + + for (const f of folders) { + if (allResults.length >= maxResults) break; + const remaining = maxResults - allResults.length; + const items = await client.findItems(f.id, { limit: remaining, restriction, traversal: "Shallow" }); + const formatted = formatSearchResults(items, client, remaining); + + for (const r of formatted) { + r.folderId = f.id; + r.folderName = f.name; + allResults.push(r); + if (allResults.length >= maxResults) break; + } + } + + return { + content: [{ type: "text" as const, text: JSON.stringify({ query, searchScope, folderId: "all", totalResults: allResults.length, results: allResults }) }], + }; + } + } catch (error: any) { + return { + content: [{ type: "text" as const, text: JSON.stringify({ error: error.message ?? String(error) }) }], + }; + } + }, + ); +} + +function formatSearchResults(items: any[], client: EwsClient, maxResults: number): any[] { + const results: any[] = []; + for (const item of items) { + if (results.length >= maxResults) break; + const summary = client.extractEmailSummary(item); + + const bodyHtml = item.Body?.Value ?? item.Body?.["#text"] ?? ""; + const bodyType = item.Body?.["@_BodyType"] ?? "HTML"; + let bodyPreview = ""; + if (bodyType === "HTML" && bodyHtml) { + bodyPreview = bodyHtml.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 200); + } else { + bodyPreview = bodyHtml.slice(0, 200); + } + + summary.preview = bodyPreview; + + results.push(summary); + } + return results; +} \ No newline at end of file diff --git a/tools/folders.ts b/tools/folders.ts new file mode 100644 index 0000000..debb305 --- /dev/null +++ b/tools/folders.ts @@ -0,0 +1,30 @@ +import { z } from "zod/v4"; +import { EwsClient, loadConfig } from "../ews_client.ts"; +import { type McpServer } from "@modelcontextprotocol/server"; + +export function registerFolderTools(server: McpServer): void { + server.registerTool( + "get_folders", + { + description: "List mail folders from the Exchange mailbox", + inputSchema: z.object({ + parentFolderId: z.string().default("msgfolderroot").describe("Parent folder to list children of (default: msgfolderroot)"), + recursive: z.boolean().default(false).describe("If True, traverse all subfolders recursively"), + }), + }, + async ({ parentFolderId, recursive }) => { + try { + const client = new EwsClient(loadConfig()); + const folders = await client.findFolders(parentFolderId, recursive); + + return { + content: [{ type: "text" as const, text: JSON.stringify(folders) }], + }; + } catch (error: any) { + return { + content: [{ type: "text" as const, text: JSON.stringify({ error: error.message ?? String(error) }) }], + }; + } + }, + ); +} \ No newline at end of file -- 2.52.0 From cefb27349c126b712db126a29323da5ad96d8a16 Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 00:57:54 +0300 Subject: [PATCH 03/13] w --- main.ts | 75 ++++++++++++++++++++++++++++++++++++++++++++++- package-lock.json | 54 ++++++++++++++++++++++++++++++++++ package.json | 2 ++ 3 files changed, 130 insertions(+), 1 deletion(-) diff --git a/main.ts b/main.ts index f225d16..f122c62 100644 --- a/main.ts +++ b/main.ts @@ -1,9 +1,24 @@ import { McpServer } from "@modelcontextprotocol/server"; import { serveStdio } from "@modelcontextprotocol/server/stdio"; +import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { Command } from "commander"; +import http from "node:http"; +import crypto from "node:crypto"; import { registerAuthTools } from "./tools/auth.ts"; import { registerEmailTools } from "./tools/email.ts"; import { registerFolderTools } from "./tools/folders.ts"; +const program = new Command() + .name("exchange-mcp") + .description("Exchange MCP Server — EWS integration via MCP") + .option("--transport ", "Transport: stdio or sse", "stdio") + .option("--token-hash ", "SHA-256 hash of bearer token for HTTP transport auth") + .option("--host ", "HTTP host", "127.0.0.1") + .option("--port ", "HTTP port", "8000"); + +program.parse(process.argv); +const options = program.opts(); + function buildServer(): McpServer { const server = new McpServer({ name: "exchange-mcp", @@ -17,4 +32,62 @@ function buildServer(): McpServer { return server; } -serveStdio(buildServer); \ No newline at end of file +async function main() { + const transportType: string = options.transport; + + if (transportType === "stdio") { + serveStdio(buildServer); + return; + } + + if (transportType === "sse") { + const server = buildServer(); + + const mcpTransport = new NodeStreamableHTTPServerTransport({ + sessionIdGenerator: () => crypto.randomUUID(), + }); + + await server.connect(mcpTransport); + + const httpServer = http.createServer(async (req, res) => { + if (options.tokenHash) { + const auth = req.headers.authorization || ""; + const token = auth.startsWith("Bearer ") ? auth.slice(7) : ""; + const hash = crypto.createHash("sha256").update(token).digest("hex"); + if (hash !== options.tokenHash) { + res.writeHead(401, { "Content-Type": "text/plain" }); + res.end("Unauthorized"); + return; + } + } + + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); + + if (req.method === "OPTIONS") { + res.writeHead(200); + res.end(); + return; + } + + await mcpTransport.handleRequest(req, res); + }); + + const port = Number(options.port); + httpServer.listen(port, options.host); + + console.error( + `Exchange MCP Server running via SSE on http://${options.host}:${port}`, + ); + return; + } + + console.error(`Unsupported transport: ${transportType}. Use "stdio" or "sse".`); + process.exit(1); +} + +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 742b892..a0dd93b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "exchange-mcp", "version": "0.0.0", "dependencies": { + "@modelcontextprotocol/node": "^2.0.0-beta.2", "@modelcontextprotocol/server": "^2.0.0-beta.2", + "commander": "^15.0.0", "fast-xml-parser": "^5.2.0", "httpntlm": "^1.8.13", "zod": "^4.4.3" @@ -461,6 +463,39 @@ "node": ">=18" } }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/node": { + "version": "2.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/node/-/node-2.0.0-beta.2.tgz", + "integrity": "sha512-1ZV1t98ZpwsoiSpIxKOxZCB2qMhDkh4XMQIWhQVDsCG2pVyhSqduxsFiwMd1R0333+KzJ+ELY3RIR+iEvM9A+g==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/server": "^2.0.0-beta.2", + "hono": "^4.11.4" + }, + "peerDependenciesMeta": { + "hono": { + "optional": true + } + } + }, "node_modules/@modelcontextprotocol/server": { "version": "2.0.0-beta.2", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0-beta.2.tgz", @@ -507,6 +542,15 @@ ], "license": "MIT" }, + "node_modules/commander": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", + "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + } + }, "node_modules/des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", @@ -613,6 +657,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/hono": { + "version": "4.12.28", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.28.tgz", + "integrity": "sha512-YwUvVpSF7m1yOblFPrU3Hbo8XhPheBoiyfGuII6z19LnOr6JpDnyyp7LFNrfV56wS8tpvtBFGRISHN02pDdLOA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/httpntlm": { "version": "1.8.13", "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz", diff --git a/package.json b/package.json index a39c2e4..c2baf3b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "format": "dprint fmt" }, "dependencies": { + "@modelcontextprotocol/node": "^2.0.0-beta.2", "@modelcontextprotocol/server": "^2.0.0-beta.2", + "commander": "^15.0.0", "fast-xml-parser": "^5.2.0", "httpntlm": "^1.8.13", "zod": "^4.4.3" -- 2.52.0 From 4d621e0a54e7361b325227498838a38956b3e473 Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 01:12:40 +0300 Subject: [PATCH 04/13] w --- main.ts | 123 +++-- package-lock.json | 1135 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- tools/auth.ts | 2 +- tools/email.ts | 2 +- tools/folders.ts | 2 +- 6 files changed, 1193 insertions(+), 74 deletions(-) diff --git a/main.ts b/main.ts index f122c62..1806d2a 100644 --- a/main.ts +++ b/main.ts @@ -1,6 +1,6 @@ -import { McpServer } from "@modelcontextprotocol/server"; -import { serveStdio } from "@modelcontextprotocol/server/stdio"; -import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { Command } from "commander"; import http from "node:http"; import crypto from "node:crypto"; @@ -32,59 +32,92 @@ function buildServer(): McpServer { return server; } -async function main() { - const transportType: string = options.transport; +async function runStdio() { + const server = buildServer(); + const transport = new StdioServerTransport(); + await server.connect(transport); +} - if (transportType === "stdio") { - serveStdio(buildServer); - return; - } +async function runSSE() { + const transports = new Map(); - if (transportType === "sse") { - const server = buildServer(); - - const mcpTransport = new NodeStreamableHTTPServerTransport({ - sessionIdGenerator: () => crypto.randomUUID(), - }); - - await server.connect(mcpTransport); - - const httpServer = http.createServer(async (req, res) => { - if (options.tokenHash) { - const auth = req.headers.authorization || ""; - const token = auth.startsWith("Bearer ") ? auth.slice(7) : ""; - const hash = crypto.createHash("sha256").update(token).digest("hex"); - if (hash !== options.tokenHash) { - res.writeHead(401, { "Content-Type": "text/plain" }); - res.end("Unauthorized"); - return; - } + const httpServer = http.createServer(async (req, res) => { + if (options.tokenHash) { + const auth = req.headers.authorization || ""; + const token = auth.startsWith("Bearer ") ? auth.slice(7) : ""; + const hash = crypto.createHash("sha256").update(token).digest("hex"); + if (hash !== options.tokenHash) { + res.writeHead(401, { "Content-Type": "text/plain" }); + res.end("Unauthorized"); + return; } + } - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); - if (req.method === "OPTIONS") { - res.writeHead(200); - res.end(); + if (req.method === "OPTIONS") { + res.writeHead(200); + res.end(); + return; + } + + const url = new URL(req.url || "/", `http://${req.headers.host}`); + + if (req.method === "GET") { + const sessionId = crypto.randomUUID(); + const transport = new SSEServerTransport("/message", res); + transports.set(sessionId, transport); + + res.on("close", () => { + transports.delete(sessionId); + }); + + const server = buildServer(); + await server.connect(transport); + return; + } + + if (req.method === "POST" && url.pathname === "/message") { + const sessionId = url.searchParams.get("sessionId") || ""; + const transport = transports.get(sessionId); + if (!transport) { + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("Session not found"); return; } - await mcpTransport.handleRequest(req, res); - }); + await transport.handlePostMessage(req, res); + return; + } - const port = Number(options.port); - httpServer.listen(port, options.host); + res.writeHead(404); + res.end("Not found"); + }); - console.error( - `Exchange MCP Server running via SSE on http://${options.host}:${port}`, - ); - return; + const port = Number(options.port); + httpServer.listen(port, options.host); + + console.error( + `Exchange MCP Server running via SSE on http://${options.host}:${port}`, + ); +} + +async function main() { + const transportType: string = options.transport; + + switch (transportType) { + case "stdio": + await runStdio(); + break; + case "sse": + await runSSE(); + break; + default: + console.error(`Unsupported transport: "${transportType}". Use "stdio" or "sse".`); + process.exit(1); } - - console.error(`Unsupported transport: ${transportType}. Use "stdio" or "sse".`); - process.exit(1); } main().catch((error) => { diff --git a/package-lock.json b/package-lock.json index a0dd93b..8ddf856 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,7 @@ "name": "exchange-mcp", "version": "0.0.0", "dependencies": { - "@modelcontextprotocol/node": "^2.0.0-beta.2", - "@modelcontextprotocol/server": "^2.0.0-beta.2", + "@modelcontextprotocol/sdk": "^1.29.0", "commander": "^15.0.0", "fast-xml-parser": "^5.2.0", "httpntlm": "^1.8.13", @@ -475,39 +474,46 @@ "hono": "^4" } }, - "node_modules/@modelcontextprotocol/node": { - "version": "2.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/node/-/node-2.0.0-beta.2.tgz", - "integrity": "sha512-1ZV1t98ZpwsoiSpIxKOxZCB2qMhDkh4XMQIWhQVDsCG2pVyhSqduxsFiwMd1R0333+KzJ+ELY3RIR+iEvM9A+g==", + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.9" + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" }, "engines": { - "node": ">=20" + "node": ">=18" }, "peerDependencies": { - "@modelcontextprotocol/server": "^2.0.0-beta.2", - "hono": "^4.11.4" + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { - "hono": { + "@cfworker/json-schema": { "optional": true + }, + "zod": { + "optional": false } } }, - "node_modules/@modelcontextprotocol/server": { - "version": "2.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0-beta.2.tgz", - "integrity": "sha512-XK+sgFntT5ZzeqtTGpT9uti+Sqmq7YJZdx/N76eLJv9UA9vKvP04Qh9Hc45LCeIHGfTKRwDG6eh4C1Hs4omyIg==", - "license": "MIT", - "dependencies": { - "zod": "^4.2.0" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/@nodable/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", @@ -530,6 +536,52 @@ "undici-types": "~6.21.0" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/anynum": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", @@ -542,6 +594,81 @@ ], "license": "MIT" }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/commander": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", @@ -551,6 +678,103 @@ "node": ">=22.12.0" } }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", @@ -561,6 +785,65 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", @@ -603,6 +886,125 @@ "@esbuild/win32-x64": "0.28.1" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-builder": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.1.tgz", @@ -642,6 +1044,45 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -657,16 +1098,117 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hono": { "version": "4.12.28", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.28.tgz", "integrity": "sha512-YwUvVpSF7m1yOblFPrU3Hbo8XhPheBoiyfGuII6z19LnOr6JpDnyyp7LFNrfV56wS8tpvtBFGRISHN02pDdLOA==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/httpntlm": { "version": "1.8.13", "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz", @@ -700,12 +1242,52 @@ "node": ">= 6.15.1" } }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "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/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-unsafe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-1.0.1.tgz", @@ -718,18 +1300,166 @@ ], "license": "MIT" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "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/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-expression-matcher": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", @@ -745,6 +1475,275 @@ "node": ">=14.0.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/strnum": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", @@ -760,6 +1759,15 @@ "anynum": "^1.0.1" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tsx": { "version": "4.23.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.0.tgz", @@ -779,6 +1787,37 @@ "fsevents": "~2.3.3" } }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -806,6 +1845,45 @@ "dev": true, "license": "MIT" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/xml-naming": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", @@ -829,6 +1907,15 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } } } } diff --git a/package.json b/package.json index c2baf3b..50916ce 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,7 @@ "format": "dprint fmt" }, "dependencies": { - "@modelcontextprotocol/node": "^2.0.0-beta.2", - "@modelcontextprotocol/server": "^2.0.0-beta.2", + "@modelcontextprotocol/sdk": "^1.29.0", "commander": "^15.0.0", "fast-xml-parser": "^5.2.0", "httpntlm": "^1.8.13", diff --git a/tools/auth.ts b/tools/auth.ts index ca481ad..0694814 100644 --- a/tools/auth.ts +++ b/tools/auth.ts @@ -1,6 +1,6 @@ import { z } from "zod/v4"; import { EwsClient, loadConfig, saveConfig, clearConfig } from "../ews_client.ts"; -import { type McpServer } from "@modelcontextprotocol/server"; +import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; export function registerAuthTools(server: McpServer): void { server.registerTool( diff --git a/tools/email.ts b/tools/email.ts index 78a650f..cead16d 100644 --- a/tools/email.ts +++ b/tools/email.ts @@ -1,6 +1,6 @@ import { z } from "zod/v4"; import { EwsClient, loadConfig } from "../ews_client.ts"; -import { type McpServer } from "@modelcontextprotocol/server"; +import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; function getClient(): EwsClient { return new EwsClient(loadConfig()); diff --git a/tools/folders.ts b/tools/folders.ts index debb305..01c2763 100644 --- a/tools/folders.ts +++ b/tools/folders.ts @@ -1,6 +1,6 @@ import { z } from "zod/v4"; import { EwsClient, loadConfig } from "../ews_client.ts"; -import { type McpServer } from "@modelcontextprotocol/server"; +import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; export function registerFolderTools(server: McpServer): void { server.registerTool( -- 2.52.0 From 9697d8724d99a1f2baad8bdf2d62242649b3b649 Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 01:20:09 +0300 Subject: [PATCH 05/13] w --- main.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/main.ts b/main.ts index 1806d2a..9988dd1 100644 --- a/main.ts +++ b/main.ts @@ -66,16 +66,14 @@ async function runSSE() { const url = new URL(req.url || "/", `http://${req.headers.host}`); if (req.method === "GET") { - const sessionId = crypto.randomUUID(); const transport = new SSEServerTransport("/message", res); - transports.set(sessionId, transport); - - res.on("close", () => { - transports.delete(sessionId); - }); - const server = buildServer(); await server.connect(transport); + + transports.set(transport.sessionId, transport); + res.on("close", () => { + transports.delete(transport.sessionId); + }); return; } -- 2.52.0 From 653eaecf2e81b929f1730b7d02c16f826f364054 Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 02:19:40 +0300 Subject: [PATCH 06/13] w --- .gitignore | 3 ++- ews_client.ts | 19 +++++++++---------- tools/auth.ts | 11 ++++++----- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index c46c3e7..d59519f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ .tmp .vscode .yarn -node_modules \ No newline at end of file +node_modules +config.json \ No newline at end of file diff --git a/ews_client.ts b/ews_client.ts index f0f6620..7c09d35 100644 --- a/ews_client.ts +++ b/ews_client.ts @@ -91,9 +91,7 @@ export class EwsClient { return this.config.domain ?? "corp"; } - private async soapRequest(body: string, soapAction: string): Promise { - const envelope = buildSoapEnvelope(body); - +private async soapRequest(body: string, soapAction: string): Promise { const res = await postAsync({ url: this.ewsUrl, username: this.config.username, @@ -104,14 +102,15 @@ export class EwsClient { "Content-Type": "text/xml; charset=utf-8", SOAPAction: soapAction, }, - body: envelope, + body, }); if (typeof res.body !== "string") { throw new Error(`NTLM request failed, status=${res.statusCode}`); } - return PARSER.parse(res.body); + const parsed = PARSER.parse(res.body); + return parsed; } private extractResponseMessages(data: any): any[] { @@ -131,12 +130,12 @@ export class EwsClient { return Array.isArray(msgs) ? msgs : [msgs]; } - async verifyConnection(): Promise { + async verifyConnection(): Promise<{ ok: boolean; error?: string }> { try { await this.getFolderId("inbox"); - return true; - } catch { - return false; + return { ok: true }; + } catch (error: any) { + return { ok: false, error: error.message ?? String(error) }; } } @@ -155,7 +154,7 @@ export class EwsClient { `); - const data = await this.soapRequest( +const data = await this.soapRequest( soap, "http://schemas.microsoft.com/exchange/services/2006/messages/GetFolder", ); diff --git a/tools/auth.ts b/tools/auth.ts index 0694814..d467e4d 100644 --- a/tools/auth.ts +++ b/tools/auth.ts @@ -26,11 +26,11 @@ export function registerAuthTools(server: McpServer): void { }; const client = new EwsClient(config); - const ok = await client.verifyConnection(); + const result = await client.verifyConnection(); - if (!ok) { + if (!result.ok) { return { - content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: "Connection verification failed. Check credentials and server URL." }) }], + content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: result.error ?? "Connection verification failed" }) }], }; } @@ -57,15 +57,16 @@ export function registerAuthTools(server: McpServer): void { try { const config = loadConfig(); const client = new EwsClient(config); - const ok = await client.verifyConnection(); + const result = await client.verifyConnection(); return { content: [{ type: "text" as const, text: JSON.stringify({ - authenticated: ok, + authenticated: result.ok, email: config.email, serverUrl: config.serverUrl, + error: result.error, }), }], }; -- 2.52.0 From 43821c791ca3635bfdf2eaf3aad58e51a338cba5 Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 09:02:22 +0300 Subject: [PATCH 07/13] w --- main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.ts b/main.ts index 9988dd1..9a7138b 100644 --- a/main.ts +++ b/main.ts @@ -66,7 +66,7 @@ async function runSSE() { const url = new URL(req.url || "/", `http://${req.headers.host}`); if (req.method === "GET") { - const transport = new SSEServerTransport("/message", res); + const transport = new SSEServerTransport("/sse", res); const server = buildServer(); await server.connect(transport); @@ -77,7 +77,7 @@ async function runSSE() { return; } - if (req.method === "POST" && url.pathname === "/message") { + if (req.method === "POST" && url.pathname === "/sse") { const sessionId = url.searchParams.get("sessionId") || ""; const transport = transports.get(sessionId); if (!transport) { -- 2.52.0 From d09b6145d2c68be990d1d1f9ec4aab9cc01a4638 Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 09:06:46 +0300 Subject: [PATCH 08/13] w --- package-lock.json | 519 ---------------------------------------------- package.json | 3 +- 2 files changed, 1 insertion(+), 521 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ddf856..78cceca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,452 +16,9 @@ }, "devDependencies": { "@types/node": "^22.20.1", - "tsx": "^4.23.0", "typescript": "^5.9.3" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@hono/node-server": { "version": "1.19.14", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", @@ -844,48 +401,6 @@ "node": ">= 0.4" } }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1083,21 +598,6 @@ "node": ">= 0.8" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1768,25 +1268,6 @@ "node": ">=0.6" } }, - "node_modules/tsx": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.0.tgz", - "integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.28.0" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, "node_modules/type-is": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", diff --git a/package.json b/package.json index 50916ce..a07ca7c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "start": "tsx main.ts", + "start": "node main.ts", "format": "dprint fmt" }, "dependencies": { @@ -15,7 +15,6 @@ }, "devDependencies": { "@types/node": "^22.20.1", - "tsx": "^4.23.0", "typescript": "^5.9.3" } } -- 2.52.0 From d5171620816db2acb10fd94239c079a6f0c3ab5d Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 09:08:39 +0300 Subject: [PATCH 09/13] w --- ews_client.ts | 60 +++++++++++++------- main.ts | 20 +++++-- models.ts | 2 +- tools/auth.ts | 55 +++++++++++++++--- tools/email.ts | 141 ++++++++++++++++++++++++++++++++++++++--------- tools/folders.ts | 17 ++++-- tsconfig.json | 2 +- 7 files changed, 228 insertions(+), 69 deletions(-) diff --git a/ews_client.ts b/ews_client.ts index 7c09d35..731d490 100644 --- a/ews_client.ts +++ b/ews_client.ts @@ -1,10 +1,10 @@ import { XMLParser } from "fast-xml-parser"; -import { promisify } from "node:util"; -import { createRequire } from "node:module"; -import https from "node:https"; import fs from "node:fs"; +import https from "node:https"; +import { createRequire } from "node:module"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; import type { Email, Folder, LoginConfig } from "./models.ts"; const ntlm: any = createRequire(import.meta.url)("httpntlm"); @@ -91,7 +91,7 @@ export class EwsClient { return this.config.domain ?? "corp"; } -private async soapRequest(body: string, soapAction: string): Promise { + private async soapRequest(body: string, soapAction: string): Promise { const res = await postAsync({ url: this.ewsUrl, username: this.config.username, @@ -154,7 +154,7 @@ private async soapRequest(body: string, soapAction: string): Promise { `); -const data = await this.soapRequest( + const data = await this.soapRequest( soap, "http://schemas.microsoft.com/exchange/services/2006/messages/GetFolder", ); @@ -238,7 +238,8 @@ ${restriction} ); for (const msg of this.extractResponseMessages(data)) { - const items = msg?.RootFolder?.Items?.Message ?? msg?.RootFolder?.Items?.CalendarItem; + const items = msg?.RootFolder?.Items?.Message + ?? msg?.RootFolder?.Items?.CalendarItem; if (!items) continue; return Array.isArray(items) ? items : [items]; } @@ -268,7 +269,13 @@ ${restriction} if (!items) continue; const itemKey = Object.keys(items).find((k) => - ["Message", "CalendarItem", "MeetingRequest", "MeetingResponse", "MeetingCancellation"].includes(k) + [ + "Message", + "CalendarItem", + "MeetingRequest", + "MeetingResponse", + "MeetingCancellation", + ].includes(k) ); if (itemKey) { return items[itemKey]; @@ -283,7 +290,8 @@ ${restriction} recursive: boolean = false, ): Promise { const traversal = recursive ? "Deep" : "Shallow"; - const isDistinguished = DISTINGUISHED_FOLDERS[parentFolderId.toLowerCase()] !== undefined + const isDistinguished = + DISTINGUISHED_FOLDERS[parentFolderId.toLowerCase()] !== undefined || ["msgfolderroot"].includes(parentFolderId.toLowerCase()); const folderIdXml = isDistinguished @@ -329,7 +337,10 @@ ${restriction} extractEmailSummary(item: any): Email { const itemType = item["@_xsi_type"] ?? item.__type ?? ""; - const isMeeting = /MeetingRequest|MeetingResponse|MeetingCancellation|CalendarItem/i.test(itemType); + const isMeeting = + /MeetingRequest|MeetingResponse|MeetingCancellation|CalendarItem/i.test( + itemType, + ); const fromMailbox = item.From?.Mailbox ?? item.Organizer?.Mailbox @@ -340,9 +351,11 @@ ${restriction} subject: item.Subject ?? "(No subject)", from: fromMailbox.EmailAddress ?? "", fromName: fromMailbox.Name ?? "", - date: item.DateTimeSent ?? item.DateTimeReceived ?? item.DateTimeCreated ?? "", + date: item.DateTimeSent ?? item.DateTimeReceived ?? item.DateTimeCreated + ?? "", isRead: item.IsRead === "true" || item.IsRead === true, - hasAttachments: item.HasAttachments === "true" || item.HasAttachments === true, + hasAttachments: item.HasAttachments === "true" + || item.HasAttachments === true, hasLinks: false, itemId: item.ItemId?.["@_Id"] ?? "", size: item.Size ? Number(item.Size) : 0, @@ -357,15 +370,20 @@ ${restriction} }; if (item.DisplayTo) { - email.to = item.DisplayTo.split(";").map((t: string) => t.trim()).filter(Boolean); + email.to = item.DisplayTo.split(";").map((t: string) => t.trim()).filter( + Boolean, + ); } if (item.DisplayCc) { - email.cc = item.DisplayCc.split(";").map((c: string) => c.trim()).filter(Boolean); + email.cc = item.DisplayCc.split(";").map((c: string) => c.trim()).filter( + Boolean, + ); } if (isMeeting) { email.location = item.Location ?? ""; - email.start = item.Start ?? item.StartWallClock ?? item.ReminderDueBy ?? ""; + email.start = item.Start ?? item.StartWallClock ?? item.ReminderDueBy + ?? ""; email.end = item.End ?? item.EndWallClock ?? ""; } @@ -416,11 +434,13 @@ ${restriction} isInline: a.IsInline === "true" || a.IsInline === true, })); - const isMeeting = /MeetingRequest|MeetingResponse|MeetingCancellation|CalendarItem/i.test( - item["@_xsi_type"] ?? "", - ); + const isMeeting = + /MeetingRequest|MeetingResponse|MeetingCancellation|CalendarItem/i.test( + item["@_xsi_type"] ?? "", + ); if (isMeeting) { - email.location = item.Location ?? item.EnhancedLocation?.DisplayName ?? ""; + email.location = item.Location ?? item.EnhancedLocation?.DisplayName + ?? ""; email.start = item.Start ?? ""; email.end = item.End ?? ""; @@ -462,10 +482,10 @@ ${restriction} text = text.replace(/&/g, "&"); text = text.replace(/</g, "<"); text = text.replace(/>/g, ">"); - text = text.replace(/"/g, '"'); + text = text.replace(/"/g, "\""); text = text.replace(/'/g, "'"); text = text.replace(/\n\s*\n/g, "\n\n"); text = text.replace(/[ \t]+/g, " "); return text.trim(); } -} \ No newline at end of file +} diff --git a/main.ts b/main.ts index 9a7138b..bf53607 100644 --- a/main.ts +++ b/main.ts @@ -1,9 +1,9 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { Command } from "commander"; -import http from "node:http"; import crypto from "node:crypto"; +import http from "node:http"; import { registerAuthTools } from "./tools/auth.ts"; import { registerEmailTools } from "./tools/email.ts"; import { registerFolderTools } from "./tools/folders.ts"; @@ -12,7 +12,10 @@ const program = new Command() .name("exchange-mcp") .description("Exchange MCP Server — EWS integration via MCP") .option("--transport ", "Transport: stdio or sse", "stdio") - .option("--token-hash ", "SHA-256 hash of bearer token for HTTP transport auth") + .option( + "--token-hash ", + "SHA-256 hash of bearer token for HTTP transport auth", + ) .option("--host ", "HTTP host", "127.0.0.1") .option("--port ", "HTTP port", "8000"); @@ -55,7 +58,10 @@ async function runSSE() { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); + res.setHeader( + "Access-Control-Allow-Headers", + "Content-Type, Authorization", + ); if (req.method === "OPTIONS") { res.writeHead(200); @@ -113,7 +119,9 @@ async function main() { await runSSE(); break; default: - console.error(`Unsupported transport: "${transportType}". Use "stdio" or "sse".`); + console.error( + `Unsupported transport: "${transportType}". Use "stdio" or "sse".`, + ); process.exit(1); } } @@ -121,4 +129,4 @@ async function main() { main().catch((error) => { console.error("Fatal error:", error); process.exit(1); -}); \ No newline at end of file +}); diff --git a/models.ts b/models.ts index 4d9575d..e374a83 100644 --- a/models.ts +++ b/models.ts @@ -100,4 +100,4 @@ export interface MeetingResult { requiredAttendees: string[]; optionalAttendees: string[]; error: string; -} \ No newline at end of file +} diff --git a/tools/auth.ts b/tools/auth.ts index d467e4d..b5a6427 100644 --- a/tools/auth.ts +++ b/tools/auth.ts @@ -1,6 +1,11 @@ -import { z } from "zod/v4"; -import { EwsClient, loadConfig, saveConfig, clearConfig } from "../ews_client.ts"; import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod/v4"; +import { + clearConfig, + EwsClient, + loadConfig, + saveConfig, +} from "../ews_client.ts"; export function registerAuthTools(server: McpServer): void { server.registerTool( @@ -8,7 +13,9 @@ export function registerAuthTools(server: McpServer): void { { description: "Authenticate to Exchange EWS using NTLM credentials", inputSchema: z.object({ - serverUrl: z.string().describe("EWS server URL (e.g. https://mail.example.com)"), + serverUrl: z.string().describe( + "EWS server URL (e.g. https://mail.example.com)", + ), email: z.string().describe("Email address"), username: z.string().describe("NTLM username"), password: z.string().describe("NTLM password"), @@ -30,18 +37,36 @@ export function registerAuthTools(server: McpServer): void { if (!result.ok) { return { - content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: result.error ?? "Connection verification failed" }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ + success: false, + error: result.error ?? "Connection verification failed", + }), + }], }; } saveConfig(config); return { - content: [{ type: "text" as const, text: JSON.stringify({ success: true, message: `Logged in as ${email} to ${serverUrl}` }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ + success: true, + message: `Logged in as ${email} to ${serverUrl}`, + }), + }], }; } catch (error: any) { return { - content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: error.message ?? String(error) }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ + success: false, + error: error.message ?? String(error), + }), + }], }; } }, @@ -72,7 +97,13 @@ export function registerAuthTools(server: McpServer): void { }; } catch (error: any) { return { - content: [{ type: "text" as const, text: JSON.stringify({ authenticated: false, error: error.message ?? String(error) }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ + authenticated: false, + error: error.message ?? String(error), + }), + }], }; } }, @@ -87,8 +118,14 @@ export function registerAuthTools(server: McpServer): void { async () => { clearConfig(); return { - content: [{ type: "text" as const, text: JSON.stringify({ success: true, message: "Logged out. Credentials cleared." }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ + success: true, + message: "Logged out. Credentials cleared.", + }), + }], }; }, ); -} \ No newline at end of file +} diff --git a/tools/email.ts b/tools/email.ts index cead16d..e9876b2 100644 --- a/tools/email.ts +++ b/tools/email.ts @@ -1,6 +1,6 @@ +import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod/v4"; import { EwsClient, loadConfig } from "../ews_client.ts"; -import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; function getClient(): EwsClient { return new EwsClient(loadConfig()); @@ -12,12 +12,24 @@ export function registerEmailTools(server: McpServer): void { { description: "Get emails from a mailbox folder", inputSchema: z.object({ - folder: z.string().default("Inbox").describe("Folder name (Inbox, Sent, Drafts, Deleted, Junk, or custom)"), - limit: z.number().default(10).describe("Maximum number of emails to return (default 10, max 50)"), - offset: z.number().default(0).describe("Number of emails to skip for pagination"), - includeBody: z.boolean().default(false).describe("If True, fetch full body for each email (slower)"), - unreadOnly: z.boolean().default(false).describe("If True, only return unread emails"), - idsOnly: z.boolean().default(false).describe("If True, return only item IDs and dates (max limit 500)"), + folder: z.string().default("Inbox").describe( + "Folder name (Inbox, Sent, Drafts, Deleted, Junk, or custom)", + ), + limit: z.number().default(10).describe( + "Maximum number of emails to return (default 10, max 50)", + ), + offset: z.number().default(0).describe( + "Number of emails to skip for pagination", + ), + includeBody: z.boolean().default(false).describe( + "If True, fetch full body for each email (slower)", + ), + unreadOnly: z.boolean().default(false).describe( + "If True, only return unread emails", + ), + idsOnly: z.boolean().default(false).describe( + "If True, return only item IDs and dates (max limit 500)", + ), }), }, async ({ folder, limit, offset, includeBody, unreadOnly, idsOnly }) => { @@ -42,7 +54,12 @@ export function registerEmailTools(server: McpServer): void { `; } - const items = await client.findItems(folderId, { limit, offset, baseShape, restriction }); + const items = await client.findItems(folderId, { + limit, + offset, + baseShape, + restriction, + }); if (idsOnly) { const result = items.map((item: any) => ({ @@ -51,7 +68,10 @@ export function registerEmailTools(server: McpServer): void { subject: item.Subject ?? "", })); return { - content: [{ type: "text" as const, text: JSON.stringify({ itemIds: result, count: result.length }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ itemIds: result, count: result.length }), + }], }; } @@ -71,11 +91,17 @@ export function registerEmailTools(server: McpServer): void { } return { - content: [{ type: "text" as const, text: JSON.stringify({ emails, count: emails.length }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ emails, count: emails.length }), + }], }; } catch (error: any) { return { - content: [{ type: "text" as const, text: JSON.stringify({ error: error.message ?? String(error) }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ error: error.message ?? String(error) }), + }], }; } }, @@ -86,7 +112,9 @@ export function registerEmailTools(server: McpServer): void { { description: "Get a single email with full body and details", inputSchema: z.object({ - itemId: z.string().describe("The Exchange ItemId of the email to retrieve"), + itemId: z.string().describe( + "The Exchange ItemId of the email to retrieve", + ), }), }, async ({ itemId }) => { @@ -100,7 +128,10 @@ export function registerEmailTools(server: McpServer): void { }; } catch (error: any) { return { - content: [{ type: "text" as const, text: JSON.stringify({ error: error.message ?? String(error) }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ error: error.message ?? String(error) }), + }], }; } }, @@ -112,9 +143,14 @@ export function registerEmailTools(server: McpServer): void { description: "Search emails by text across one or all folders", inputSchema: z.object({ query: z.string().describe("The text to search for"), - folderId: z.string().optional().describe("Optional folder ID to limit the search. When omitted, searches all mail folders"), - maxResults: z.number().default(20).describe("Maximum number of results (default 20, max 100)"), - searchScope: z.enum(["all", "subject", "body", "from"]).default("all").describe("Where to search"), + folderId: z.string().optional().describe( + "Optional folder ID to limit the search. When omitted, searches all mail folders", + ), + maxResults: z.number().default(20).describe( + "Maximum number of results (default 20, max 100)", + ), + searchScope: z.enum(["all", "subject", "body", "from"]).default("all") + .describe("Where to search"), }), }, async ({ query, folderId, maxResults, searchScope }) => { @@ -123,7 +159,13 @@ export function registerEmailTools(server: McpServer): void { if (!query.trim()) { return { - content: [{ type: "text" as const, text: JSON.stringify({ error: "query must not be empty", results: [] }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ + error: "query must not be empty", + results: [], + }), + }], }; } @@ -139,7 +181,12 @@ export function registerEmailTools(server: McpServer): void { return `\ - /g, ">").replace(/"/g, """)}"/> + /g, + ">", + ).replace(/"/g, """) + }"/> `; } @@ -156,7 +203,13 @@ export function registerEmailTools(server: McpServer): void { const fieldUri = fieldUriMap[searchScope]; if (!fieldUri) { return { - content: [{ type: "text" as const, text: JSON.stringify({ error: `unsupported search_scope: ${searchScope}`, results: [] }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ + error: `unsupported search_scope: ${searchScope}`, + results: [], + }), + }], }; } restriction = `\ @@ -168,11 +221,24 @@ export function registerEmailTools(server: McpServer): void { const traversal = folderId ? "Shallow" : "Deep"; if (folderId) { - const items = await client.findItems(folderId, { limit: maxResults, restriction, traversal }); + const items = await client.findItems(folderId, { + limit: maxResults, + restriction, + traversal, + }); const results = formatSearchResults(items, client, maxResults); return { - content: [{ type: "text" as const, text: JSON.stringify({ query, searchScope, folderId, totalResults: results.length, results }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ + query, + searchScope, + folderId, + totalResults: results.length, + results, + }), + }], }; } else { const folders = await client.findFolders("msgfolderroot", true); @@ -181,7 +247,11 @@ export function registerEmailTools(server: McpServer): void { for (const f of folders) { if (allResults.length >= maxResults) break; const remaining = maxResults - allResults.length; - const items = await client.findItems(f.id, { limit: remaining, restriction, traversal: "Shallow" }); + const items = await client.findItems(f.id, { + limit: remaining, + restriction, + traversal: "Shallow", + }); const formatted = formatSearchResults(items, client, remaining); for (const r of formatted) { @@ -193,19 +263,35 @@ export function registerEmailTools(server: McpServer): void { } return { - content: [{ type: "text" as const, text: JSON.stringify({ query, searchScope, folderId: "all", totalResults: allResults.length, results: allResults }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ + query, + searchScope, + folderId: "all", + totalResults: allResults.length, + results: allResults, + }), + }], }; } } catch (error: any) { return { - content: [{ type: "text" as const, text: JSON.stringify({ error: error.message ?? String(error) }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ error: error.message ?? String(error) }), + }], }; } }, ); } -function formatSearchResults(items: any[], client: EwsClient, maxResults: number): any[] { +function formatSearchResults( + items: any[], + client: EwsClient, + maxResults: number, +): any[] { const results: any[] = []; for (const item of items) { if (results.length >= maxResults) break; @@ -215,7 +301,8 @@ function formatSearchResults(items: any[], client: EwsClient, maxResults: number const bodyType = item.Body?.["@_BodyType"] ?? "HTML"; let bodyPreview = ""; if (bodyType === "HTML" && bodyHtml) { - bodyPreview = bodyHtml.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 200); + bodyPreview = bodyHtml.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ") + .trim().slice(0, 200); } else { bodyPreview = bodyHtml.slice(0, 200); } @@ -225,4 +312,4 @@ function formatSearchResults(items: any[], client: EwsClient, maxResults: number results.push(summary); } return results; -} \ No newline at end of file +} diff --git a/tools/folders.ts b/tools/folders.ts index 01c2763..1615a65 100644 --- a/tools/folders.ts +++ b/tools/folders.ts @@ -1,6 +1,6 @@ +import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod/v4"; import { EwsClient, loadConfig } from "../ews_client.ts"; -import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; export function registerFolderTools(server: McpServer): void { server.registerTool( @@ -8,8 +8,12 @@ export function registerFolderTools(server: McpServer): void { { description: "List mail folders from the Exchange mailbox", inputSchema: z.object({ - parentFolderId: z.string().default("msgfolderroot").describe("Parent folder to list children of (default: msgfolderroot)"), - recursive: z.boolean().default(false).describe("If True, traverse all subfolders recursively"), + parentFolderId: z.string().default("msgfolderroot").describe( + "Parent folder to list children of (default: msgfolderroot)", + ), + recursive: z.boolean().default(false).describe( + "If True, traverse all subfolders recursively", + ), }), }, async ({ parentFolderId, recursive }) => { @@ -22,9 +26,12 @@ export function registerFolderTools(server: McpServer): void { }; } catch (error: any) { return { - content: [{ type: "text" as const, text: JSON.stringify({ error: error.message ?? String(error) }) }], + content: [{ + type: "text" as const, + text: JSON.stringify({ error: error.message ?? String(error) }), + }], }; } }, ); -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 424bf37..1bc424c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -39,4 +39,4 @@ "useUnknownInCatchVariables": true, "verbatimModuleSyntax": true } -} \ No newline at end of file +} -- 2.52.0 From a79db30ea931221279d450a6821ffc416a01c580 Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 09:21:04 +0300 Subject: [PATCH 10/13] w --- ews_client.ts | 33 +++++++++++++++++++++++++++++++-- models.ts | 1 - tools/auth.ts | 16 ++++++++++++++-- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/ews_client.ts b/ews_client.ts index 731d490..aff3de6 100644 --- a/ews_client.ts +++ b/ews_client.ts @@ -7,6 +7,24 @@ import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; import type { Email, Folder, LoginConfig } from "./models.ts"; +let inMemoryPassword: string | null = null; + +export function setPassword(password: string): void { + inMemoryPassword = password; +} + +export function getPassword(): string | null { + return inMemoryPassword; +} + +export function hasPassword(): boolean { + return inMemoryPassword !== null; +} + +export function clearPassword(): void { + inMemoryPassword = null; +} + const ntlm: any = createRequire(import.meta.url)("httpntlm"); const postAsync = promisify(ntlm.post.bind(ntlm)); @@ -76,27 +94,38 @@ export function clearConfig(): void { if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } + clearPassword(); } export class EwsClient { private config: LoginConfig; private ewsUrl: string; - constructor(config: LoginConfig) { + constructor(config: LoginConfig, password?: string) { this.config = config; this.ewsUrl = `${config.serverUrl.replace(/\/+$/, "")}/EWS/Exchange.asmx`; + + if (password) { + setPassword(password); + } } private get domain(): string { return this.config.domain ?? "corp"; } + private get password(): string { + const pw = getPassword(); + if (!pw) throw new Error("Not logged in. Password not found in memory."); + return pw; + } + private async soapRequest(body: string, soapAction: string): Promise { const res = await postAsync({ url: this.ewsUrl, username: this.config.username, domain: this.domain, - password: this.config.password, + password: this.password, agent: AGENT, headers: { "Content-Type": "text/xml; charset=utf-8", diff --git a/models.ts b/models.ts index e374a83..0646dc5 100644 --- a/models.ts +++ b/models.ts @@ -2,7 +2,6 @@ export interface LoginConfig { serverUrl: string; email: string; username: string; - password: string; domain?: string; } diff --git a/tools/auth.ts b/tools/auth.ts index b5a6427..b566d3c 100644 --- a/tools/auth.ts +++ b/tools/auth.ts @@ -3,8 +3,10 @@ import { z } from "zod/v4"; import { clearConfig, EwsClient, + hasPassword, loadConfig, saveConfig, + setPassword, } from "../ews_client.ts"; export function registerAuthTools(server: McpServer): void { @@ -28,11 +30,10 @@ export function registerAuthTools(server: McpServer): void { serverUrl: serverUrl.replace(/\/+$/, ""), email, username, - password, domain: domain ?? "corp", }; - const client = new EwsClient(config); + const client = new EwsClient(config, password); const result = await client.verifyConnection(); if (!result.ok) { @@ -80,6 +81,17 @@ export function registerAuthTools(server: McpServer): void { }, async () => { try { + if (!hasPassword()) { + return { + content: [{ + type: "text" as const, + text: JSON.stringify({ + authenticated: false, + error: "Not logged in. Password not found in memory.", + }), + }], + }; + } const config = loadConfig(); const client = new EwsClient(config); const result = await client.verifyConnection(); -- 2.52.0 From 147b774e44e174575323ea8917205f3f1e8641f7 Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 09:35:22 +0300 Subject: [PATCH 11/13] w --- ews_client.ts | 4 +- models.ts | 102 ---------------------------------------- types/calendar_event.ts | 17 +++++++ types/email.ts | 32 +++++++++++++ types/folder.ts | 7 +++ types/free_slot.ts | 6 +++ types/login_config.ts | 6 +++ types/meeting_result.ts | 11 +++++ types/person.ts | 17 +++++++ 9 files changed, 99 insertions(+), 103 deletions(-) delete mode 100644 models.ts create mode 100644 types/calendar_event.ts create mode 100644 types/email.ts create mode 100644 types/folder.ts create mode 100644 types/free_slot.ts create mode 100644 types/login_config.ts create mode 100644 types/meeting_result.ts create mode 100644 types/person.ts diff --git a/ews_client.ts b/ews_client.ts index aff3de6..5440120 100644 --- a/ews_client.ts +++ b/ews_client.ts @@ -5,7 +5,9 @@ import { createRequire } from "node:module"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; -import type { Email, Folder, LoginConfig } from "./models.ts"; +import type { Email } from "./types/email.ts"; +import type { Folder } from "./types/folder.ts"; +import type { LoginConfig } from "./types/login_config.ts"; let inMemoryPassword: string | null = null; diff --git a/models.ts b/models.ts deleted file mode 100644 index 0646dc5..0000000 --- a/models.ts +++ /dev/null @@ -1,102 +0,0 @@ -export interface LoginConfig { - serverUrl: string; - email: string; - username: string; - domain?: string; -} - -export interface Email { - subject: string; - from: string; - fromName: string; - date: string; - isRead: boolean; - hasAttachments: boolean; - hasLinks: boolean; - itemId: string; - size: number; - isMeeting: boolean; - itemType: string; - to: string[]; - cc: string[]; - body: string; - bodyType: string; - attachments: Attachment[]; - preview: string; - location?: string; - start?: string; - end?: string; - requiredAttendees?: string[]; - optionalAttendees?: string[]; -} - -export interface Attachment { - name: string; - size: number; - contentType: string; - attachmentId: string; - isInline: boolean; -} - -export interface Folder { - name: string; - id: string; - totalCount: number; - unreadCount: number; - childFolderCount: number; -} - -export interface Person { - name: string; - email: string; - mailboxType: string; - firstName: string; - lastName: string; - jobTitle: string; - department: string; - company: string; - office: string; - alias: string; - manager: string; - managerEmail: string; - phones: Record; - address: string; - directReports: Array<{ name: string; email: string }>; -} - -export interface CalendarEvent { - subject: string; - start: string; - end: string; - location: string; - isAllDay: boolean; - isCancelled: boolean; - isMeeting: boolean; - isRecurring: boolean; - organizer: string; - organizerEmail: string; - myResponse: string; - itemId: string; - body: string; - requiredAttendees: string[]; - optionalAttendees: string[]; -} - -export interface FreeSlot { - date: string; - start: string; - end: string; - durationMinutes: number; -} - -export interface MeetingResult { - success: boolean; - subject: string; - date: string; - startTime: string; - endTime: string; - location: string; - requiredAttendees: string[]; - optionalAttendees: string[]; - error: string; -} diff --git a/types/calendar_event.ts b/types/calendar_event.ts new file mode 100644 index 0000000..cdae6f0 --- /dev/null +++ b/types/calendar_event.ts @@ -0,0 +1,17 @@ +export interface CalendarEvent { + subject: string; + start: string; + end: string; + location: string; + isAllDay: boolean; + isCancelled: boolean; + isMeeting: boolean; + isRecurring: boolean; + organizer: string; + organizerEmail: string; + myResponse: string; + itemId: string; + body: string; + requiredAttendees: string[]; + optionalAttendees: string[]; +} \ No newline at end of file diff --git a/types/email.ts b/types/email.ts new file mode 100644 index 0000000..c74e267 --- /dev/null +++ b/types/email.ts @@ -0,0 +1,32 @@ +export interface Email { + subject: string; + from: string; + fromName: string; + date: string; + isRead: boolean; + hasAttachments: boolean; + hasLinks: boolean; + itemId: string; + size: number; + isMeeting: boolean; + itemType: string; + to: string[]; + cc: string[]; + body: string; + bodyType: string; + attachments: Attachment[]; + preview: string; + location?: string; + start?: string; + end?: string; + requiredAttendees?: string[]; + optionalAttendees?: string[]; +} + +export interface Attachment { + name: string; + size: number; + contentType: string; + attachmentId: string; + isInline: boolean; +} \ No newline at end of file diff --git a/types/folder.ts b/types/folder.ts new file mode 100644 index 0000000..8338c68 --- /dev/null +++ b/types/folder.ts @@ -0,0 +1,7 @@ +export interface Folder { + name: string; + id: string; + totalCount: number; + unreadCount: number; + childFolderCount: number; +} \ No newline at end of file diff --git a/types/free_slot.ts b/types/free_slot.ts new file mode 100644 index 0000000..8493a0a --- /dev/null +++ b/types/free_slot.ts @@ -0,0 +1,6 @@ +export interface FreeSlot { + date: string; + start: string; + end: string; + durationMinutes: number; +} \ No newline at end of file diff --git a/types/login_config.ts b/types/login_config.ts new file mode 100644 index 0000000..d75aae1 --- /dev/null +++ b/types/login_config.ts @@ -0,0 +1,6 @@ +export interface LoginConfig { + serverUrl: string; + email: string; + username: string; + domain?: string; +} \ No newline at end of file diff --git a/types/meeting_result.ts b/types/meeting_result.ts new file mode 100644 index 0000000..f35d406 --- /dev/null +++ b/types/meeting_result.ts @@ -0,0 +1,11 @@ +export interface MeetingResult { + success: boolean; + subject: string; + date: string; + startTime: string; + endTime: string; + location: string; + requiredAttendees: string[]; + optionalAttendees: string[]; + error: string; +} \ No newline at end of file diff --git a/types/person.ts b/types/person.ts new file mode 100644 index 0000000..a117682 --- /dev/null +++ b/types/person.ts @@ -0,0 +1,17 @@ +export interface Person { + name: string; + email: string; + mailboxType: string; + firstName: string; + lastName: string; + jobTitle: string; + department: string; + company: string; + office: string; + alias: string; + manager: string; + managerEmail: string; + phones: Record; + address: string; + directReports: Array<{ name: string; email: string }>; +} \ No newline at end of file -- 2.52.0 From 66b1fa0ecc06e6ec77797a9e1c13d9089b6156af Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 09:41:01 +0300 Subject: [PATCH 12/13] w --- dockerfile | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 dockerfile diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..09a75a2 --- /dev/null +++ b/dockerfile @@ -0,0 +1,5 @@ +FROM node:26-slim +WORKDIR /app +COPY . . +RUN npm install +CMD ["npm", "start"] -- 2.52.0 From bafe2992494f56127fc644f6ac45403164549cda Mon Sep 17 00:00:00 2001 From: albnnc Date: Thu, 9 Jul 2026 09:41:53 +0300 Subject: [PATCH 13/13] w --- types/calendar_event.ts | 2 +- types/email.ts | 2 +- types/folder.ts | 2 +- types/free_slot.ts | 2 +- types/login_config.ts | 2 +- types/meeting_result.ts | 2 +- types/person.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/types/calendar_event.ts b/types/calendar_event.ts index cdae6f0..1e65734 100644 --- a/types/calendar_event.ts +++ b/types/calendar_event.ts @@ -14,4 +14,4 @@ export interface CalendarEvent { body: string; requiredAttendees: string[]; optionalAttendees: string[]; -} \ No newline at end of file +} diff --git a/types/email.ts b/types/email.ts index c74e267..1ac1b24 100644 --- a/types/email.ts +++ b/types/email.ts @@ -29,4 +29,4 @@ export interface Attachment { contentType: string; attachmentId: string; isInline: boolean; -} \ No newline at end of file +} diff --git a/types/folder.ts b/types/folder.ts index 8338c68..6bef6e0 100644 --- a/types/folder.ts +++ b/types/folder.ts @@ -4,4 +4,4 @@ export interface Folder { totalCount: number; unreadCount: number; childFolderCount: number; -} \ No newline at end of file +} diff --git a/types/free_slot.ts b/types/free_slot.ts index 8493a0a..cae3873 100644 --- a/types/free_slot.ts +++ b/types/free_slot.ts @@ -3,4 +3,4 @@ export interface FreeSlot { start: string; end: string; durationMinutes: number; -} \ No newline at end of file +} diff --git a/types/login_config.ts b/types/login_config.ts index d75aae1..ef2c777 100644 --- a/types/login_config.ts +++ b/types/login_config.ts @@ -3,4 +3,4 @@ export interface LoginConfig { email: string; username: string; domain?: string; -} \ No newline at end of file +} diff --git a/types/meeting_result.ts b/types/meeting_result.ts index f35d406..fc8cbf7 100644 --- a/types/meeting_result.ts +++ b/types/meeting_result.ts @@ -8,4 +8,4 @@ export interface MeetingResult { requiredAttendees: string[]; optionalAttendees: string[]; error: string; -} \ No newline at end of file +} diff --git a/types/person.ts b/types/person.ts index a117682..1916cc3 100644 --- a/types/person.ts +++ b/types/person.ts @@ -14,4 +14,4 @@ export interface Person { phones: Record; address: string; directReports: Array<{ name: string; email: string }>; -} \ No newline at end of file +} -- 2.52.0