feat: wire explicit msglib receive source
This commit is contained in:
@@ -24,6 +24,15 @@ const (
|
||||
EnvMsgLibDBKey = "ISPHERE_MSGLIB_DB"
|
||||
)
|
||||
|
||||
type msgLibClient interface {
|
||||
tools.DisplayEntitySource
|
||||
isphere.MsgLibMessageLister
|
||||
}
|
||||
|
||||
var newMsgLibClient = func(cfg msglib.Config) msgLibClient {
|
||||
return msglib.NewClient(cfg)
|
||||
}
|
||||
|
||||
func NewServer() *mcp.Server {
|
||||
return NewServerWithReceiveSource(isphere.EncryptedPacketLogSource{})
|
||||
}
|
||||
@@ -48,14 +57,14 @@ func NewServerFromEnv() (*mcp.Server, error) {
|
||||
}
|
||||
source = loaded
|
||||
}
|
||||
displaySource, err := msgLibDisplayEntitySourceFromEnv()
|
||||
displaySource, msglibReceiveSource, err := msgLibSourcesFromEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewServerWithSources(source, displaySource), nil
|
||||
return NewServerWithSourcesAndMsgLibReceive(source, displaySource, msglibReceiveSource), nil
|
||||
}
|
||||
|
||||
func msgLibDisplayEntitySourceFromEnv() (tools.DisplayEntitySource, error) {
|
||||
func msgLibSourcesFromEnv() (tools.DisplayEntitySource, tools.ReceiveMessagesSource, error) {
|
||||
sidecarExe := strings.TrimSpace(os.Getenv(EnvMsgLibSidecarExeKey))
|
||||
sqliteDLL := strings.TrimSpace(os.Getenv(EnvMsgLibSQLiteDLLKey))
|
||||
dbPath := strings.TrimSpace(os.Getenv(EnvMsgLibDBKey))
|
||||
@@ -66,16 +75,21 @@ func msgLibDisplayEntitySourceFromEnv() (tools.DisplayEntitySource, error) {
|
||||
}
|
||||
}
|
||||
if configured == 0 {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
if configured != 3 {
|
||||
return nil, fmt.Errorf("configure MsgLib source: set all of %s, %s, and %s, or set none", EnvMsgLibSidecarExeKey, EnvMsgLibSQLiteDLLKey, EnvMsgLibDBKey)
|
||||
return nil, nil, fmt.Errorf("configure MsgLib source: set all of %s, %s, and %s, or set none", EnvMsgLibSidecarExeKey, EnvMsgLibSQLiteDLLKey, EnvMsgLibDBKey)
|
||||
}
|
||||
cfg, err := msglib.ConfigFromEnv()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("configure MsgLib source: %w", err)
|
||||
return nil, nil, fmt.Errorf("configure MsgLib source: %w", err)
|
||||
}
|
||||
return msglib.NewClient(cfg), nil
|
||||
client := newMsgLibClient(cfg)
|
||||
return client, isphere.MsgLibMessageSource{
|
||||
Lister: client,
|
||||
IncludeBody: true,
|
||||
IncludeAttachmentMetadata: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewServerWithReceiveSource(source tools.ReceiveMessagesSource) *mcp.Server {
|
||||
@@ -83,13 +97,17 @@ func NewServerWithReceiveSource(source tools.ReceiveMessagesSource) *mcp.Server
|
||||
}
|
||||
|
||||
func NewServerWithSources(source tools.ReceiveMessagesSource, displaySource tools.DisplayEntitySource) *mcp.Server {
|
||||
return NewServerWithSourcesAndMsgLibReceive(source, displaySource, nil)
|
||||
}
|
||||
|
||||
func NewServerWithSourcesAndMsgLibReceive(source tools.ReceiveMessagesSource, displaySource tools.DisplayEntitySource, msglibReceiveSource tools.ReceiveMessagesSource) *mcp.Server {
|
||||
server := mcp.NewServer(&mcp.Implementation{
|
||||
Name: ServerName,
|
||||
Title: ServerTitle,
|
||||
Version: ServerVersion,
|
||||
}, nil)
|
||||
tools.RegisterWinHelperTools(server, helperclient.Client{})
|
||||
tools.RegisterISphereReadToolsWithDisplayEntities(server, source, displaySource)
|
||||
tools.RegisterISphereReadToolsWithReceiveSources(server, source, msglibReceiveSource, displaySource)
|
||||
tools.RegisterISphereContactToolsWithDisplayEntities(server, source, displaySource)
|
||||
tools.RegisterISphereGroupToolsWithDisplayEntities(server, source, displaySource)
|
||||
tools.RegisterISphereFileTools(server, source)
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
||||
"isphere-ai-bridge/internal/msglib"
|
||||
)
|
||||
|
||||
func TestNewServerConstructsMCPServer(t *testing.T) {
|
||||
@@ -77,6 +79,90 @@ func TestNewServerFromEnvRejectsPartialMsgLibConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServerFromEnvWiresExplicitMsgLibReceiveSource(t *testing.T) {
|
||||
clearMsgLibEnvForServerTest(t)
|
||||
fake := &fakeMsgLibClientForServerTest{
|
||||
listResult: msglib.ListMessagesResult{
|
||||
ReadOnly: true,
|
||||
MessageBodyValuesReturned: true,
|
||||
RawRowsReturned: false,
|
||||
FilePathsReturned: false,
|
||||
SourceTables: []string{"tblPersonMsg"},
|
||||
Messages: []msglib.ListMessage{{
|
||||
MessageID: "db-env-msg-1",
|
||||
ID: "db-env-msg-1",
|
||||
ConversationID: "db-env-conversation",
|
||||
ConversationType: "direct",
|
||||
SenderID: "db-env-sender",
|
||||
ReceiverID: "db-env-receiver",
|
||||
ContentText: "db env body",
|
||||
Timestamp: "1783423809000",
|
||||
Source: "local_readonly",
|
||||
RawRef: "msglib:tblPersonMsg",
|
||||
}},
|
||||
},
|
||||
}
|
||||
oldFactory := newMsgLibClient
|
||||
newMsgLibClient = func(msglib.Config) msgLibClient {
|
||||
return fake
|
||||
}
|
||||
defer func() { newMsgLibClient = oldFactory }()
|
||||
t.Setenv(EnvMsgLibSidecarExeKey, `C:\fixture\MsgLibReadSidecar.exe`)
|
||||
t.Setenv(EnvMsgLibSQLiteDLLKey, `C:\fixture\System.Data.SQLite.dll`)
|
||||
t.Setenv(EnvMsgLibDBKey, `C:\fixture\MsgLib.db`)
|
||||
|
||||
server, err := NewServerFromEnv()
|
||||
if err != nil {
|
||||
t.Fatalf("NewServerFromEnv returned error: %v", err)
|
||||
}
|
||||
session, cleanup := connectServerTestSession(t, server)
|
||||
defer cleanup()
|
||||
|
||||
defaultResult, err := session.CallTool(context.Background(), &mcp.CallToolParams{
|
||||
Name: "isphere_receive_messages",
|
||||
Arguments: map[string]any{"limit": 5},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("default receive call: %v", err)
|
||||
}
|
||||
if defaultResult.IsError {
|
||||
t.Fatalf("default receive returned error: %+v", defaultResult)
|
||||
}
|
||||
if len(fake.listCalls) != 0 {
|
||||
t.Fatalf("default receive called MsgLib ListMessages: %+v", fake.listCalls)
|
||||
}
|
||||
|
||||
dbResult, err := session.CallTool(context.Background(), &mcp.CallToolParams{
|
||||
Name: "isphere_receive_messages",
|
||||
Arguments: map[string]any{"source_preference": "msglib_readonly", "limit": 5},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("msglib receive call: %v", err)
|
||||
}
|
||||
if dbResult.IsError {
|
||||
t.Fatalf("msglib receive returned error: %+v", dbResult)
|
||||
}
|
||||
payload, err := json.Marshal(dbResult.StructuredContent)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal structured content: %v", err)
|
||||
}
|
||||
var decoded struct {
|
||||
Messages []struct {
|
||||
ID string `json:"id"`
|
||||
RawRef string `json:"raw_ref"`
|
||||
} `json:"messages"`
|
||||
}
|
||||
if err := json.Unmarshal(payload, &decoded); err != nil {
|
||||
t.Fatalf("decode structured content %s: %v", payload, err)
|
||||
}
|
||||
if len(decoded.Messages) != 1 || decoded.Messages[0].ID != "db-env-msg-1" || decoded.Messages[0].RawRef != "msglib:tblPersonMsg" {
|
||||
t.Fatalf("messages = %+v, want explicit MsgLib result", decoded.Messages)
|
||||
}
|
||||
if len(fake.listCalls) != 1 || !fake.listCalls[0].IncludeBody || !fake.listCalls[0].IncludeAttachmentMetadata {
|
||||
t.Fatalf("ListMessages calls = %+v", fake.listCalls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServerFromEnvUsesPacketLogFile(t *testing.T) {
|
||||
clearMsgLibEnvForServerTest(t)
|
||||
plaintext := `--------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -197,6 +283,22 @@ func TestNewServerFromEnvUsesPacketLogDirectory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type fakeMsgLibClientForServerTest struct {
|
||||
displayCalls []msglib.DisplayEntitiesOptions
|
||||
listCalls []msglib.ListMessagesOptions
|
||||
listResult msglib.ListMessagesResult
|
||||
}
|
||||
|
||||
func (f *fakeMsgLibClientForServerTest) DisplayEntities(_ context.Context, opts msglib.DisplayEntitiesOptions) ([]msglib.DisplayEntity, error) {
|
||||
f.displayCalls = append(f.displayCalls, opts)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeMsgLibClientForServerTest) ListMessages(_ context.Context, opts msglib.ListMessagesOptions) (msglib.ListMessagesResult, error) {
|
||||
f.listCalls = append(f.listCalls, opts)
|
||||
return f.listResult, nil
|
||||
}
|
||||
|
||||
func clearMsgLibEnvForServerTest(t *testing.T) {
|
||||
t.Helper()
|
||||
t.Setenv(EnvMsgLibSidecarExeKey, "")
|
||||
|
||||
Reference in New Issue
Block a user