@leonardocrdso/office365-mcp-server 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/build/index.js +236 -83
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -23555,6 +23555,64 @@ class StdioServerTransport {
23555
23555
  });
23556
23556
  }
23557
23557
  }
23558
+ // package.json
23559
+ var package_default = {
23560
+ name: "@leonardocrdso/office365-mcp-server",
23561
+ version: "1.1.0",
23562
+ description: "MCP server for Microsoft 365 — Outlook, Calendar, OneDrive, SharePoint & Teams via Graph API",
23563
+ type: "module",
23564
+ main: "build/index.js",
23565
+ bin: {
23566
+ "office365-mcp-server": "build/index.js"
23567
+ },
23568
+ files: [
23569
+ "build/",
23570
+ "README.md",
23571
+ "LICENSE"
23572
+ ],
23573
+ scripts: {
23574
+ dev: "bun run --watch src/index.ts",
23575
+ start: "bun run src/index.ts",
23576
+ build: "bun build src/index.ts --outdir build --target node",
23577
+ prepublishOnly: "bun run build"
23578
+ },
23579
+ keywords: [
23580
+ "office365",
23581
+ "microsoft-365",
23582
+ "outlook",
23583
+ "teams",
23584
+ "onedrive",
23585
+ "sharepoint",
23586
+ "calendar",
23587
+ "mcp",
23588
+ "model-context-protocol",
23589
+ "microsoft-graph",
23590
+ "claude",
23591
+ "ai"
23592
+ ],
23593
+ author: "leonardocrdso",
23594
+ license: "MIT",
23595
+ repository: {
23596
+ type: "git",
23597
+ url: "git+https://github.com/leonardocrdso/office365-mcp-server.git"
23598
+ },
23599
+ homepage: "https://github.com/leonardocrdso/office365-mcp-server#readme",
23600
+ bugs: {
23601
+ url: "https://github.com/leonardocrdso/office365-mcp-server/issues"
23602
+ },
23603
+ publishConfig: {
23604
+ access: "public"
23605
+ },
23606
+ dependencies: {
23607
+ "@azure/msal-node": "^3.8.8",
23608
+ "@modelcontextprotocol/sdk": "^1.26.0",
23609
+ zod: "^3.25.0"
23610
+ },
23611
+ devDependencies: {
23612
+ "@types/node": "^22.0.0",
23613
+ typescript: "^5.0.0"
23614
+ }
23615
+ };
23558
23616
 
23559
23617
  // node_modules/@azure/msal-node/dist/cache/serializer/Serializer.mjs
23560
23618
  /*! @azure/msal-node v3.8.8 2026-02-23 */
