Building a Working Copilot Cowork Plugin

Building a Working Copilot Cowork Plugin (The Hard Way)

So, Microsoft released Copilot Cowork as part of their Frontier program, and naturally I had to poke at it. Cowork is Microsoft’s new agentic experience baked into Microsoft 365 Copilot — think of it as Copilot that can actually *do* things on your behalf, not just answer questions. You can extend it with custom plugins that add new skills and connect to external services via MCP (Model Context Protocol) servers.

Sounds cool, right? It is! But wow, getting there was an adventure. 😅

I decided to build a plugin that lets Cowork draft and publish LinkedIn posts on your behalf. Simple enough concept — tell Cowork what you want to share, it writes a professional post, you approve it, it publishes it. Done. Except… not done. Not even close to done.

The Documentation Problem

Here’s the thing about Copilot Cowork plugins — Microsoft released this as a Frontier preview, which means the documentation is best described as “vibes-based.” What little exists is incomplete, some of it is just plain wrong, and some critical things are not documented at all.

I’m not complaining… I get it, it’s a preview. But I want to save you the hours I lost, so let me share what actually works.

LinkedIn OAuth Is Broken (Don’t Even Try)

My first instinct was to wire up LinkedIn OAuth so each user could connect their own account. Reasonable assumption. Completely wrong.

No matter what I tried, the OAuth flow would get through the LinkedIn login screen successfully and then immediately fail with “Unable to sign in.” After a lot of digging, I figured out why: LinkedIn requires OAuth client credentials to be sent as POST body parameters during the token exchange. Copilot Studio sends them as HTTP Basic Auth instead. LinkedIn silently rejects this. You get a failed login with zero useful error information.

On top of that, LinkedIn doesn’t seem to support refresh tokens for standard developer apps, and Copilot Studio needs a working refresh URL to maintain connections. So even if you got past the first problem, you’d be dealing with expired tokens every 60 days with no way to auto-renew.

The solution? Skip OAuth entirely. The MCP server holds a pre-generated LinkedIn access token as an environment variable. Copilot Studio connects with no authentication. The server handles everything. It works, it’s reliable, and the only maintenance is regenerating the token every 60 days via LinkedIn’s token generator tool.

Apparently Copilot Cowork Uses SSE and but it must be a secret

This one cost me the most time. When I got the plugin uploaded and loaded, the MCP tools just… didn’t show up. Cowork acted like the connector didn’t exist.

After a lot of trial and error, I discovered that Copilot Cowork does **not** use plain HTTP POST to communicate with MCP servers. It uses the MCP SSE (Server-Sent Events) transport. The client opens a persistent `GET /mcp` connection, the server responds with an SSE stream, and the first event tells Cowork where to POST its actual tool call messages.

Without that `GET /mcp` SSE endpoint, Cowork simply cannot discover your server. It fails silently. This is documented exactly nowhere by Microsoft as of today, so hopefully this saves someone else the headache.

Integrated Apps Is the Right Place

When you’re ready to deploy your plugin, you go to M365 Admin Center and look for… where exactly? I tried a few places before landing on the right one.

The answer is **Settings → Integrated Apps** — not the Teams admin center, not Manage Apps under Teams. Integrated Apps, under Settings. That’s where you upload the plugin package and deploy it to users, and that’s what triggers the deployment flow that makes it actually appear in Cowork.

#The End Result

After all of that, it works! You can tell Cowork “post to LinkedIn about our conference coming up in October” and it will draft a post, show it to you for approval, and publish it with a link back to the live post.

I’ve open sourced the whole thing at **https://github.com/mrackley/linkedin-cowork-plugin** with a comprehensive README that documents every hurdle I hit, including:

  • Full step-by-step Azure deployment instructions
  • How to generate a LinkedIn access token manually
  • Why OAuth doesn’t work and why the static token approach is the right call
  • A full explanation of the SSE transport requirement with code
  • The correct M365 Admin Center location for deploying plugins
  • Troubleshooting for all the things that will go wrong

If you’re on the Frontier program and want to extend Cowork, hopefully this saves you a few hours of frustration. And if you’re not on Frontier yet it’s worth getting on the list. This stuff is genuinely exciting once you get past the rough edges.

Now if you’ll excuse me, I’m going to go eat some bacon and recover. 🥓

Be the first to comment

Leave a Reply

Your email address will not be published.


*