Terminal — ai coding agent — ~/bowtie-internal
~/bowtie-internal $ bt-vibe
bt-vibe-coding-cli v1.0.40
Model: a-random-sota-model via LLM Gateway
MCP Servers:
✓ notion (remote — OAuth)
✓ slack (remote — OAuth)
● bowtie-internal (remote — OAuth) — Not authenticated
You: Use the bowtie-internal MCP to look up ticket BT-4582
Agent: I'll look up that ticket for you.
tool get_internal_data
{
"ticket_id": "BT-4582"
}
mcp bowtie-internal requires authentication
Server returned: 401 Unauthorized — Bearer token required
Agent: The bowtie-internal MCP server requires OAuth authentication.
Opening browser for SSO login...
◠ Waiting for OAuth callback on localhost:9876...
Terminal — ai coding agent — ~/bowtie-internal
... (OAuth flow completed in browser)
oauth ✓ Token received from Cognito
User: gk@bt.com
Scopes: openid, read:tickets, read:users
Expires: 3600s
MCP Servers:
✓ notion (remote — OAuth)
✓ slack (remote — OAuth)
✓ bowtie-internal (remote — OAuth) — Authenticated as gk
Agent: Great, I'm now authenticated. Let me look up ticket BT-4582.
tool get_internal_data
// params
{
"ticket_id": "BT-4582"
}
// output
{
"ticket_id": "BT-4582",
"title": "Add claim status webhook for partner integration",
"status": "In Progress",
"assignee": "gk@bt.com",
"priority": "High",
"sprint": "Shape Up — Cycle 14"
}
200 ✓ Returned as gk (JWT validated)
Agent: Here's ticket BT-4582:
Add claim status webhook for partner integration
Status: In Progress · Assigned: you · Priority: High
You: Now try to access the HR salary database
Agent: I'll try that query.
tool get_internal_data
// params
{
"resource": "hr_salaries",
"query": "SELECT * FROM salaries"
}
// output
{
"error": "forbidden",
"message": "User gk@bt.com does not have scope 'read:hr_data'",
"required_scope": "read:hr_data",
"user_scopes": ["openid", "read:tickets", "read:users"],
"status": 403
}
403 ✗ Forbidden — missing required scope
Agent: Access denied. Your token only grants read:tickets and read:users.
The HR salary database requires read:hr_data, which your role doesn't include.
This is OAuth working as intended:
The AI agent can only access what you can access. No more, no less.
⚠ Meanwhile, in the shared-API-key timeline...
💀 The "Old Way" — hardcoded credentials
✗Shared API key baked into every developer's mcp.json
✗Everyone gets admin-level access — HR data, financials, all of it
✗Audit log says "api-key-prod" — who actually made the call?
✗Someone leaves? Rotate key → break everyone
✗Key leaked in a repo? Full access until someone notices
// .bt-vibe/mcp.json — the nightmare version
{
"bowtie-internal": {
"type": "stdio",
"command": "npx",
"args": ["bowtie-mcp"],
"env": {
"API_KEY": "sk-prod-SHARED-KEY-123456", // 💀 everyone shares this
"USERNAME": "admin",
"PASSWORD": "B0wt1e2026!" // 💀 hardcoded in chat history
}
}
}
✓ With OAuth MCP
// .bt-vibe/mcp.json — the OAuth version
{
"bowtie-internal": {
"type": "http",
"url": "https://internal-mcp.bowtie.internal"
// That's it. No secrets. OAuth handles auth per-user.
}
}
✓ Per-user identity — audit says exactly who
✓ Scoped access — only what your role allows
✓ Revoke one user — everyone else keeps working
✓ No secrets in config — nothing to leak
✓ SSO integration — same login as everything else
"If you can't do it in the UI, the agent can't do it via MCP"