feat(mcp): operators can switch MCP (and OAuth) off
Admin switch in the system settings, MCP_ENABLED fixes it. Off: the endpoint answers 404, OAuth is off, the API key settings hide the MCP tab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
39a04082fc
commit
c6f28aa816
17 files changed
+197
-30
No files matched your search
@@ -288,3 +288,70 @@ func TestMCPCreateDeleteAndOrigin(t *testing.T) {
|
||||
t.Fatalf("webhook:task key tools: %s", names)
|
||||
}
|
||||
}
|
||||
|
||||
// Operators can switch MCP off: in the admin settings, or fixed by MCP_ENABLED. OAuth goes with it.
|
||||
func TestMCPSwitchOff(t *testing.T) {
|
||||
srv, _, session := setupTestServer(t)
|
||||
srv.cfg.BaseURL = "https://ms.example/"
|
||||
srv.cfg.BaseURLSet = true
|
||||
router := srv.Router()
|
||||
const list = `{"jsonrpc":"2.0","id":1,"method":"tools/list"}`
|
||||
status := func() map[string]interface{} {
|
||||
_, _, st := oauthDo(t, router, http.MethodGet, "/api/v1/auth/status", "", "", "")
|
||||
return st
|
||||
}
|
||||
|
||||
if code, _ := rpc(t, router, session, list); code != http.StatusOK {
|
||||
t.Fatalf("MCP should be on by default, got %d", code)
|
||||
}
|
||||
if st := status(); st["mcpEnabled"] != true || st["mcpConnectorUrl"] == "" {
|
||||
t.Fatalf("status with MCP on: %v", st)
|
||||
}
|
||||
|
||||
if code, _, _ := oauthDo(t, router, http.MethodPut, "/api/v1/admin/settings", session, "application/json", `{"mcpEnabled":false}`); code != http.StatusOK {
|
||||
t.Fatalf("switch off: %d", code)
|
||||
}
|
||||
if code, _ := rpc(t, router, session, list); code != http.StatusNotFound {
|
||||
t.Fatalf("MCP switched off must answer 404, got %d", code)
|
||||
}
|
||||
code, h, _ := oauthDo(t, router, http.MethodPost, mcpPath, "", "application/json", list)
|
||||
if code != http.StatusNotFound || h.Get("WWW-Authenticate") != "" {
|
||||
t.Fatalf("no login hint while MCP is off: %d %q", code, h.Get("WWW-Authenticate"))
|
||||
}
|
||||
if code, _, _ := oauthDo(t, router, http.MethodGet, "/.well-known/oauth-authorization-server", "", "", ""); code != http.StatusNotFound {
|
||||
t.Fatalf("OAuth must be off with MCP, got %d", code)
|
||||
}
|
||||
if st := status(); st["mcpEnabled"] != false || st["mcpConnectorUrl"] != "" {
|
||||
t.Fatalf("status with MCP off: %v", st)
|
||||
}
|
||||
// Saving other settings (without the field) leaves the switch as it is
|
||||
if code, _, _ := oauthDo(t, router, http.MethodPut, "/api/v1/admin/settings", session, "application/json", `{"allowPrivateWebhooks":true}`); code != http.StatusOK {
|
||||
t.Fatalf("save other settings: %d", code)
|
||||
}
|
||||
if code, _ := rpc(t, router, session, list); code != http.StatusNotFound {
|
||||
t.Fatalf("MCP must stay off, got %d", code)
|
||||
}
|
||||
|
||||
// MCP_ENABLED wins over the stored switch and locks it
|
||||
on := true
|
||||
srv.cfg.MCPEnabled = &on
|
||||
if code, _ := rpc(t, router, session, list); code != http.StatusOK {
|
||||
t.Fatalf("MCP_ENABLED=true must win, got %d", code)
|
||||
}
|
||||
_, _, adm := oauthDo(t, router, http.MethodGet, "/api/v1/admin/settings", session, "", "")
|
||||
if adm["mcpEnabled"] != true || adm["mcpConfiguredViaEnv"] != true {
|
||||
t.Fatalf("admin settings must show the environment's value: %v", adm)
|
||||
}
|
||||
off := false
|
||||
srv.cfg.MCPEnabled = &off
|
||||
if code, _, _ := oauthDo(t, router, http.MethodPut, "/api/v1/admin/settings", session, "application/json", `{"mcpEnabled":true}`); code != http.StatusOK {
|
||||
t.Fatalf("save: %d", code)
|
||||
}
|
||||
if code, _ := rpc(t, router, session, list); code != http.StatusNotFound {
|
||||
t.Fatalf("MCP_ENABLED=false must win, got %d", code)
|
||||
}
|
||||
srv.cfg.MCPEnabled = nil
|
||||
if code, _ := rpc(t, router, session, list); code != http.StatusNotFound {
|
||||
t.Fatalf("the locked switch must not have changed the stored value, got %d", code)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user