@@ -31708,16 +31766,12 @@ var SOURCES_THAT_SUPPORT_TOKEN_REVOCATION = [ManagedIdentitySourceNames.SERVICE_
31708
31766
  // node_modules/@azure/msal-node/dist/index.mjs
31709
31767
  /*! @azure/msal-node v3.8.8 2026-02-23 */
31710
31768
 
31711
- // src/auth/msal-client.ts
31712
- import { readFile, writeFile, unlink } from "node:fs/promises";
31713
- import { homedir } from "node:os";
31714
- import { join } from "node:path";
31715
-
31716
31769
  // src/constants.ts
31717
31770
  var DEFAULT_PAGE_SIZE_SMALL = 10;
31718
31771
  var DEFAULT_PAGE_SIZE_LARGE = 20;
31719
31772
  var DEFAULT_MEETING_DURATION_MINUTES = 30;
31720
31773
  var DEFAULT_TIMEZONE = "America/Sao_Paulo";
31774
+ var PREFER_TIMEZONE_HEADER = `outlook.timezone="${DEFAULT_TIMEZONE}"`;
31721
31775
  var BODY_PREVIEW_MAX_LENGTH = 100;
31722
31776
  var MESSAGE_CONTENT_MAX_LENGTH = 200;
31723
31777
  var SCOPES = {
@@ -31833,7 +31887,7 @@ var GRAPH_CODE_MAP = {
31833
31887
  ErrorSendAsDenied: ErrorCode2.PERMISSION_DENIED
31834
31888
  };
31835
31889
  var ERROR_MESSAGES = {
31836
- [ErrorCode2.AUTH_NOT_CONFIGURED]: "AZURE_CLIENT_ID não configurado. Defina a variável de ambiente AZURE_CLIENT_ID com o ID do seu app registration no Azure AD.",
31890
+ [ErrorCode2.AUTH_NOT_CONFIGURED]: "AZURE_CLIENT_ID não configurado. Use a tool 'configure' para informar suas credenciais Azure, ou defina a variável de ambiente AZURE_CLIENT_ID.",
31837
31891
  [ErrorCode2.AUTH_NOT_AUTHENTICATED]: "Usuário não autenticado. Use a tool 'login' para iniciar a autenticação.",
31838
31892
  [ErrorCode2.AUTH_TOKEN_EXPIRED]: "Token expirado ou inválido. Use a tool 'login' para re-autenticar.",
31839
31893
  [ErrorCode2.AUTH_TOKEN_REFRESH_FAILED]: "Falha ao renovar token silenciosamente. Use a tool 'login' para re-autenticar.",
@@ -31877,23 +31931,65 @@ Detalhe: ${error2.message}`;
31877
31931
  return `[${ErrorCode2.UNKNOWN_ERROR}] ${String(error2)}`;
31878
31932
  }
31879
31933
 
31934
+ // src/auth/config-manager.ts
31935
+ import { readFile, writeFile } from "node:fs/promises";
31936
+ import { homedir } from "node:os";
31937
+ import { join } from "node:path";
31938
+ var CONFIG_PATH = join(homedir(), ".office365-mcp-config.json");
31939
+ async function loadConfig() {
31940
+ try {
31941
+ const data = await readFile(CONFIG_PATH, "utf-8");
31942
+ return JSON.parse(data);
31943
+ } catch {
31944
+ return {};
31945
+ }
31946
+ }
31947
+ async function saveConfig(clientId, tenantId) {
31948
+ await writeFile(CONFIG_PATH, JSON.stringify({ clientId, tenantId }, null, 2), { mode: 384 });
31949
+ }
31950
+
31951
+ // src/auth/token-cache-manager.ts
31952
+ import { readFile as readFile2, writeFile as writeFile2, unlink } from "node:fs/promises";
31953
+ import { homedir as homedir2 } from "node:os";
31954
+ import { join as join2 } from "node:path";
31955
+ var TOKEN_CACHE_PATH = join2(homedir2(), ".office365-mcp-tokens.json");
31956
+ async function loadTokenCache() {
31957
+ try {
31958
+ return await readFile2(TOKEN_CACHE_PATH, "utf-8");
31959
+ } catch {
31960
+ return null;
31961
+ }
31962
+ }
31963
+ async function saveTokenCache(data) {
31964
+ await writeFile2(TOKEN_CACHE_PATH, data, { mode: 384 });
31965
+ }
31966
+ async function removeTokenCache() {
31967
+ try {
31968
+ await unlink(TOKEN_CACHE_PATH);
31969
+ } catch {}
31970
+ }
31971
+
31880
31972
  // src/auth/msal-client.ts
31881
- var TOKEN_CACHE_PATH = join(homedir(), ".office365-mcp-tokens.json");
31882
31973
  var ALL_SCOPES = ["User.Read", ...Object.values(SCOPES).flat()];
31883
31974
 
31884
31975
  class MsalClient {
31885
31976
  pca = null;
31977
+ fileConfig = null;
31886
31978
  deviceCodeCallback = null;
31887
31979
  pendingLogin = null;
31888
31980
  setDeviceCodeCallback(callback) {
31889
31981
  this.deviceCodeCallback = callback;
31890
31982
  }
31983
+ resetPca() {
31984
+ this.pca = null;
31985
+ this.fileConfig = null;
31986
+ }
31891
31987
  getConfig() {
31892
- const clientId = process.env.AZURE_CLIENT_ID;
31988
+ const clientId = process.env.AZURE_CLIENT_ID ?? this.fileConfig?.clientId;
31893
31989
  if (!clientId) {
31894
31990
  throw new AuthError2("AZURE_CLIENT_ID não configurado.", ErrorCode2.AUTH_NOT_CONFIGURED);
31895
31991
  }
31896
- const tenantId = process.env.AZURE_TENANT_ID || "common";
31992
+ const tenantId = process.env.AZURE_TENANT_ID ?? this.fileConfig?.tenantId ?? "common";
31897
31993
  return {
31898
31994
  auth: {
31899
31995
  clientId,
@@ -31903,10 +31999,15 @@ class MsalClient {
31903
31999
  }
31904
32000
  async getPca() {
31905
32001
  if (!this.pca) {
32002
+ if (!this.fileConfig) {
32003
+ this.fileConfig = await loadConfig();
32004
+ }
31906
32005
  this.pca = new PublicClientApplication(this.getConfig());
31907
32006
  try {
31908
- const cacheData = await readFile(TOKEN_CACHE_PATH, "utf-8");
31909
- this.pca.getTokenCache().deserialize(cacheData);
32007
+ const cacheData = await loadTokenCache();
32008
+ if (cacheData) {
32009
+ this.pca.getTokenCache().deserialize(cacheData);
32010
+ }
31910
32011
  } catch (error2) {
31911
32012
  console.error("Failed to load token cache:", error2);
31912
32013
  }
@@ -31917,7 +32018,7 @@ class MsalClient {
31917
32018
  if (!this.pca)
31918
32019
  return;
31919
32020
  const cacheData = this.pca.getTokenCache().serialize();
31920
- await writeFile(TOKEN_CACHE_PATH, cacheData, { mode: 384 });
32021
+ await saveTokenCache(cacheData);
31921
32022
  }
31922
32023
  async login() {
31923
32024
  const pca = await this.getPca();
@@ -31990,16 +32091,29 @@ class MsalClient {
31990
32091
  await this.pca.getTokenCache().removeAccount(account);
31991
32092
  }
31992
32093
  }
31993
- try {
31994
- await unlink(TOKEN_CACHE_PATH);
31995
- } catch (error2) {
31996
- console.error("Failed to remove token cache file:", error2);
31997
- }
32094
+ await removeTokenCache();
31998
32095
  this.pca = null;
31999
32096
  }
32000
32097
  }
32001
32098
  var msalClient = new MsalClient;
32002
32099
 
32100
+ // src/types/graph.ts
32101
+ function recipientAddress(recipient) {
32102
+ return recipient?.emailAddress?.address ?? "unknown";
32103
+ }
32104
+ function recipientAddresses(recipients) {
32105
+ return recipients?.map((r) => r.emailAddress?.address).join(", ") ?? "";
32106
+ }
32107
+ function messageSenderName(message) {
32108
+ return message.from?.user?.displayName ?? "Desconhecido";
32109
+ }
32110
+ function toRecipient(email2) {
32111
+ return { emailAddress: { address: email2 } };
32112
+ }
32113
+ function toAttendee(email2, type = "required") {
32114
+ return { emailAddress: { address: email2 }, type };
32115
+ }
32116
+
32003
32117
  // src/utils/graph-client.ts
32004
32118
  async function executeRequest(accessToken, url, options = {}) {
32005
32119
  const baseUrl = url.startsWith("https://") ? url : `https://graph.microsoft.com/v1.0${url}`;
@@ -32042,11 +32156,14 @@ async function graphFetchVoid(accessToken, url, options = {}) {
32042
32156
  await executeRequest(accessToken, url, options);
32043
32157
  }
32044
32158
 
32159
+ // src/utils/auth-helper.ts
32160
+ function createGetToken(auth, scopes) {
32161
+ return () => auth.getAccessToken([...scopes]);
32162
+ }
32163
+
32045
32164
  // src/services/mail.ts
32046
32165
  function createMailService(auth) {
32047
- async function getToken() {
32048
- return auth.getAccessToken([...SCOPES.MAIL]);
32049
- }
32166
+ const getToken = createGetToken(auth, SCOPES.MAIL);
32050
32167
  async function listEmails(params = {}) {
32051
32168
  const token = await getToken();
32052
32169
  const { folder, top = DEFAULT_PAGE_SIZE_SMALL, skip = 0, filter } = params;
@@ -32079,15 +32196,9 @@ function createMailService(auth) {
32079
32196
  async function sendEmail(params) {
32080
32197
  const token = await getToken();
32081
32198
  const { to, subject, body, cc, bcc, contentType = "Text" } = params;
32082
- const toRecipients = to.map((email2) => ({
32083
- emailAddress: { address: email2 }
32084
- }));
32085
- const ccRecipients = cc?.map((email2) => ({
32086
- emailAddress: { address: email2 }
32087
- }));
32088
- const bccRecipients = bcc?.map((email2) => ({
32089
- emailAddress: { address: email2 }
32090
- }));
32199
+ const toRecipients = to.map(toRecipient);
32200
+ const ccRecipients = cc?.map(toRecipient);
32201
+ const bccRecipients = bcc?.map(toRecipient);
32091
32202
  await graphFetchVoid(token, "/me/sendMail", {
32092
32203
  method: "POST",
32093
32204
  body: JSON.stringify({
@@ -32123,9 +32234,7 @@ function createMailService(auth) {
32123
32234
 
32124
32235
  // src/services/calendar.ts
32125
32236
  function createCalendarService(auth) {
32126
- async function getToken() {
32127
- return auth.getAccessToken([...SCOPES.CALENDAR]);
32128
- }
32237
+ const getToken = createGetToken(auth, SCOPES.CALENDAR);
32129
32238
  async function listEvents(params) {
32130
32239
  const token = await getToken();
32131
32240
  const { startDateTime, endDateTime, top = DEFAULT_PAGE_SIZE_LARGE } = params;
@@ -32136,7 +32245,11 @@ function createCalendarService(auth) {
32136
32245
  $select: "id,subject,start,end,location,organizer,attendees,isOnlineMeeting,onlineMeetingUrl,bodyPreview",
32137
32246
  $orderby: "start/dateTime"
32138
32247
  });
32139
- const result = await graphFetch(token, `/me/calendarView?${queryParams}`);
32248
+ const result = await graphFetch(token, `/me/calendarView?${queryParams}`, {
32249
+ headers: {
32250
+ Prefer: PREFER_TIMEZONE_HEADER
32251
+ }
32252
+ });
32140
32253
  return result.value;
32141
32254
  }
32142
32255
  async function createEvent(params) {
@@ -32164,10 +32277,7 @@ function createCalendarService(auth) {
32164
32277
  eventBody.location = { displayName: location };
32165
32278
  }
32166
32279
  if (attendees?.length) {
32167
- eventBody.attendees = attendees.map((email2) => ({
32168
- emailAddress: { address: email2 },
32169
- type: "required"
32170
- }));
32280
+ eventBody.attendees = attendees.map((email2) => toAttendee(email2));
32171
32281
  }
32172
32282
  return graphFetch(token, "/me/events", {
32173
32283
  method: "POST",
@@ -32208,11 +32318,11 @@ function createCalendarService(auth) {
32208
32318
  } = params;
32209
32319
  return graphFetch(token, "/me/findMeetingTimes", {
32210
32320
  method: "POST",
32321
+ headers: {
32322
+ Prefer: PREFER_TIMEZONE_HEADER
32323
+ },
32211
32324
  body: JSON.stringify({
32212
- attendees: attendees.map((email2) => ({
32213
- emailAddress: { address: email2 },
32214
- type: "required"
32215
- })),
32325
+ attendees: attendees.map((email2) => toAttendee(email2)),
32216
32326
  timeConstraint: {
32217
32327
  timeslots: [
32218
32328
  {
@@ -32230,9 +32340,7 @@ function createCalendarService(auth) {
32230
32340
 
32231
32341
  // src/services/onedrive.ts
32232
32342
  function createOneDriveService(auth) {
32233
- async function getToken() {
32234
- return auth.getAccessToken([...SCOPES.DRIVE]);
32235
- }
32343
+ const getToken = createGetToken(auth, SCOPES.DRIVE);
32236
32344
  async function listFiles(params = {}) {
32237
32345
  const token = await getToken();
32238
32346
  const { itemId, path, top = DEFAULT_PAGE_SIZE_LARGE } = params;
@@ -32255,6 +32363,19 @@ function createOneDriveService(auth) {
32255
32363
  const token = await getToken();
32256
32364
  return graphFetch(token, `/me/drive/items/${itemId}/content`);
32257
32365
  }
32366
+ async function readSharedFileContent(params) {
32367
+ const token = await getToken();
32368
+ const { driveId, itemId, path } = params;
32369
+ let endpoint;
32370
+ if (itemId) {
32371
+ endpoint = `/drives/${driveId}/items/${itemId}/content`;
32372
+ } else if (path) {
32373
+ endpoint = `/drives/${driveId}/root:/${path.replace(/^\//, "")}:/content`;
32374
+ } else {
32375
+ throw new Error("É necessário informar itemId ou path do arquivo.");
32376
+ }
32377
+ return graphFetch(token, endpoint);
32378
+ }
32258
32379
  async function uploadFile(params) {
32259
32380
  const token = await getToken();
32260
32381
  const { path, content } = params;
@@ -32282,14 +32403,12 @@ function createOneDriveService(auth) {
32282
32403
  body: JSON.stringify({ type, scope })
32283
32404
  });
32284
32405
  }
32285
- return { listFiles, readFileContent, uploadFile, searchFiles, shareFile };
32406
+ return { listFiles, readFileContent, readSharedFileContent, uploadFile, searchFiles, shareFile };
32286
32407
  }
32287
32408
 
32288
32409
  // src/services/sharepoint.ts
32289
32410
  function createSharePointService(auth) {
32290
- async function getToken() {
32291
- return auth.getAccessToken([...SCOPES.SHAREPOINT]);
32292
- }
32411
+ const getToken = createGetToken(auth, SCOPES.SHAREPOINT);
32293
32412
  async function listSites(query) {
32294
32413
  const token = await getToken();
32295
32414
  const searchQuery = query ?? "*";
@@ -32338,9 +32457,7 @@ function createSharePointService(auth) {
32338
32457
 
32339
32458
  // src/services/teams.ts
32340
32459
  function createTeamsService(auth) {
32341
- async function getToken() {
32342
- return auth.getAccessToken([...SCOPES.TEAMS]);
32343
- }
32460
+ const getToken = createGetToken(auth, SCOPES.TEAMS);
32344
32461
  async function listTeams() {
32345
32462
  const token = await getToken();
32346
32463
  const result = await graphFetch(token, "/me/joinedTeams?$select=id,displayName,description");
@@ -32408,9 +32525,33 @@ function safeTool(handler) {
32408
32525
  }
32409
32526
 
32410
32527
  // src/tools/auth-tools.ts
32411
- function registerAuthTools(server) {
32528
+ function registerAuthTools(server, auth) {
32529
+ server.tool("configure", "Configura as credenciais do Azure (Client ID e Tenant ID) para autenticação com Microsoft 365. Salva em arquivo persistente (~/.office365-mcp-config.json).", {
32530
+ clientId: exports_external.string().describe("Azure App Registration Client ID"),
32531
+ tenantId: exports_external.string().optional().default("common").describe("Azure Tenant ID (padrão: 'common')")
32532
+ }, safeTool(async (params) => {
32533
+ await saveConfig(params.clientId, params.tenantId);
32534
+ auth.resetPca();
32535
+ return {
32536
+ content: [
32537
+ {
32538
+ type: "text",
32539
+ text: [
32540
+ "## Configuração salva com sucesso",
32541
+ "",
32542
+ `**Client ID:** \`${params.clientId}\``,
32543
+ `**Tenant ID:** \`${params.tenantId}\``,
32544
+ "",
32545
+ "As credenciais foram salvas em `~/.office365-mcp-config.json`.",
32546
+ "Agora use a tool **'login'** para autenticar com sua conta Microsoft."
32547
+ ].join(`
32548
+ `)
32549
+ }
32550
+ ]
32551
+ };
32552
+ }));
32412
32553
  server.tool("login", "Inicia autenticação com Microsoft 365 via Device Code Flow. Retorna um código e URL para o usuário autenticar no navegador.", {}, safeTool(async () => {
32413
- const result = await msalClient.login();
32554
+ const result = await auth.login();
32414
32555
  return {
32415
32556
  content: [
32416
32557
  {
@@ -32432,7 +32573,7 @@ function registerAuthTools(server) {
32432
32573
  };
32433
32574
  }));
32434
32575
  server.tool("auth-status", "Verifica o status da autenticação e mostra informações do usuário logado.", {}, safeTool(async () => {
32435
- const info = await msalClient.getAccountInfo();
32576
+ const info = await auth.getAccountInfo();
32436
32577
  if (!info.isLoggedIn) {
32437
32578
  return {
32438
32579
  content: [
@@ -32461,7 +32602,7 @@ function registerAuthTools(server) {
32461
32602
  };
32462
32603
  }));
32463
32604
  server.tool("logout", "Faz logout e remove tokens salvos.", {}, safeTool(async () => {
32464
- await msalClient.logout();
32605
+ await auth.logout();
32465
32606
  return {
32466
32607
  content: [
32467
32608
  {
@@ -32473,15 +32614,17 @@ function registerAuthTools(server) {
32473
32614
  }));
32474
32615
  }
32475
32616
 
32476
- // src/types/graph.ts
32477
- function recipientAddress(recipient) {
32478
- return recipient?.emailAddress?.address ?? "unknown";
32479
- }
32480
- function recipientAddresses(recipients) {
32481
- return recipients?.map((r) => r.emailAddress?.address).join(", ") ?? "";
32617
+ // src/utils/date.ts
32618
+ function formatDateBR(isoDate) {
32619
+ return new Date(isoDate).toLocaleString("pt-BR", { timeZone: DEFAULT_TIMEZONE });
32482
32620
  }
32483
- function messageSenderName(message) {
32484
- return message.from?.user?.displayName ?? "Desconhecido";
32621
+ function formatCalendarDate(dateTime) {
32622
+ const match = dateTime.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/);
32623
+ if (match) {
32624
+ const [, year, month, day, hour, minute] = match;
32625
+ return `${day}/${month}/${year}, ${hour}:${minute}`;
32626
+ }
32627
+ return new Date(dateTime).toLocaleString("pt-BR", { timeZone: DEFAULT_TIMEZONE });
32485
32628
  }
32486
32629
 
32487
32630
  // src/formatters/mail.ts
@@ -32490,7 +32633,7 @@ function formatEmailList(emails) {
32490
32633
  return "Nenhum email encontrado.";
32491
32634
  const formatted = emails.map((e) => {
32492
32635
  const from = recipientAddress(e.from);
32493
- const date4 = new Date(e.receivedDateTime).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" });
32636
+ const date4 = formatDateBR(e.receivedDateTime);
32494
32637
  const read = e.isRead ? "" : " [NÃO LIDO]";
32495
32638
  const attach = e.hasAttachments ? " [ANEXO]" : "";
32496
32639
  return `- **${e.subject}**${read}${attach}
@@ -32509,7 +32652,7 @@ function formatEmailSearchResults(query, emails) {
32509
32652
  return `Nenhum email encontrado para "${query}".`;
32510
32653
  const formatted = emails.map((e) => {
32511
32654
  const from = recipientAddress(e.from);
32512
- const date4 = new Date(e.receivedDateTime).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" });
32655
+ const date4 = formatDateBR(e.receivedDateTime);
32513
32656
  return `- **${e.subject}**
32514
32657
  De: ${from} | ${date4}
32515
32658
  ID: ${e.id}`;
@@ -32524,7 +32667,7 @@ function formatEmailDetail(email2) {
32524
32667
  const from = recipientAddress(email2.from);
32525
32668
  const to = recipientAddresses(email2.toRecipients);
32526
32669
  const cc = recipientAddresses(email2.ccRecipients);
32527
- const date4 = new Date(email2.receivedDateTime).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" });
32670
+ const date4 = formatDateBR(email2.receivedDateTime);
32528
32671
  const bodyContent = email2.body?.content ?? "(sem conteúdo)";
32529
32672
  const text = [
32530
32673
  `## ${email2.subject}`,
@@ -32629,8 +32772,8 @@ function formatEventList(events) {
32629
32772
  if (events.length === 0)
32630
32773
  return "Nenhum evento encontrado no período.";
32631
32774
  const formatted = events.map((e) => {
32632
- const start = new Date(e.start.dateTime).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" });
32633
- const end = new Date(e.end.dateTime).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" });
32775
+ const start = formatCalendarDate(e.start.dateTime);
32776
+ const end = formatCalendarDate(e.end.dateTime);
32634
32777
  const location = e.location?.displayName ? ` @ ${e.location.displayName}` : "";
32635
32778
  const online = e.isOnlineMeeting ? " [Online]" : "";
32636
32779
  const attendees = e.attendees?.length ? `
@@ -32662,8 +32805,8 @@ function formatFreeSlotsResult(result) {
32662
32805
  return "Nenhum horário disponível encontrado para todos os participantes no período.";
32663
32806
  }
32664
32807
  const formatted = suggestions.map((s, i) => {
32665
- const start = new Date(s.meetingTimeSlot.start.dateTime).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" });
32666
- const end = new Date(s.meetingTimeSlot.end.dateTime).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" });
32808
+ const start = formatCalendarDate(s.meetingTimeSlot.start.dateTime);
32809
+ const end = formatCalendarDate(s.meetingTimeSlot.end.dateTime);
32667
32810
  return `${i + 1}. ${start} → ${end} (confiança: ${s.confidence}%)`;
32668
32811
  });
32669
32812
  return `## Horários Sugeridos
@@ -32741,7 +32884,7 @@ function registerCalendarTools(server, calendar) {
32741
32884
  }));
32742
32885
  }
32743
32886
 
32744
- // src/formatters/onedrive.ts
32887
+ // src/utils/format.ts
32745
32888
  function formatSize(bytes) {
32746
32889
  if (bytes < 1024)
32747
32890
  return `${bytes} B`;
@@ -32751,6 +32894,8 @@ function formatSize(bytes) {
32751
32894
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
32752
32895
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
32753
32896
  }
32897
+
32898
+ // src/formatters/onedrive.ts
32754
32899
  function formatDriveItems(items) {
32755
32900
  if (items.length === 0)
32756
32901
  return "Pasta vazia.";
@@ -32758,7 +32903,7 @@ function formatDriveItems(items) {
32758
32903
  const isFolder = !!item.folder;
32759
32904
  const icon = isFolder ? "[pasta]" : "[arquivo]";
32760
32905
  const size = item.size ? ` (${formatSize(item.size)})` : "";
32761
- const modified = new Date(item.lastModifiedDateTime).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" });
32906
+ const modified = formatDateBR(item.lastModifiedDateTime);
32762
32907
  const childCount = item.folder?.childCount != null ? ` — ${item.folder.childCount} itens` : "";
32763
32908
  return `- ${icon} **${item.name}**${size}${childCount}
32764
32909
  Modificado: ${modified}
@@ -32804,6 +32949,9 @@ function formatShareLink(result) {
32804
32949
  }
32805
32950
 
32806
32951
  // src/tools/onedrive-tools.ts
32952
+ function formatFileContent(content) {
32953
+ return typeof content === "string" ? content : JSON.stringify(content, null, 2);
32954
+ }
32807
32955
  function registerOneDriveTools(server, onedrive) {
32808
32956
  server.tool("list-drive-files", "Lista arquivos e pastas do OneDrive. Por padrão lista a raiz.", {
32809
32957
  itemId: exports_external.string().optional().describe("ID do item/pasta para listar conteúdo"),
@@ -32820,12 +32968,17 @@ function registerOneDriveTools(server, onedrive) {
32820
32968
  }, safeTool(async (params) => {
32821
32969
  const content = await onedrive.readFileContent(params.itemId);
32822
32970
  return {
32823
- content: [
32824
- {
32825
- type: "text",
32826
- text: typeof content === "string" ? content : JSON.stringify(content, null, 2)
32827
- }
32828
- ]
32971
+ content: [{ type: "text", text: formatFileContent(content) }]
32972
+ };
32973
+ }));
32974
+ server.tool("read-shared-file-content", "Lê o conteúdo de um arquivo de um drive compartilhado (SharePoint ou OneDrive de outro usuário). Use o driveId e itemId retornados por search-sharepoint ou list-library-items.", {
32975
+ driveId: exports_external.string().describe("ID do drive compartilhado"),
32976
+ itemId: exports_external.string().optional().describe("ID do arquivo no drive compartilhado"),
32977
+ path: exports_external.string().optional().describe("Caminho do arquivo no drive (alternativa ao itemId, ex: 'Documents/pasta/arquivo.docx')")
32978
+ }, safeTool(async (params) => {
32979
+ const content = await onedrive.readSharedFileContent(params);
32980
+ return {
32981
+ content: [{ type: "text", text: formatFileContent(content) }]
32829
32982
  };
32830
32983
  }));
32831
32984
  server.tool("upload-file", "Faz upload de um arquivo para o OneDrive (até 4MB de conteúdo texto).", {
@@ -32902,7 +33055,7 @@ function formatLibraryItems(items) {
32902
33055
  const formatted = items.map((item) => {
32903
33056
  const isFolder = !!item.folder;
32904
33057
  const icon = isFolder ? "[pasta]" : "[arquivo]";
32905
- const size = item.size ? ` (${item.size} bytes)` : "";
33058
+ const size = item.size ? ` (${formatSize(item.size)})` : "";
32906
33059
  return `- ${icon} **${item.name}**${size}
32907
33060
  URL: ${item.webUrl}
32908
33061
  ID: ${item.id}`;
@@ -33007,7 +33160,7 @@ function formatChannelMessages(messages) {
33007
33160
  return "Nenhuma mensagem encontrada.";
33008
33161
  const formatted = messages.map((m) => {
33009
33162
  const from = messageSenderName(m);
33010
- const date4 = new Date(m.createdDateTime).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" });
33163
+ const date4 = formatDateBR(m.createdDateTime);
33011
33164
  const content = m.body?.content?.substring(0, MESSAGE_CONTENT_MAX_LENGTH) ?? "";
33012
33165
  return `- **${from}** (${date4})
33013
33166
  ${content}`;
@@ -33024,7 +33177,7 @@ function formatChatList(chats) {
33024
33177
  const formatted = chats.map((c) => {
33025
33178
  const topic = c.topic ?? "Chat sem título";
33026
33179
  const type = c.chatType ?? "unknown";
33027
- const updated = c.lastUpdatedDateTime ? new Date(c.lastUpdatedDateTime).toLocaleString("pt-BR", { timeZone: "America/Sao_Paulo" }) : "N/A";
33180
+ const updated = c.lastUpdatedDateTime ? formatDateBR(c.lastUpdatedDateTime) : "N/A";
33028
33181
  const members = c.members?.map((m) => m.displayName).join(", ") ?? "";
33029
33182
  return `- **${topic}** (${type})
33030
33183
  Membros: ${members}
@@ -33102,14 +33255,14 @@ function registerTeamsTools(server, teams) {
33102
33255
  // src/index.ts
33103
33256
  var server = new McpServer({
33104
33257
  name: "office365-mcp-server",
33105
- version: "1.0.0"
33258
+ version: package_default.version
33106
33259
  });
33107
33260
  var mailService = createMailService(msalClient);
33108
33261
  var calendarService = createCalendarService(msalClient);
33109
33262
  var oneDriveService = createOneDriveService(msalClient);
33110
33263
  var sharePointService = createSharePointService(msalClient);
33111
33264
  var teamsService = createTeamsService(msalClient);
33112
- registerAuthTools(server);
33265
+ registerAuthTools(server, msalClient);
33113
33266
  registerMailTools(server, mailService);
33114
33267
  registerCalendarTools(server, calendarService);
33115
33268
  registerOneDriveTools(server, oneDriveService);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leonardocrdso/office365-mcp-server",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "MCP server for Microsoft 365 — Outlook, Calendar, OneDrive, SharePoint & Teams via Graph API",
5
5
  "type": "module",
6
6
  "main": "build/index.js",