Skip to content

Agents

Building an Authenticated GitHub MCP Plugin for Cowork

← Back to Writing

Building an Authenticated GitHub MCP Plugin for Cowork

5 min read

This is a reference walkthrough for wiring up authenticated access in a Cowork plugin — specifically, connecting to GitHub's API as a signed-in user via OAuthPluginVault.

I wanted to use Cowork to query my private GitHub repos — things like finding stale repositories or spotting outdated tech stacks. But Cowork by default work anonymously, which means public repos only. Private data requires the plugin to authenticate as a specific user.

So I built a plugin for Cowork that does that. What follows is exactly how the OAuth wiring works, step by step. It's a narrow use case (GitHub private repos via a Cowork plugin), but the OAuthPluginVault pattern applies to any service that uses OAuth.

Quick context: what's a Cowork plugin?

Copilot Cowork can be extended with plugins - small packages that teach it new tricks. A plugin bundles two things:

  • Skills - plain-language instructions that tell Cowork how to do a task ("when someone asks to find a repo, do this")
  • Connectors - the wiring to an external service, so Cowork can actually go fetch real data instead of guessing

The simplest connectors need no login at all - point at a public endpoint and you're done. Fine for public docs. Not fine for a question about my private repos.

The first attempt: browser sign-in works, but I wanted more control

When I asked about private repos, Cowork checked for an existing GitHub session, didn't find one, and prompted me to sign in via the browser. Once I did — it worked.

So why not stop there? Because that sign-in is tied to the browser session. I wanted OAuthPluginVault wiring instead: credentials stored in the Teams developer portal, a proper token exchange per user, and a clear record of what scopes were granted.

That meant the plugin needed its own OAuth registration.

What it took to get authentication working

Three moves from anonymous to authenticated. I put the finished plugin here if you want to see the real files: cowork-github-plugin. Below is exactly what I did.

Step 1: Register an OAuth App with GitHub

This is GitHub saying "yes, I'll vouch for whoever signs in through this app, and I'll tell you who they are."

  1. Go to github.com/settings/developers and select OAuth AppsNew OAuth App.
  2. Fill in the basics:
  3. Application name - anything recognizable, e.g. GitHub for Cowork
  4. Homepage URL - any valid URL, I used my plugin's repo
  5. Authorization callback URL - this one matters, and it's always the same value no matter what you're building:
    https://teams.microsoft.com/api/platform/v1.0/oAuthRedirect
    
    This is the address Microsoft's side hands the login back to once GitHub says "yep, that's them." Get this wrong and sign-in just quietly fails.
  6. Select Register application.
  7. On the app's page, select Generate a new client secret. Copy both the Client ID and the Client secret somewhere safe right now - GitHub only shows you the secret once.

That's the whole GitHub side. Two values in hand: a client ID and a client secret.

Step 2: Set up the OAuth registration in Teams developer portal

