← All posts
  • AI
  • TOOLING
  • AWS

Build plugins, not harnesses

The best internal AI tool is often not an app at all. It's a plugin in the ChatGPT or Claude your colleagues already have open.

An internal AI tool usually starts life as a new app: a chat window, a login page, one more URL to bookmark. Then it sits next to the ChatGPT or Claude tab people already have open, and usually loses.

Over the last months I’ve built, among other things, a fair amount of internal AI tooling, and the tools that actually get used show up where people already work. In practice, that means plugins.

Why not your own harness

The harness is the program that runs the model in a loop: tool calls, context management, permissions, sandboxing, subagents, and a UI on every device. The labs build that full time. I have watched teams compete with them on that ground and lose to a single release.

What the labs don’t have is your company’s context: internal APIs, cloud accounts, auth, standards, workflows. A plugin packages exactly that, and it improves every time the harness and the model do.

You buildThe pluginYour company's context: internal APIs, cloud, auth, standards.
The lab buildsThe harnessAgent loop, tools, context, permissions, sandbox, UI.
The lab trainsThe modelGets better without you.
Only the top layer is yours. The two below improve on their own.

What a plugin is

In Claude Code, a plugin is “a directory of skills, agents, hooks, MCP servers, or other components” installed as one unit. ChatGPT and Codex share one plugin directory with the same idea:

  • Skills: SKILL.md instructions and reference files. The knowledge.
  • MCP servers: tools that reach your systems.
  • Hooks: commands the harness runs at fixed points, such as before every tool call.
deploy-helper/
├── .claude-plugin/plugin.json
├── skills/deploy-to-aws/SKILL.md
├── hooks/hooks.json
└── .mcp.json

Skills carry over almost unchanged, and OpenAI accepts Claude Code plugins and converts the manifest. Hooks, agents and local MCP servers are where the harnesses still differ. Distribution can be a git repository, so a new version is a pull request.

Spotify built its Ads API integration this way: one plain-English request creates a campaign, ad set and ad. It is skills, an agent, a hook and the API’s OpenAPI spec, with no MCP server at all. In their words: “There is no compiled code in this plugin. There is no build step, no package manager, no bundler, no transpiler.”

What you can do with one

Plugins are not only for developers. Most of what teams build separate internal apps for fits in one:

  • Ask your own data. “How did this change last quarter?” gets answered from an internal database or API through an MCP tool, behind your own auth.
  • Analyse and visualise it. The tool fetches the numbers, and the model works through them and draws the chart right in the chat. An MCP server can also return its own UI when a chart or a form helps.
  • Get everyday work done. Reports, summaries and drafts in the company’s own format and tone, because a skill holds the template and the rules.
  • Follow how you work. Steps for a release, an onboarding or a monthly report, with scripts and templates next to them.
  • Reach the systems you already pay for. MCP connectors reach the SaaS tools and partner APIs the company uses.
  • Stay inside the lines. In Claude Code, hooks can block an action before it runs, and Claude Team and Enterprise show how many people use a plugin and how often.

It reaches people without a terminal

Non-technical colleagues already work in ChatGPT and Claude. On ChatGPT Business and Enterprise, admins publish custom MCP apps to the workspace. On Claude Team and Enterprise, an owner can make a plugin available, installed by default or required. No new URL, no new interface. That is what drives adoption.

Connecting it to your own AWS

The useful case is internal: query a database in your AWS account, or call a partner API with machine-to-machine credentials. My pattern is Amazon Bedrock AgentCore Gateway as the MCP server, which turns Lambda functions and APIs into MCP tools, with the company IdP doing the sign-in. If you have used API Gateway with a JWT authorizer, you know the shape.

Internal tools through AgentCore Gateway A harness with your plugin calls AgentCore Gateway in your AWS account. The user signs in at the company identity provider. The gateway validates the token against the identity provider, invokes Lambda tools that read internal data, and calls a partner API with a separate credential from AgentCore Identity. HARNESS YOUR AWS ACCOUNT ChatGPT · Claude Codex · Claude Code Your plugin skills · MCP config Company IdP SSO · OIDC discovery AgentCore Gateway MCP · JWT authorizer Lambda tools gateway IAM role Internal data your database AgentCore Identity partner credential Partner API its own OAuth 1 2 3 4 5 6
One tool call, from the harness to your data. Dashed lines are discovery and credentials; solid lines carry the request.
  1. Discovery. No token: the gateway answers 401 and points to its protected resource metadata, which names the IdP.
  2. Sign-in. The client registers with the IdP, runs OAuth with PKCE and asks for a token for the gateway. ChatGPT can register through CIMD, DCR or a pre-registered client, and IdPs differ in which they support. That is where most setups get stuck.
  3. Tool call. The client calls again with Authorization: Bearer <token>.
  4. Validation. The gateway checks the JWT against the IdP’s keys: signature, expiry, issuer, audience.
  5. Internal tools. The gateway invokes Lambda with its own IAM role.
  6. Partners. The gateway gets a separate credential from AgentCore Identity for the partner call.

The inbound check is a few lines of the CreateGateway request:

{
  "authorizerType": "CUSTOM_JWT",
  "authorizerConfiguration": {
    "customJWTAuthorizer": {
      "discoveryUrl": "https://<your-idp>/.well-known/openid-configuration",
      "allowedAudience": ["<your gateway audience>"]
    }
  }
}

Always restrict which tokens it accepts: set allowedAudience, or allowedClients if your IdP identifies the client in client_id instead of aud, as Cognito access tokens do. Without it, a token issued for another API can work on yours.

Can we reuse the SSO token?

No. ChatGPT runs its own OAuth flow against your MCP server’s authorization server, and the MCP spec says servers “MUST NOT accept or transit any other tokens”. You reuse the identity, not the token: point the gateway at the same IdP as your SSO, and users sign in with the account they already have. The token only opens your gateway.

The same goes for outbound calls. Don’t pass the user’s token along to partner APIs. Give them their own credential, or use on-behalf-of token exchange, where the gateway swaps the user’s token for a new one scoped to the downstream service. The gateway’s IAM role is shared by every target that uses it, so put separate trust boundaries behind separate gateways.

Start where people already work

When I look at an internal AI product, the first question is where the employees already work. If the organization already sits in Claude or ChatGPT, building on that harness with a plugin is usually the smart move. People keep the tool they know, and you spend your time on the part only you can build: the context.

That flips the burden of proof. Building your own agent or app is fine, but it needs a clear argument, such as a real product requirement the harness can’t meet. “It would be fun to build” is not one of them.