#
tokens: 3861/50000 8/8 files
lines: off (toggle) GitHub
raw markdown copy
# 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);
});
```