Metadata-Version: 2.4
Name: ado-team-activity-mcp
Version: 0.2.0
Summary: MCP server that surfaces Azure DevOps work-item activity for one or more team rosters (multi-org / multi-tenant), so VS Code Copilot Chat can answer manager-style questions like 'what did Ashish work on last week?' or 'analyze the team's last 6 months'.
Author-email: Ashish <ashish301201@gmail.com>
License: MIT
License-File: LICENSE
Keywords: ado,azure-devops,copilot,mcp,standup,team,vscode,wiql
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Bug Tracking
Classifier: Topic :: Software Development :: Version Control
Requires-Python: >=3.11
Requires-Dist: azure-devops>=7.1.0b4
Requires-Dist: azure-identity>=1.15.0
Requires-Dist: mcp[cli]>=1.0.0
Requires-Dist: msrest>=0.7.1
Requires-Dist: pydantic>=2.5.0
Requires-Dist: python-dateutil>=2.8.2
Requires-Dist: pyyaml>=6.0.1
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: twine>=5.1; extra == 'dev'
Description-Content-Type: text/markdown

# ado-team-activity-mcp

A local **Model Context Protocol (MCP) server** that lets you ask **GitHub Copilot Chat in VS Code**
manager-style questions about Azure DevOps work-item activity across one or more orgs / tenants:

> "What did Ashish work on yesterday?"
> "Give me a team standup for the last 3 days."
> "Analyze Vibhuti's last 6 months."
> "Discover the members of the PCOE Team and add them to my config."
> "Compare Ashish across the pcoe and contoso orgs."

**Read-only against ADO by design** — no work-item create / update / delete / comment is ever made.
The only writes are to your own `config.yaml`, and every write is a **dry-run-by-default, diff-then-confirm, backed-up** operation.

---

## Why 0.2.0 is a hard break

The config schema changed from single-org to multi-org. If you're on 0.1.x:

```yaml
# OLD (0.1.x) — will fail to load on 0.2.0 with a helpful migration message:
organization_url: https://dev.azure.com/your-org
project: YourProject
team: [...]
```

```yaml
# NEW (0.2.0):
default_lookback_days: 1
orgs:
  - id: pcoe
    organization_url: https://dev.azure.com/your-org
    project: YourProject
    # tenant_id: <optional Entra tenant GUID>
    team: [...]
```

That's it — wrap your old config in `orgs:` and give the org a short `id`. The loader will tell you exactly what's wrong if you forget.

---

## Architecture

```
Copilot Chat  ──(MCP tool call)──►  ado-team-activity-mcp (Python, stdio)
                                          │
                          ┌───────────────┼────────────────┐
                          ▼               ▼                ▼
                       ADO org A      ADO org B        local config.yaml
                    (tenant X)      (tenant Y)        (atomic + backed up)
                          ▲
                          │
                  DefaultAzureCredential per tenant (az login)
```

### Read tools

| Tool | Purpose |
|---|---|
| `list_orgs()` | What orgs are configured (id, URL, project, tenant, team size). |
| `list_team_members(org=None)` | Roster for one org or every org. |
| `get_teammate_activity(name, days=?, org=?, include_comments=True)` | One teammate's work-item activity over the window. If alias collides across orgs and `org` isn't given, returns `needs_disambiguation` with the candidate list. |
| `get_team_activity(days=?, org=?, include_comments=True)` | Same, but for the whole roster of one org or every org. |
| `summarize_contributions(name, days=180, org=?, ...)` | Aggregated stats for long windows: by-month with gaps, ownership, cadence, active items, comment samples. Use for trend / perf-review / quarterly questions. |
| `get_work_item(id, org, include_comments=True)` | Drill into one item — fields + last 10 revisions + comments. `org` is required (IDs are per-org). |
| `get_work_item_comments(id, org, top=50)` | Comments only. `org` required. |

### Discovery tools

