Skip to content

Commit 9a94213

Browse files
committed
Enhance Azure DevOps Pull Request Agent to support both direct Anthropic API and Microsoft Foundry providers, updating environment variable handling and documentation accordingly.
1 parent 6db8d38 commit 9a94213

5 files changed

Lines changed: 135 additions & 17 deletions

File tree

‎.azdo/azure-pipeline.yaml‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pool:
77
name: '<value>'
88

99
variables:
10-
- group: 'PullRequestAgentSecrets' # Variable group containing secrets like ANTHROPIC_API_KEY
10+
- group: 'PullRequestAgentSecrets' # Contains provider secrets (ANTHROPIC_API_KEY or Foundry vars)
1111

1212
stages:
1313
- stage: Build
@@ -21,6 +21,17 @@ stages:
2121
inputs:
2222
containerRegistry: $(DOCKER_SERVICECONNECTION_NAME)
2323
command: 'run'
24+
# === Provider Configuration (choose ONE) ===
25+
# Option A: Direct Anthropic API
26+
# -e ANTHROPIC_API_KEY=$(ANTHROPIC_API_KEY)
27+
# Option B: Microsoft Foundry (API key auth)
28+
# -e CLAUDE_CODE_USE_FOUNDRY=1
29+
# -e ANTHROPIC_FOUNDRY_RESOURCE=$(ANTHROPIC_FOUNDRY_RESOURCE)
30+
# -e ANTHROPIC_FOUNDRY_API_KEY=$(ANTHROPIC_FOUNDRY_API_KEY)
31+
# Option B alt: Microsoft Foundry (Entra ID auth — no API key)
32+
# -e CLAUDE_CODE_USE_FOUNDRY=1
33+
# -e ANTHROPIC_FOUNDRY_RESOURCE=$(ANTHROPIC_FOUNDRY_RESOURCE)
34+
# =============================================
2435
arguments: >-
2536
--name pr-agent-container
2637
-e ANTHROPIC_API_KEY=$(ANTHROPIC_API_KEY)

