• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

valksor / go-assern / 21338598851

25 Jan 2026 07:54PM UTC coverage: 45.704% (-6.8%) from 52.533%
21338598851

push

github

k0d3r1s
Updates go.sum dependencies

Removes an outdated dependency and updates to a newer version.
This ensures that the project uses the most recent and compatible version.

2101 of 4597 relevant lines covered (45.7%)

88.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.93
/internal/instance/client.go
1
package instance
2

3
import (
4
        "bufio"
5
        "context"
6
        "encoding/json"
7
        "errors"
8
        "fmt"
9
        "net"
10
        "time"
11
)
12

13
// ClientTimeout is the default timeout for client operations.
14
const ClientTimeout = 10 * time.Second
15

16
// ToolInfo represents tool information returned from a query.
17
type ToolInfo struct {
18
        Name        string `json:"name"`
19
        Description string `json:"description"`
20
}
21

22
// ListResult contains the result of querying a running instance.
23
type ListResult struct {
24
        Tools []ToolInfo
25
}
26

27
// Client connects to a running assern instance to query information.
28
type Client struct {
29
        socketPath string
30
        conn       net.Conn
31
        reader     *bufio.Reader
32
        requestID  int
33
}
34

35
// NewClient creates a new client for the given socket path.
36
func NewClient(socketPath string) *Client {
11✔
37
        return &Client{
11✔
38
                socketPath: socketPath,
11✔
39
                requestID:  0,
11✔
40
        }
11✔
41
}
11✔
42

43
// Connect establishes connection to the instance.
44
func (c *Client) Connect(ctx context.Context) error {
9✔
45
        var dialer net.Dialer
9✔
46
        conn, err := dialer.DialContext(ctx, "unix", c.socketPath)
9✔
47
        if err != nil {
11✔
48
                return fmt.Errorf("connect to socket: %w", err)
2✔
49
        }
2✔
50

51
        c.conn = conn
7✔
52
        c.reader = bufio.NewReader(conn)
7✔
53

7✔
54
        return nil
7✔
55
}
56

57
// Close closes the connection.
58
func (c *Client) Close() error {
8✔
59
        if c.conn != nil {
15✔
60
                return c.conn.Close()
7✔
61
        }
7✔
62

63
        return nil
1✔
64
}
65

66
// Initialize performs the MCP initialize handshake.
67
func (c *Client) Initialize(ctx context.Context) error {
6✔
68
        // Wait for handshake timeout to pass (server expects internal commands first)
6✔
69
        time.Sleep(handshakeTimeout + 10*time.Millisecond)
6✔
70

6✔
71
        c.requestID++
6✔
72
        initReq := map[string]any{
6✔
73
                "jsonrpc": "2.0",
6✔
74
                "id":      c.requestID,
6✔
75
                "method":  "initialize",
6✔
76
                "params": map[string]any{
6✔
77
                        "protocolVersion": "2024-11-05",
6✔
78
                        "capabilities":    map[string]any{},
6✔
79
                        "clientInfo": map[string]any{
6✔
80
                                "name":    "assern-client",
6✔
81
                                "version": "1.0.0",
6✔
82
                        },
6✔
83
                },
6✔
84
        }
6✔
85

6✔
86
        if err := c.sendRequest(initReq); err != nil {
6✔
87
                return fmt.Errorf("send initialize: %w", err)
×
88
        }
×
89

90
        // Read initialize response
91
        var initResp struct {
6✔
92
                ID     int `json:"id"`
6✔
93
                Result any `json:"result"`
6✔
94
                Error  *struct {
6✔
95
                        Code    int    `json:"code"`
6✔
96
                        Message string `json:"message"`
6✔
97
                } `json:"error"`
6✔
98
        }
6✔
99

6✔
100
        if err := c.readResponse(&initResp); err != nil {
6✔
101
                return fmt.Errorf("read initialize response: %w", err)
×
102
        }
×
103

104
        if initResp.Error != nil {
6✔
105
                return fmt.Errorf("initialize error: %s", initResp.Error.Message)
×
106
        }
×
107

108
        // Send initialized notification
109
        initializedNotif := map[string]any{
6✔
110
                "jsonrpc": "2.0",
6✔
111
                "method":  "notifications/initialized",
6✔
112
        }
6✔
113

6✔
114
        if err := c.sendRequest(initializedNotif); err != nil {
6✔
115
                return fmt.Errorf("send initialized notification: %w", err)
×
116
        }
×
117

118
        return nil
6✔
119
}
120

