Skip to content
ArticlesSharePoint

SharePoint

SharePoint Copilot Apps: What the Community Demo Skips

The pitch says test SharePoint Copilot Apps in minutes, no code, no license. I took that literally on a development tenant and documented the real maker path. No license is true and undersold. But a chain of undocumented walls stands between clone and a running agent, and the final step everyone treats as the human-only one turns out to be redundant: the deployment already publishes the agent.

The community pitch for SharePoint Copilot Apps is clean: download a sample, copy one solution file into your tenant, add the agent, and test the UX inside Microsoft 365 Copilot. No Copilot license. No code. A few minutes. The hashtags underneath tell the real story of the stack: SPFx, MCP, agents.

I took the pitch literally. Fresh development tenant, official sample, no shortcuts. I wanted to know whether a consultant could actually reproduce the demo, or whether "in minutes" was a slide that skipped the setup.

Here is the honest version. One part of the promise is true and actually undersold. The rest hides a chain of walls that nobody mentions on stage, and the last one holds a genuine surprise: a single action in the whole path that no automation can perform, proven against Microsoft's own tooling.

The one promise that holds: no Copilot license

Straight from the Microsoft Learn tutorial prerequisites:

"During the public preview, no Microsoft 365 Copilot license is required to build, deploy, or run SharePoint Copilot Apps."

This is real, and it is the most interesting fact in the whole feature. You can build a custom app that renders inside the Microsoft 365 Copilot canvas, deploy it to your tenant, and run it, without paying for a single Copilot seat during preview. For anyone evaluating Copilot-adjacent development, that removes the usual license wall before the first experiment. The community got this part right and then buried it under "no code."

The two paths, and why they are not the same

"Copy one file" collapses two very different journeys into one sentence.

The demo path. Someone hands you a pre-built .sppkg package. If your tenant already has an App Catalog, you upload it, enable it, and click a button. That genuinely takes minutes. This is the path in the stage demo.

The maker path. You clone the sample and build the .sppkg from source. This is what a consultant actually does when they want to change the app, not just run someone else's. First time through, budget one to two hours. After that, the build itself is 45 seconds and 172 tests pass green.

The gap between those two paths is the whole article. The demo path assumes an App Catalog that may not exist and a package someone else built. The maker path is where the undocumented walls live.

What SharePoint Copilot Apps actually are

The feature ships with SPFx 1.24 (in beta as of July 2026) under a model Microsoft calls "MCP Apps." Components derive from a base class, BaseCopilotComponent. Each app exposes tools with typed input schemas, and the tools render custom UX in two display modes, inline or fullscreen, inside the Microsoft 365 Copilot canvas.

The build produces two artifacts, not one. There is the .sppkg that lands in SharePoint, and a separate declarative agent package that syncs to the tenant agent catalog. That second artifact is what appears in Microsoft 365 Copilot after deployment. Understanding that there are two outputs matters, because the "add the agent" step in the pitch is a real, separate deployment action, not a rename of the upload.

Wall 1: Node 22 LTS or nothing

The SPFx 1.24 toolchain runs on Heft, and Heft wants Node 22 LTS specifically. The sample declares engines: ">=22.14.0 < 23.0.0".

My machine ran Node 24, the current non-LTS line. SPFx supports LTS releases only, so the generator refuses anything above 22. There was no nvm and no side-by-side install, just one Node on the PATH.

The fix is a portable Node 22 prefixed to the shell PATH, leaving the global Node untouched:

Code
node --version
# v24.14.0  -> rejected by SPFx 1.24

# portable Node 22, prefixed to PATH for this shell only
export PATH="/c/Users/you/tools/node22:$PATH"
node --version
# v22.23.1  -> satisfies engines ">=22.14.0 <23.0.0"

Nothing in the community pitch mentions a Node version constraint. It is the first thing that breaks.

Wall 2: the lockfile points at a private Microsoft feed

This is the trap that would cost anyone reproducing the sample, and it is the least obvious.

Clone the sample, run npm install, and watch it fail in about 19 seconds:

Code
npm error Unable to authenticate, your authentication token seems to be invalid.

The confusing part: npm config get registry returns the public registry, and there is no local .npmrc. So where is the auth wall coming from?

The committed package-lock.json. It pins 1288 tarball URLs to an internal Azure DevOps feed, onedrive.pkgs.visualstudio.com, the private feed of the OneDrive/ODSP team at Microsoft. Zero URLs point at the public registry. The lockfile was generated inside the Microsoft network and committed as-is. Anyone outside that network hits the auth wall immediately.