‎AGENTS.md‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ This file provides context and rules for AI-assisted coding platforms (GitHub Co
5757
# Restore and build
5858
dotnet build src/ADOPullRequestAgent/ADOPullRequestAgent.csproj
5959

60-
# Run (requires ANTHROPIC_API_KEY env var set)
60+
# Run (requires provider env vars — see Environment Variables below)
6161
dotnet run --project src/ADOPullRequestAgent -- \
6262
--ado-token "<token>" \
6363
--pull-request-id <id> \
@@ -73,16 +73,27 @@ docker build -t ado-pr-agent .
7373

7474
## Environment Variables
7575

76+
The agent supports two Claude Code providers, selected at runtime. Set one group:
77+
7678
| Variable | Required | Description |
7779
|---|---|---|
78-
| `ANTHROPIC_API_KEY` | Yes | Anthropic API key — used by Claude Code CLI for authentication |
80+
| **Direct Anthropic API** | | |
81+
| `ANTHROPIC_API_KEY` | Conditional | Anthropic API key — required when using direct Anthropic API |
82+
| **Microsoft Foundry** | | |
83+
| `CLAUDE_CODE_USE_FOUNDRY` | Conditional | Set to `1` to enable Microsoft Foundry provider |
84+
| `ANTHROPIC_FOUNDRY_RESOURCE` | Conditional | Foundry resource name — used when Foundry is enabled |
85+
| `ANTHROPIC_FOUNDRY_API_KEY` | Conditional | Foundry API key — used when Foundry is enabled with API key auth |
86+
| `ANTHROPIC_DEFAULT_SONNET_MODEL` | No | Optional model deployment name override for Foundry |
87+
| `ANTHROPIC_DEFAULT_HAIKU_MODEL` | No | Optional model deployment name override for Foundry |
88+
| **Internal** | | |
7989
| `ADO_MCP_AUTH_TOKEN` | Runtime | Azure DevOps access token — set internally by `PullRequestAgent` for the MCP server; passed via `--ado-token` CLI arg |
8090

8191
## Key Architecture Decisions
8292

8393
- **Claude Code CLI invocation:** `PullRequestAgent.RunAsync()` shells out to `claude -p` (non-interactive mode) via `System.Diagnostics.Process`. It builds an MCP config JSON at runtime with two MCP servers (Azure DevOps for PR data, Microsoft Learn for best practices) and passes the system prompt via `--system-prompt-file`.
8494
- **System prompt:** The review persona and checklist live in `pullreview.prompt`, which is loaded at runtime. Edits to the review behavior should target this file, not C# code.
8595
- **MCP server configuration:** The Azure DevOps MCP server is configured in a JSON file passed to `--mcp-config`. The ADO authentication token is passed through the `env` field in the MCP config, keeping it out of command-line arguments.
96+
- **Provider selection:** The agent supports both direct Anthropic API and Microsoft Foundry as Claude Code providers, selected at runtime via environment variables. The `ConfigureProviderEnvironment` method in `PullRequestAgent.cs` detects `CLAUDE_CODE_USE_FOUNDRY=1` for Foundry mode, otherwise falls back to `ANTHROPIC_API_KEY`.
8697
- **Output:** The review is written to stdout and optionally to `pull_request_<id>_review.md` in the specified output directory. Claude also saves structured review files (`review/code_review.md` and optionally `review/code_review_summary.md`) as instructed by the system prompt.
8798

8899
## Guidelines for AI Assistants

‎Dockerfile‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ RUN npm install -g @anthropic-ai/claude-code
4141
# Pre-install the Azure DevOps MCP package globally so npx doesn't download it at runtime
4242
RUN npm install -g @azure-devops/mcp@latest
4343

44+
# (Optional) Install Azure CLI for Microsoft Foundry with Entra ID authentication.
45+
# Uncomment the line below if using Foundry with Entra ID auth instead of API key auth.
46+
RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash
47+
4448
COPY --from=build /app/publish .
4549

4650
# Create writable directories for the non-root user

‎README.md‎

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ The review output is a Markdown report that can be saved to a file or posted dir
4747
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
4848
- [Node.js](https://nodejs.org/) (for the Azure DevOps MCP server via `npx`)
4949
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed (`npm install -g @anthropic-ai/claude-code`)
50-
- An **Anthropic API key** (set as `ANTHROPIC_API_KEY` environment variable)
50+
- **One** of the following authentication providers:
51+
- **Direct Anthropic API** — an Anthropic API key (set as `ANTHROPIC_API_KEY`)
52+
- **Microsoft Foundry** — a Foundry resource with API key (`ANTHROPIC_FOUNDRY_API_KEY`) or Entra ID auth (`az login`)
5153
- An **Azure DevOps access token** with read access to the target repository and pull requests
5254

5355
## Getting started
@@ -63,9 +65,21 @@ cd ADOPullRequestAgent
6365

6466
### 2. Set environment variables
6567

68+
Choose **one** provider:
69+
6670
```bash
67-
# Required by Claude Code CLI
71+
# Option A: Direct Anthropic API
6872
export ANTHROPIC_API_KEY="<your-anthropic-api-key>"
73+
74+
# Option B: Microsoft Foundry (API key auth)
75+
export CLAUDE_CODE_USE_FOUNDRY=1
76+
export ANTHROPIC_FOUNDRY_RESOURCE="<your-foundry-resource-name>"
77+
export ANTHROPIC_FOUNDRY_API_KEY="<your-foundry-api-key>"
78+
79+
# Option B alt: Microsoft Foundry (Entra ID auth — no API key needed)
80+
export CLAUDE_CODE_USE_FOUNDRY=1
81+
export ANTHROPIC_FOUNDRY_RESOURCE="<your-foundry-resource-name>"
82+
az login # authenticate with Azure first
6983
```
7084

7185
### 3. Obtain an Azure DevOps token
@@ -115,6 +129,8 @@ Build and run the agent as a container:
115129

116130
```bash
117131
docker build -t ado-pr-agent .
132+
133+
# Option A: Direct Anthropic API
118134
docker run --rm \
119135
-e ANTHROPIC_API_KEY="<your-api-key>" \
120136
-v /path/to/local/clone:/sources \
@@ -126,6 +142,21 @@ docker run --rm \
126142
--repository-name <repo> \
127143
--model "claude-sonnet-4-20250514" \
128144
--sources-directory /sources
145+
146+
# Option B: Microsoft Foundry
147+
docker run --rm \
148+
-e CLAUDE_CODE_USE_FOUNDRY=1 \
149+
-e ANTHROPIC_FOUNDRY_RESOURCE="<your-resource-name>" \
150+
-e ANTHROPIC_FOUNDRY_API_KEY="<your-foundry-api-key>" \
151+
-v /path/to/local/clone:/sources \
152+
ado-pr-agent \
153+
--ado-token "<ado-token>" \
154+
--pull-request-id <pr-id> \
155+
--organization-name <org> \
156+
--project-name <project> \
157+
--repository-name <repo> \
158+
--model "claude-sonnet-4-6" \
159+
--sources-directory /sources
129160
```
130161

131162
## Azure DevOps pipeline integration
@@ -144,16 +175,24 @@ The agent is designed to run as a **build validation policy** on pull requests.
144175
145176
### Required pipeline variables
146177

147-
These should be configured in a variable group (e.g., `Claude-Code`):
178+
These should be configured in a variable group (e.g., `PullRequestAgentSecrets`):
148179

149180
| Variable | Description |
150181
|---|---|
151-
| `ANTHROPIC_API_KEY` | Anthropic API key for Claude Code CLI |
152182
| `ACR_NAME` | Azure Container Registry name |
153183
| `DOCKER_SERVICECONNECTION_NAME` | ACR service connection |
154184
| `ADO_ORGANIZATION_NAME` | Azure DevOps organization |
155185
| `MODEL_NAME` | The Claude model to use (e.g., `claude-sonnet-4-20250514`) |
156186

187+
**Provider variables** (choose one set):
188+
189+
| Variable | Provider | Description |
190+
|---|---|---|
191+
| `ANTHROPIC_API_KEY` | Direct Anthropic | Anthropic API key |
192+
| `CLAUDE_CODE_USE_FOUNDRY` | Foundry | Set to `1` to enable Foundry mode |
193+
| `ANTHROPIC_FOUNDRY_RESOURCE` | Foundry | Foundry resource name |
194+
| `ANTHROPIC_FOUNDRY_API_KEY` | Foundry | Foundry API key (if not using Entra ID) |
195+
157196
> [!NOTE]
158197
> The pipeline uses `$(System.AccessToken)` for Azure DevOps authentication, so no additional ADO PAT is required when running within a pipeline.
159198

‎src/ADOPullRequestAgent/PullRequestAgent.cs‎

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ private string BuildMcpConfig(string organizationName)
114114
},
115115
["microsoft-learn"] = new
116116
{
117-
type = "url",
117+
type = "sse",
118118
url = "https://learn.microsoft.com/api/mcp"
119119
}
120120
}
@@ -135,7 +135,8 @@ private List<string> BuildClaudeArguments(string systemPromptPath, string mcpCon
135135
"--model", _agentOptions.Model,
136136
"--system-prompt-file", systemPromptPath,
137137
"--mcp-config", mcpConfigPath,
138-
"--no-session-persistence"
138+
"--no-session-persistence",
139+
"--allowedTools", "mcp__azure-devops__*,mcp__microsoft-learn__*,Bash(git *),Bash(cat *),Read,Write,Glob,Grep,Edit"
139140
};
140141

