Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions lib/client/db/mcp/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import (
func FormatErrorMessage(err error) error {
switch {
case errors.Is(err, apiclient.ErrClientCredentialsHaveExpired) || utils.IsCertExpiredError(err):
return trace.BadParameter(ReloginRequiredErrorMessage)
return trace.BadParameter(mcp.ReloginRequiredErrorMessage)
case strings.Contains(err.Error(), "connection reset by peer") || errors.Is(err, io.ErrClosedPipe):
return trace.BadParameter(LocalProxyConnectionErrorMessage)
}
Expand All @@ -42,12 +42,6 @@ func FormatErrorMessage(err error) error {
}

const (
// ReloginRequiredErrorMessage is the message returned to the MCP client
// when the tsh session expired.
ReloginRequiredErrorMessage = `It looks like your Teleport session expired,
you must relogin (using "tsh login" on a terminal) before continue using this
tool. After that, there is no need to update or relaunch the MCP client - just
try using it again.`
// LocalProxyConnectionErrorMessage is the message returned to the MCP client when
// the database client cannot connect to the local proxy.
LocalProxyConnectionErrorMessage = `Teleport MCP server is having issue while
Expand Down
2 changes: 1 addition & 1 deletion lib/client/db/postgres/mcp/mcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func TestFormatErrors(t *testing.T) {
},
},
expectErrorMessage: func(tt require.TestingT, i1 any, i2 ...any) {
require.Equal(t, dbmcp.ReloginRequiredErrorMessage, i1)
require.Equal(t, clientmcp.ReloginRequiredErrorMessage, i1)
},
},
} {
Expand Down
84 changes: 84 additions & 0 deletions lib/client/mcp/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package mcp

import (
"errors"
"fmt"
"net"
"syscall"

"github.com/gravitational/trace"
"github.com/mark3labs/mcp-go/mcp"
)

const (
// ReloginRequiredErrorMessage is the message returned to the MCP client
// when the tsh session expired.
ReloginRequiredErrorMessage = `It looks like your Teleport session expired,
you must relogin (using "tsh login" on a terminal) before continue using this
tool. After that, there is no need to update or relaunch the MCP client - just
try using it again.`
)

// IsLikelyTemporaryNetworkError returns true if the error is likely a temporary
// network error.
func IsLikelyTemporaryNetworkError(err error) bool {
if trace.IsConnectionProblem(err) ||
isTemporarySyscallNetError(err) {
return true
}

var dnsErr *net.DNSError
if errors.As(err, &dnsErr) {
return dnsErr.Temporary()
Comment thread
greedy52 marked this conversation as resolved.
}

var netErr net.Error
if errors.As(err, &netErr) {
return netErr.Timeout()
}

return false
}

func isTemporarySyscallNetError(err error) bool {
return errors.Is(err, syscall.EHOSTUNREACH) ||
errors.Is(err, syscall.ENETUNREACH) ||
errors.Is(err, syscall.ETIMEDOUT) ||
errors.Is(err, syscall.ECONNREFUSED)
}
Comment thread
greedy52 marked this conversation as resolved.

// IsServerInfoChangedError returns true if the error indicates the remote MCP
// server's info has changed from previous connections. Auto-reconnection
// reports this scenario as an error case to be on the safe side in case things
// like tools have changed.
func IsServerInfoChangedError(err error) bool {
var serverInfoChangedError *serverInfoChangedError
return errors.As(err, &serverInfoChangedError)
}

type serverInfoChangedError struct {
expectedInfo mcp.Implementation
currentInfo mcp.Implementation
}

func (e *serverInfoChangedError) Error() string {
return fmt.Sprintf("server info has changed, expected %v, got %v", e.expectedInfo, e.currentInfo)
}
40 changes: 40 additions & 0 deletions lib/client/mcp/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package mcp

import (
"testing"

"github.com/mark3labs/mcp-go/mcp"
"github.com/stretchr/testify/require"
)

func TestIsServerInfoChangedError(t *testing.T) {
err := &serverInfoChangedError{
expectedInfo: mcp.Implementation{
Name: "i-am-mcp",
Version: "1.0.0",
},
currentInfo: mcp.Implementation{
Name: "i-am-mcp",
Version: "1.1.0",
},
}
require.True(t, IsServerInfoChangedError(err))
}
Loading
Loading