Move internal workspace packages from peer deps to direct deps#474
Merged
heyitsaamir merged 8 commits intomainfrom Mar 23, 2026
Merged
Move internal workspace packages from peer deps to direct deps#474heyitsaamir merged 8 commits intomainfrom
heyitsaamir merged 8 commits intomainfrom
Conversation
Internal @microsoft/teams.* packages were declared as peer dependencies, forcing consumers to manually install internal plumbing. Convert to real dependencies so consumers only install packages they consciously choose. Clean up examples and CLI templates to only declare what they import. Remove teams.dev dependency from teams.mcp (and DevtoolsPlugin code). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Separate activity sending from HTTP transport layer
The previous architecture tightly coupled HTTP transport concerns with
activity sending logic:
**Previous Architecture:**
```
HttpPlugin (transport) → implements ISender (sending)
→ has send() method (creates new Client per call)
→ has createStream() method
→ knows about Activity protocol details
ActivityContext → depends on ISender plugin
→ cannot work without transport plugin
→ conflates transport and sending concerns
```
There are a few issues with this:
- HttpPlugin created NEW Client instances on every send() call. So
there's really no benefit of this logic being in the "httpclient"
plugin.
- Transport plugins (HttpPlugin) were forced to implement
send/createStream. This makes it more cumbersome to build your own
HttpPlugin with your own servier.
- Users couldn't "bring their own server" without implementing ISender
- ActivityContext was tightly coupled to plugin architecture. ("Sender"
was coupled with an activity, without any necessary benefits.)
## New Architecture
```
HttpPlugin (transport) → only handles HTTP server/routing/auth
→ emits ICoreActivity (minimal protocol knowledge)
→ just passes body payload to app
ActivitySender (NEW) → dedicated class for sending activities
→ receives injected, reusable Client
→ handles all send/stream logic
→ private to App class
ActivityContext → uses ActivitySender now (which is not a plugin)
```
In this PR, I am mainly decoupling responsibilities of HttpPlugin from
being BOTH a listener AND a sender, to being just a listener. The sender
bit is now separated to a different `ActivitySender` class. Other than
better code organization, the main thing this lets us do is **not
require the app to run to be able to send proactive messages**. This is
a huge plus point because now the App can be used in scenarios where it
doesn't necessarily need to _listen_ to incoming messages (like agentic
notifications!)
## Major Decisions
### 1. Created ActivitySender Class
- Centralized all activity sending logic
- Receives reusable Client in constructor (no per-send instantiation)
- Private to App class - internal implementation detail
- Provides send() and createStream() methods
- **Separate from HttpPlugin**
### 2. Introduced ICoreActivity Interface
- Minimal fields transport layer needs: serviceUrl, id, type
- Extensible via [key: string]: any for protocol-specific fields
- Transport plugins work with this instead of full Activity type. So
it's easier to create these.
- Parsing to Activity happens in app.process.ts now, NOT in HttpPlugin.
### 3. Removed ISender Interface
- No longer needed - plugins don't send activities
- Plugins only handle transport (receiving requests)
- Breaking change, but simplifies plugin architecture. This pattern
wasn't documented (intentionally) because the design was subject to
change. So it should be okay hopefully to change this.
## Breaking Changes
### For Plugin Authors:
1. **ISender removed** - Custom plugins should implement IPlugin only
2. **IActivityEvent changed** - Now has body: ICoreActivity instead of
activity: Activity
#### PR Dependency Tree
* **PR #424** 👈
* **PR #433**
This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
## Summary - Run `npm audit fix` to resolve 13 vulnerabilities in transitive dependencies (hono, @hono/node-server, express-rate-limit, flatted, js-yaml, minimatch, tmp) - Change `botbuilder` from 4.23.1 to ^4.23.1 in `packages/botbuilder` and change to 4.23.3 in `examples/botbuilder` to resolve the elliptic vulnerability chain ## Test plan - [x] Tested `examples/mcp` - [x] Tested `examples/mcpclient` - [x] Tested `examples/botbuilder` - [x] `npm audit` returns 0 vulnerabilities --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
In this PR, we introduce a new object called HttpServer and begin to
deprecate HttpPlugin.
## Main changes
1. Create `HttpServer` internal class. It accepts an
`IHttpServerAdapter` which is the server implementation.
2. Pulled out the express implementation as an `IHttpServerAdapter`
3. Deprecated HttpPlugin, and made it use HttpServer + ExpressAdapter.
4. Changed BotBuilder/A2A/Mcp plugins to depend on HttpServer vs.
HttpPlugin.
5. Added examples to show how powerful IHttpServerAdapter can be with
different types of servers (hono, fastify).
6. Minor refactor of the jwt middleware such that we can reuse it in
HttpServer and app.embed.
## Why:
HTTP is a core part of our sdk. Our App object uses HTTP to set up a
server, perform auth validations, and pipe the request to the handlers
that are attached, and then return the response. Key part is that Http
is a *core* part of App, not a plugin, since core functionality is
dependent on it.
Even inside the App object, we were doing special casing for this
Http"Plugin" whereas it should never have really been a plugin to begin
with. By making it a plugin, we were exposing many non-plugin essential
things to the plugin system in general.
So what should it have been? Well, HTTP Plugin had these
responsibilities
1. Set up the express server
2. Perform validations if credentials were present
3. Pass the incoming request to App
4. Once App handlers have had a chance to process this incoming request,
pass the response back to the server.
So, we introduce a new object called `HttpServer` whose responsibilities
are essentially that ^. This object is not a plugin, but an object
that's created by App itself.
## Customization
Now this idealogical shift doesn't really warrant us doing this
refactor, but we started seeing requests from folks who wanted to hook
Teams functionality into existing servers, or replace the underlying
server infra with a non-express server. Our recommendation was to
rebuild a new HttpPlugin. But rebuilding this plugin is not simple
(since we don't really document it anywhere, and didn't expect folks to
build their own).
So `HttpServer` exposes an `IHttpServerAdapter` concept. To build the
adapter, one simply needs to build out a handler for extracting request
data, and a handler for responses. This means that you can build simple
custom adapters for your own _existing_ servers. (And if you don't pass
one in, we'll build a default express one.) Examples of servers are in
the http-adapters folder under examples/.
## Adapter Interface
The `IHttpServerAdapter` interface adapters need to implement:
```typescript
interface IHttpServerAdapter {
registerRoute(method: HttpMethod, path: string, handler: HttpRouteHandler): void;
serveStatic?(path: string, directory: string): void;
start?(port: number): Promise<void>;
stop?(): Promise<void>;
}
```
Handlers are pure functions — `({ body, headers }) → { status, body }`.
No framework-specific request/response objects leak through the
abstraction.
### Why `registerRoute`?
Some adapter patterns have the adapter own routing internally and just
receive a single callback. But our SDK creates routes dynamically —
`app.function('myFunc')` registers `/api/functions/myFunc` at runtime,
in addition to the core `/api/messages` endpoint. The adapter needs a
`registerRoute` method so that both `HttpServer` and `app.function()`
can tell it what paths to listen on.
### Optional methods
`start`/`stop` are optional — serverless adapters (Vercel, Lambda) don't
need them. `serveStatic` is optional — only needed for tab hosting.
`HttpMethod` is currently just `'POST'` (the only method the Teams
protocol uses). It may expand to a union if needed.
## Backward Compat
We've updated `HttpPlugin` to basically use `HttpServer` with an
`ExpressAdapter` internally for backward compat. I don't think this
should lead to any breaking changes (even if someone passes in their own
`HttpPlugin`). (Tested BotBuilderPlugin, from examples, and it worked
without any changes).
However, it should be noted that I marked HttpPlugin as deprecated in
this PR, so it should be discouraged going forward, and after the next
few versions, it'll be removed.
## Testing
I tested by running the following examples:
1. Echo bot
2. Devtools
3. BotBuilder
4. HttpPlugin
5. Tabs
6. AI (streaming and regular completions)
skip-test-verification (added manifest for tabs)
#### PR Dependency Tree
* **PR #424**
* **PR #433** 👈
* **PR #442**
#### PR Dependency Tree
* **PR #424**
* **PR #433** 👈
This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
corinagum
previously approved these changes
Mar 17, 2026
Collaborator
corinagum
left a comment
There was a problem hiding this comment.
lgtm, tentative approval - what's the plan for the teams new scaffolding testing?
- property was incorrectly named `objectId` instead of `aadObjectId`, field was being dropped during serialization when the `meetingEnd` event was triggered - schema that we are using: https://learn.microsoft.com/en-us/dotnet/api/microsoft.bot.schema.teams.teamschannelaccount?view=botbuilder-dotnet-stable - equivalent python fix: microsoft/teams.py#300 - dotnet has it correctly set Co-authored-by: lilydu <lilydu+odspmdb@microsoft.com>
…e-aamirj/direct-deps Resolve conflicts in examples/botbuilder/package.json, packages/botbuilder/package.json, and package-lock.json by taking the PR branch changes (remove redundant internal peer deps, pin botbuilder to exact version). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace relative dist path imports of ILogger with @microsoft/teams.common - Add @microsoft/teams.common as dependency in examples/ai - Remove unused Dependency import from mcp plugin Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Collaborator
Author
Verified, works :) |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
corinagum
previously approved these changes
Mar 23, 2026
The merge-base changed after approval.
corinagum
previously approved these changes
Mar 23, 2026
The merge-base changed after approval.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Internal
@microsoft/teams.*packages were declared as peer dependencies of each other, forcing consumers to manually install packages liketeams.api,teams.common,teams.graph, andteams.cardseven though they had no version choice — all packages are lockstep-versioned and released together. This added DX friction without any architectural benefit.This was fine when agents were being created from scratch (users would scaffold a project and install everything at once). That's no longer the case — users are integrating the SDK into existing projects, and having to figure out which internal plumbing packages to install is unnecessary. Peer deps are meant for plugin/host relationships and external SDKs where the consumer provides their own version, not for tightly-coupled internal packages.
Summary
@microsoft/teams.*peer dependencies to real dependencies across all packages. Consumers now only install the packages they consciously choose (teams.apps,teams.dev,teams.ai, etc.) and internal plumbing comes in transitively.botbuilder,dev,mcp,a2a,mcpclient) correctly peer on their host (teams.appsorteams.ai) while keeping directly-imported internals as real deps. External third-party peers (botbuilder,openai,@microsoft/teams-js,@modelcontextprotocol/sdk,@a2a-js/sdk) remain as peer deps.@microsoft/teams.devdependency fromteams.mcp(and associatedDevtoolsPlugincode) - This requires a dependency on dev, which is not right. Users should be able to run this plugin without installing dev since dev is an optional plugin.Dependency principles applied
Test plan
teams newscaffolds correct depsskip-test-verification