Now I needed somewhere for those two values to live that wasn't "hardcoded in my plugin files." That's what the Teams developer portal's OAuth client registration does - it stores the credentials securely and hands your plugin back a reference ID to point at instead.

  1. Open the Teams developer portal and go to ToolsOAuth client registration.
  2. Select Register client (or New OAuth client registration if you've got others already).
  3. Fill in the form:
  4. Registration name - something you'll recognize later, e.g. github-mcp-oauth
  5. Base URL - https://api.githubcopilot.com, the GitHub MCP server's address
  6. Restrict usage by org - My organization only while you're testing, or Any Microsoft 365 organization if this needs to work beyond your own tenant
  7. Restrict usage by app - Any Teams app for now, you can lock it down later once your plugin has a stable ID
  8. Client ID - paste the one GitHub gave you
  9. Client secret - paste the one GitHub gave you
  10. Authorization endpoint - https://github.com/login/oauth/authorize
  11. Token endpoint - https://github.com/login/oauth/access_token
  12. Scope - repo read:user (more on why repo specifically, below)
  13. Enable PKCE - optional, turn it on if you want the extra layer
  14. Select Save.
  15. You'll get back an auth config ID - the portal currently labels it OAuth client registration ID. Copy it.

Step 3: Point the plugin at it

That ID from Step 2 goes straight into the connector definition in manifest.json as in the sample I shared:

"authorization": {
    "type": "OAuthPluginVault",
    "referenceId": "PasteyourOAuthVaultReferenceIdHere"
}

That's it. For anonymous this type is None but now for authenticated it is OAuthPluginVault. Paste your real reference ID where you see PasteyourOAuthVaultReferenceIdHere , and now every user who installs this plugin gets prompted to sign in with their own GitHub account before Cowork can use it on their behalf. Nowhere in that file is there a client secret sitting in plain text - just a pointer to where the real credential lives.

You can see the exact shape of this in my manifest.json - same connector block, just swap in your own reference ID.

Upload the plugin to Cowork

Once the plugin is packaged and zipped, getting it into Cowork took about thirty seconds:

  1. Open Cowork and select the + icon.
  2. Scroll down to Customize (manage skills and plugins).
  3. On the Customize page, upload the plugin zip file.

That's it - no "connect" step needed. Once uploaded, Cowork picks it up and you're ready to test.

The first time you prompt Cowork to use the plugin, it'll show a Sign in required card asking you to authenticate. Hit Authenticate, sign in with GitHub, and you're in - Cowork won't ask again after that.

image for sign in

Testing it

With the plugin uploaded and authenticated, I asked:

"Find my private repos that haven't had a commit since before 2024-01-01. For each one, make a table with: repo name, last updated date, primary language, and one-line description. Then add a column flagging whether the tech stack looks outdated (e.g., unmaintained framework, EOL language version, dependency now widely deprecated) — and briefly say why."

Image of response

It returned a table of private repos with last-updated dates and language info. The "outdated tech" flagging is only as good as the model's knowledge — it's pattern-matching on framework names, not actually checking dependency trees — but for a quick triage of what to look at first, it was useful enough.

The costs worth knowing

Remember that repo scope I set in the Teams developer portal back in Step 2? It's blunt on purpose — it's the only way a classic GitHub OAuth App can see private repos at all. There's no "read-only" version. Granting it technically hands the plugin write access too — push, delete, the works — even though all we asked it to do was search.

This is a real tradeoff, not a footnote. For a personal experiment it's acceptable; for anything shared or production-facing, you'd want to explore GitHub Apps (which offer fine-grained permissions) instead of classic OAuth Apps. That's a meaningfully different integration path.

Why this walkthrough exists

The OAuthPluginVault wiring isn't complicated once you've done it, but the documentation is scattered across. This post puts the three pieces in one place. The GitHub-specific use case is just a concrete example — the pattern (register OAuth app → store credentials in Teams portal → reference the ID in your connector) is the same for any OAuth-based service.

If you want to do the same

The steps again, stripped down:

  1. Register an OAuth app with whatever service you're connecting to
  2. Register those credentials in the Teams developer portal and grab the reference ID
  3. Flip the authorization type in your connector config from None to OAuthPluginVault
  4. Test with a query that requires authenticated access

Grab the full working plugin and swap in your own reference ID: github.com/rabwill/cowork-github-plugin

References

Why I Built One Substack MCP Server for Four AI Apps

← Back to Writing

3 min read

One server. Every AI app. No rebuilding.

This post is based on the MCP server I built for my own workflow: analysing my Substack notes, spotting what people engage with most, and pulling every signal I can from Substack to shape what I write next. You can find it here: substack-mcp.

Remember that drawer full of old chargers? One for a phone, one for a camera, one for headphones you do not even use anymore.

Then USB-C arrived. One connector. One cable. Everything works with it.

That is what MCP feels like for AI tools. The ecosystem keeps growing, but integration is getting simpler.

AI is having its USB-C moment, and you do not need to be a developer to see why it matters.

So what actually is MCP?

The Model Context Protocol (MCP) is a standard way to plug your stuff - your notes, your files, your data, your tools - into any AI assistant that speaks it.

Before MCP, every AI app was its own island. If you wanted Copilot to see your data, you built one thing. Perplexity? Build it again. Claude? Again. It was the charger drawer all over again.

MCP flips that. You build one server that knows how to reach your information, and then any MCP-aware assistant can plug into it. Build once. Connect everywhere.

One thing I built, four apps that understood it

To test this, I built a small MCP server that connects to my own Substack notes. I didn't rewrite it for each app. I built it once - then plugged the same server into four different tools and asked four different questions.

Microsoft 365 Copilot summarised my week and pulled the top signals out of my last 20 notes:

Microsoft 365 Copilot summarising the week from my Substack notes Copilot in Microsoft 365 - turning 20 scattered notes into one clear summary.

Then I asked it to go deeper, and it reframed the whole thing into a mentoring plan:

Copilot going deeper: the best mentoring frame and three pillars ...same server, a sharper question - no extra setup.

Claude took the exact same notes and ranked the themes my audience keeps coming back to:

Claude identifying the top recurring themes from the same notes Claude, pointed at the same server - ranking my highest-engagement themes.

Perplexity looked at the same material and told me the angles I was missing:

Perplexity analysing the writing angles I hadn't explored Perplexity - same notes, a completely different lens.

And Word turned all of it into a finished newsletter, right inside the document:

Word with Copilot drafting a monthly newsletter from the notes Word - where the insight becomes something I can actually publish.

Same notes. Four different kinds of intelligence. That's the part that surprised me: the value wasn't only in my data - it was in letting each tool bring its own lens to it. And I only had to build the connection once.

The Microsoft angle: your agent, right where you work

Here's the part I love as someone in the Microsoft 365 world. You don't have to build a whole AI system to use this.

With a Declarative Agent, you build on top of Copilot - its model, its orchestration, its familiar chat - and simply declare what your agent should know and do. Point it at your MCP server, and now your agent can reach your tools in plain language, right inside Teams, Word, and Copilot chat.

Your data and logic live in one place. Your agent lives where you already work. The plug does the rest.

Why this matters - even if you never write code

Modern AI isn't about one clever chatbot. It's about your knowledge being reachable from every place you work, without duplicating effort every single time.

Build the capability once. Connect it everywhere. Let each assistant do what it's best at. Less busywork, more insight - and a lot less time lost to the digital equivalent of the charger drawer.

Your turn

Curious to build one yourself? It's more approachable than you'd think.

Head to Copilot Developer Camp and take the FREE MCP Foundations course that I wrote as part of my day job. In about four hours you'll:

  1. Run an MCP server and connect it to an agent in Copilot - a full, working "ask in plain English, get real answers from your tools" flow.
  2. Secure the connection - add OAuth 2.0 and Microsoft Entra ID, so only trusted requests ever reach your server.

Build it once. Plug it in everywhere. Ship it safely.

References