Skip to main content
Glama

SharePoint Online MCP Server

by Zerg00s
getQuickNavigationLinks.ts11.1 kB
// src/tools/getQuickNavigationLinks.ts import request from 'request-promise'; import { IToolResult } from '../interfaces'; import { getSharePointHeaders, getRequestDigest } from '../auth_factory'; import { SharePointConfig } from '../config'; export interface GetQuickNavigationLinksParams { url: string; } /** * Get quick navigation links (left navigation) from a SharePoint site using MenuState API * @param params Parameters including site URL * @param config SharePoint configuration * @returns Tool result with quick navigation links data */ export async function getQuickNavigationLinks( params: GetQuickNavigationLinksParams, config: SharePointConfig ): Promise<IToolResult> { const { url } = params; console.error(`getQuickNavigationLinks tool called with URL: ${url}`); try { // Log configuration credentials (masked for security) console.error("SharePoint Configuration Check:"); console.error(`Client ID: ${config.clientId ? '✓ Present' : '✗ Missing'}`); console.error(`Tenant ID: ${config.tenantId ? '✓ Present' : '✗ Missing'}`); console.error(`Auth Type: ${config.authType}`); // Authenticate with SharePoint console.error("Attempting to authenticate with SharePoint..."); const headers = await getSharePointHeaders(url, config); console.error("Headers prepared with authentication: ", JSON.stringify({ ...headers, // Mask any auth tokens for security Authorization: headers.Authorization ? "Bearer [MASKED]" : undefined, "X-RequestDigest": headers["X-RequestDigest"] ? "[MASKED]" : undefined })); // Try an alternative approach first - get web navigation nodes directly console.error("Trying direct navigation nodes approach..."); try { const quickLaunchUrl = `${url}/_api/web/Navigation/QuickLaunch`; console.error(`Requesting navigation from: ${quickLaunchUrl}`); const navResponse = await request({ url: quickLaunchUrl, headers: headers, json: true, method: 'GET', timeout: 30000, resolveWithFullResponse: true, simple: false // Don't throw on non-2xx responses }); // Check response status console.error(`Response status code: ${navResponse.statusCode}`); if (navResponse.statusCode >= 400) { // If this approach fails, fall back to the original MenuState approach console.error("Direct approach failed, falling back to MenuState API..."); const menuStateUrl = `${url}/_api/navigation/MenuState?mapProviderName='QuickLaunch'`; console.error(`Requesting menu state from: ${menuStateUrl}`); const menuStateResponse = await request({ url: menuStateUrl, headers: headers, json: true, method: 'GET', timeout: 30000, resolveWithFullResponse: true, simple: false }); if (menuStateResponse.statusCode >= 400) { throw new Error(`HTTP Error ${menuStateResponse.statusCode}: ${JSON.stringify(menuStateResponse.body)}`); } // Process the menu state to extract navigation nodes (original approach) const menuState = menuStateResponse.body.MenuState; if (!menuState) { throw new Error("MenuState not found in response: " + JSON.stringify(menuStateResponse.body)); } const navNodes = menuState.Nodes || []; console.error(`Retrieved ${navNodes.length} quick navigation links using MenuState API`); // Format the navigation nodes for display const formattedNodes = navNodes .filter((node: any) => !node.IsDeleted) // Filter out deleted nodes .map((node: any) => ({ Key: node.Key, Title: node.Title, Url: node.SimpleUrl, IsExternal: node.FriendlyUrlSegment === '' && menuState.SPSitePrefix && !node.SimpleUrl.startsWith(menuState.SPSitePrefix), HasChildren: (node.Nodes && node.Nodes.length > 0) || false, ParentKey: node.ParentKey || null, Children: (node.Nodes && node.Nodes.length > 0) ? node.Nodes .filter((childNode: any) => !childNode.IsDeleted) .map((childNode: any) => ({ Key: childNode.Key, Title: childNode.Title, Url: childNode.SimpleUrl, IsExternal: childNode.FriendlyUrlSegment === '' && menuState.SPSitePrefix && !childNode.SimpleUrl.startsWith(menuState.SPSitePrefix), ParentKey: node.Key })) : [] })); return { content: [{ type: "text", text: JSON.stringify({ siteUrl: url, navLinksCount: formattedNodes.length, menuState: { version: menuState.Version, startingNodeKey: menuState.StartingNodeKey, startingNodeTitle: menuState.StartingNodeTitle, sitePrefix: menuState.SPSitePrefix, webPrefix: menuState.SPWebPrefix }, navLinks: formattedNodes }, null, 2) }] } as IToolResult; } // Process the direct navigation nodes response const navNodes = navResponse.body.d?.results || []; console.error(`Retrieved ${navNodes.length} quick navigation links using direct API`); // Format the navigation nodes for display const formattedNodes = navNodes.map((node: any) => ({ Key: node.Id?.toString() || "", Title: node.Title || "", Url: node.Url || "", IsExternal: node.IsExternal || false, HasChildren: false, // Direct API doesn't provide children info ParentKey: null, Children: [] // Direct API doesn't provide children })); return { content: [{ type: "text", text: JSON.stringify({ siteUrl: url, navLinksCount: formattedNodes.length, accessMethod: "direct", navLinks: formattedNodes }, null, 2) }] } as IToolResult; } catch (requestError: any) { // Handle request specific errors console.error("Error during request:", requestError); // Try to get more information from the error let detailedError = ""; if (requestError.response) { detailedError = `Status: ${requestError.response.statusCode}, ` + `Headers: ${JSON.stringify(requestError.response.headers)}, ` + `Body: ${JSON.stringify(requestError.response.body)}`; } else if (requestError.error) { detailedError = `${JSON.stringify(requestError.error)}`; } // Try a third approach - see if we can get left navigation using Web ClientObject Model try { console.error("Trying ClientObject Model approach..."); const clientObjectUrl = `${url}/_api/web/Navigation`; console.error(`Requesting navigation from: ${clientObjectUrl}`); const coResponse = await request({ url: clientObjectUrl, headers: headers, json: true, method: 'GET', timeout: 30000 }); // If we have a successful response, try to extract any usable navigation info if (coResponse && coResponse.d) { const navData = { siteUrl: url, navLinksCount: 0, accessMethod: "clientobject", warning: "Limited navigation data available. Using root navigation properties.", navigationInfo: coResponse.d }; return { content: [{ type: "text", text: JSON.stringify(navData, null, 2) }] } as IToolResult; } } catch (finalError) { console.error("All navigation retrieval approaches failed:", finalError); } throw new Error(`API request failed: ${requestError.message || requestError}. Details: ${detailedError}`); } } catch (error: unknown) { // Type-safe error handling with more detailed information let errorMessage: string; if (error instanceof Error) { errorMessage = error.message; console.error("Error stack:", error.stack); } else if (typeof error === 'string') { errorMessage = error; } else { errorMessage = "Unknown error occurred"; console.error("Unknown error type:", error); } console.error("Error in getQuickNavigationLinks tool:", errorMessage); return { content: [{ type: "text", text: `Error getting quick navigation links: ${errorMessage}\n\nTry the following to troubleshoot:\n1. Verify SharePoint App permissions\n2. Check if the SharePoint API endpoints are accessible\n3. Ensure the app credentials are correct and not expired\n4. Try using getSite first to test basic connectivity` }], isError: true } as IToolResult; } } export default getQuickNavigationLinks;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Zerg00s/server-sharepoint'

If you have feedback or need assistance with the MCP directory API, please join our Discord server