121
// ListTools queries the available tools from the running instance.
122
func (c *Client) ListTools(ctx context.Context) (*ListResult, error) {
5✔
123
        c.requestID++
5✔
124
        listReq := map[string]any{
5✔
125
                "jsonrpc": "2.0",
5✔
126
                "id":      c.requestID,
5✔
127
                "method":  "tools/list",
5✔
128
                "params":  map[string]any{},
5✔
129
        }
5✔
130

5✔
131
        if err := c.sendRequest(listReq); err != nil {
5✔
132
                return nil, fmt.Errorf("send tools/list: %w", err)
×
133
        }
×
134

135
        var resp struct {
5✔
136
                ID     int `json:"id"`
5✔
137
                Result struct {
5✔
138
                        Tools []ToolInfo `json:"tools"`
5✔
139
                } `json:"result"`
5✔
140
                Error *struct {
5✔
141
                        Code    int    `json:"code"`
5✔
142
                        Message string `json:"message"`
5✔
143
                } `json:"error"`
5✔
144
        }
5✔
145

5✔
146
        if err := c.readResponse(&resp); err != nil {
5✔
147
                return nil, fmt.Errorf("read tools/list response: %w", err)
×
148
        }
×
149

150
        if resp.Error != nil {
5✔
151
                return nil, fmt.Errorf("tools/list error: %s", resp.Error.Message)
×
152
        }
×
153

154
        return &ListResult{
5✔
155
                Tools: resp.Result.Tools,
5✔
156
        }, nil
5✔
157
}
158

159
func (c *Client) sendRequest(req any) error {
17✔
160
        data, err := json.Marshal(req)
17✔
161
        if err != nil {
17✔
162
                return err
×
163
        }
×
164

165
        data = append(data, '\n')
17✔
166

17✔
167
        if _, err := c.conn.Write(data); err != nil {
17✔
168
                return err
×
169
        }
×
170

171
        return nil
17✔
172
}
173

174
func (c *Client) readResponse(resp any) error {
11✔
175
        if err := c.conn.SetReadDeadline(time.Now().Add(ClientTimeout)); err != nil {
11✔
176
                return err
×
177
        }
×
178
        defer func() { _ = c.conn.SetReadDeadline(time.Time{}) }()
22✔
179

180
        line, err := c.reader.ReadBytes('\n')
11✔
181
        if err != nil {
11✔
182
                return err
×
183
        }
×
184

185
        return json.Unmarshal(line, resp)
11✔
186
}
187

188
// QueryTools connects to a running instance and returns the available tools.
189
// This is a convenience function that handles the full connection lifecycle.
190
func QueryTools(ctx context.Context, socketPath string) (*ListResult, error) {
7✔
191
        client := NewClient(socketPath)
7✔
192

7✔
193
        if err := client.Connect(ctx); err != nil {
9✔
194
                return nil, err
2✔
195
        }
2✔
196
        defer func() { _ = client.Close() }()
10✔
197

198
        if err := client.Initialize(ctx); err != nil {
5✔
199
                return nil, err
×
200
        }
×
201

202
        return client.ListTools(ctx)
5✔
203
}
204

205
// ReloadResult contains the result of a reload operation.
206
type ReloadResult struct {
207
        Added   int      `json:"added"`
208
        Removed int      `json:"removed"`
209
        Errors  []string `json:"errors,omitempty"`
210
}
211

212
// Reload triggers a configuration reload on a running instance.
213
// This uses the internal command protocol (not MCP).
214
func Reload(ctx context.Context, socketPath string) (*ReloadResult, error) {
4✔
215
        var dialer net.Dialer
4✔
216
        conn, err := dialer.DialContext(ctx, "unix", socketPath)
4✔
217
        if err != nil {
5✔
218
                return nil, fmt.Errorf("connect to socket: %w", err)
1✔
219
        }
1✔
220
        defer func() { _ = conn.Close() }()
6✔
221

222
        // Send reload request
223
        reloadReq := map[string]any{
3✔
224
                "jsonrpc": "2.0",
3✔
225
                "id":      1,
3✔
226
                "method":  "assern/reload",
3✔
227
        }
3✔
228
        if err := json.NewEncoder(conn).Encode(reloadReq); err != nil {
3✔
229
                return nil, fmt.Errorf("send reload request: %w", err)
×
230
        }
×
231

232
        // Set read deadline
233
        if err := conn.SetReadDeadline(time.Now().Add(ClientTimeout)); err != nil {
3✔
234
                return nil, fmt.Errorf("set read deadline: %w", err)
×
235
        }
×
236

237
        // Read response
238
        var resp struct {
3✔
239
                Result *ReloadResult `json:"result"`
3✔
240
                Error  *struct {
3✔
241
                        Code    int    `json:"code"`
3✔
242
                        Message string `json:"message"`
3✔
243
                } `json:"error"`
3✔
244
        }
3✔
245
        if err := json.NewDecoder(conn).Decode(&resp); err != nil {
4✔
246
                return nil, fmt.Errorf("read reload response: %w", err)
1✔
247
        }
1✔
248

249
        if resp.Error != nil {
3✔
250
                return nil, fmt.Errorf("reload error: %s", resp.Error.Message)
1✔
251
        }
1✔
252

253
        if resp.Result == nil {
1✔
254
                return nil, errors.New("empty reload response")
×
255
        }
×
256

257
        return resp.Result, nil
1✔
258
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc