Skip to content

Commit 911d032

Browse files
committed
Merge PR smart-mcp-proxy#255 - OAuth token refresh reliability
2 parents 2a05427 + da4ca40 commit 911d032

File tree

183 files changed

+4753
-1549
lines changed

Some content is hidden

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

183 files changed

+4753
-1549
lines changed

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,8 @@ See `docs/prerelease-builds.md` for download instructions.
393393
- BBolt database (`~/.mcpproxy/config.db`) - activity log extended with request_id field (021-request-id-logging)
394394
- Go 1.24 (toolchain go1.24.10) + mcp-go v0.43.1 (OAuth client), BBolt (storage), Prometheus (metrics), Zap (logging) (023-oauth-state-persistence)
395395
- BBolt database (`~/.mcpproxy/config.db`) - `oauth_tokens` bucket with `OAuthTokenRecord` model (023-oauth-state-persistence)
396+
- Go 1.24 (toolchain go1.24.10) + TypeScript 5.x / Vue 3.5 + Cobra CLI, Chi router, BBolt storage, Zap logging, mark3labs/mcp-go, Vue 3, Tailwind CSS, DaisyUI (024-expand-activity-log)
397+
- BBolt database (`~/.mcpproxy/config.db`) - ActivityRecord model (024-expand-activity-log)
396398

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

cmd/mcpproxy-tray/internal/api/adapter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"path/filepath"
88
"runtime"
99

10-
internalRuntime "mcpproxy-go/internal/runtime"
10+
internalRuntime "github.com/smart-mcp-proxy/mcpproxy-go/internal/runtime"
1111
)
1212

1313
// ClientInterface defines the methods required by ServerAdapter from the API client.

cmd/mcpproxy-tray/internal/api/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import (
2020

2121
"go.uber.org/zap"
2222

23-
"mcpproxy-go/internal/socket"
24-
"mcpproxy-go/internal/tray"
23+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/socket"
24+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/tray"
2525
)
2626

2727
// HealthStatus represents the unified health status of an upstream MCP server.

cmd/mcpproxy-tray/internal/monitor/health.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import (
1010

1111
"go.uber.org/zap"
1212

13-
"mcpproxy-go/cmd/mcpproxy-tray/internal/api"
14-
"mcpproxy-go/cmd/mcpproxy-tray/internal/state"
15-
"mcpproxy-go/internal/socket"
13+
"github.com/smart-mcp-proxy/mcpproxy-go/cmd/mcpproxy-tray/internal/api"
14+
"github.com/smart-mcp-proxy/mcpproxy-go/cmd/mcpproxy-tray/internal/state"
15+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/socket"
1616
)
1717

1818
// HealthStatus represents the health status of the core service

cmd/mcpproxy-tray/internal/monitor/process.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616

1717
"go.uber.org/zap"
1818

19-
"mcpproxy-go/cmd/mcpproxy-tray/internal/state"
19+
"github.com/smart-mcp-proxy/mcpproxy-go/cmd/mcpproxy-tray/internal/state"
2020
)
2121

2222
// ProcessStatus represents the status of a monitored process

cmd/mcpproxy-tray/internal/monitor/process_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515

1616
"go.uber.org/zap"
1717

18-
"mcpproxy-go/cmd/mcpproxy-tray/internal/state"
18+
"github.com/smart-mcp-proxy/mcpproxy-go/cmd/mcpproxy-tray/internal/state"
1919
)
2020

2121
// Windows-specific ProcessMonitor implementation without POSIX process groups.

cmd/mcpproxy-tray/main.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ import (
2828
"go.uber.org/zap"
2929
"go.uber.org/zap/zapcore"
3030

31-
"mcpproxy-go/cmd/mcpproxy-tray/internal/api"
32-
"mcpproxy-go/cmd/mcpproxy-tray/internal/monitor"
33-
"mcpproxy-go/cmd/mcpproxy-tray/internal/state"
34-
"mcpproxy-go/internal/socket"
35-
"mcpproxy-go/internal/tray"
31+
"github.com/smart-mcp-proxy/mcpproxy-go/cmd/mcpproxy-tray/internal/api"
32+
"github.com/smart-mcp-proxy/mcpproxy-go/cmd/mcpproxy-tray/internal/monitor"
33+
"github.com/smart-mcp-proxy/mcpproxy-go/cmd/mcpproxy-tray/internal/state"
34+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/socket"
35+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/tray"
3636
)
3737

3838
const (

cmd/mcpproxy/activity_cmd.go

Lines changed: 184 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ import (
1818
"github.com/spf13/cobra"
1919
"go.uber.org/zap"
2020

21-
"mcpproxy-go/internal/cli/output"
22-
"mcpproxy-go/internal/cliclient"
23-
"mcpproxy-go/internal/config"
24-
"mcpproxy-go/internal/logs"
25-
"mcpproxy-go/internal/socket"
21+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/cli/output"
22+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/cliclient"
23+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/config"
24+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/logs"
25+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/socket"
2626
)
2727

2828
// Activity command flags
@@ -71,18 +71,26 @@ type ActivityFilter struct {
7171

7272
// Validate validates the filter options
7373
func (f *ActivityFilter) Validate() error {
74-
// Validate type
74+
// Validate type(s) - supports comma-separated values (Spec 024)
7575
if f.Type != "" {
76-
validTypes := []string{"tool_call", "policy_decision", "quarantine_change", "server_change"}
77-
valid := false
78-
for _, t := range validTypes {
79-
if f.Type == t {
80-
valid = true
81-
break
82-
}
76+
validTypes := []string{
77+
"tool_call", "policy_decision", "quarantine_change", "server_change",
78+
"system_start", "system_stop", "internal_tool_call", "config_change", // Spec 024: new types
8379
}
84-
if !valid {
85-
return fmt.Errorf("invalid type '%s': must be one of %v", f.Type, validTypes)
80+
// Split by comma for multi-type support
81+
types := strings.Split(f.Type, ",")
82+
for _, t := range types {
83+
t = strings.TrimSpace(t)
84+
valid := false
85+
for _, vt := range validTypes {
86+
if t == vt {
87+
valid = true
88+
break
89+
}
90+
}
91+
if !valid {
92+
return fmt.Errorf("invalid type '%s': must be one of %v", t, validTypes)
93+
}
8694
}
8795
}
8896

@@ -517,7 +525,7 @@ func init() {
517525
activityCmd.AddCommand(activityExportCmd)
518526

519527
// List command flags
520-
activityListCmd.Flags().StringVarP(&activityType, "type", "t", "", "Filter by type: tool_call, policy_decision, quarantine_change, server_change")
528+
activityListCmd.Flags().StringVarP(&activityType, "type", "t", "", "Filter by type (comma-separated for multiple): tool_call, system_start, system_stop, internal_tool_call, config_change, policy_decision, quarantine_change, server_change")
521529
activityListCmd.Flags().StringVarP(&activityServer, "server", "s", "", "Filter by server name")
522530
activityListCmd.Flags().StringVar(&activityTool, "tool", "", "Filter by tool name")
523531
activityListCmd.Flags().StringVar(&activityStatus, "status", "", "Filter by status: success, error, blocked")
@@ -531,7 +539,7 @@ func init() {
531539
activityListCmd.Flags().BoolVar(&activityNoIcons, "no-icons", false, "Disable emoji icons in output (use text instead)")
532540

533541
// Watch command flags
534-
activityWatchCmd.Flags().StringVarP(&activityType, "type", "t", "", "Filter by type: tool_call, policy_decision")
542+
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")
535543
activityWatchCmd.Flags().StringVarP(&activityServer, "server", "s", "", "Filter by server name")
536544

537545
// Show command flags
@@ -547,7 +555,7 @@ func init() {
547555
activityExportCmd.Flags().StringVarP(&activityExportFormat, "format", "f", "json", "Export format: json, csv")
548556
activityExportCmd.Flags().BoolVar(&activityIncludeBodies, "include-bodies", false, "Include full request/response bodies")
549557
// Reuse list filter flags for export
550-
activityExportCmd.Flags().StringVarP(&activityType, "type", "t", "", "Filter by type")
558+
activityExportCmd.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")
551559
activityExportCmd.Flags().StringVarP(&activityServer, "server", "s", "", "Filter by server name")
552560
activityExportCmd.Flags().StringVar(&activityTool, "tool", "", "Filter by tool name")
553561
activityExportCmd.Flags().StringVar(&activityStatus, "status", "", "Filter by status")
@@ -863,8 +871,9 @@ func watchActivityStream(ctx context.Context, sseURL string, outputFormat string
863871
eventData = strings.TrimPrefix(line, "data: ")
864872
case line == "":
865873
// Empty line = event complete
866-
// Only display completed events (started events have no status/duration)
867-
if strings.HasPrefix(eventType, "activity.") && strings.HasSuffix(eventType, ".completed") {
874+
// Display all activity events except .started (which have no meaningful status/duration)
875+
// Includes: .completed, policy_decision, system_start, system_stop, config_change
876+
if strings.HasPrefix(eventType, "activity.") && !strings.HasSuffix(eventType, ".started") {
868877
displayActivityEvent(eventType, eventData, outputFormat)
869878
}
870879
eventType, eventData = "", ""
@@ -895,42 +904,81 @@ func displayActivityEvent(eventType, eventData, outputFormat string) {
895904
event = wrapper
896905
}
897906

907+
// Determine event category from eventType (e.g., "activity.tool_call.completed" -> "tool_call")
908+
parts := strings.Split(eventType, ".")
909+
eventCategory := ""
910+
if len(parts) >= 2 {
911+
eventCategory = parts[1]
912+
}
913+
898914
// Apply client-side filters
899915
if activityServer != "" {
900-
if server := getStringField(event, "server_name"); server != activityServer {
916+
// For tool_call events, check server_name
917+
// For internal_tool_call events, check target_server
918+
server := getStringField(event, "server_name")
919+
if server == "" {
920+
server = getStringField(event, "target_server")
921+
}
922+
if server == "" {
923+
server = getStringField(event, "affected_entity") // for config_change
924+
}
925+
if server != activityServer {
901926
return
902927
}
903928
}
904929
if activityType != "" {
905-
// Event type is like "activity.tool_call.completed", extract the middle part
906-
parts := strings.Split(eventType, ".")
907-
if len(parts) >= 2 && parts[1] != activityType {
930+
if eventCategory != activityType {
931+
return
932+
}
933+
}
934+
935+
// Skip successful call_tool_* internal tool calls to avoid duplicates
936+
// These have a corresponding tool_call entry that shows the actual upstream call.
937+
// Failed call_tool_* calls are shown since they have no corresponding tool_call.
938+
if eventCategory == "internal_tool_call" {
939+
internalToolName := getStringField(event, "internal_tool_name")
940+
status := getStringField(event, "status")
941+
if status == "success" && strings.HasPrefix(internalToolName, "call_tool_") {
908942
return
909943
}
910944
}
911945

912-
// Format for table output: [HH:MM:SS] [SRC] server:tool status duration
946+
// Format output based on event type
913947
timestamp := time.Now().Format("15:04:05")
948+
949+
var line string
950+
switch eventCategory {
951+
case "tool_call":
952+
line = formatToolCallEvent(event, timestamp)
953+
case "internal_tool_call":
954+
line = formatInternalToolCallEvent(event, timestamp)
955+
case "policy_decision":
956+
line = formatPolicyDecisionEvent(event, timestamp)
957+
case "system_start":
958+
line = formatSystemStartEvent(event, timestamp)
959+
case "system_stop":
960+
line = formatSystemStopEvent(event, timestamp)
961+
case "config_change":
962+
line = formatConfigChangeEvent(event, timestamp)
963+
default:
964+
// Fallback for unknown event types
965+
line = fmt.Sprintf("[%s] [?] %s", timestamp, eventType)
966+
}
967+
968+
fmt.Println(line)
969+
}
970+
971+
// formatToolCallEvent formats a tool_call event for display
972+
func formatToolCallEvent(event map[string]interface{}, timestamp string) string {
914973
source := getStringField(event, "source")
915974
server := getStringField(event, "server_name")
916975
tool := getStringField(event, "tool_name")
917976
status := getStringField(event, "status")
918977
durationMs := getIntField(event, "duration_ms")
919978
errMsg := getStringField(event, "error_message")
920979

921-
// Source indicator
922980
sourceIcon := formatSourceIndicator(source)
923-
924-
// Status indicator
925-
statusIcon := "?"
926-
switch status {
927-
case "success":
928-
statusIcon = "\u2713" // checkmark
929-
case "error":
930-
statusIcon = "\u2717" // X
931-
case "blocked":
932-
statusIcon = "\u2298" // circle with slash
933-
}
981+
statusIcon := formatStatusIcon(status)
934982

935983
line := fmt.Sprintf("[%s] [%s] %s:%s %s %s", timestamp, sourceIcon, server, tool, statusIcon, formatActivityDuration(int64(durationMs)))
936984
if errMsg != "" {
@@ -939,8 +987,106 @@ func displayActivityEvent(eventType, eventData, outputFormat string) {
939987
if status == "blocked" {
940988
line += " BLOCKED"
941989
}
990+
return line
991+
}
942992

943-
fmt.Println(line)
993+
// formatInternalToolCallEvent formats an internal_tool_call event for display
994+
func formatInternalToolCallEvent(event map[string]interface{}, timestamp string) string {
995+
internalTool := getStringField(event, "internal_tool_name")
996+
targetServer := getStringField(event, "target_server")
997+
targetTool := getStringField(event, "target_tool")
998+
status := getStringField(event, "status")
999+
durationMs := getIntField(event, "duration_ms")
1000+
errMsg := getStringField(event, "error_message")
1001+
1002+
statusIcon := formatStatusIcon(status)
1003+
1004+
// Format: [HH:MM:SS] [INT] internal_tool -> target_server:target_tool status duration
1005+
target := ""
1006+
if targetServer != "" && targetTool != "" {
1007+
target = fmt.Sprintf(" -> %s:%s", targetServer, targetTool)
1008+
} else if targetServer != "" {
1009+
target = fmt.Sprintf(" -> %s", targetServer)
1010+
}
1011+
1012+
line := fmt.Sprintf("[%s] [INT] %s%s %s %s", timestamp, internalTool, target, statusIcon, formatActivityDuration(int64(durationMs)))
1013+
if errMsg != "" {
1014+
line += " " + errMsg
1015+
}
1016+
return line
1017+
}
1018+
1019+
// formatPolicyDecisionEvent formats a policy_decision event for display
1020+
func formatPolicyDecisionEvent(event map[string]interface{}, timestamp string) string {
1021+
server := getStringField(event, "server_name")
1022+
tool := getStringField(event, "tool_name")
1023+
decision := getStringField(event, "decision")
1024+
reason := getStringField(event, "reason")
1025+
1026+
statusIcon := "\u2298" // circle with slash for blocked
1027+
if decision == "allowed" {
1028+
statusIcon = "\u2713"
1029+
}
1030+
1031+
line := fmt.Sprintf("[%s] [POL] %s:%s %s", timestamp, server, tool, statusIcon)
1032+
if reason != "" {
1033+
line += " " + reason
1034+
}
1035+
return line
1036+
}
1037+
1038+
// formatSystemStartEvent formats a system_start event for display
1039+
func formatSystemStartEvent(event map[string]interface{}, timestamp string) string {
1040+
version := getStringField(event, "version")
1041+
listenAddr := getStringField(event, "listen_address")
1042+
startupMs := getIntField(event, "startup_duration_ms")
1043+
1044+
return fmt.Sprintf("[%s] [SYS] \u25B6 Started v%s on %s (%s)", timestamp, version, listenAddr, formatActivityDuration(int64(startupMs)))
1045+
}
1046+
1047+
// formatSystemStopEvent formats a system_stop event for display
1048+
func formatSystemStopEvent(event map[string]interface{}, timestamp string) string {
1049+
reason := getStringField(event, "reason")
1050+
signal := getStringField(event, "signal")
1051+
uptimeSec := getIntField(event, "uptime_seconds")
1052+
errMsg := getStringField(event, "error_message")
1053+
1054+
line := fmt.Sprintf("[%s] [SYS] \u25A0 Stopped: %s", timestamp, reason)
1055+
if signal != "" {
1056+
line += fmt.Sprintf(" (signal: %s)", signal)
1057+
}
1058+
if uptimeSec > 0 {
1059+
line += fmt.Sprintf(" uptime: %ds", uptimeSec)
1060+
}
1061+
if errMsg != "" {
1062+
line += " error: " + errMsg
1063+
}
1064+
return line
1065+
}
1066+
1067+
// formatConfigChangeEvent formats a config_change event for display
1068+
func formatConfigChangeEvent(event map[string]interface{}, timestamp string) string {
1069+
action := getStringField(event, "action")
1070+
entity := getStringField(event, "affected_entity")
1071+
source := getStringField(event, "source")
1072+
1073+
sourceIcon := formatSourceIndicator(source)
1074+
1075+
return fmt.Sprintf("[%s] [%s] \u2699 Config: %s %s", timestamp, sourceIcon, action, entity)
1076+
}
1077+
1078+
// formatStatusIcon returns a status icon for the given status
1079+
func formatStatusIcon(status string) string {
1080+
switch status {
1081+
case "success":
1082+
return "\u2713" // checkmark
1083+
case "error":
1084+
return "\u2717" // X
1085+
case "blocked":
1086+
return "\u2298" // circle with slash
1087+
default:
1088+
return "?"
1089+
}
9441090
}
9451091

9461092
// runActivityShow implements the activity show command

cmd/mcpproxy/auth_cmd.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ import (
99
"strings"
1010
"time"
1111

12-
"mcpproxy-go/internal/cliclient"
13-
"mcpproxy-go/internal/config"
14-
"mcpproxy-go/internal/contracts"
15-
"mcpproxy-go/internal/logs"
16-
"mcpproxy-go/internal/socket"
17-
"mcpproxy-go/internal/upstream/cli"
12+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/cliclient"
13+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/config"
14+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/contracts"
15+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/logs"
16+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/socket"
17+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/upstream/cli"
1818

1919
"github.com/spf13/cobra"
2020
"go.uber.org/zap"

0 commit comments

Comments
 (0)