| Tool | Purpose |
|---|---|
| `discover_team_members(org, team_name=None)` | `None` → list ADO Teams in the org. With a team name → return its members (display_name, unique_name, is_admin). |
| `apply_discovered_team(org, team_name, overwrite=False, dry_run=True)` | One-shot: pull members from ADO and merge into your config (with auto-generated unique aliases). Dry-runs by default. |

### Config-edit tools (all dry-run-by-default)

`add_org`, `remove_org`, `add_team_member`, `update_team_member`, `remove_team_member`, `set_default_lookback_days` — each accepts `dry_run: bool = True`. Dry-run returns a structured diff + `before_yaml`/`after_yaml`. Applying creates a timestamped `.bak` next to the config first, then writes atomically.

### PM-workflow slash-prompts

These appear in Copilot Chat as `/standup`, `/weekly_digest`, `/perf_review`, `/coverage_check`, `/cross_team_lookup`. Each is a templated workflow bound to the right tools so you pick the workflow, not the implementation.

### Cross-tenant re-auth — no crashes

If a tool fails because your `az login` is in the wrong tenant, you don't see a stack trace. You see:

```json
{
  "error": "needs_reauth",
  "org_id": "contoso",
  "tenant_id": "00000000-0000-0000-0000-000000000000",
  "remediation": "az login --tenant 00000000-0000-0000-0000-000000000000",
  "detail": "..."
}
```

…which Copilot Chat will surface to you verbatim: *"Run `az login --tenant 00000000-...` in your terminal."*

---

## Install (recommended path)

You need **Python 3.11+**, **Azure CLI**, and **VS Code** with **GitHub Copilot Chat**.

### 1. Install the server

