mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: add coder_workspace_read_file MCP tool (#19562)
Follows similarly to the bash tool (and some code to connect to an agent was extracted from it). There are two main parts: a new agent endpoint, and then a new MCP tool that consumes that endpoint.
This commit is contained in:
@@ -60,6 +60,7 @@ type AgentConn interface {
|
||||
PrometheusMetrics(ctx context.Context) ([]byte, error)
|
||||
ReconnectingPTY(ctx context.Context, id uuid.UUID, height uint16, width uint16, command string, initOpts ...AgentReconnectingPTYInitOption) (net.Conn, error)
|
||||
RecreateDevcontainer(ctx context.Context, devcontainerID string) (codersdk.Response, error)
|
||||
ReadFile(ctx context.Context, path string, offset, limit int64) (io.ReadCloser, string, error)
|
||||
SSH(ctx context.Context) (*gonet.TCPConn, error)
|
||||
SSHClient(ctx context.Context) (*ssh.Client, error)
|
||||
SSHClientOnPort(ctx context.Context, port uint16) (*ssh.Client, error)
|
||||
@@ -476,6 +477,30 @@ func (c *agentConn) RecreateDevcontainer(ctx context.Context, devcontainerID str
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ReadFile reads from a file from the workspace, returning a file reader and
|
||||
// the mime type.
|
||||
func (c *agentConn) ReadFile(ctx context.Context, path string, offset, limit int64) (io.ReadCloser, string, error) {
|
||||
ctx, span := tracing.StartSpan(ctx)
|
||||
defer span.End()
|
||||
|
||||
//nolint:bodyclose // we want to return the body so the caller can stream.
|
||||
res, err := c.apiRequest(ctx, http.MethodGet, fmt.Sprintf("/api/v0/read-file?path=%s&offset=%d&limit=%d", path, offset, limit), nil)
|
||||
if err != nil {
|
||||
return nil, "", xerrors.Errorf("do request: %w", err)
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
// codersdk.ReadBodyAsError will close the body.
|
||||
return nil, "", codersdk.ReadBodyAsError(res)
|
||||
}
|
||||
|
||||
mimeType := res.Header.Get("Content-Type")
|
||||
if mimeType == "" {
|
||||
mimeType = "application/octet-stream"
|
||||
}
|
||||
|
||||
return res.Body, mimeType, nil
|
||||
}
|
||||
|
||||
// apiRequest makes a request to the workspace agent's HTTP API server.
|
||||
func (c *agentConn) apiRequest(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) {
|
||||
ctx, span := tracing.StartSpan(ctx)
|
||||
|
||||
@@ -232,6 +232,22 @@ func (mr *MockAgentConnMockRecorder) PrometheusMetrics(ctx any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrometheusMetrics", reflect.TypeOf((*MockAgentConn)(nil).PrometheusMetrics), ctx)
|
||||
}
|
||||
|
||||
// ReadFile mocks base method.
|
||||
func (m *MockAgentConn) ReadFile(ctx context.Context, path string, offset, limit int64) (io.ReadCloser, string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ReadFile", ctx, path, offset, limit)
|
||||
ret0, _ := ret[0].(io.ReadCloser)
|
||||
ret1, _ := ret[1].(string)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// ReadFile indicates an expected call of ReadFile.
|
||||
func (mr *MockAgentConnMockRecorder) ReadFile(ctx, path, offset, limit any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadFile", reflect.TypeOf((*MockAgentConn)(nil).ReadFile), ctx, path, offset, limit)
|
||||
}
|
||||
|
||||
// ReconnectingPTY mocks base method.
|
||||
func (m *MockAgentConn) ReconnectingPTY(ctx context.Context, id uuid.UUID, height, width uint16, command string, initOpts ...workspacesdk.AgentReconnectingPTYInitOption) (net.Conn, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Reference in New Issue
Block a user