# Directory Structure ``` ├── .env.example ├── .gitignore ├── Dockerfile ├── eslint.config.ts ├── package.json ├── pnpm-lock.yaml ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` TENANT_ID=your-tenant-id CLIENT_ID=your-client-id CLIENT_SECRET=your-client-secret SITE_ID=your-site-id DRIVE_ID=your-drive-id ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # ts gitignore # Ignore build output dist/ build/ node_modules/ # Ignore local config config.local.ts # Ignore IDE files .vscode/ .idea/ # Ignore test output test-output/ # Ignore logs logs/ # Ignore coverage coverage/ # Ignore package-lock.json package-lock.json # Ignore .env .env # folders /output ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Sharepoint - WIP, just for R&D ATM A Model Context Protocol server that provides access to Organisational Sharepoint. ## Implementation | Component | Operation | Resource | Dynamic Resource | Tool | |--------------------|---------------------|----------|------------------|------| | Users | | ❌ | ❌ | ❌ | | | Read User | ❌ | ❌ | ❌ | | | Find User | ❌ | ❌ | ❌ | | Sites | | ❌ | ❌ | ❌ | | | List Sites | ✅ | ❌ | ❌ | | | Get Site Details | ❌ | ❌ | ❌ | | | Create Subsite | ❌ | ❌ | ❌ | | | Delete Site | ❌ | ❌ | ❌ | | Drives | | ❌ | ❌ | ❌ | | | List Folders | ❌ | ❌ | ❌ | | | Search Folders | ❌ | ❌ | ✅ | | | Create Folder | ❌ | ❌ | ❌ | | | Delete Folder | ❌ | ❌ | ❌ | | | Upload File | ❌ | ❌ | ❌ | | | List Items | ❌ | ✅ | ❌ | | | Download File | ❌ | ❌ | ✅ | | | Read File | ✅ | ❌ | ❌ | | | Move File | ❌ | ❌ | ❌ | | | Copy File | ❌ | ❌ | ❌ | | Lists | | ❌ | ❌ | ❌ | | | Create List | ❌ | ❌ | ❌ | | | Read List | ❌ | ❌ | ❌ | | | Add to List | ❌ | ❌ | ❌ | | | Update List | ❌ | ❌ | ❌ | | | Delete List | ❌ | ❌ | ❌ | | Calendar | | ❌ | ❌ | ❌ | | | Create Event | ❌ | ❌ | ❌ | | | Read Event | ❌ | ❌ | ❌ | | | Update Event | ❌ | ❌ | ❌ | | | Delete Event | ❌ | ❌ | ❌ | ### Prompts - document-summary - find-relevant-documents - explore-folder ## Enviremental Variables - Copy .env.example as .env - Fill the requires fields ## Inspector From root ```Bash npx @modelcontextprotocol/inspector -e TENANT_ID=your_tenant_id -e CLIENT_ID=your_client_id -e CLIENT_SECRET=your_client_secret -e SITE_ID=your_site_id -e DRIVE_ID=your_drive_id -- node dist/index.js ``` ## Usage with Claude Desktop To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`: ### Docker * Docker build and tag `docker build -t mcp/sharepoint .` ```json { "mcpServers": { "sharepoint": { "command": "docker", "args": [ "run", "-i", "--rm", "--init", "-e", "DOCKER_CONTAINER=true", "-e", "TENANT_ID=your-tenant-id", "-e", "CLIENT_ID=your-client-id", "-e", "CLIENT_SECRET=your-client-secret", "-e", "SITE_ID=your-site-id", "-e", "DRIVE_ID=your-drive-id", "mcp/sharepoint" ] } } } ``` ### MCP configuration file ```bash pnpm run build ``` ```json { "mcpServers": { "sharepoint": { "command": "node", "args": ["run", "start"], "env": { "TENANT_ID": "your-tenant-id", "CLIENT_ID": "your-client-id", "CLIENT_SECRET": "your-client-secret", "SITE_ID": "your-site-id", "DRIVE_ID": "your-drive-id", } } } } ``` ## License This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository. ``` -------------------------------------------------------------------------------- /eslint.config.ts: -------------------------------------------------------------------------------- ```typescript import eslint from '@eslint/js'; import tseslint from 'typescript-eslint'; export default tseslint.config( eslint.configs.recommended, tseslint.configs.recommended, tseslint.configs.strictTypeChecked, tseslint.configs.stylistic, { files: ["src/**/*.ts"], ignores: ['node_modules', 'dist', 'build'], } ); ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2024", "module": "NodeNext", "moduleResolution": "NodeNext", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "noImplicitAny": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "sharepoint", "version": "1.0.0", "description": "", "main": "index.js", "type": "module", "bin": { "sharepoint": "./dist/index.js" }, "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc", "start": "node dist/index.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "@azure/identity": "^4.8.0", "@microsoft/microsoft-graph-client": "^3.0.7", "@modelcontextprotocol/sdk": "^1.9.0", "dotenv": "^16.4.7", "microsoft-graph": "^2.8.0", "zod": "^3.24.2" }, "devDependencies": { "@eslint/js": "^9.23.0", "@types/node": "^22.13.10", "eslint": "^9.23.0", "globals": "^16.0.0", "jiti": "^2.4.2", "typescript": "^5.8.2", "typescript-eslint": "^8.28.0", "tsx": "^4.19.3" } } ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile # Use the official Node.js image as the base image FROM node:22-slim AS builder # Install pnpm globally RUN corepack enable && corepack prepare pnpm@latest --activate # Set the working directory inside the container WORKDIR /app # Copy the source code and configuration files COPY src /app/src COPY tsconfig.json /app/tsconfig.json COPY package.json /app/package.json COPY pnpm-lock.yaml /app/pnpm-lock.yaml # Install dependencies using pnpm RUN pnpm install # Compile TypeScript to JavaScript RUN pnpm run build # Use a lightweight Node.js runtime image for the release stage FROM node:22-slim AS release # Install pnpm globally RUN corepack enable && corepack prepare pnpm@latest --activate # Set the working directory inside the container WORKDIR /app # Copy the compiled code and dependencies from the builder stage COPY --from=builder /app /app # Set the environment to production ENV NODE_ENV=production # Run the application ENTRYPOINT ["pnpm", "start"] ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { register } from "microsoft-graph/services/context"; import { getEnvironmentVariable } from "microsoft-graph/services/environmentVariable"; import { TenantId } from "microsoft-graph/models/TenantId"; import { ClientId } from "microsoft-graph/models/ClientId"; import { SiteId } from "microsoft-graph/models/SiteId"; import { ClientSecret } from "microsoft-graph/models/ClientSecret"; import dotenv from "dotenv"; import { createDriveRef } from "microsoft-graph/services/drive"; import { createSiteRef } from "microsoft-graph/services/site"; import { DriveId } from "microsoft-graph/models/DriveId"; import listDriveItems from "microsoft-graph/operations/driveItem/listDriveItems"; import { createDriveItemRef } from "microsoft-graph/services/driveItem"; import { DriveItemId } from "microsoft-graph/models/DriveItemId"; import getDriveItem from "microsoft-graph/operations/driveItem/getDriveItem"; import listSites from "microsoft-graph/operations/site/listSites"; dotenv.config(); // Initialize the MCP server and SharePoint connector async function createSharepointMcpServer() { const tenantId = getEnvironmentVariable("TENANT_ID") as TenantId; const clientId = getEnvironmentVariable("CLIENT_ID") as ClientId; const clientSecret = getEnvironmentVariable("CLIENT_SECRET") as ClientSecret; const driveId = getEnvironmentVariable("DRIVE_ID") as DriveId; const siteId = getEnvironmentVariable("SITE_ID") as SiteId; // Create the server const server = new McpServer({ name: "SharePoint Server", version: "1.0.0" }); const contextRef = register(tenantId, clientId, clientSecret); const siteRef = createSiteRef(contextRef, siteId); const driveRef = createDriveRef(siteRef, driveId); // Resource: Folder contents (root or specific folder) server.resource( "folder", new ResourceTemplate("sharepoint://folder/{folderId?}", { list: undefined }), async (uri) => { const items = await listDriveItems(driveRef); return { contents: [{ uri: uri.href, text: JSON.stringify(items, null, 2) }] }; } ); // Resource: Sites server.resource( "sites", "sharepoint://sites", async (uri) => { const items = await listSites(contextRef); return { contents: [{ uri: uri.href, text: JSON.stringify(items, null, 2) }] }; } ); // Resource: Document content server.resource( "document", new ResourceTemplate("sharepoint://document/{documentId}", { list: undefined }), async (uri, { documentId }) => { const driveItemRef = createDriveItemRef(driveRef, documentId as DriveItemId); const result = await getDriveItem(driveItemRef); return { contents: [{ uri: uri.href, text: result.content ? (typeof result.content === 'string' ? result.content : JSON.stringify(result.content, null, 2)) : "No content available" // Fallback value }] }; } ); // Tool: Search for documents server.tool( "search-documents", { query: z.string().describe("Search query to find documents"), maxResults: z.string().optional().describe("Maximum number of results to return (as a string)") }, async ({ query, maxResults = 10 }) => { try { const driveRef = createDriveRef(siteRef, driveId); const driveItems = await listDriveItems(driveRef); const results = driveItems.filter((item: any) => item.name.includes(query)).slice(0, parseInt(maxResults.toString(), 10)); return { content: [{ type: "text", text: JSON.stringify(results, null, 2) // Ensure this is a valid string }] }; } catch (error) { return { content: [{ type: "text", text: `Error searching documents: ${error}` }], isError: true }; } } ); // Download document content server.tool( "download-document", { documentId: z.string().describe("The ID of the document to download") }, async ({ documentId }) => { try { const driveItemRef = createDriveItemRef(driveRef, documentId as DriveItemId); const result = await getDriveItem(driveItemRef); return { content: [{ type: "text", text: result.content ? (typeof result.content === 'string' ? result.content : JSON.stringify(result.content, null, 2)) : "No content available" // Fallback value }] }; } catch (error) { return { content: [{ type: "text", text: `Error downloading document: ${error}` }], isError: true }; } } ); // Prompt: Search and summarize a document server.prompt( "document-summary", { documentId: z.string().describe("The ID of the document to summarize") }, ({ documentId }) => ({ messages: [{ role: "user", content: { type: "text", text: `Please retrieve the document with ID ${documentId} using the sharepoint://document/${documentId} resource, then provide a concise summary of its key points, main topics, and important information.` } }] }) ); // Prompt: Find relevant documents server.prompt( "find-relevant-documents", { topic: z.string().describe("The topic or subject to find documents about"), maxResults: z.string().optional().describe("Maximum number of results to return (as a string)") }, ({ topic, maxResults = "5" }) => ({ messages: [{ role: "user", content: { type: "text", text: `Please use the search-documents tool to find up to ${maxResults} documents related to "${topic}". For each document, provide the title, author, last modified date, and a brief description of what it appears to contain based on the metadata.` } }] }) ); // Prompt: Explore folder contents server.prompt( "explore-folder", { folderId: z.string().optional().describe("The ID of the folder to explore (leave empty for root folder)") }, ({ folderId }) => ({ messages: [{ role: "user", content: { type: "text", text: folderId ? `Please explore the contents of the folder with ID ${folderId} using the sharepoint://folder/${folderId} resource. List all documents and subfolders, organizing them by type and providing key details about each item.` : `Please explore the contents of the root folder using the sharepoint://folder resource. List all documents and subfolders, organizing them by type and providing key details about each item.` } }] }) ); return server; } // Example usage async function main() { // Create and start the server const server = await createSharepointMcpServer(); // Connect using stdio transport const transport = new StdioServerTransport(); await server.connect(transport); } // Run the server main().catch(error => { console.error("Error starting server:", error); process.exit(1); }); ```