I wrote a blog post about setting up PyPI registries — the architecture, the Nexus deployment, the debugging, the gotchas. What that post doesn’t mention is that I didn’t do most of it. Claude Code did.
Not “Claude helped me brainstorm.” Not “I used Claude to generate some config.” The agent sat in my terminal, queried Azure DevOps feeds, created Nexus repositories via REST API, exec’d into Kubernetes pods to debug network issues, refreshed OAuth tokens, compared HTML responses from two different registries, found the root cause of a 404, fixed it with one API call, and then wrote the blog post you read about it.
This post is about how that worked — what tools made it possible, how the agent coordinated across all of them, and what this actually looks like in practice.
The toolchain
An AI agent without tools is a chatbot. Claude Code with the right MCPs and CLIs is a junior engineer who never forgets a flag and never gets distracted by Slack.
Here’s every tool the agent used during the PyPI infrastructure work:
| Tool | What the agent did with it |
|---|---|
| Azure DevOps MCP | Queried ADO feeds, checked permissions, discovered Falcon_PublicPackages feed (141 packages), tried creating mai-pypi across multiple projects |
az CLI |
Got OAuth tokens (az account get-access-token --resource 499b84ac-...), used for auth throughout |
| Nexus REST API | Created all 4 PyPI repos (hosted, azure-proxy, public-proxy, group) via curl to /service/rest/v1/repositories/ |
kubectl |
Exec’d into the Nexus pod to debug network/auth from inside the cluster |
pip / uv |
Tested package installs from ADO feed directly and through Nexus proxy |
| Slack MCP | Sent a message to a team channel asking for ADO feed creation permissions |
gh CLI |
Worked with the GitOps repo — Helm chart, Kustomize overlay, ArgoCD app |
| zblog (custom command) | Wrote the blog post documenting everything |
None of these are exotic. They’re the same tools a human would use. The difference is the agent used all of them in one session, in the right order, without context-switching.
From sketch to architecture
I started with a rough description: “I want a centralized PyPI registry on Azure DevOps, with per-cluster Nexus instances acting as local proxies.” Something like what my team had been discussing in meetings, but I didn’t have the details worked out.
The agent mapped that out. It figured out the layered architecture — ADO as the source of truth, Nexus per cluster as local cache — and produced the ASCII diagram you see in the blog post:
ADO Feed (central)
│
├── Cluster 1: Nexus (local proxy + cache)
├── Cluster 2: Nexus (local proxy + cache)
└── Cluster 3: Nexus (local proxy + cache)
Then it went one level deeper and worked out the Nexus internal structure — the four repos (hosted, azure-proxy, public-proxy, group) and how the group repo combines them into a single URL. I didn’t specify this. The agent understood the Nexus grouping model and applied it to our ADO-plus-public-PyPI situation.
This part is underappreciated. I gave it the “what” and it gave me back the “how,” structured clearly enough that I could review it, understand it, and learn my own architecture. The blog post’s architecture diagram is something the agent produced and I approved — not something I designed and the agent typed up.
The ADO exploration
Before creating anything, the agent needed to understand what we already had in Azure DevOps. This is where the ADO MCP earned its keep.
The agent queried our ADO organization and found the Falcon_PublicPackages feed — 141 existing PyPI packages, already configured with pypi.org as an upstream source. It also tried to create a dedicated mai-pypi feed, which would have been cleaner. Tried the Falcon org, the MAI UX project, the MAI_DPS project, org-scoped — all came back 403: CreateFeed permission denied.
At that point the agent did something I didn’t expect: it used the Slack MCP to send a message to the team channel asking about feed creation permissions. A real question to real people, composed and sent by the agent.
When the permission route turned out to be an enterprise bureaucracy dead-end, the agent pivoted. Falcon_PublicPackages already existed, already had the packages we needed, already proxied pypi.org. Instead of fighting the permission system, the agent pointed our Nexus proxy at the existing feed and moved on.
This is the kind of pragmatic decision you’d want from a junior engineer: try the ideal path, hit a wall, evaluate alternatives, pick the one that works. The agent didn’t ask me what to do — it assessed the situation and made the call.
Building the Nexus repos
Once the agent had the target ADO feed URL and an OAuth token (via az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798), it created all four Nexus repositories through the REST API.
Four curl calls:
pypi-hosted— for packages published directly to this cluster’s Nexuspypi-azure-proxy— proxies to the ADO feed, with the OAuth token as credentialspypi-public-proxy— proxies to pypi.org as fallbackpypi-group— combines all three into one URL
Each call was a POST to /service/rest/v1/repositories/pypi/{type} with the correct JSON payload. The agent constructed all of this from the Nexus API documentation and our specific configuration — ADO feed URL, authentication block, blob store settings, group member ordering (hosted first, then azure-proxy, then public-proxy).
No Nexus UI. No clicking through forms. Just API calls that could be replayed, version-controlled, and debugged.
The debugging was the real showcase
The repos were created. The public proxy worked. The azure proxy returned 404.
What followed was the most impressive part of the whole session. The agent didn’t just try random things — it ran a systematic investigation across multiple tools simultaneously.
The investigation sequence
Step 1: Rule out auth.
The agent refreshed the OAuth token via az CLI, updated the Nexus proxy config via REST API, and invalidated the cache. Still 404. Not auth.
Step 2: Test from inside the pod.
The agent used kubectl exec to get a shell inside the Nexus pod:
kubectl exec -it nexus-repository-manager-6b67997f75-2ltnp -n nexus -- /bin/bash
From inside the pod, it curled the ADO feed directly with the token. HTTP 200. Valid HTML. Package links everywhere. The pod could reach ADO. Network wasn’t the problem.
Step 3: Read the logs.
kubectl logs on the Nexus pod. The error wasn’t a 401 or a timeout — it was:
Failed to resolve package Unable to find reference for itsdangerous-2.2.0-py3-none-any.whl
A format parsing error. Nexus was connecting to ADO, getting the package index, but failing to resolve download URLs. The investigation pivoted from “what’s broken” to “what does Nexus expect vs. what is it getting.”
Step 4: Compare Simple index formats. The agent curled both ADO and pypi.org for the same package’s Simple index page. Both returned valid HTML. Both had absolute download URLs. Both conformed to PEP 503.
Step 5: Follow the redirect chain. ADO’s download URLs do a 303 redirect to Azure Blob Storage. The agent traced this chain and confirmed the redirects worked with proper auth. Downloads were reachable. So why was Nexus failing?
Step 6: Find the root cause.
The agent compared the remoteUrl config of both proxy repos:
// pypi-public-proxy (works)
"remoteUrl": "https://pypi.org/"
// pypi-azure-proxy (broken)
"remoteUrl": "https://pkgs.dev.azure.com/.../pypi/simple/"
Nexus automatically appends /simple/ to the remoteUrl. The public proxy was hitting pypi.org/simple/ — correct. The azure proxy was hitting .../pypi/simple/simple/ — double /simple/. The listing happened to work (ADO is lenient on the index endpoint), but download URL resolution was broken because Nexus’s base URL math was wrong.
Step 7: Fix it.
One API call. Remove /simple/ from the remoteUrl. Invalidate cache. Test again. Flask plus all 7 dependencies installed through ADO, through Nexus, zero errors.
The entire debugging sequence — from “it’s 404” to “it’s fixed” — happened without me touching the terminal. The agent used az, curl, kubectl exec, kubectl logs, the Nexus REST API, and pip in sequence, each step informed by the results of the previous one.
The Nexus deployment itself
I’ve been talking about configuring Nexus, but the agent also handled the deployment — the Helm chart, Kustomize overlay, ArgoCD application, node scheduling, and ingress configuration.
This part used gh CLI and direct work with the GitOps repo. The agent analyzed existing deployment patterns in the repo — how other services were deployed, what the Kustomize structure looked like, what ArgoCD applications expected — and followed the same patterns for Nexus.
Helm values, resource limits, persistent volume claims, Tailscale ingress for internal access — all derived from looking at how similar services were already deployed in the same cluster. The agent didn’t invent a deployment pattern; it read the existing ones and matched them.
The blog as documentation and context recovery
After all the infra work was done, I ran the zblog command — a custom Claude Code slash command that generates blog posts. The agent wrote the entire PyPI registries post from the work it had just done.
This is one of those things that sounds like a gimmick but turns out to be genuinely useful. The blog post serves three purposes:
1. Team documentation. Anyone on the team can read it and understand the architecture, the gotchas, the debugging story. It’s better documentation than a Confluence page because it has narrative and context, not just bullet points.
2. Personal reference. I can revisit the blog to understand my own infrastructure. The agent did the work, but the blog captures the why behind every decision. Six months from now when I need to add another cluster, the blog is my runbook.
3. Context recovery for future sessions. If I start a new Claude Code session to modify the PyPI setup, the agent can read the blog post and immediately know: what architecture we chose, what gotchas we hit, what the current state is. The blog is a context artifact that survives across sessions.
The agent writing documentation about its own work creates a feedback loop: do work → document work → future agent reads documentation → does more work with full context. The blog isn’t just output; it’s infrastructure for future sessions.
What made this work
It’s tempting to frame this as “AI magic.” It’s not. The agent is a coordinator with access to the same CLIs and APIs a human would use. What made it effective was a specific combination of things:
The right MCPs
The Azure DevOps MCP and Slack MCP gave the agent the ability to interact with organizational systems — not just code and terminals, but feeds, permissions, and team communication. Without the ADO MCP, the agent would have been blind to what feeds existed. Without the Slack MCP, it couldn’t have asked for permissions.
CLI fluency
The agent is genuinely good with az, kubectl, curl, pip, uv, and gh. Not “it can run commands” — it knows the flags, understands the output formats, and chains them together logically. az account get-access-token --resource <guid> --query accessToken -o tsv piped into a variable that gets used in the next curl call. That’s not template completion; that’s understanding a workflow.
No context-switching
A human doing this work would have bounced between: the Azure DevOps web UI, a terminal for kubectl, another terminal for curl, a browser tab with Nexus docs, Slack for asking about permissions, and a text editor for the blog post. Each switch costs time and mental state.
The agent does all of it in one session. It queries ADO, runs kubectl, makes API calls, sends Slack messages, and writes prose without ever losing the thread. The work that would take a human a scattered afternoon happens in one coherent session.
Systematic debugging
The debugging story is where the agent was most impressive — not because of any single clever insight, but because of the systematic approach. Rule out auth. Test from inside the pod. Read the logs. Compare the formats. Follow the redirect chain. Each step was chosen based on what the previous step revealed.
A human might have done this in the same order. But they also might have gone down the auth rabbit hole for an hour, or forgotten to invalidate the cache, or not thought to compare the remoteUrl configs side by side. The agent’s advantage isn’t intelligence — it’s consistency. It doesn’t get frustrated, doesn’t skip steps, doesn’t forget what it tried.
What was clunky
Honest accounting. Not everything was smooth.
OAuth token expiry. The az tokens expire in about an hour. For long sessions, the agent had to refresh them multiple times. This is a workflow pain point — ideally Nexus would use a PAT or managed identity for production, but for the initial setup, short-lived tokens added friction.
ADO permissions. The agent hit the CreateFeed permission wall and there was nothing it could do about it. Enterprise permission systems are not API-friendly. The agent’s workaround (use the existing feed) was pragmatic, but the ideal solution (dedicated feed) required a human filing a ServiceNow ticket.
Slack as async communication. The agent can send a Slack message, but it can’t wait for a response in real time. It sent the permissions question and moved on. The response came later, from a human, outside the session. The agent handled this gracefully by not blocking on it, but it’s a reminder that async human interaction is still a boundary.
DNS/networking was still manual. The agent deployed Nexus, configured registries, debugged proxies — but setting up the public DNS record (nexus-falconstaging.mai.microsoft.com) required a human. DNS provisioning in enterprise environments involves different teams, different systems, different approval flows. The agent can’t file a DNS request.
What someone else would need
If you want similar capabilities from your own Claude Code setup, here’s what to install:
| Component | What it enables | Setup |
|---|---|---|
| Azure DevOps MCP | Query feeds, repos, pipelines, permissions | MCP server config |
| Slack MCP | Send messages, read channels | MCP server config |
az CLI |
Azure auth, token generation | brew install azure-cli + az login |
kubectl |
Cluster access, pod exec, log reading | Cluster kubeconfig |
gh CLI |
GitHub repo operations, PR creation | brew install gh + gh auth login |
uv |
Fast Python package management for testing | brew install uv |
| Custom commands | Blog writing, project workflows | .claude/commands/ directory |
The MCPs are the multiplier. Without them, the agent is limited to what’s in your terminal. With them, it can interact with organizational systems — ticketing, communication, artifact management — the same way a human team member would.
The meta-observation
The PyPI infrastructure blog post reads like I did the work. First person, “I configured,” “I debugged,” “we tried.” That’s accurate in the sense that I directed the work and approved the decisions. But the hands on the keyboard — querying APIs, writing curl commands, reading logs, composing the blog — were the agent’s.
This isn’t about replacing engineers. I still needed to understand the architecture to approve it. I still needed to evaluate whether “use the existing feed” was an acceptable compromise. I still needed to know enough about Nexus, ADO, and Kubernetes to recognize when the agent was doing the right thing.
What changed is the ratio. Instead of spending 80% of my time on execution and 20% on decisions, I spent 80% on decisions and 20% on reviewing execution. The agent handled the tedious, error-prone, multi-tool coordination that normally eats an afternoon. I handled the judgment calls that require organizational context.
The blog post about it? Also written by the agent. This one too.