The fix is counterintuitive, because it means distrusting a committed lockfile:

Code
mv package-lock.json package-lock.PRIVATE-FEED.json.bak
rm -rf node_modules
npm install --registry=https://registry.npmjs.org/

npm re-resolves from the public registry and writes a clean lockfile. The package.json itself is healthy, normal version ranges, so the public resolution succeeds with no conflicts. That second install took 2 minutes 49 seconds for 1290 packages, zero peer-dependency errors. The audit reports transitive vulnerabilities, none of which block the build.

Wall 3: the App Catalog has to exist first

The Learn prerequisites list it plainly: "A Microsoft 365 tenant with the SharePoint app catalog provisioned." The community pitch does not.

On a fresh tenant, the App Catalog is not there. The tenant settings return an empty CorporateCatalogUrl, and the catalog site paths return 404. Without it, you cannot deploy the .sppkg or publish the agent. Full stop.

The community framing implies you just drop a file somewhere. In reality you first provision tenant-level infrastructure. The good news, and this contradicts the "manual admin only" assumption, is that it is scriptable. A single PnP PowerShell call against the admin URL provisions it:

Code
# app-only, certificate auth, PnP PowerShell 3.x on pwsh 7
Register-PnPAppCatalogSite -Url "https://your-tenant.sharepoint.com/sites/appcatalog" `
  -Owner "admin@your-tenant.onmicrosoft.com" -TimeZoneId 3

Provisioning runs in about two minutes of commands, then propagates. After that, Get-PnPTenantAppCatalogUrl returns the catalog URL and the site is live.

Wall 4: headless deployment, and the sync that turned out redundant

Two smaller surprises close out the deployment.

First, Add-PnPApp is unusable headless. Even with -Overwrite -SkipFeatureDeployment, add-only or add-and-publish, it forces an interactive prompt and dies with "PowerShell is in NonInteractive mode." The way around it is raw SharePoint REST with an app-only certificate token, skipping the cmdlet entirely:

Code
POST /_api/web/tenantappcatalog/Add(overwrite=true, url='time-off.sppkg')
  body = <package bytes>
POST .../AvailableApps/GetById('<app-id>')/Deploy
  body = {"skipFeatureDeployment": true}

That path deployed the package built from source. Verification returned Deployed=True, Enabled=True, Version=1.0.0.0, No errors. The custom-built .sppkg was live in the tenant, upload and deploy under 30 seconds once the token was minted.

Second, and this is where the whole story lands: the "Sync to Teams" step that supposedly adds the agent to the Microsoft 365 Copilot catalog. The SyncSolutionToTeams endpoint rejects app-only tokens with HTTP 400, code 10005, "Authentication method is not allowed." The obvious assumption is that a signed-in user would clear it, and that this click is therefore the one human step in the path. Both halves of that assumption turn out to be wrong, and unwinding them is the most interesting result of the whole run. More on that below.

The mechanics of the click itself are worth knowing, because the UI hides them. The tenant serves the App Catalog in the classic interface. The button is not in the list command bar (the "Sync" there is OneDrive sync). You select the package row, open the classic ribbon's FILES tab, and the "Sync to Teams" button sits on the right. Roughly 30 seconds once you find it, but easily 15 minutes lost hunting for it the first time. The community walkthroughs never show this screen.

Wall 5: the "green build" ships an incomplete package

A build that passes 172 tests and exits zero still shipped a broken app. After deploying the from-source .sppkg, the agent appeared in Microsoft 365 Copilot and rendered a blank canvas.

The diagnosis came from comparing the two zips. The from-source package held 23 entries and 11 assets. The pre-built Microsoft package held 26 and 14. Whole component bundles were missing from the local build, including the request component. The declarative agent referenced components that were not in the package, so the canvas came up empty. A green Heft build does not guarantee a complete package. The fix was to redeploy the official pre-built package over the top, same name, overwrite true. Deployed, no errors.

Wall 6: the App Catalog blocks its own configuration

Provisioning the sample's data lists and a tenant property pointer ran into an Access denied 0x80070005 on the property-bag write. The App Catalog site ships as a NoScript site by default, and NoScript blocks property-bag writes outright.

The app-only fix is two calls: lift NoScript, then set the storage entity.

Code
Set-PnPSite -Identity "https://yourtenant.sharepoint.com/sites/appcatalog" -NoScriptSite:$false
Set-PnPStorageEntity -Key "TimeOffSite" -Value "/sites/timeoff"

Wall 7: the agent snapshot freezes on the old version

Blank canvas again, now with a complete package, real data, and the pointer set. The browser console told the real story: the McpWidgetHost container booted (iframe, sandbox, CSP all fine), but the component resource returned $value 404.

The cause is documented in the SPFx 1.24 release notes: if the version does not change, Copilot can keep using the previously synced agent. Two redeployments had all kept Version=1.0.0.0, so the agent snapshot still pointed at the resources of the first, incomplete package. The fix is package surgery: bump AppManifest.xml to 1.1.0.0 and the Teams manifest.json inside the agent zip to 1.1.0, repack, redeploy, then re-sync and start a fresh conversation (the old one keeps the stale binding).

The lesson generalizes: any content change to a Copilot App demands a version bump, or you get a silent agent cache.

The last wall dissolves: the deploy already published the agent

Back to SyncSolutionToTeams and its 10005. The natural next move after "app-only is rejected" is a delegated user token, then the strongest test of all: the access token from the Azure CLI itself, decoded to confirm it is the genuine first-party Microsoft client (app_displayname: Microsoft Azure CLI), carrying a real user context and the correct SharePoint scopes.

Same POST SyncSolutionToTeams(id=2), same result:

Code
{"error":{"code":"10005","message":{"lang":"en-US","value":"Authentication method is not allowed."}}}
[HTTP 400]

The endpoint resolved the item (no 404), so the failure sits at the authentication gate. So far this reads like an unscriptable wall, and the tempting conclusion is that a human at the SharePoint UI is the one escape hatch. That conclusion is wrong, on two counts.

First, the human path fails too. Driving SyncSolutionToTeams from a fully interactive, signed-in administrator session clears the 10005 auth gate and then returns a different error: HTTP 500, "This app couldn't be added to Teams because it's blocked in the Teams admin center." The ribbon button surfaces the same failure as a curt "Failed to sync to Teams." The interactive path does not succeed where the tokens failed. It just fails later, and more opaquely, even though the app is in fact available org-wide.

Second, and this is what dissolves the wall: the agent was already published. Open the Teams admin center and it is there, "My Time Off," published version 1.2.0, available to everyone, and it answers in Microsoft 365 Copilot. It got there from the .sppkg deployment, exactly as Microsoft's docs state: "there is no separate publishing step... deploying the .sppkg makes the agent available to users." SyncSolutionToTeams was never the thing that publishes the agent. It is a legacy path, now superseded on tenants moved to unified app management, sitting on top of a publish the deployment already did.

What is genuinely undersold

Strip away the setup and the feature is more interesting than the pitch makes it sound.

Hosting is automatic and in-tenant. The build bundles the client-side assets into the package. There is no external CDN to configure, no separate hosting to stand up. The app is served from your tenant.

The declarative agent publishes on deploy. Deploying the .sppkg to the SharePoint app catalog is what lands the agent in the tenant catalog and Microsoft 365 Copilot. The docs are explicit that there is no separate publishing step. No "Add to Teams" click, no manifest juggling in a separate portal. On this tenant the button failed and the agent was live regardless.

The programming model is real. MCP Apps plus BaseCopilotComponent, typed tool inputs, and two display modes give you a genuine component model for Copilot-canvas UX, not a low-code veneer. For a developer, this is the part worth the hour of setup.

The verdict

The "no license" claim is true and the best reason to look at this now, while preview keeps the gate open. The "no code, copy one file, minutes" claim describes the demo path only, and only if your App Catalog already exists.

For the maker path, budget more than the first hour you might expect. You will hit Node 22, a lockfile wired to a private feed, a missing App Catalog, and a headless deployment quirk, then a green build that ships an incomplete package, a NoScript site that blocks its own configuration, and an agent snapshot that freezes on the old version until you bump it. After that, the loop is fast: 45-second build, 172 tests green, script almost all of it.

And it turns out you can script all of it. The "Sync to Teams" call is refused for every token, app-only, third-party delegated, even Microsoft's own first-party Azure CLI, all with the same 10005, and it fails from the interactive UI too. It simply does not matter, because the deployment already publishes the agent to Teams and Copilot. The honest shape of the feature is a path that automates end to end, with a legacy sync button on top that neither works nor needs to.

None of these walls are dealbreakers. All of them are invisible in the community framing. If you are going to try SharePoint Copilot Apps, and the no-license preview window is a good reason to, go in knowing the real path, including that the step everyone points to at the end is the one that turns out not to matter.