[pipx](https://pipx.pypa.io) is the cleanest option (isolated venv, exposes the command globally):

```powershell
pipx install ado-team-activity-mcp
```

If you don't have pipx:

```powershell
python -m pip install --user pipx
python -m pipx ensurepath
# restart your shell, then:
pipx install ado-team-activity-mcp
```

Confirm it's on PATH:

```powershell
where.exe ado-team-activity-mcp
```

### 2. Sign in to Azure DevOps

```powershell
az login
```

Add `--tenant <tenant-id>` if your org lives in a non-default tenant. You can sign in to multiple tenants over time — the server caches a separate token per tenant.

### 3. Create your config

Save this as `config.yaml` somewhere stable, e.g. `C:\Users\<you>\.ado-team-activity\config.yaml`:

```yaml
default_lookback_days: 1

orgs:
  - id: pcoe                                       # short id you pick — used as `org="pcoe"` arg
    organization_url: https://dev.azure.com/your-org
    project: YourProject
    # tenant_id: 72f988bf-86f1-41af-91ab-2d7cd011db47   # optional; set for cross-tenant orgs
    team: []                                       # start empty — let discover_team_members fill it
```

Tip: leave `team: []` and ask Copilot to populate it for you (see "Auto-fill your roster" below).

### 4. Register the server with VS Code

**Command Palette → "MCP: Open User Configuration"** — add this under `servers`:

```jsonc
{
  "servers": {
    "ado-team-activity": {
      "type": "stdio",
      "command": "ado-team-activity-mcp",
      "env": {
        "ADO_ACTIVITY_CONFIG": "${userHome}/.ado-team-activity/config.yaml"
      }
    }
  }
}
```

`${userHome}` is a built-in VS Code variable — no per-user editing needed. If `${userHome}` doesn't resolve, use the absolute path with forward slashes (e.g. `C:/Users/<you>/.ado-team-activity/config.yaml`).

If `pipx`'s shim isn't on PATH (some Windows setups), set `command` to the absolute path you got from `(Get-Command ado-team-activity-mcp).Source`.

### 5. Reload VS Code

Then open Copilot Chat in **Agent mode** and try the examples below.

---

## Auto-fill your roster from ADO (no manual typing)

Once an org is configured with `team: []`, just ask:

> *"Discover the team members of the PCOE Team in the pcoe org and add them to my config."*

Copilot will:
1. Call `discover_team_members(org="pcoe")` to list available ADO Teams.
2. Call `discover_team_members(org="pcoe", team_name="PCOE Team")` to fetch members.
3. Call `apply_discovered_team(org="pcoe", team_name="PCOE Team", dry_run=True)` and show you the diff.
4. Wait for you to say "apply" before re-calling with `dry_run=False`. A timestamped `.bak` is created on apply.

---

## Edit your config from chat

> *"Add Vibhuti to the pcoe org. Her UPN is v-vivasani@microsoft.com."*
> *"Mark Ashish as is_admin in the pcoe org."*
> *"Set default_lookback_days to 7."*

Each goes through `dry_run=True` → diff shown → you confirm → atomic write + `.bak`. You can roll back any change by copying the `.bak` file back over `config.yaml`.

---

## Example prompts

**Day-to-day**
- `What did Ashish work on yesterday?`
- `Run a standup for the last 3 days.` *(invokes `/standup`)*
- `Anything blocked on the team this week?`
- `Tell me more about work item 1738 in the pcoe org.`
- `What's the discussion on 1738?`

**Trend / review**
- `Analyze Vibhuti's last 6 months.` *(routes via `summarize_contributions`)*
- `Run a perf-review packet for Abhishek over 6 months.` *(invokes `/perf_review`)*
- `Is Abhishek still active in this project?`

**Multi-org**
- `Compare Ashish across pcoe and contoso.` *(invokes `/cross_team_lookup`)*
- `Give me a weekly digest for every org.` *(invokes `/weekly_digest`)*

**Coverage / risk**
- `Find Active items still assigned to absent owners.` *(invokes `/coverage_check`)*

---

## Privacy & security

- **Read-only against ADO.** The server calls only `query_by_wiql`, `get_work_items`, `get_work_item`, `get_revisions`, `get_comments`, `get_projects`, `get_teams`, `get_team_members_with_extended_properties`. No create/update/delete endpoints exist in the code.
- **Writes are local-only**, always dry-run-by-default, always preceded by a `.bak.<timestamp>` backup of your config.
- **Tokens live in memory** (per-tenant cache), refreshed automatically before expiry. No PAT in config, no credentials in YAML.
- **No telemetry.** Network traffic is only to Azure DevOps and Entra ID for token acquisition.
- **Roster is local to you.** Every manager keeps their own `config.yaml`.

---

## Troubleshooting

| Symptom | Fix |
|---|---|
| `ado-team-activity-mcp: command not found` | `pipx ensurepath`, restart shell (and fully quit VS Code from the tray, not just reload). Or pin the absolute path in `mcp.json`. |
| `Config file uses the old single-org format` | Wrap your old config in `orgs:` as the loader's error message describes. |
| `error: needs_reauth` in a tool response | Run the `remediation` command from the payload verbatim (`az login` or `az login --tenant <id>`). |
| `error: needs_disambiguation` | The alias exists in multiple orgs. Re-call the tool with `org="<one of the candidates>"`. |
| `error: unknown_name` | Check `list_team_members()`. If the person isn't there, ask Copilot to add them or run `discover_team_members` to pull them from ADO. |
| Zero items for someone you know is active | Their `unique_name` doesn't match ADO's UPN. Open a work item assigned to them in ADO, click the Assignee chip, and use the email shown under their name. |

---

## Develop locally (contributors)

```powershell
git clone <repo-url>
cd ado-team-activity-mcp
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -e ".[dev]"
Copy-Item config.example.yaml config.yaml          # then fill in
az login
python scripts\check_auth.py                        # per-org pre-flight
pytest                                              # 24 tests
```

The workspace's [.vscode/mcp.json](.vscode/mcp.json) is wired for development — runs the server out of the local `.venv` using `python -m ado_activity_mcp`. Reload VS Code and the server is available alongside any pipx-installed copy.

### Publish a new version (maintainer)

```powershell
# bump version in pyproject.toml first
pip install --upgrade build twine
python -m build
twine upload dist/*
```

Needs a [PyPI account](https://pypi.org/account/register/) and an API token saved in `~/.pypirc`.

---

## License

MIT — see [LICENSE](LICENSE).
