Skip to content

Commit bd9a8d5

Browse files
committed
fix(httpapi): decode percent-encoded server id in handleGetServerLogs
Closes the daemon server-side half of the namespaced-name log fix (Codex review round 3 on PR #604): the client now encodes the name, but the handler read chi.URLParam("id") raw, so %2F reached the lookup un-decoded. Route it through decodePathParam (matching handleAddFromRegistry) + router regression test proving /servers/io.github.evidai%2Fpolymarket-guard/logs decodes. Related #598
1 parent 425a618 commit bd9a8d5

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

internal/httpapi/server.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2678,7 +2678,10 @@ func (s *Server) handleGetGlobalTools(w http.ResponseWriter, r *http.Request) {
26782678
// @Failure 500 {object} contracts.ErrorResponse "Internal server error"
26792679
// @Router /api/v1/servers/{id}/logs [get]
26802680
func (s *Server) handleGetServerLogs(w http.ResponseWriter, r *http.Request) {
2681-
serverID := chi.URLParam(r, "id")
2681+
// chi routes on RawPath, so a namespace/name server id (e.g.
2682+
// io.github.evidai/polymarket-guard) arrives percent-encoded. Decode it
2683+
// before lookup, matching handleAddFromRegistry (MCP-1111 / #598).
2684+
serverID := decodePathParam(chi.URLParam(r, "id"))
26822685
if serverID == "" {
26832686
s.writeError(w, r, http.StatusBadRequest, "Server ID required")
26842687
return
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package httpapi
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
"go.uber.org/zap/zaptest"
12+
13+
"github.com/smart-mcp-proxy/mcpproxy-go/internal/contracts"
14+
)
15+
16+
// capturingLogsController records the serverID the logs handler forwards, so the
17+
// test can assert it was percent-decoded before lookup.
18+
type capturingLogsController struct {
19+
*MockServerController
20+
gotServerID string
21+
}
22+
23+
func (c *capturingLogsController) GetServerLogs(serverID string, _ int) ([]contracts.LogEntry, error) {
24+
c.gotServerID = serverID
25+
return []contracts.LogEntry{}, nil
26+
}
27+
28+
// TestGetServerLogs_SlashServerIDUnescaped reproduces the daemon-backed half of
29+
// MCP-1111 / #598: official-registry server ids are namespace/name and chi routes
30+
// on RawPath, so the {id} param arrives percent-encoded
31+
// (io.github.evidai%2Fpolymarket-guard). handleGetServerLogs must url.PathUnescape
32+
// it before lookup, otherwise `mcpproxy upstream logs io.github.evidai/polymarket-guard`
33+
// targets the literal encoded name and never finds the server.
34+
func TestGetServerLogs_SlashServerIDUnescaped(t *testing.T) {
35+
logger := zaptest.NewLogger(t).Sugar()
36+
controller := &capturingLogsController{MockServerController: &MockServerController{}}
37+
server := NewServer(controller, logger, nil)
38+
39+
req := httptest.NewRequest(http.MethodGet, "/api/v1/servers/io.github.evidai%2Fpolymarket-guard/logs?tail=10", http.NoBody)
40+
w := httptest.NewRecorder()
41+
server.ServeHTTP(w, req)
42+
43+
require.Equal(t, http.StatusOK, w.Code, "logs request should succeed; body=%s", w.Body.String())
44+
var resp contracts.APIResponse
45+
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
46+
assert.Equal(t, "io.github.evidai/polymarket-guard", controller.gotServerID,
47+
"server id must be percent-decoded before lookup")
48+
}

0 commit comments

Comments
 (0)