Skip to content

Commit 522a9e9

Browse files
Dumbrisclaude
andauthored
feat(auth): scoped agent tokens for autonomous AI agents [#28] (#319)
* feat(auth): add agent tokens design spec and implementation plan Design and speckit artifacts for scoped agent tokens feature. Agent tokens allow autonomous AI agents to access MCPProxy with restricted server access, permission tiers, and automatic expiry. ## Artifacts - Design doc: docs/plans/2026-03-06-agent-tokens-design.md - Teams auth design: docs/plans/2026-03-06-mcpproxy-teams-auth-design.md - Spec: specs/028-agent-tokens/spec.md (6 user stories, 20 FRs) - Plan: specs/028-agent-tokens/plan.md - Research: specs/028-agent-tokens/research.md - Data model: specs/028-agent-tokens/data-model.md - API contracts: specs/028-agent-tokens/contracts/agent-tokens-api.yaml - Tasks: specs/028-agent-tokens/tasks.md (43 tasks across 8 phases) * feat(auth): implement agent token foundation (Phase 1+2, T001-T009) Add the internal/auth package with token generation, HMAC-SHA256 hashing, format validation, permission constants, AuthContext for request-scoped identity propagation, and file-based HMAC key management. Add BBolt storage layer with dual-bucket design (hash->record, name->hash) supporting CRUD, revocation, regeneration, last-used tracking, and token validation with expiry/revocation checks. Includes 37 passing tests covering all functionality with race detection clean. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(auth): implement REST API token management and auth middleware (Phase 3+4, T010-T022) Phase 3 (Auth Middleware): - Add AuthContext injection in apiKeyAuthMiddleware for admin/agent token auth - Add mcpAuthMiddleware for MCP endpoint agent token scope enforcement - Support agent token validation via mcp_agt_ prefix in X-API-Key header - Wire ExtractToken helper for unified token extraction from headers/query params - Tray connections automatically get admin AuthContext Phase 4 (REST API Token Management): - Create internal/httpapi/tokens.go with 5 REST handlers: - POST /api/v1/tokens (create with name/permissions/servers/expiry validation) - GET /api/v1/tokens (list without secrets) - GET /api/v1/tokens/{name} (get single token info) - DELETE /api/v1/tokens/{name} (revoke) - POST /api/v1/tokens/{name}/regenerate (regenerate secret) - All endpoints reject agent token auth with 403 - TokenStore interface for testable storage abstraction - Validation helpers: name regex, permissions, expiry parsing (max 365d), server names - Wire storage via SetTokenStore() in server initialization - Register routes in setupRoutes() under /api/v1/tokens Tests (27 test functions): - Token CRUD lifecycle tests (create, list, get, revoke, regenerate) - Validation: name format, permissions, expiry duration, allowed servers - Security: agent token rejection (403), admin access, no-store handling (500) - Validation helper unit tests (name, expiry, allowed servers) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(auth): add CLI token commands and comprehensive auth/scope tests (Phase 3 completion) Add token CLI subcommands (create/list/show/revoke) and test suites for: - Auth middleware: token extraction priority, agent token validation (valid/expired/revoked/Bearer), admin context propagation, tray bypass - MCP scope enforcement: server access blocking, permission tier checks (read/write/destructive), admin passthrough, upstream server list filtering, quarantine security blocking for agent tokens Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(cli): add token regenerate subcommand (T026, T023) Add `mcpproxy token regenerate <name>` CLI command that calls POST /api/v1/tokens/{name}/regenerate to invalidate the old secret and generate a new one. Displays the new token with a save warning, supports -o json output. Includes test verifying command registration and argument validation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(activity): add agent identity metadata to activity logging (Phase 6, T028-T031) Add auth identity tracking to activity records so tool calls can be attributed to specific agent tokens. Includes: - getAuthMetadata/injectAuthMetadata helpers in mcp.go that extract auth context and inject _auth_ prefixed fields into activity args - Auth metadata injected in handleRetrieveTools, handleCallToolVariant, and legacy handleCallTool before any activity emit calls - AgentName and AuthType filters on ActivityFilter (storage + httpapi) - CLI --agent and --auth-type flags on activity list command - Swagger annotations for new query parameters - Unit tests for getAuthMetadata and injectAuthMetadata functions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(ui): add Agent Tokens web UI page (Phase 7, T034-T037) Add complete web UI for managing agent tokens: - Token API methods in api.ts (list, create, revoke, regenerate) - AgentTokens.vue view with stats bar, table, create dialog, and token secret display with copy-to-clipboard - Route at /tokens and sidebar navigation entry - TypeScript types for AgentTokenInfo, CreateAgentTokenRequest, CreateAgentTokenResponse Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: fix lint issues — remove unused field and function Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: update CLAUDE.md with agent tokens tech context Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add agent tokens feature documentation Covers motivation, quick start, permission tiers, server scoping, require_mcp_auth enforcement, token management CLI/API, activity logging integration, and security model. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(auth): add require_mcp_auth config flag to enforce /mcp authentication When enabled, the /mcp endpoint rejects unauthenticated requests with 401. Tray/socket connections always bypass this check (OS-level auth). Adds CLI flag --require-mcp-auth and config field require_mcp_auth (default: false). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(ui): improve agent tokens with server checkbox list and fix API responses Replace text input for allowed_servers with a checkbox list showing all configured servers with connected/offline badges, plus an "All servers" wildcard option. Fix token API handlers to wrap responses in the standard {success, data} envelope expected by the frontend. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): update token tests for API response envelope format Tests now unwrap the {success, data} envelope before asserting on response fields, matching the writeSuccess/NewSuccessResponse changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(ui): add agent/auth filters to Activity Log and fix token UI issues - Add Auth Type filter (Admin/Agent) and Agent Name filter to Activity Log - Agent name dropdown auto-populates from activity metadata - Fix token secret display: use bg-neutral for dark theme visibility - Fix Copy button: use btn-neutral for dark theme contrast - Fix revoke: handle 204 No Content response in API client Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): skip HMAC key file permissions check on Windows Windows does not support Unix file permissions, so os.FileMode(0600) assertion always fails. Skip this check on Windows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: remove docs/plans/ from tracked files Keep design docs as local-only uncommitted files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com>
1 parent ed10f89 commit 522a9e9

43 files changed

Lines changed: 6940 additions & 28 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ mcpproxy activity export --output audit.jsonl # Export for compliance
9696

9797
See [docs/cli/activity-commands.md](docs/cli/activity-commands.md) for complete reference.
9898

99+
### Agent Token CLI
100+
```bash
101+
mcpproxy token create --name deploy-bot --servers github,gitlab --permissions read,write
102+
mcpproxy token list # List all agent tokens
103+
mcpproxy token show deploy-bot # Show token details
104+
mcpproxy token revoke deploy-bot # Revoke a token
105+
mcpproxy token regenerate deploy-bot # Regenerate token secret
106+
```
107+
108+
See [docs/features/agent-tokens.md](docs/features/agent-tokens.md) for complete reference.
109+
99110
### CLI Output Formatting
100111
```bash
101112
mcpproxy upstream list -o json # JSON output for scripting
@@ -153,6 +164,7 @@ See [docs/socket-communication.md](docs/socket-communication.md) for details.
153164
{
154165
"listen": "127.0.0.1:8080",
155166
"api_key": "your-secret-api-key-here",
167+
"require_mcp_auth": false,
156168
"enable_socket": true,
157169
"enable_web_ui": true,
158170
"mcpServers": [
@@ -220,6 +232,11 @@ See [docs/configuration.md](docs/configuration.md) for complete reference.
220232
| `GET /api/v1/activity` | List activity records with filtering |
221233
| `GET /api/v1/activity/{id}` | Get activity record details |
222234
| `GET /api/v1/activity/export` | Export activity records (JSON/CSV) |
235+
| `POST /api/v1/tokens` | Create agent token |
236+
| `GET /api/v1/tokens` | List agent tokens |
237+
| `GET /api/v1/tokens/{name}` | Get agent token details |
238+
| `DELETE /api/v1/tokens/{name}` | Revoke agent token |
239+
| `POST /api/v1/tokens/{name}/regenerate` | Regenerate agent token secret |
223240
| `GET /events` | SSE stream for live updates |
224241

225242
**Authentication**: Use `X-API-Key` header or `?apikey=` query parameter.
@@ -322,10 +339,12 @@ See `docs/code_execution/` for complete guides:
322339

323340
- **Localhost-only by default**: Core server binds to `127.0.0.1:8080`
324341
- **API key always required**: Auto-generated if not provided
342+
- **Agent tokens**: Scoped credentials for AI agents with server and permission restrictions (`mcp_agt_` prefix, HMAC-SHA256 hashed)
343+
- **`require_mcp_auth`**: When enabled, `/mcp` endpoint rejects unauthenticated requests (default: false for backward compatibility)
325344
- **Quarantine system**: New servers quarantined until manually approved
326345
- **Tool Poisoning Attack (TPA) protection**: Automatic detection of malicious descriptions
327346

328-
See [docs/features/security-quarantine.md](docs/features/security-quarantine.md) for details.
347+
See [docs/features/agent-tokens.md](docs/features/agent-tokens.md) and [docs/features/security-quarantine.md](docs/features/security-quarantine.md) for details.
329348

330349
## Sensitive Data Detection
331350

@@ -496,6 +515,8 @@ See `docs/prerelease-builds.md` for download instructions.
496515
- BBolt database (`~/.mcpproxy/config.db`) - ActivityRecord.Metadata extension (026-pii-detection)
497516
- Go 1.24 (toolchain go1.24.10) + Cobra (CLI), Chi router (HTTP), Zap (logging), existing cliclient, socket detection, config loader (027-status-command)
498517
- `~/.mcpproxy/mcp_config.json` (config file), `~/.mcpproxy/config.db` (BBolt - not directly used) (027-status-command)
518+
- Go 1.24 (toolchain go1.24.10) + Cobra (CLI), Chi router (HTTP), BBolt (storage), Zap (logging), mcp-go (MCP protocol), crypto/hmac + crypto/sha256 (token hashing) (028-agent-tokens)
519+
- BBolt database (`~/.mcpproxy/config.db`) — new `agent_tokens` bucket (028-agent-tokens)
499520

500521
## Recent Changes
501522
- 001-update-version-display: Added Go 1.24 (toolchain go1.24.10)

cmd/mcpproxy/activity_cmd.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ var (
4242
activityNoIcons bool // Disable emoji icons in output
4343
activityDetectionType string // Spec 026: Filter by detection type (e.g., "aws_access_key")
4444
activitySeverity string // Spec 026: Filter by severity level (critical, high, medium, low)
45+
activityAgent string // Spec 028: Filter by agent token name
46+
activityAuthType string // Spec 028: Filter by auth type (admin, agent)
4547

4648
// Show command flags
4749
activityIncludeResponse bool
@@ -72,6 +74,8 @@ type ActivityFilter struct {
7274
SensitiveData *bool // Spec 026: Filter by sensitive data detection
7375
DetectionType string // Spec 026: Filter by detection type
7476
Severity string // Spec 026: Filter by severity level
77+
AgentName string // Spec 028: Filter by agent token name
78+
AuthType string // Spec 028: Filter by auth type (admin, agent)
7579
}
7680

7781
// Validate validates the filter options
@@ -144,6 +148,21 @@ func (f *ActivityFilter) Validate() error {
144148
}
145149
}
146150

151+
// Validate auth_type (Spec 028)
152+
if f.AuthType != "" {
153+
validAuthTypes := []string{"admin", "agent"}
154+
valid := false
155+
for _, at := range validAuthTypes {
156+
if f.AuthType == at {
157+
valid = true
158+
break
159+
}
160+
}
161+
if !valid {
162+
return fmt.Errorf("invalid auth-type '%s': must be one of %v", f.AuthType, validAuthTypes)
163+
}
164+
}
165+
147166
// Validate time formats
148167
if f.StartTime != "" {
149168
if _, err := time.Parse(time.RFC3339, f.StartTime); err != nil {
@@ -213,6 +232,13 @@ func (f *ActivityFilter) ToQueryParams() url.Values {
213232
if f.Severity != "" {
214233
q.Set("severity", f.Severity)
215234
}
235+
// Spec 028: Agent token identity filters
236+
if f.AgentName != "" {
237+
q.Set("agent", f.AgentName)
238+
}
239+
if f.AuthType != "" {
240+
q.Set("auth_type", f.AuthType)
241+
}
216242
return q
217243
}
218244

@@ -722,6 +748,9 @@ func init() {
722748
activityListCmd.Flags().Bool("sensitive-data", false, "Filter to show only activities with sensitive data detected")
723749
activityListCmd.Flags().StringVar(&activityDetectionType, "detection-type", "", "Filter by detection type (e.g., aws_access_key, stripe_key)")
724750
activityListCmd.Flags().StringVar(&activitySeverity, "severity", "", "Filter by severity level: critical, high, medium, low")
751+
// Spec 028: Agent token identity filters
752+
activityListCmd.Flags().StringVar(&activityAgent, "agent", "", "Filter by agent token name")
753+
activityListCmd.Flags().StringVar(&activityAuthType, "auth-type", "", "Filter by auth type: admin, agent")
725754

726755
// Watch command flags
727756
activityWatchCmd.Flags().StringVarP(&activityType, "type", "t", "", "Filter by type (comma-separated): tool_call, system_start, system_stop, internal_tool_call, config_change, policy_decision, quarantine_change, server_change")
@@ -816,6 +845,8 @@ func runActivityList(cmd *cobra.Command, _ []string) error {
816845
SensitiveData: sensitiveDataPtr,
817846
DetectionType: activityDetectionType,
818847
Severity: activitySeverity,
848+
AgentName: activityAgent,
849+
AuthType: activityAuthType,
819850
}
820851

821852
if err := filter.Validate(); err != nil {

cmd/mcpproxy/main.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ var (
6161
logDir string
6262

6363
// Security flags
64+
requireMCPAuth bool
6465
readOnlyMode bool
6566
disableManagement bool
6667
allowServerAdd bool
@@ -122,6 +123,7 @@ func main() {
122123
serverCmd.Flags().BoolVar(&enableSocket, "enable-socket", true, "Enable Unix socket/named pipe for local IPC (default: true)")
123124
serverCmd.Flags().BoolVar(&debugSearch, "debug-search", false, "Enable debug search tool for search relevancy debugging")
124125
serverCmd.Flags().IntVar(&toolResponseLimit, "tool-response-limit", 0, "Tool response limit in characters (0 = disabled, default: 20000 from config)")
126+
serverCmd.Flags().BoolVar(&requireMCPAuth, "require-mcp-auth", false, "Require authentication on /mcp endpoint (agent tokens or API key)")
125127
serverCmd.Flags().BoolVar(&readOnlyMode, "read-only", false, "Enable read-only mode")
126128
serverCmd.Flags().BoolVar(&disableManagement, "disable-management", false, "Disable management features")
127129
serverCmd.Flags().BoolVar(&allowServerAdd, "allow-server-add", true, "Allow adding new servers")
@@ -164,6 +166,9 @@ func main() {
164166
// Add status command
165167
statusCmd := GetStatusCommand()
166168

169+
// Add token command (Spec 028: Agent tokens)
170+
tokenCmd := GetTokenCommand()
171+
167172
// Add commands to root
168173
rootCmd.AddCommand(serverCmd)
169174
rootCmd.AddCommand(searchCmd)
@@ -178,6 +183,7 @@ func main() {
178183
rootCmd.AddCommand(activityCmd)
179184
rootCmd.AddCommand(tuiCmd)
180185
rootCmd.AddCommand(statusCmd)
186+
rootCmd.AddCommand(tokenCmd)
181187

182188
// Setup --help-json for machine-readable help discovery
183189
// This must be called AFTER all commands are added
@@ -384,6 +390,7 @@ func runServer(cmd *cobra.Command, _ []string) error {
384390
cmdLogDir, _ := cmd.Flags().GetString("log-dir")
385391
cmdDebugSearch, _ := cmd.Flags().GetBool("debug-search")
386392
cmdToolResponseLimit, _ := cmd.Flags().GetInt("tool-response-limit")
393+
cmdRequireMCPAuth, _ := cmd.Flags().GetBool("require-mcp-auth")
387394
cmdReadOnlyMode, _ := cmd.Flags().GetBool("read-only")
388395
cmdDisableManagement, _ := cmd.Flags().GetBool("disable-management")
389396
cmdAllowServerAdd, _ := cmd.Flags().GetBool("allow-server-add")
@@ -473,6 +480,9 @@ func runServer(cmd *cobra.Command, _ []string) error {
473480
}
474481

475482
// Apply security settings from command line ONLY if explicitly set
483+
if cmd.Flags().Changed("require-mcp-auth") {
484+
cfg.RequireMCPAuth = cmdRequireMCPAuth
485+
}
476486
if cmd.Flags().Changed("read-only") {
477487
cfg.ReadOnlyMode = cmdReadOnlyMode
478488
}
@@ -492,6 +502,7 @@ func runServer(cmd *cobra.Command, _ []string) error {
492502
logger.Info("Configuration loaded",
493503
zap.String("data_dir", cfg.DataDir),
494504
zap.Int("servers_count", len(cfg.Servers)),
505+
zap.Bool("require_mcp_auth", cfg.RequireMCPAuth),
495506
zap.Bool("read_only_mode", cfg.ReadOnlyMode),
496507
zap.Bool("disable_management", cfg.DisableManagement),
497508
zap.Bool("allow_server_add", cfg.AllowServerAdd),

0 commit comments

Comments
 (0)