Back to Blog

TypeSpec for Microsoft 365 Copilot - The Authoring Experience Finally Catches Up

May 29, 20268 min readMichael Ridland

If you've ever tried to hand-author a Microsoft 365 Copilot declarative agent manifest, you'll know the feeling. You open the JSON, you add a capability, you forget a comma, you spend 20 minutes finding the comma, you deploy, you get a validation error that points to line 47 of a file you've never seen, and you wonder why anyone thought this was a good developer experience.

TypeSpec for Microsoft 365 Copilot is Microsoft's answer to that pain. It's a domain-specific language layered on top of TypeSpec, with decorators specific to Copilot agents and API plugins. You write something that reads like a strongly-typed config file. The compiler emits the JSON manifests and the OpenAPI specs for you.

We've been building Copilot extensions for clients for about a year. Three months ago I started using TypeSpec on a real engagement instead of hand-rolling manifests. Here's the honest review.

What it actually replaces

A declarative agent in Microsoft 365 Copilot is defined by a manifest JSON file. The file has metadata, instructions, capabilities, conversation starters, references to knowledge sources, and configuration for any actions or plugins. For a non-trivial agent the manifest gets to several hundred lines, with nested objects that all need to validate against the latest schema version.

If you're also exposing an API plugin, you write an OpenAPI spec describing the operations the agent can call. For an API with twenty operations and decent schema reuse, that file gets to a couple of thousand lines easily. And the OpenAPI spec has to be precisely formatted for the Copilot to consume it. A wrong reference, a missing operationId, a parameter in the wrong place, and the plugin won't register.

TypeSpec replaces both of these files with TypeSpec source. The compiler emits the manifest JSON and the OpenAPI spec. You write the high-level definitions. The tedious correctness work is done for you.

The syntax looks like this for a basic agent:

@agent(
  "Customer Support Assistant",
  "Helps with support inquiries and ticket management"
)
@instructions("...")
namespace CustomerSupportAgent {
  // capabilities here
}

The decorators (@agent, @instructions, @conversationStarter) tell the compiler what kind of thing you're defining. The namespace holds the operations and types. The compiler emits a valid manifest from this.

Where it earns its keep

The big win is the development loop. Hand-authoring JSON, you'd write, deploy, find an error, fix, redeploy. The cycle was 30 to 60 seconds per iteration and the error messages were often unhelpful. With TypeSpec, the compiler catches structural errors at build time, before you ever touch a deploy command. The cycle drops to 5 seconds and the errors point at the line that's wrong with a sensible message.

The second win is type safety across the agent definition. If you reference a capability that doesn't exist, you get an immediate compile error. If you pass the wrong type to a decorator, immediate compile error. With raw JSON, you'd only find these at registration time, often with a vague message that didn't tell you which property was wrong.

For API plugins the difference is bigger. OpenAPI is verbose. A 20-operation API with shared schemas can be a couple of thousand lines of YAML. The TypeSpec equivalent is maybe 200 to 300 lines. The compiler emits the OpenAPI spec from the higher-level description.

The example Microsoft uses in the docs is a project management API with listProjects, getProject, and createProject operations. The TypeSpec for that is maybe 30 lines. The emitted OpenAPI is around 150 lines once you include all the schemas, references, and metadata. That ratio holds at scale. For our Microsoft AI consultants team, the time saved on plugin authoring has been the biggest direct productivity win from adopting it.

The IntelliSense in VS Code is good. Auto-complete works on decorators, capabilities, and types. Inline documentation appears when you hover. Errors highlight inline. If you've worked with the TypeScript language server you'll feel at home.

What it's like in practice

A real agent we built recently for a client had:

  • An agent persona with detailed instructions for an internal HR assistant
  • Web search restricted to specific corporate sites
  • SharePoint integration for policy documents
  • A Code Interpreter capability for benefits calculations
  • A custom API plugin with eight operations for the HRIS

In TypeSpec, the entire thing came out to around 400 lines split across three source files. The agent definition, the API operation definitions, and the shared models. The compiler emitted both the declarative agent manifest and the OpenAPI spec for the plugin. We never touched either output file directly.

Compare that to the previous version of the same agent which we'd built six months earlier in raw JSON. That was about 1,800 lines across the manifest and the OpenAPI spec, plus a build script that did some templating to inject environment values. The TypeSpec version is shorter, more readable, and didn't need the templating layer because TypeSpec has its own.

The thing I notice now when I open the old JSON-based projects is how much defensive boilerplate I'd written. Lots of small validation scripts. Lots of careful indentation. Lots of comments explaining what specific properties did because the schema docs were hard to find. TypeSpec deletes most of that because the language itself encodes the structure.

Where it's still rough

Worth being honest about the bits that aren't great yet.

Documentation is patchy. The reference docs cover the basics but skip a lot of the harder cases. When we wanted to do conditional auth (one auth method for dev, another for prod) the docs didn't help and we had to dig into examples on GitHub. The error messages from the compiler are good but the docs that explain what the errors mean are thin.

Tooling integration is uneven. VS Code is well supported. JetBrains IDEs less so. The Microsoft 365 Agents Toolkit integration works but had quirks with project initialisation that we had to work around manually. None of this is blocking but it's not as polished as the marketing suggests.

The translation to OpenAPI sometimes produces output that's correct but not idiomatic. If you have a downstream consumer that's strict about OpenAPI conventions, you may need to post-process or add hints to the TypeSpec to get the OpenAPI you actually want. We hit this once with a third-party tool that expected operationIds in a particular format.

Adaptive cards are still tedious. TypeSpec helps with the structural validation but you still write the adaptive card JSON for any cards your plugin returns. The card schema is large and the binding rules are fiddly. TypeSpec validates references but doesn't give you a great authoring experience for the cards themselves. We've built a handful of these for client work and the adaptive card portion is still where most of the development time goes.

Some Copilot capabilities lag behind in TypeSpec support. New features that ship in the JSON schema sometimes take a couple of weeks to appear in the TypeSpec decorators. If you're trying to use a brand new capability the day it ships, you may have to fall back to raw JSON. This will improve, but right now it's something to be aware of.

When to use it, when not to

Use it when:

You're building a non-trivial agent or plugin that you expect to maintain. The compile-time validation and the cleaner source pay off the moment you need to make changes.

You're working in a team. The TypeSpec source reviews better than JSON manifests. Pull requests are easier to read. Conflicts are easier to resolve.

You're building an API plugin with more than a few operations. The OpenAPI compression is significant.

You want to keep the agent definition close to your TypeScript code. TypeSpec syntax is familiar to TypeScript developers and feels like a natural extension.

Skip it when:

You're prototyping something tiny for an internal demo. The toolchain setup overhead isn't worth it for a 50-line manifest you'll delete next week.

You're using a brand new Copilot capability that hasn't landed in TypeSpec yet. Use raw JSON until it does.

You don't have anyone on the team who can take responsibility for the TypeSpec build pipeline. It's not heavy, but it's another thing to maintain.

For our agent builder work we've defaulted to TypeSpec for anything that's going past the prototype stage. The maintainability win compounds. Six months from now, when someone needs to add a new capability or update a knowledge source, they'll be glad they're editing TypeSpec instead of finding the right place in a 1,500-line manifest.

What's coming

Microsoft is clearly investing in TypeSpec across multiple product areas. Azure, Microsoft Graph, and now Copilot all have TypeSpec libraries. The pattern feels like a strategic bet, not a side project. That gives me confidence that the rough edges will get filed down rather than the whole thing being abandoned.

The thing I'm watching for is whether the TypeSpec decorators stay in sync with the JSON schema as Copilot capabilities evolve. If they do, TypeSpec becomes the default authoring path. If they lag, people will get frustrated and go back to JSON. Right now it's roughly in sync. We'll see.

For Australian teams doing serious work with Microsoft 365 Copilot extensibility, I'd say TypeSpec is now the right starting point. It's not perfect. It's much better than hand-rolling manifests. And given Microsoft's direction, it's where the tooling investment is going to land first.

Reference: TypeSpec for Microsoft 365 Copilot overview