-
Notifications
You must be signed in to change notification settings - Fork 39
feat: add Anthropic support to AI MCP sample #726
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Umang (umangsehgal)
wants to merge
3
commits into
main
Choose a base branch
from
umangsehgal/anthropic-ai-sample
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,111 +1,122 @@ | ||
| # ai-mcp — Azure OpenAI + MCP sample | ||
| # ai-mcp — multi-provider AI + MCP sample | ||
|
|
||
| A Teams bot powered by **Azure OpenAI** (via the `openai` SDK's `AzureOpenAI` client) and the **Model Context Protocol** SDK. Demonstrates streaming responses, per-conversation memory, a local clarification-card tool, remote MCP server tools, inline citations, follow-up suggestions, and custom feedback. | ||
| A Teams bot powered by either **Azure OpenAI** or **Anthropic Claude**, plus the **Model Context Protocol** SDK. It demonstrates streaming responses, per-conversation memory, a local clarification-card tool, remote MCP tools, inline citations, follow-up suggestions, and custom feedback. | ||
|
|
||
| This is the TypeScript counterpart to the .NET [`ExtAIBot`](https://github.com/microsoft/teams.net/pull/486) sample. The structural shape matches: a single bot process owns the AI plumbing, runs the tool-call loop in-process, and connects to MCP servers directly. The auto-loop comes from `openai.chat.completions.runTools()` — the OpenAI SDK's helper that auto-executes each tool's `function` callback and feeds the result back to the model until it produces final text. | ||
|
|
||
| > **Provider scope.** This sample is bound to the OpenAI chat-completions wire protocol — Azure OpenAI works; vanilla OpenAI works; non-OpenAI providers do not. (See the .NET sample for an `IChatClient` abstraction that's provider-agnostic — TS has no equivalent today.) | ||
| The Teams layer is provider-neutral: activity handlers call `agent.run(conversationId, text, stream)`. Each provider implementation owns its message history and tool loop. | ||
|
|
||
| ## Features | ||
|
|
||
| - **Streaming** — `runner.on('content', delta => stream.emit(delta))` forwards text token-by-token | ||
| - **Conversation memory** — each conversation keeps its own `Map<conv, ChatCompletionMessageParam[]>` in process | ||
| - **Local tool** — `request_clarification`: a `RunnableToolFunction` whose `function` callback pushes an Adaptive Card into a per-turn bucket and returns a placeholder string. The agent discards its wrap-up text and sends only the card. | ||
| - **MCP client** — connects to the [Microsoft Learn docs MCP server](https://learn.microsoft.com/api/mcp) at startup. Each MCP tool is wrapped as a `RunnableToolFunction` whose `function` callback invokes the server and feeds the raw result into the citation collector before returning it to the model. | ||
| - **Inline citations** — `CitationCollector` parses MCP tool results for `{ contentUrl, title, content }` records; `[N]` markers in the final reply become clickable Teams citation entities. | ||
| - **Follow-up suggestions** — a separate non-streaming `chat.completions.create({ response_format: { type: 'json_schema', ... }})` call produces two short prompts shown as suggested-action chips. | ||
| - **Custom feedback** — every text reply enables `addFeedback('custom')`; clicking thumbs up/down opens a bot-rendered task module, and submissions hit `message.submit.feedback`. | ||
| - **Provider selection** — set `AI_PROVIDER=azure-openai` or `AI_PROVIDER=anthropic`. | ||
| - **Streaming** — forwards model text into the Teams `IStreamer`. | ||
| - **Conversation memory** — keeps provider-native history per Teams conversation. | ||
| - **Local tool** — shows an Adaptive Card when the request needs clarification. | ||
| - **MCP client** — connects to the Microsoft Learn MCP server and exposes its tools to either provider. | ||
| - **Inline citations** — turns `[N]` references from grounded answers into Teams citation entities. | ||
| - **Follow-up suggestions** — generates two suggested-action prompts after a text response. | ||
| - **Custom feedback** — enables Teams feedback controls and handles the feedback dialog. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - Node.js 20+ | ||
| - An **Azure OpenAI resource** with a deployed model (e.g. `gpt-4o`) and an API key. No Foundry project required. | ||
| - A Teams bot registration (App ID + secret). | ||
| - A Teams bot registration with an App ID and secret | ||
| - One model provider: | ||
| - An Azure OpenAI resource, deployment, and API key | ||
| - An Anthropic API key and supported Claude model | ||
|
|
||
| ## Teams CLI | ||
|
|
||
| Use the official Teams CLI (`@microsoft/teams.cli`) to create and manage the Teams app for this sample: | ||
| Install the Teams CLI and create the app registration: | ||
|
|
||
| ```bash | ||
| npm install -g @microsoft/teams.cli | ||
| teams --version | ||
| teams login | ||
| ``` | ||
|
|
||
| Expose this sample's local `/api/messages` endpoint with a tunnel, then create the Teams app: | ||
|
|
||
| ```bash | ||
| teams app create --name "ai-mcp" --endpoint "https://<your-tunnel>/api/messages" --env .env --json | ||
| ``` | ||
|
|
||
| The CLI writes `CLIENT_ID`, `CLIENT_SECRET`, and `TENANT_ID` to your `.env` file and prints an install link for Teams. | ||
| The CLI writes `CLIENT_ID`, `CLIENT_SECRET`, and `TENANT_ID` to `.env`. | ||
|
|
||
| ## Setup | ||
| ## Configure Azure OpenAI | ||
|
|
||
| Add the Azure OpenAI settings to the `.env` created by the CLI: | ||
| Azure OpenAI remains the default provider: | ||
|
|
||
| ```env | ||
| AI_PROVIDER=azure-openai | ||
| AZURE_OPENAI_ENDPOINT=https://<your-resource>.openai.azure.com | ||
| AZURE_OPENAI_API_KEY=<your-api-key> | ||
| AZURE_OPENAI_MODEL_DEPLOYMENT_NAME=<deployment-name> | ||
| AZURE_OPENAI_API_VERSION=2024-10-21 | ||
| ``` | ||
|
|
||
| # Optional — defaults to the public MS Learn MCP endpoint. | ||
| `AZURE_OPENAI_MODEL_DEPLOYMENT_NAME` is the deployment name, not the underlying model name. | ||
|
|
||
| ## Configure Anthropic | ||
|
|
||
| ```env | ||
| AI_PROVIDER=anthropic | ||
| ANTHROPIC_API_KEY=<your-api-key> | ||
| ANTHROPIC_MODEL=<supported-claude-model> | ||
|
|
||
| # Optional; defaults to 4096. | ||
| ANTHROPIC_MAX_TOKENS=4096 | ||
| ``` | ||
|
|
||
| The Anthropic implementation uses the Messages API directly. It streams text with `messages.stream()`, executes returned `tool_use` blocks, appends matching `tool_result` blocks, and continues until Claude returns a final response. | ||
|
|
||
| ## Optional MCP configuration | ||
|
|
||
| Both providers use the same MCP client and tool dispatcher: | ||
|
|
||
| ```env | ||
| MCP_SERVER_URL=https://learn.microsoft.com/api/mcp | ||
| ``` | ||
|
|
||
| `AZURE_OPENAI_MODEL_DEPLOYMENT_NAME` is the **deployment name** on your Azure OpenAI resource, not the base model name. | ||
| The Microsoft Learn endpoint is the default. Startup fails if the configured MCP server cannot be reached. | ||
|
|
||
| ## Running | ||
| ## Run | ||
|
|
||
| ```bash | ||
| npm install | ||
| npm run dev --workspace=@examples/ai-mcp | ||
| ``` | ||
|
|
||
| The bot connects to the MS Learn MCP server at startup and lists its tools before accepting messages. If the MCP server is unreachable, startup fails — by design, since the sample is meant to demonstrate the MCP path. | ||
|
|
||
| ## Example interactions | ||
|
|
||
| - `Tell me about streaming` — ambiguous: the agent calls `request_clarification`; the bot replies with a clarification card. Pick an option → that choice arrives as the next user turn and the agent answers based on it. | ||
| - `How do I stream in teams.ts?` — agent calls an MS Learn search tool, replies with a docs-grounded answer and inline citations, plus two follow-up chips. | ||
| - `How do I list users with Microsoft Graph?` — same MCP search path, lands on Graph docs. | ||
|
|
||
| ## How the pieces fit | ||
|
|
||
| ``` | ||
| src/index.ts — AzureOpenAI client + MCP init + Agent + handler registration | ||
| src/agent.ts — per-conv chat history, runTools auto-loop, follow-ups | ||
| src/local-tools.ts — request_clarification RunnableToolFunction + card builder | ||
| src/mcp-tools.ts — MCP client lifecycle, tool listing, wraps each tool | ||
| as a RunnableToolFunction | ||
| src/citation-collector.ts — parses MCP results, attaches Teams citations to the reply | ||
| src/handlers.ts — message, card.action.clarification, message.fetch-task, | ||
| message.submit.feedback | ||
| - `Tell me about streaming` — asks for clarification with an Adaptive Card. | ||
| - `How do I stream in teams.ts?` — searches Microsoft Learn and returns a cited answer. | ||
| - `How do I list users with Microsoft Graph?` — invokes an MCP search tool and suggests follow-up prompts. | ||
|
|
||
| ## Project structure | ||
|
|
||
| ```text | ||
| src/index.ts Provider selection, MCP initialization, Teams app startup | ||
| src/agent.ts Azure OpenAI agent and shared agent contract | ||
| src/anthropic-agent.ts Anthropic streaming and tool-use loop | ||
| src/prompts.ts Shared system and follow-up prompts | ||
| src/local-tools.ts Provider-neutral clarification behavior plus OpenAI adapter | ||
| src/mcp-tools.ts Provider-neutral MCP execution plus OpenAI adapter | ||
| src/citation-collector.ts MCP citation parsing and Teams citation attachment | ||
| src/handlers.ts Provider-neutral Teams activity handlers | ||
| ``` | ||
|
|
||
| ### Tool loop | ||
| ## Anthropic tool loop | ||
|
|
||
| `openai.chat.completions.runTools({ stream: true, tools })` does the heavy lifting: | ||
| For each model round, `AnthropicAgent`: | ||
|
|
||
| 1. Sends the request with our tool definitions to Azure OpenAI. | ||
| 2. If the model emits a `tool_calls` choice, the runner invokes the matching tool's `function` callback (passing parsed args). | ||
| 3. The tool's return value is appended as a `role: 'tool'` message and the model is re-prompted. | ||
| 4. Steps 2-3 repeat until the model produces a `content` message instead of a tool call. | ||
| 5. Throughout, `content` events fire for each text delta — we forward them straight to the Teams stream. | ||
| 1. Sends the provider-native conversation history, system prompt, and tools. | ||
| 2. Streams text deltas into Teams. | ||
| 3. Appends Claude's complete assistant content to history. | ||
| 4. Executes every returned `tool_use` block. | ||
| 5. Appends corresponding `tool_result` blocks as the next user message. | ||
| 6. Repeats until no tool calls remain. | ||
|
|
||
| Each tool function is responsible for its own side effects: | ||
| Tool failures are returned to Claude with `is_error: true`. A maximum-round guard prevents an unbounded tool loop. | ||
|
|
||
| - `request_clarification` pushes the card into `pendingCards[]`. | ||
| - Each MCP tool invokes the server, feeds the raw result into `CitationCollector.tryExtract`, and returns the text to the model. | ||
| ## Provider-neutral Teams boundary | ||
|
|
||
| After `runner.done()`, the agent inspects `pendingCards` and the citation collector to assemble the final Teams activity. | ||
| `handlers.ts` depends only on `IAgentRunner`: | ||
|
|
||
| ### Clarification flow | ||
| ```typescript | ||
| agent.run(activity.conversation.id, userText, stream); | ||
| ``` | ||
|
|
||
| 1. User: ambiguous question. | ||
| 2. Model calls `request_clarification`. The callback builds the card, pushes it onto `pendingCards`, returns `"Clarification card attached."`. | ||
| 3. The model is re-prompted with that tool result and produces a brief wrap-up (e.g. "I've asked for clarification — please pick an option"). We discard this — `pendingCard` is set, so `replyText` is forced to `''`. | ||
| 4. Bot sends an attachment-only message containing only the card. | ||
| 5. User picks an option, submits → `card.action.clarification` invoke → handler calls `agent.run(conv, choice, stream)` exactly as if the choice were a new user message. | ||
| 6. Model now has full context (its own previous tool call + the user's choice) and answers based on it. | ||
| Switching providers does not change Teams routing, card actions, citations, feedback, or final activity construction. | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,7 @@ | |
| "termsOfUseUrl": "https://www.microsoft.com/legal/terms-of-use" | ||
| }, | ||
| "description": { | ||
| "short": "Teams bot powered by Azure OpenAI + MS Learn MCP", | ||
| "short": "Teams bot powered by AI + MS Learn MCP", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: AI is broad. Consider narrowing it to LLM. For example, this won't run with Supervised learning or Boosted Trees or Reinforcement Learning ... etc. |
||
| "full": "A Teams bot that streams answers grounded in MS Learn docs via the MCP protocol, with inline citations, follow-up chips, a clarification card tool, and custom feedback." | ||
| }, | ||
| "icons": { | ||
|
|
||
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rewrite drops the tool-loop walkthrough, the clarification-flow steps, and the "how the pieces fit" section, and replaces them with Anthropic-specific equivalents. Azure OpenAI is still the default provider, so the default path loses its explanation while the opt-in path gains one.
Could the Azure OpenAI walkthrough come back alongside the Anthropic one? The provider-neutral framing reads well otherwise.