365center-mcp 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +14 -14
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ server.tool("create_site", "Create a new SharePoint site. Template: 'communicati
41
41
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
42
42
  });
43
43
  // ============ DOCUMENTS ============
44
- server.tool("list_document_libraries", "List all document libraries (drives) in a SharePoint site. Returns driveId for each library use driveId in list_documents, upload_document, delete_document, and other document tools. Also returns listId for metadata operations.", { siteId: z.string().describe("SharePoint site ID") }, async ({ siteId }) => {
44
+ server.tool("list_document_libraries", "List all document libraries (drives) in a SharePoint site. Returns driveId and listId for each library. Use driveId for file operations (upload, download, delete, list, versions). Use listId for metadata operations (list_columns, get/set_document_metadata, create columns). Call this first when working with documents — you need both IDs.", { siteId: z.string().describe("SharePoint site ID") }, async ({ siteId }) => {
45
45
  const libraries = await listDocumentLibraries(siteId);
46
46
  return { content: [{ type: "text", text: JSON.stringify(libraries, null, 2) }] };
47
47
  });
@@ -53,7 +53,7 @@ server.tool("list_documents", "List documents in a document library folder. Retu
53
53
  const docs = await listDocuments(siteId, driveId, folderId || "root");
54
54
  return { content: [{ type: "text", text: JSON.stringify(docs, null, 2) }] };
55
55
  });
56
- server.tool("upload_document", "Upload a local file to a SharePoint document library. File is uploaded to the root folder by default, or to a specific folder if folderId is provided. After upload, use set_document_metadata to set metadata fields on the document. Supports files up to 250 GB (automatic session upload for files over 4 MB).", {
56
+ server.tool("upload_document", "Upload a single local file to a SharePoint document library. Root folder by default, or specific folder via folderId. After upload, use set_document_metadata to tag it. Supports up to 250 GB (auto session upload for >4 MB). For multiple files, prefer upload_documents — up to 30 files per call with inline metadata and built-in rate limit protection. NOTE: If using Highlighted Content web parts, filename prefix determines filtering. The 'Title includes the words' filter is SUBSTRING-based — e.g. 'Report' will also match 'ReportQ1' or 'ReportAnnual'. Use non-overlapping prefixes.", {
57
57
  siteId: z.string().describe("SharePoint site ID"),
58
58
  driveId: z.string().describe("Document library (drive) ID"),
59
59
  fileName: z.string().describe("Name for the file in SharePoint"),
@@ -72,7 +72,7 @@ server.tool("download_document", "Download a document from a SharePoint document
72
72
  const result = await downloadDocument(siteId, driveId, itemId, localPath);
73
73
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
74
74
  });
75
- server.tool("upload_documents", "Upload one or more files to SharePoint, optionally setting metadata on each. Max 30 files per call. Files over 4 MB use resumable upload automatically. 500ms delay between files for API rate limits. Returns per-file status for upload and metadata independently.", {
75
+ server.tool("upload_documents", "Batch upload up to 30 files to SharePoint with optional inline metadata per file. Files >4 MB use resumable upload. Built-in 500ms delay between files prevents HTTP 429 from Microsoft. Returns per-file status for upload and metadata independently. When setting metadata, listId is required. Choice values must match predefined choices exactly (case-sensitive). For large batches, split into multiple calls of max 30 files each. Same substring warning as upload_document — if using Highlighted Content web parts, filename prefixes determine which section shows which documents.", {
76
76
  siteId: z.string().describe("SharePoint site ID"),
77
77
  driveId: z.string().describe("Document library (drive) ID"),
78
78
  listId: z.string().optional().describe("List ID — required only if setting metadata via fields"),
@@ -89,7 +89,7 @@ server.tool("upload_documents", "Upload one or more files to SharePoint, optiona
89
89
  const results = await uploadDocuments(siteId, driveId, listId, files, folderId || "root");
90
90
  return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
91
91
  });
92
- server.tool("search_documents", "Search for documents in a SharePoint site", {
92
+ server.tool("search_documents", "Search for documents across all libraries in a SharePoint site by keyword. Search indexes may take a few minutes to reflect newly uploaded documents.", {
93
93
  siteId: z.string().describe("SharePoint site ID"),
94
94
  query: z.string().describe("Search query"),
95
95
  }, async ({ siteId, query }) => {
@@ -122,7 +122,7 @@ server.tool("get_document_versions", "Get version history of a document (audit t
122
122
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
123
123
  });
124
124
  // ============ METADATA ============
125
- server.tool("list_columns", "List all custom metadata columns in a SharePoint list/library. The listId can be the list display name (e.g. 'Dokumenty') or the list GUID. Returns column name, type, and choices for choice columns. Use this to discover available metadata fields before calling set_document_metadata.", {
125
+ server.tool("list_columns", "List custom metadata columns in a SharePoint list/library. listId can be display name (e.g. 'Documents') or GUID. Returns internal name, display name, type, and choices. Call this before set_document_metadata internal column names (used in API) often differ from display names (shown in UI). Using wrong name fails silently.", {
126
126
  siteId: z.string().describe("SharePoint site ID"),
127
127
  listId: z.string().describe("List or document library list ID"),
128
128
  }, async ({ siteId, listId }) => {
@@ -149,7 +149,7 @@ server.tool("create_text_column", "Create a single-line text metadata column in
149
149
  const result = await createTextColumn(siteId, listId, name, displayName);
150
150
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
151
151
  });
152
- server.tool("set_document_metadata", "Set metadata fields on a document. The 'fields' parameter is a JSON string of key-value pairs where keys are column internal names. For choice columns, value must match one of the predefined choices exactly. For multi-select choice columns, value is an array of strings. IMPORTANT: columns must exist before setting values use list_columns to check, or create_choice_column/create_text_column to create them. Accepts both drive item ID and numeric list item ID — if using drive item ID, provide driveId.", {
152
+ server.tool("set_document_metadata", "Set metadata fields on a document. 'fields' is a JSON string of key-value pairs using column internal names. Choice values must match predefined choices exactly (case-sensitive). Multi-select: use array of strings. Columns must exist first — check with list_columns. Accepts drive item ID (requires driveId) or numeric list item ID. Only change fields the user asked for leave others untouched. TIP: Changing a Status field (e.g. to 'Pending Approval') can trigger Power Automate flows with 'When item modified' trigger — no HTTP webhooks or extra tools needed.", {
153
153
  siteId: z.string().describe("SharePoint site ID"),
154
154
  listId: z.string().describe("List or document library list ID"),
155
155
  itemId: z.string().describe("Document ID — either numeric list item ID or drive item ID"),
@@ -170,11 +170,11 @@ server.tool("get_document_metadata", "Get all metadata fields of a document incl
170
170
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
171
171
  });
172
172
  // ============ PAGES ============
173
- server.tool("list_pages", "List all pages in a SharePoint site. Returns page ID, name, title, URL, and publishing state (checkout/published). Use the page ID for publish_page, delete_page, and add_quick_links.", { siteId: z.string().describe("SharePoint site ID") }, async ({ siteId }) => {
173
+ server.tool("list_pages", "List all pages via Graph API. Returns page GUID, name, title, URL, and state. Use page GUID for publish_page, delete_page, add_quick_links. NOTE: Canvas tools (get/set_page_canvas_content) need NUMERIC item IDs from list_site_pages instead — GUIDs from this tool won't work there.", { siteId: z.string().describe("SharePoint site ID") }, async ({ siteId }) => {
174
174
  const pages = await listPages(siteId);
175
175
  return { content: [{ type: "text", text: JSON.stringify(pages, null, 2) }] };
176
176
  });
177
- server.tool("create_page", "Create a new empty SharePoint page. Page is created in 'checkout' state (draft) use publish_page to make it visible to users. The 'name' becomes the URL slug (e.g. 'my-page' → my-page.aspx). Use create_page_with_content if you need to add text/sections at creation time.", {
177
+ server.tool("create_page", "Create a new empty page in checkout/draft state. MUST call publish_page after to make it visible. 'name' becomes URL slug (e.g. 'my-page' → my-page.aspx). For pages with text sections, use create_page_with_content. For pages with web parts (Highlighted Content etc.), create empty page first, then set_page_canvas_content, then publish_page.", {
178
178
  siteId: z.string().describe("SharePoint site ID"),
179
179
  title: z.string().describe("Page title"),
180
180
  name: z.string().describe("Page file name (without .aspx)"),
@@ -182,7 +182,7 @@ server.tool("create_page", "Create a new empty SharePoint page. Page is created
182
182
  const result = await createPage(siteId, title, name);
183
183
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
184
184
  });
185
- server.tool("create_page_with_content", "Create a SharePoint page with sections containing HTML content. Page is created in 'checkout' state — use publish_page to make it visible. The 'sections' parameter is a JSON string array. Available layouts: oneColumn (width:12), twoColumns (width:6+6), threeColumns (width:4+4+4), oneThirdLeftColumn (width:4+8), oneThirdRightColumn (width:8+4), fullWidth (width:12). Each column contains HTML text. Example: [{\"layout\":\"twoColumns\",\"columns\":[{\"width\":6,\"html\":\"<h2>Left</h2>\"},{\"width\":6,\"html\":\"<h2>Right</h2>\"}]}]", {
185
+ server.tool("create_page_with_content", "Create a page with HTML text sections. Draft state — MUST call publish_page after. Sections is a JSON string array. Layouts: oneColumn (12), twoColumns (6+6), threeColumns (4+4+4), oneThirdLeftColumn (4+8), oneThirdRightColumn (8+4), fullWidth (12). Example: [{\"layout\":\"twoColumns\",\"columns\":[{\"width\":6,\"html\":\"<h2>Left</h2>\"},{\"width\":6,\"html\":\"<h2>Right</h2>\"}]}]. NOTE: Only supports text/HTML. For web parts like Highlighted Content, use create_page + set_page_canvas_content instead.", {
186
186
  siteId: z.string().describe("SharePoint site ID"),
187
187
  title: z.string().describe("Page title"),
188
188
  name: z.string().describe("Page file name (without .aspx)"),
@@ -203,7 +203,7 @@ server.tool("add_quick_links", "Add a Quick Links web part to a SharePoint page
203
203
  const result = await addQuickLinksWebPart(siteId, pageId, links);
204
204
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
205
205
  });
206
- server.tool("publish_page", "Publish a SharePoint page to make it visible to all site users. Pages are created in 'checkout' (draft) state and must be published to appear on the site. After publishing, the page gets a version number and is accessible via its URL.", {
206
+ server.tool("publish_page", "Publish a page to make it visible. MUST call after: create_page, create_page_with_content, or set_page_canvas_content without publishing, the page appears EMPTY to users. This is the most commonly forgotten step.", {
207
207
  siteId: z.string().describe("SharePoint site ID"),
208
208
  pageId: z.string().describe("Page ID"),
209
209
  }, async ({ siteId, pageId }) => {
@@ -224,7 +224,7 @@ server.tool("get_navigation", "Get the top navigation menu links of a SharePoint
224
224
  const result = await getNavigation(siteUrl);
225
225
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
226
226
  });
227
- server.tool("add_navigation_link", "Add a link to the top navigation menu of a SharePoint site. Uses SharePoint REST API. The siteUrl must be full URL with https://. The url parameter is the link target (can be internal SharePoint URL or external URL).", {
227
+ server.tool("add_navigation_link", "Add a link to top navigation. REST API, delegated auth. siteUrl must include https://. url = link target (internal or external). Check get_navigation first to avoid duplicates.", {
228
228
  siteUrl: z.string().describe("Full SharePoint site URL"),
229
229
  title: z.string().describe("Navigation link title"),
230
230
  url: z.string().describe("Navigation link URL"),
@@ -268,14 +268,14 @@ server.tool("remove_user_from_group", "Remove a user from a SharePoint group. Us
268
268
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
269
269
  });
270
270
  // ============ REST API — CANVAS CONTENT ============
271
- server.tool("get_page_canvas_content", "Read the raw CanvasContent1 of a SharePoint page via REST API. Returns the full HTML/JSON content of the page including all web parts. Use this to inspect how existing pages are built (especially Highlighted Content web parts) so you can replicate them. The pageItemId is the numeric list item ID from Site Pages list use list_site_pages to find it. Uses delegated auth.", {
271
+ server.tool("get_page_canvas_content", "Read raw CanvasContent1 of a page via REST API. Returns full HTML including all web parts. Use to inspect existing page structure before modifying. pageItemId = numeric ID from list_site_pages (NOT GUID from list_pages). Uses delegated auth. ALWAYS read before calling set_page_canvas_content understand what's there before replacing it.", {
272
272
  siteUrl: z.string().describe("Full SharePoint site URL (e.g. https://contoso.sharepoint.com/sites/MySite)"),
273
273
  pageItemId: z.number().describe("Numeric item ID from Site Pages list (use list_site_pages to find it)"),
274
274
  }, async ({ siteUrl, pageItemId }) => {
275
275
  const result = await getPageCanvasContent(siteUrl, pageItemId);
276
276
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
277
277
  });
278
- server.tool("set_page_canvas_content", "Write raw CanvasContent1 to a SharePoint page via REST API. This replaces the ENTIRE page content. Use get_page_canvas_content first to understand the format. WARNING: this overwrites all existing content on the page. Useful for adding Highlighted Content web parts or any web part not supported by Graph API. Uses delegated auth.", {
278
+ server.tool("set_page_canvas_content", "Replace entire page canvas via REST API. Overwrites ALL existing content. Uses delegated auth. pageItemId = numeric ID from list_site_pages. MUST call publish_page after — page reverts to draft and appears EMPTY without it. Canvas HTML rules: use pre-encoded entities directly (&#123; &#125; &#58; &quot;) — never JSON.stringify then replace (double-escaping on file round-trips). Omit titleHTML property (causes escaping corruption) titles render from searchablePlainTexts. Read current canvas with get_page_canvas_content first.", {
279
279
  siteUrl: z.string().describe("Full SharePoint site URL"),
280
280
  pageItemId: z.number().describe("Numeric item ID from Site Pages list"),
281
281
  canvasContent: z.string().describe("Raw HTML/JSON canvas content string — get format from get_page_canvas_content on an existing page"),
@@ -291,7 +291,7 @@ server.tool("copy_page", "Copy an existing SharePoint page to create a new one.
291
291
  const result = await copyPage(siteUrl, sourceFileName, targetFileName);
292
292
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
293
293
  });
294
- server.tool("list_site_pages", "List all pages in a SharePoint site via REST API. Returns numeric item IDs needed for get_page_canvas_content and set_page_canvas_content. Also returns title and file name. Uses delegated auth.", {
294
+ server.tool("list_site_pages", "List pages via REST API. Returns NUMERIC item IDs needed for canvas tools (get/set_page_canvas_content). Also returns title and file name. Uses delegated auth. NOTE: This gives numeric IDs for canvas operations. For page GUIDs (needed by publish_page, delete_page), use list_pages instead.", {
295
295
  siteUrl: z.string().describe("Full SharePoint site URL"),
296
296
  }, async ({ siteUrl }) => {
297
297
  const result = await listSitePages(siteUrl);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "365center-mcp",
3
- "version": "1.2.1",
4
- "description": "MCP server for Microsoft 365 / Office 365 SharePoint — 32 tools for full read-write access to sites, documents, pages, metadata, navigation, and permissions",
3
+ "version": "1.2.2",
4
+ "description": "MCP server for Microsoft 365 / Office 365 SharePoint — full read-write access to sites, documents, pages, metadata, navigation, and permissions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {