Skip to content

Commit d646e1c

Browse files
Copilotjoshuap
andauthored
feat: include project token in tool call responses (#6)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Joshua Wood <josh@joshuawood.net>
1 parent e6bb0f7 commit d646e1c

File tree

2 files changed

+62
-73
lines changed

2 files changed

+62
-73
lines changed

internal/hbmcp/projects.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,6 @@ func handleListProjects(ctx context.Context, client *hbapi.Client, req mcp.CallT
216216
return mcp.NewToolResultError(fmt.Sprintf("Failed to list projects: %v", err)), nil
217217
}
218218

219-
// Sanitize the response to remove API tokens
220-
for i := range response.Results {
221-
sanitizeProject(&response.Results[i])
222-
}
223-
224219
// Return JSON response
225220
jsonBytes, err := json.Marshal(response)
226221
if err != nil {
@@ -241,9 +236,6 @@ func handleGetProject(ctx context.Context, client *hbapi.Client, req mcp.CallToo
241236
return mcp.NewToolResultError(fmt.Sprintf("Failed to get project: %v", err)), nil
242237
}
243238

244-
// Sanitize the response to remove API tokens
245-
sanitizeProject(project)
246-
247239
// Return JSON response
248240
jsonBytes, err := json.Marshal(project)
249241
if err != nil {
@@ -293,9 +285,6 @@ func handleCreateProject(ctx context.Context, client *hbapi.Client, req mcp.Call
293285
return mcp.NewToolResultError(fmt.Sprintf("Failed to create project: %v", err)), nil
294286
}
295287

296-
// Sanitize the response to remove API tokens
297-
sanitizeProject(project)
298-
299288
// Return JSON response
300289
jsonBytes, err := json.Marshal(project)
301290
if err != nil {
@@ -469,10 +458,3 @@ func handleGetProjectReport(ctx context.Context, client *hbapi.Client, req mcp.C
469458

470459
return mcp.NewToolResultText(string(jsonBytes)), nil
471460
}
472-
473-
// Sanitization functions to remove sensitive data like API tokens
474-
475-
func sanitizeProject(project *hbapi.Project) {
476-
// Remove token field
477-
project.Token = ""
478-
}

internal/hbmcp/projects_test.go

Lines changed: 62 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"net/http/httptest"
99
"strings"
1010
"testing"
11-
"time"
1211

1312
"github.com/honeybadger-io/honeybadger-mcp-server/internal/hbapi"
1413
"github.com/mark3labs/mcp-go/mcp"
@@ -67,15 +66,23 @@ func TestHandleListProjects(t *testing.T) {
6766
t.Fatal("expected successful result, got error")
6867
}
6968

70-
// Check that tokens are sanitized
69+
// Verify the response can be unmarshaled as a projects list response
7170
resultText := getResultText(result)
72-
if strings.Contains(resultText, "secret123") {
73-
t.Error("Token should be sanitized from response")
71+
var response hbapi.ProjectsResponse
72+
if err := json.Unmarshal([]byte(resultText), &response); err != nil {
73+
t.Errorf("Response should be valid JSON projects list response: %v", err)
74+
}
75+
76+
if len(response.Results) != 2 {
77+
t.Errorf("expected 2 projects, got %d", len(response.Results))
78+
}
79+
80+
if response.Results[0].Name != "Project 1" {
81+
t.Errorf("expected first project name 'Project 1', got %s", response.Results[0].Name)
7482
}
7583

76-
// Check that project data is still present
77-
if !strings.Contains(resultText, "Project 1") {
78-
t.Error("Project name should be present in response")
84+
if response.Results[1].Name != "Project 2" {
85+
t.Errorf("expected second project name 'Project 2', got %s", response.Results[1].Name)
7986
}
8087
}
8188

@@ -136,7 +143,6 @@ func TestHandleListProjects_WithAccountID(t *testing.T) {
136143
WithBaseURL(server.URL).
137144
WithAuthToken("test-token")
138145

139-
// Test with account_id parameter
140146
req := mcp.CallToolRequest{
141147
Params: mcp.CallToolParams{
142148
Arguments: map[string]interface{}{
@@ -154,15 +160,23 @@ func TestHandleListProjects_WithAccountID(t *testing.T) {
154160
t.Fatal("expected successful result, got error")
155161
}
156162

157-
// Check that tokens are sanitized
163+
// Verify the response can be unmarshaled as a projects list response
158164
resultText := getResultText(result)
159-
if strings.Contains(resultText, "secret123") {
160-
t.Error("Token should be sanitized from response")
165+
var response hbapi.ProjectsResponse
166+
if err := json.Unmarshal([]byte(resultText), &response); err != nil {
167+
t.Errorf("Response should be valid JSON projects list response: %v", err)
168+
}
169+
170+
if len(response.Results) != 1 {
171+
t.Errorf("expected 1 project, got %d", len(response.Results))
172+
}
173+
174+
if response.Results[0].Name != "Project 1" {
175+
t.Errorf("expected project name 'Project 1', got %s", response.Results[0].Name)
161176
}
162177

163-
// Check that project data is still present
164-
if !strings.Contains(resultText, "Project 1") {
165-
t.Error("Project name should be present in response")
178+
if response.Results[0].ID != 1 {
179+
t.Errorf("expected project ID 1, got %d", response.Results[0].ID)
166180
}
167181
}
168182

@@ -203,14 +217,23 @@ func TestHandleGetProject(t *testing.T) {
203217
t.Fatal("expected successful result, got error")
204218
}
205219

206-
// Check that token is sanitized
207-
if strings.Contains(getResultText(result), "secret123") {
208-
t.Error("Token should be sanitized from response")
220+
// Verify the response can be unmarshaled as a project
221+
resultText := getResultText(result)
222+
var project hbapi.Project
223+
if err := json.Unmarshal([]byte(resultText), &project); err != nil {
224+
t.Errorf("Response should be valid JSON project: %v", err)
209225
}
210226

211-
// Check that project data is still present
212-
if !strings.Contains(getResultText(result), "Test Project") {
213-
t.Error("Project name should be present in response")
227+
if project.ID != 123 {
228+
t.Errorf("expected project ID 123, got %d", project.ID)
229+
}
230+
231+
if project.Name != "Test Project" {
232+
t.Errorf("expected project name 'Test Project', got %s", project.Name)
233+
}
234+
235+
if !project.Active {
236+
t.Error("expected project to be active")
214237
}
215238
}
216239

@@ -319,14 +342,27 @@ func TestHandleCreateProject(t *testing.T) {
319342
t.Fatal("expected successful result, got error")
320343
}
321344

322-
// Check that API key is sanitized
323-
if strings.Contains(getResultText(result), "secret789") {
324-
t.Error("API key should be sanitized from response")
345+
// Verify the response can be unmarshaled as a project
346+
resultText := getResultText(result)
347+
var project hbapi.Project
348+
if err := json.Unmarshal([]byte(resultText), &project); err != nil {
349+
t.Errorf("Response should be valid JSON project: %v", err)
350+
}
351+
352+
if project.ID != 456 {
353+
t.Errorf("expected project ID 456, got %d", project.ID)
354+
}
355+
356+
if project.Name != "New Project" {
357+
t.Errorf("expected project name 'New Project', got %s", project.Name)
358+
}
359+
360+
if !project.Active {
361+
t.Error("expected created project to be active")
325362
}
326363

327-
// Check that project data is still present
328-
if !strings.Contains(getResultText(result), "New Project") {
329-
t.Error("Project name should be present in response")
364+
if project.Owner.Email != "user@example.com" {
365+
t.Errorf("expected owner email 'user@example.com', got %s", project.Owner.Email)
330366
}
331367
}
332368

@@ -617,35 +653,6 @@ func TestHandleDeleteProject_Error(t *testing.T) {
617653
}
618654
}
619655

620-
func TestSanitizeProject(t *testing.T) {
621-
project := &hbapi.Project{
622-
ID: 123,
623-
Name: "Test Project",
624-
Active: true,
625-
CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
626-
Token: "secret123",
627-
Owner: hbapi.Account{ID: 1, Email: "user@example.com", Name: "User 1"},
628-
}
629-
630-
sanitizeProject(project)
631-
632-
// Check that token field is removed
633-
if project.Token != "" {
634-
t.Error("token field should be cleared")
635-
}
636-
637-
// Check that non-sensitive fields remain
638-
if project.ID != 123 {
639-
t.Error("project id should remain")
640-
}
641-
if project.Name != "Test Project" {
642-
t.Error("project name should remain")
643-
}
644-
if project.Owner.Email != "user@example.com" {
645-
t.Error("owner fields should remain")
646-
}
647-
}
648-
649656
func TestHandleGetProjectReport(t *testing.T) {
650657
mockResponse := `[["RuntimeError", 8347], ["SocketError", 4651]]`
651658

0 commit comments

Comments
 (0)