Claude Code sends extra fields (e.g. "title") in initialize params that caused the server to reject the request due to DisallowUnknownFields. Use lenient JSON decoding for protocol messages while keeping strict validation for tool arguments. Also defer KWallet credential loading from server startup to tool invocation time, and negotiate protocol versions per MCP spec instead of rejecting unknown ones. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
94 lines
3 KiB
Go
94 lines
3 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"testing"
|
|
|
|
"email-mcp/internal/imapclient"
|
|
"email-mcp/internal/mcpserver"
|
|
"email-mcp/internal/secretstore"
|
|
"email-mcp/internal/secretstore/kwallet"
|
|
)
|
|
|
|
type entrypointPromptStub struct {
|
|
credential secretstore.Credential
|
|
}
|
|
|
|
func (p *entrypointPromptStub) PromptSetup(context.Context) (secretstore.Credential, error) {
|
|
return p.credential, nil
|
|
}
|
|
|
|
type entrypointStoreStub struct {
|
|
saveErr error
|
|
loadErr error
|
|
}
|
|
|
|
func (s *entrypointStoreStub) Save(context.Context, string, secretstore.Credential) error {
|
|
return s.saveErr
|
|
}
|
|
|
|
func (s *entrypointStoreStub) Load(context.Context, string) (secretstore.Credential, error) {
|
|
return secretstore.Credential{}, s.loadErr
|
|
}
|
|
|
|
type entrypointMailServiceStub struct{}
|
|
|
|
func (entrypointMailServiceStub) ListMailboxes(context.Context, secretstore.Credential) ([]imapclient.Mailbox, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (entrypointMailServiceStub) ListMessages(context.Context, secretstore.Credential, string, int) ([]imapclient.MessageSummary, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (entrypointMailServiceStub) GetMessage(context.Context, secretstore.Credential, string, uint32) (imapclient.Message, error) {
|
|
return imapclient.Message{}, nil
|
|
}
|
|
|
|
func TestExecuteSetupWritesWalletGuidanceAndReturnsExitCodeOne(t *testing.T) {
|
|
app := NewAppWithDependencies(
|
|
&entrypointPromptStub{
|
|
credential: secretstore.Credential{
|
|
Host: "imap.example.com",
|
|
Username: "alice",
|
|
Password: "secret",
|
|
},
|
|
},
|
|
&entrypointStoreStub{saveErr: kwallet.ErrKWalletUnavailable},
|
|
nil,
|
|
nil,
|
|
)
|
|
|
|
stderr := &bytes.Buffer{}
|
|
if code := Execute(app, []string{"setup"}, stderr); code != 1 {
|
|
t.Fatalf("expected exit code 1, got %d", code)
|
|
}
|
|
if got := stderr.String(); got != "kwallet is not available; make sure KDE Wallet is installed and your session D-Bus is running\n" {
|
|
t.Fatalf("unexpected stderr: %q", got)
|
|
}
|
|
}
|
|
|
|
func TestExecuteMCPReturnsMissingCredentialErrorOnToolCall(t *testing.T) {
|
|
store := &entrypointStoreStub{loadErr: kwallet.ErrCredentialNotFound}
|
|
mail := entrypointMailServiceStub{}
|
|
input := bytes.NewBufferString(
|
|
"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0.0\"}}}\n" +
|
|
"{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}\n" +
|
|
"{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"list_mailboxes\"}}\n",
|
|
)
|
|
output := &bytes.Buffer{}
|
|
runner := mcpserver.NewRunner(mcpserver.New(store, mail), input, output, &bytes.Buffer{})
|
|
app := NewAppWithDependencies(nil, store, runner, nil)
|
|
|
|
stderr := &bytes.Buffer{}
|
|
if code := Execute(app, []string{"mcp"}, stderr); code != 0 {
|
|
t.Fatalf("expected exit code 0, got %d; stderr: %s", code, stderr.String())
|
|
}
|
|
|
|
// Verify the credential error appears in the tool call response
|
|
got := output.String()
|
|
if !bytes.Contains([]byte(got), []byte("credentials not configured")) {
|
|
t.Fatalf("expected credential error in output, got %q", got)
|
|
}
|
|
}
|