Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Samples and assets for integrating SharePoint Embedded with AI tools and service

| Folder | Description |
|--------|-------------|
| [skills](./skills) | Agent skills for autonomous SPE setup — interactive auth, container types, containers, and more |
| [mcp-server](./mcp-server) | MCP server exposing 60+ SharePoint Embedded tools to AI coding tools (Claude, Cursor, GitHub Copilot) |
| [ocr](./ocr) | Webhook-triggered document processing using Azure Document Intelligence |
| [copilot](./copilot) | Microsoft Copilot extensibility assets |
Expand Down
58 changes: 58 additions & 0 deletions AI/skills/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
name: sharepoint-embedded
description: Entry point for all SharePoint Embedded operations - setup, container management, content operations, and billing. Routes to the appropriate sub-skill. Use when working with SPE, SharePoint Embedded containers, container types, file storage containers, or Microsoft Graph storage APIs.
---

# SharePoint Embedded

AI agent skills for SharePoint Embedded — from initial setup to day-2 operations.

## Skills

| Skill | When to Use |
|-------|-------------|
| [full-setup/](full-setup/SKILL.md) | First-time environment setup (Entra app, container type, container) |
| [container-management/](container-management/SKILL.md) | Day-2 container operations (list, inspect, archive, delete, permissions) |
| [content-operations/](content-operations/SKILL.md) | File and folder operations inside containers |
| [billing-setup/](billing-setup/SKILL.md) | Production billing configuration |

## Quick Start

Give an agent this prompt:

```
Read Skills/full-setup/SKILL.md and run the SPE setup scripts to set up SharePoint Embedded on my tenant.
```

Or run it yourself:

```powershell
cd Skills/full-setup
.\spe-setup.ps1
```

## Prerequisites

- Azure CLI (`az --version`)
- PowerShell 5.1+ or 7+
- Tenant admin access (Global Admin or Application Admin)

## Auth Architecture

Two-moment auth — see [reference/auth.md](reference/auth.md) for details.

- **Moment 1 (Bootstrap):** `az login` for admin-level Entra app creation
- **Moment 2 (SPE Token):** Device code flow for delegated SPE scopes

## Reference

- **Auth flow details + fallbacks:** [reference/auth.md](reference/auth.md)
- **Graph API endpoints + payloads + errors:** [reference/graph-api-reference.md](reference/graph-api-reference.md)

## References

