An external web application (outside the M365 ecosystem) that lets users sign in with their Microsoft 365 credentials and chat with a Copilot Studio agent grounded with SharePoint knowledge.
┌────────────────────────┐ ┌─────────────────────────┐ ┌──────────────────┐
│ Next.js Frontend │────▶│ Python Backend │────▶│ Copilot Studio │
│ (React · Port 3000) │◀────│ (aiohttp · MSAL │◀────│ Agent (SharePoint)│
│ │ │ · Agents SDK · 3978) │ │ │
└────────────────────────┘ └─────────────────────────┘ └──────────────────┘
Auth flow (OAuth2 Authorization Code — Confidential Client):
- Frontend calls
GET /api/auth/login→ backend returns the Entra ID authorize URL. - User authenticates at Entra ID → redirected back to the frontend with
?code=.... - Frontend sends the code to
POST /api/auth/callback. - Backend exchanges the code for tokens using MSAL
ConfidentialClientApplicationand caches them. - Backend returns a
sessionIdto the frontend. - Frontend sends chat messages with the
sessionId. - Backend uses the cached delegated token to call Copilot Studio via the connection string.
| # | Requirement |
|---|---|
| 1 | Python 3.9+ |
| 2 | Node.js 18+ |
| 3 | An Agent created in Copilot Studio and published, with a SharePoint knowledge source |
| 4 | An Entra ID App Registration — Web platform (confidential client with a secret) |
| 5 | Admin consent for the Power Platform API (CopilotStudio.Copilots.Invoke) permission |
- Go to Copilot Studio.
- Create a new Agent and add a SharePoint knowledge source.

- Publish the agent.
- Go to Settings → Security → Authentication and select "Authenticate with Microsoft".
Important: Do not choose "Authenticate manually" — that triggers an in-chat OAuth card flow that conflicts with the API-level bearer token used by this app.

- Get the connection string:
- Go to Settings → Advanced → Metadata (or Channels → Web app → Microsoft 365 Agents SDK).
- Copy the full connection string URL. It looks like:
https://default6eef...bfe2.82.environment.api.powerplatform.com /copilotstudio/dataverse-backed/authenticated/bots/cra99_agent1 /conversations?api-version=2022-03-01-preview - This is your
COPILOT_CONNECTION_STRING.
- Go to Azure Portal → Entra ID → App registrations.
- Click New registration.
- Name:
Custom Copilot POC - Supported account types: Accounts in this organizational directory only (Single tenant)
- Redirect URI: Platform = Web, URI =
http://localhost:3000
- Name:
- After registration, note:
- Application (client) ID →
CLIENT_ID - Directory (tenant) ID →
TENANT_ID
- Application (client) ID →
- Go to Certificates & secrets → New client secret:
- Create a secret and copy the Value →
CLIENT_SECRET
- Create a secret and copy the Value →
- Go to API Permissions → Add a permission:
- Tab: APIs my organization uses
- Search for Power Platform API
- Select Delegated permissions → CopilotStudio → CopilotStudio.Copilots.Invoke
- Click Add permissions
- Click Grant admin consent for your tenant.
Tip: If Power Platform API doesn't appear, follow these instructions to register it in your tenant first.
Copy .env.template → .env and fill in your values:
# ── Server ──
PORT=3978
ALLOWED_ORIGIN=http://localhost:3000
# ── Entra ID App Registration ──
TENANT_ID=<Directory (tenant) ID>
CLIENT_ID=<Application (client) ID>
CLIENT_SECRET=<Client secret value>
REDIRECT_URI=http://localhost:3000
# ── Copilot Studio Connection String ──
COPILOT_CONNECTION_STRING=<Full connection string URL from Step 1>Copy my-custom-studio-poc/.env.local.template → my-custom-studio-poc/.env.local:
NEXT_PUBLIC_API_URL=http://localhost:3978The frontend only needs the backend URL. All authentication is handled server-side.
cd custom-studio-copilot-retrieval-api
python -m venv venv # one-time setup
venv\Scripts\activate # Windows (source venv/bin/activate on Mac/Linux)
pip install -r requirements.txt
python app.pyYou should see:
INFO:__main__:Copilot Studio agent: env=default6eef... schema=cra99_agent1
INFO:__main__:Starting Copilot Studio proxy on http://localhost:3978
======== Running on http://localhost:3978 ========
cd my-custom-studio-poc
npm install
npm run devOpen http://localhost:3000 and click Sign in with Microsoft.
| Method | Path | Description |
|---|---|---|
GET |
/api/auth/login |
Returns the Entra ID authorization URL |
POST |
/api/auth/callback |
Exchanges an authorization code for tokens; returns { sessionId, displayName, email } |
GET |
/api/auth/me?sessionId=... |
Returns user info for an existing session |
POST |
/api/auth/logout |
Clears the server-side session |
POST |
/api/chat |
Sends { message, sessionId } to the Copilot Studio agent; returns { responses, suggestedActions, sessionId, conversationId } |
POST |
/api/reset |
Clears the conversation so the next message starts fresh |
GET |
/api/health |
Liveness check |
| Component | Role |
|---|---|
app.py |
aiohttp web server. Handles the OAuth2 auth-code flow via MSAL ConfidentialClientApplication, caches user tokens by session, and proxies chat messages to the Copilot Studio agent using the microsoft-agents SDK CopilotClient. |
my-custom-studio-poc/ |
Next.js app that provides the login UI and chat interface. Redirects to Entra ID for sign-in, sends the authorization code to the backend, and uses the returned sessionId for subsequent chat requests. |
start_server.py |
(Optional) Alternative server starter using the full Agents SDK hosting stack with JWT middleware — useful if you later want to integrate with Azure Bot Service. |
custom-studio-copilot-retrieval-api/
├── app.py ← Python backend (auth + chat proxy)
├── start_server.py ← Alternative Agents SDK server (optional)
├── requirements.txt ← Python dependencies
├── .env.template ← Backend env vars template
└── my-custom-studio-poc/ ← Next.js frontend
├── .env.local.template ← Frontend env vars template
├── app/
│ ├── layout.tsx ← Root layout
│ ├── page.tsx ← Login screen + auth code exchange
│ └── globals.css ← Styling
├── components/
│ └── chat.tsx ← Chat UI component
├── lib/
│ └── copilot-api.ts ← API client for the Python backend
├── package.json
├── tsconfig.json
└── next.config.ts
| Symptom | Cause | Fix |
|---|---|---|
| Bot replies "I'll need you to sign in" | Copilot Studio authentication is set to "Authenticate manually" | Change to "Authenticate with Microsoft" in Copilot Studio → Settings → Authentication |
ValueError: Chunk too big |
Copilot Studio responses exceed aiohttp's default 64 KB line buffer | Already handled — app.py passes read_bufsize=1MB to the SDK's ClientSession |
AADSTS7000218: request body must contain … client_secret |
App registration is configured as a public client (SPA) | Change the redirect URI platform to Web in Entra ID → App registrations → Authentication |

