Skip to content
Open
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
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ require (
github.com/klauspost/compress v1.18.0
github.com/maypok86/otter/v2 v2.2.1
github.com/mitchellh/mapstructure v1.5.0
github.com/modelcontextprotocol/go-sdk v1.2.0
github.com/pquerna/xjwt v0.3.0
github.com/pquerna/xjwt/xkeyset v0.0.0-20241217022915-10fc997b2a9f
github.com/segmentio/ksuid v1.0.4
Expand All @@ -51,7 +52,7 @@ require (
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.34.0
golang.org/x/net v0.35.0
golang.org/x/oauth2 v0.26.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.11.0
golang.org/x/sys v0.38.0
golang.org/x/term v0.29.0
Expand Down Expand Up @@ -86,6 +87,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/jsonschema-go v0.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
Expand All @@ -108,6 +110,7 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
Expand Down
16 changes: 12 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand All @@ -117,6 +119,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
Expand Down Expand Up @@ -157,6 +161,8 @@ github.com/maypok86/otter/v2 v2.2.1 h1:hnGssisMFkdisYcvQ8L019zpYQcdtPse+g0ps2i7c
github.com/maypok86/otter/v2 v2.2.1/go.mod h1:1NKY9bY+kB5jwCXBJfE59u+zAwOt6C7ni1FTlFFMqVs=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
Expand Down Expand Up @@ -219,6 +225,8 @@ github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYI
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
Expand Down Expand Up @@ -294,8 +302,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -330,8 +338,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down
69 changes: 69 additions & 0 deletions pkg/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
v1 "github.com/conductorone/baton-sdk/pb/c1/connector_wrapper/v1"
baton_v1 "github.com/conductorone/baton-sdk/pb/c1/connectorapi/baton/v1"
"github.com/conductorone/baton-sdk/pkg/connectorrunner"
mcpPkg "github.com/conductorone/baton-sdk/pkg/mcp"

Check failure on line 29 in pkg/cli/commands.go

View workflow job for this annotation

GitHub Actions / go-lint

File is not properly formatted (goimports)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix import formatting.

The static analysis tool flagged this line as improperly formatted per goimports standards.

Run goimports -w pkg/cli/commands.go to fix the formatting.

🧰 Tools
🪛 GitHub Check: go-lint

[failure] 29-29:
File is not properly formatted (goimports)

🤖 Prompt for AI Agents
In @pkg/cli/commands.go at line 29, The import line using the alias mcpPkg
("github.com/conductorone/baton-sdk/pkg/mcp") is improperly formatted per
goimports; fix it by running goimports to reformat the imports or manually
reorder/adjust the import block to match goimports style (ensure correct
grouping, spacing, and removal of any unused imports), e.g., run goimports -w on
the commands.go in the CLI package to apply the fix.

"github.com/conductorone/baton-sdk/pkg/crypto"
"github.com/conductorone/baton-sdk/pkg/field"
"github.com/conductorone/baton-sdk/pkg/logging"
Expand Down Expand Up @@ -599,6 +600,74 @@
}
}

func MakeMCPServerCommand[T field.Configurable](
ctx context.Context,
name string,
v *viper.Viper,
confschema field.Configuration,
getconnector GetConnectorFunc2[T],
) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
err := v.BindPFlags(cmd.Flags())
if err != nil {
return err
}

runCtx, err := initLogger(
ctx,
name,
logging.WithLogFormat(v.GetString("log-format")),
logging.WithLogLevel(v.GetString("log-level")),
)
if err != nil {
return err
}

runCtx, otelShutdown, err := initOtel(runCtx, name, v, nil)
if err != nil {
return err
}
defer func() {
if otelShutdown == nil {
return
}
shutdownCtx, cancel := context.WithDeadline(context.Background(), time.Now().Add(otelShutdownTimeout))
defer cancel()
err := otelShutdown(shutdownCtx)
if err != nil {
zap.L().Error("error shutting down otel", zap.Error(err))
}
}()

l := ctxzap.Extract(runCtx)
l.Debug("starting MCP server")