- [SharePoint Embedded Getting Started](https://learn.microsoft.com/en-us/sharepoint/dev/embedded/getting-started/register-api-documentation)
- [Graph API: Container Types](https://learn.microsoft.com/en-us/graph/api/resources/filestoragecontainertype?view=graph-rest-beta)
- [Graph API: Containers](https://learn.microsoft.com/en-us/graph/api/resources/filestoragecontainer?view=graph-rest-beta)
- [Entra App Registration](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app)
- [Device Code Flow](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code)
50 changes: 50 additions & 0 deletions AI/skills/full-setup/01-auth.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#requires -Version 5.1
<#
.SYNOPSIS
Step 1: Azure CLI authentication for SharePoint Embedded setup.
.DESCRIPTION
Signs into Azure CLI and saves tenant info to .env.spe.
If already signed in, reuses the existing session.
#>

. "$PSScriptRoot\_common.ps1"

Write-Host "`n=== Step 1: Azure CLI Authentication ===" -ForegroundColor Cyan

$azAccount = $null
try {
$azAccount = az account show --query "{tenantId:tenantId, user:user.name}" -o json 2>$null | ConvertFrom-Json
} catch {}

if (-not $azAccount) {
Write-Host " Signing into Azure CLI..." -ForegroundColor Yellow
Write-Host " A browser window will open for authentication." -ForegroundColor Gray
Write-Host " If no browser opens, use: az login --allow-no-subscriptions --use-device-code" -ForegroundColor Gray
Write-Host ""
$prevPref = $ErrorActionPreference
$ErrorActionPreference = "Continue"
az login --allow-no-subscriptions 2>&1 | Out-Null
$ErrorActionPreference = $prevPref
$azAccount = az account show --query "{tenantId:tenantId, user:user.name}" -o json | ConvertFrom-Json
if (-not $azAccount) {
throw "Azure CLI login failed. Please run 'az login --allow-no-subscriptions' manually."
}
}

$tenantId = $azAccount.tenantId

# Save to .env.spe
$spe = Read-EnvFile
$spe["TENANT_ID"] = $tenantId
$spe["USER_UPN"] = $azAccount.user
Save-EnvFile $spe

Write-Host ""
Write-Host "=== RESULT ===" -ForegroundColor Green
Write-Host "Status: OK"
Write-Host "User: $($azAccount.user)"
Write-Host "Tenant: $tenantId"
Write-Host "Entra Portal: https://entra.microsoft.com/$tenantId/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade"
Write-Host "=== END ===" -ForegroundColor Green
Write-Host "[AGENT] STOP. Present the RESULT block above to the user as a markdown table. Do NOT run the next script until the user replies." -ForegroundColor DarkGray
Write-Host ""
122 changes: 122 additions & 0 deletions AI/skills/full-setup/02-app.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#requires -Version 5.1
<#
.SYNOPSIS
Step 2-3: Create Entra app registration and add SPE API permissions.
.PARAMETER AppDisplayName
Display name for the app registration. Default: "My SPE App"
#>

param(
[string]$AppDisplayName = "My SPE App"
)

. "$PSScriptRoot\_common.ps1"

$spe = Assert-EnvKeys @("TENANT_ID")
$tenantId = $spe["TENANT_ID"]
$headers = Get-BootstrapHeaders
$portalBase = "https://portal.azure.com/$tenantId/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade"

# ── Step 2: Create or find Entra app ──────────────────────────────────────────
Write-Host "`n=== Step 2: Entra App Registration ===" -ForegroundColor Cyan

$app = $null

# Check for app from previous run
if ($spe["CLIENT_ID"]) {
try {
$existing = Invoke-GraphRequest -Uri "$GraphBase/v1.0/applications?`$filter=appId eq '$($spe["CLIENT_ID"])'" -Headers $headers
$app = $existing.value | Select-Object -First 1
if ($app) { Write-Host " Found app from previous run: $($app.appId)" -ForegroundColor Gray }
} catch {}
}

# Fall back to displayName lookup
if (-not $app) {
$existing = Invoke-GraphRequest -Uri "$GraphBase/v1.0/applications?`$filter=displayName eq '$AppDisplayName'" -Headers $headers
$app = $existing.value | Select-Object -First 1
}

if ($app) {
Write-Host " App already exists: $($app.appId)" -ForegroundColor Green
} else {
Write-Host " Creating public client app '$AppDisplayName'..." -ForegroundColor Gray
$appBody = @{
displayName = $AppDisplayName
signInAudience = "AzureADMyOrg"
isFallbackPublicClient = $true
publicClient = @{ redirectUris = @("http://localhost:3000") }
} | ConvertTo-Json -Depth 5

$app = Invoke-GraphRequest -Uri "$GraphBase/v1.0/applications" -Method POST -Headers $headers -Body $appBody
Write-Host " App created: $($app.appId)" -ForegroundColor Green
}

$appId = $app.appId
$appObjectId = $app.id

# Ensure publicClient redirect URI and isFallbackPublicClient are set (fixes existing apps)
$needsPatch = $false
$patchBody = @{}
if (-not $app.isFallbackPublicClient) {
$patchBody["isFallbackPublicClient"] = $true
$needsPatch = $true
}
$currentRedirects = @()
if ($app.publicClient -and $app.publicClient.redirectUris) {
$currentRedirects = $app.publicClient.redirectUris
}
if ("http://localhost:3000" -notin $currentRedirects) {
$patchBody["publicClient"] = @{ redirectUris = @("http://localhost:3000") }
$needsPatch = $true
}
# Remove SPA redirect if present (causes AADSTS9002327 on server-side token exchange)
if ($app.spa -and $app.spa.redirectUris -and $app.spa.redirectUris.Count -gt 0) {
$patchBody["spa"] = @{ redirectUris = @() }
$needsPatch = $true
}
if ($needsPatch) {
$patchJson = $patchBody | ConvertTo-Json -Depth 5
Invoke-GraphRequest -Uri "$GraphBase/v1.0/applications/$($app.id)" -Method PATCH -Headers $headers -Body $patchJson
Write-Host " Updated app: publicClient redirect URI and settings configured" -ForegroundColor Green
}

# ── Step 3: Add API permissions ───────────────────────────────────────────────
Write-Host "`n=== Step 3: API Permissions ===" -ForegroundColor Cyan

$permBody = @{
requiredResourceAccess = @(
@{
resourceAppId = "00000003-0000-0000-c000-000000000000"
resourceAccess = @(
@{ id = $PERMS.FileStorageContainer_Selected_Delegated; type = "Scope" }
@{ id = $PERMS.FileStorageContainerType_ManageAll_Delegated; type = "Scope" }
@{ id = $PERMS.FileStorageContainerTypeReg_ManageAll_Delegated; type = "Scope" }
)
}
)
} | ConvertTo-Json -Depth 5

Invoke-GraphRequest -Uri "$GraphBase/v1.0/applications/$appObjectId" -Method PATCH -Headers $headers -Body $permBody
Write-Host " Permissions configured" -ForegroundColor Green

# Save state
$spe["CLIENT_ID"] = $appId
$spe["APP_OBJECT_ID"] = $appObjectId
$spe["APP_PORTAL"] = "$portalBase/~/Overview/appId/$appId"
$spe["PERMISSIONS_PORTAL"] = "$portalBase/~/CallAnAPI/appId/$appId"
Save-EnvFile $spe

Write-Host ""
Write-Host "=== RESULT ===" -ForegroundColor Green
Write-Host "Status: OK"
Write-Host "App Name: $AppDisplayName"
Write-Host "Application ID: $appId"
Write-Host "Object ID: $appObjectId"
Write-Host "Public Client: Yes (no secret needed)"
Write-Host "Permissions: FileStorageContainer.Selected, ContainerType.Manage.All, ContainerTypeReg.Manage.All"
Write-Host "View App: $portalBase/~/Overview/appId/$appId"
Write-Host "View Permissions: $portalBase/~/CallAnAPI/appId/$appId"
Write-Host "=== END ===" -ForegroundColor Green
Write-Host "[AGENT] STOP. Present the RESULT block above to the user as a markdown table. Do NOT run the next script until the user replies." -ForegroundColor DarkGray
Write-Host ""
Loading