141142
if (_agentOptions.MaxTurns.HasValue)
@@ -170,15 +171,10 @@ private List<string> BuildClaudeArguments(string systemPromptPath, string mcpCon
170171
WorkingDirectory = _agentOptions.SourcesDirectory
171172
};
172173

173-
// Pass ANTHROPIC_API_KEY from current environment
174-
var anthropicApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY");
175-
if (string.IsNullOrWhiteSpace(anthropicApiKey))
176-
{
177-
throw new InvalidOperationException("ANTHROPIC_API_KEY environment variable is not set. This is required for Claude Code CLI.");
178-
}
179-
startInfo.Environment["ANTHROPIC_API_KEY"] = anthropicApiKey;
174+
// Configure the Claude Code provider (direct Anthropic API or Microsoft Foundry)
175+
ConfigureProviderEnvironment(startInfo, logger);
180176

181-
// Pass ADO token for the MCP server
177+
// Pass ADO token for the MCP server (provider-independent)
182178
startInfo.Environment["ADO_MCP_AUTH_TOKEN"] = _adoMcpAuthenticationToken;
183179

184180
foreach (var arg in arguments)
@@ -235,6 +231,63 @@ private List<string> BuildClaudeArguments(string systemPromptPath, string mcpCon
235231
return (process.ExitCode, stdout, stderr);
236232
}
237233