readFromPath := true
decodeOpts := field.WithAdditionalDecodeHooks(field.FileUploadDecodeHook(readFromPath))
t, err := MakeGenericConfiguration[T](v, decodeOpts)
if err != nil {
return fmt.Errorf("failed to make configuration: %w", err)
}

if err := field.Validate(confschema, t, field.WithAuthMethod(v.GetString("auth-method"))); err != nil {
return err
}

c, err := getconnector(runCtx, t, RunTimeOpts{})
if err != nil {
return err
}

mcpServer, err := mcpPkg.NewMCPServer(runCtx, name, c)
if err != nil {
return fmt.Errorf("failed to create MCP server: %w", err)
}

l.Info("MCP server starting on stdio")
return mcpServer.Serve(runCtx)
}
}

func MakeCapabilitiesCommand[T field.Configurable](
ctx context.Context,
name string,
Expand Down
11 changes: 11 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,17 @@ func DefineConfigurationV2[T field.Configurable](
return nil, nil, err
}

_, err = cli.AddCommand(mainCMD, v, &schema, &cobra.Command{
Use: "mcp",
Short: "Run as MCP server (stdio transport)",
Long: "Run the connector as an MCP (Model Context Protocol) server using stdio transport. This allows AI assistants to interact with the connector.",
RunE: cli.MakeMCPServerCommand(ctx, connectorName, v, confschema, connector),
})

if err != nil {
return nil, nil, err
}

_, err = cli.AddCommand(mainCMD, v, &schema, &cobra.Command{
Use: "capabilities",
Short: "Get connector capabilities",
Expand Down
74 changes: 74 additions & 0 deletions pkg/mcp/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package mcp

import (
"encoding/json"

"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)

var (
marshaler = protojson.MarshalOptions{
EmitUnpopulated: false,
UseProtoNames: true,
}

unmarshaler = protojson.UnmarshalOptions{

Check failure on line 16 in pkg/mcp/convert.go

View workflow job for this annotation

GitHub Actions / go-lint

var unmarshaler is unused (unused)
DiscardUnknown: true,
}
)

// protoToMap converts a proto message to a map[string]any.
func protoToMap(msg proto.Message) (map[string]any, error) {
if msg == nil {
return nil, nil
}
jsonBytes, err := marshaler.Marshal(msg)
if err != nil {
return nil, err
}
var result map[string]any
if err := json.Unmarshal(jsonBytes, &result); err != nil {
return nil, err
}
return result, nil
}

// protoToJSON converts a proto message to a JSON string.
func protoToJSON(msg proto.Message) (string, error) {

Check failure on line 38 in pkg/mcp/convert.go

View workflow job for this annotation

GitHub Actions / go-lint

func protoToJSON is unused (unused)
if msg == nil {
return "{}", nil
}
jsonBytes, err := marshaler.Marshal(msg)
if err != nil {
return "", err
}
return string(jsonBytes), nil
}

// protoListToMaps converts a slice of proto messages to a slice of maps.
func protoListToMaps[T proto.Message](list []T) ([]map[string]any, error) {
result := make([]map[string]any, 0, len(list))
for _, item := range list {
m, err := protoToMap(item)
if err != nil {
return nil, err
}
result = append(result, m)
}
return result, nil
}

// jsonToProto unmarshals a JSON string into a proto message.
func jsonToProto(jsonStr string, msg proto.Message) error {

Check failure on line 63 in pkg/mcp/convert.go

View workflow job for this annotation

GitHub Actions / go-lint

func jsonToProto is unused (unused)
return unmarshaler.Unmarshal([]byte(jsonStr), msg)
}

// mapToProto converts a map to a proto message by going through JSON.
func mapToProto(m map[string]any, msg proto.Message) error {

Check failure on line 68 in pkg/mcp/convert.go

View workflow job for this annotation

GitHub Actions / go-lint

func mapToProto is unused (unused)
jsonBytes, err := json.Marshal(m)
if err != nil {
return err
}
return unmarshaler.Unmarshal(jsonBytes, msg)
}
Loading
Loading