234+
/// <summary>
235+
/// Detects the configured Claude Code provider and sets the appropriate environment variables
236+
/// on the process start info. Supports direct Anthropic API and Microsoft Foundry.
237+
/// </summary>
238+
/// <param name="startInfo">The process start info to configure with provider environment variables.</param>
239+
/// <param name="logger">Logger for recording which provider is selected.</param>
240+
/// <exception cref="InvalidOperationException">
241+
/// Thrown when no provider is configured (neither ANTHROPIC_API_KEY nor CLAUDE_CODE_USE_FOUNDRY is set).
242+
/// </exception>
243+
private static void ConfigureProviderEnvironment(ProcessStartInfo startInfo, ILogger logger)
244+
{
245+
var useFoundry = Environment.GetEnvironmentVariable("CLAUDE_CODE_USE_FOUNDRY");
246+
var anthropicApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY");
247+
248+
if (string.Equals(useFoundry, "1", StringComparison.Ordinal))
249+
{
250+
// Microsoft Foundry mode — forward all relevant Foundry env vars
251+
startInfo.Environment["CLAUDE_CODE_USE_FOUNDRY"] = "1";
252+
253+
string[] foundryVars =
254+
[
255+
"ANTHROPIC_FOUNDRY_RESOURCE",
256+
"ANTHROPIC_FOUNDRY_BASE_URL",
257+
"ANTHROPIC_FOUNDRY_API_KEY",
258+
"ANTHROPIC_DEFAULT_SONNET_MODEL",
259+
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
260+
"ANTHROPIC_DEFAULT_OPUS_MODEL"
261+
];
262+
263+
foreach (var name in foundryVars)
264+
{
265+
var value = Environment.GetEnvironmentVariable(name);
266+
if (!string.IsNullOrWhiteSpace(value))
267+
{
268+
startInfo.Environment[name] = value;
269+
}
270+
}
271+
272+
var resource = Environment.GetEnvironmentVariable("ANTHROPIC_FOUNDRY_RESOURCE") ?? "(not set)";
273+
logger.LogInformation("Using Microsoft Foundry provider (resource: {Resource})", resource);
274+
}
275+
else if (!string.IsNullOrWhiteSpace(anthropicApiKey))
276+
{
277+
// Direct Anthropic API mode
278+
startInfo.Environment["ANTHROPIC_API_KEY"] = anthropicApiKey;
279+
logger.LogInformation("Using direct Anthropic API provider");
280+
}
281+
else
282+
{
283+
throw new InvalidOperationException(
284+
"No Claude Code provider configured. Set one of the following:" + Environment.NewLine +
285+
" - ANTHROPIC_API_KEY for direct Anthropic API access" + Environment.NewLine +
286+
" - CLAUDE_CODE_USE_FOUNDRY=1 with ANTHROPIC_FOUNDRY_RESOURCE for Microsoft Foundry access" + Environment.NewLine +
287+
"See README.md for details.");
288+
}
289+
}
290+
238291
/// <summary>
239292
/// Attempts to delete a file, suppressing any exceptions.
240293
/// </summary>

0 commit comments

Comments
 (0)