Skip to content

feat: add MCP support #963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ require (
github.com/gptscript-ai/chat-completion-client v0.0.0-20250224164718-139cb4507b1d
github.com/gptscript-ai/cmd v0.0.0-20240802230653-326b7baf6fcb
github.com/gptscript-ai/go-gptscript v0.9.6-0.20250204133419-744b25b84a61
github.com/gptscript-ai/tui v0.0.0-20250204145344-33cd15de4cee
github.com/gptscript-ai/tui v0.0.0-20250419050840-5e79e16786c9
github.com/hexops/autogold/v2 v2.2.1
github.com/hexops/valast v1.4.4
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
github.com/mark3labs/mcp-go v0.21.1
github.com/mholt/archives v0.1.0
github.com/pkoukk/tiktoken-go v0.1.7
github.com/pkoukk/tiktoken-go-loader v0.0.2-0.20240522064338-c17e8bc0f699
Expand Down Expand Up @@ -122,6 +123,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/yuin/goldmark v1.5.4 // indirect
github.com/yuin/goldmark-emoji v1.0.2 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
Expand Down
8 changes: 6 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,8 @@ github.com/gptscript-ai/cmd v0.0.0-20240802230653-326b7baf6fcb h1:ky2J2CzBOskC7J
github.com/gptscript-ai/cmd v0.0.0-20240802230653-326b7baf6fcb/go.mod h1:DJAo1xTht1LDkNYFNydVjTHd576TC7MlpsVRl3oloVw=
github.com/gptscript-ai/go-gptscript v0.9.6-0.20250204133419-744b25b84a61 h1:QxLjsLOYlsVLPwuRkP0Q8EcAoZT1s8vU2ZBSX0+R6CI=
github.com/gptscript-ai/go-gptscript v0.9.6-0.20250204133419-744b25b84a61/go.mod h1:/FVuLwhz+sIfsWUgUHWKi32qT0i6+IXlUlzs70KKt/Q=
github.com/gptscript-ai/tui v0.0.0-20250204145344-33cd15de4cee h1:70PHW6Xw70yNNZ5aX936XqcMLwNmfMZpCV3FCOGKpxE=
github.com/gptscript-ai/tui v0.0.0-20250204145344-33cd15de4cee/go.mod h1:iwHxuueg2paOak7zIg0ESBWx7A0wIHGopAratbgaPNY=
github.com/gptscript-ai/tui v0.0.0-20250419050840-5e79e16786c9 h1:wQC8sKyeGA50WnCEG+Jo5FNRIkuX3HX8d3ubyWCCoI8=
github.com/gptscript-ai/tui v0.0.0-20250419050840-5e79e16786c9/go.mod h1:iwHxuueg2paOak7zIg0ESBWx7A0wIHGopAratbgaPNY=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
Expand Down Expand Up @@ -270,6 +270,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mark3labs/mcp-go v0.21.1 h1:7Ek6KPIIbMhEYHRiRIg6K6UAgNZCJaHKQp926MNr6V0=
github.com/mark3labs/mcp-go v0.21.1/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
Expand Down Expand Up @@ -406,6 +408,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
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.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/gptscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (r *GPTScript) listTools(ctx context.Context, gptScript *gptscript.GPTScrip
// Don't print instructions
tool.Instructions = ""

lines = append(lines, tool.String())
lines = append(lines, tool.Print())
}
fmt.Println(strings.Join(lines, "\n---\n"))
return nil
Expand Down
26 changes: 26 additions & 0 deletions pkg/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sync"

"github.com/gptscript-ai/gptscript/pkg/counter"
"github.com/gptscript-ai/gptscript/pkg/mcp"
"github.com/gptscript-ai/gptscript/pkg/types"
"github.com/gptscript-ai/gptscript/pkg/version"
)
Expand Down Expand Up @@ -41,6 +42,11 @@ type Engine struct {
RuntimeManager RuntimeManager
Env []string
Progress chan<- types.CompletionStatus
MCPRunner MCPRunner
}

type MCPRunner interface {
Run(ctx context.Context, progress chan<- types.CompletionStatus, tool types.Tool, input string) (string, error)
}

type State struct {
Expand Down Expand Up @@ -307,6 +313,21 @@ func populateMessageParams(ctx Context, completion *types.CompletionRequest, too
return nil
}

func (e *Engine) runMCPInvoke(ctx Context, tool types.Tool, input string) (*Return, error) {
runner := e.MCPRunner
if runner == nil {
runner = mcp.DefaultRunner
}
output, err := runner.Run(ctx.Ctx, e.Progress, tool, input)
if err != nil {
return nil, fmt.Errorf("failed to run MCP invoke: %w", err)
}

return &Return{
Result: &output,
}, nil
}

func (e *Engine) runCommandTools(ctx Context, tool types.Tool, input string) (*Return, error) {
if tool.IsHTTP() {
return e.runHTTP(ctx, tool, input)
Expand Down Expand Up @@ -342,6 +363,10 @@ func (e *Engine) Start(ctx Context, input string) (ret *Return, err error) {
}
}()

if tool.IsMCPInvoke() {
return e.runMCPInvoke(ctx, tool, input)
}

if tool.IsCommand() {
return e.runCommandTools(ctx, tool, input)
}
Expand Down Expand Up @@ -378,6 +403,7 @@ func addUpdateSystem(ctx Context, tool types.Tool, msgs []types.CompletionMessag
instructions = append(instructions, context.Content)
}

tool.Instructions = strings.TrimPrefix(tool.Instructions, types.PromptPrefix)
if tool.Instructions != "" {
instructions = append(instructions, tool.Instructions)
}
Expand Down
54 changes: 43 additions & 11 deletions pkg/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/gptscript-ai/gptscript/pkg/builtin"
"github.com/gptscript-ai/gptscript/pkg/cache"
"github.com/gptscript-ai/gptscript/pkg/hash"
"github.com/gptscript-ai/gptscript/pkg/mcp"
"github.com/gptscript-ai/gptscript/pkg/openapi"
"github.com/gptscript-ai/gptscript/pkg/parser"
"github.com/gptscript-ai/gptscript/pkg/system"
Expand Down Expand Up @@ -155,7 +156,23 @@ func loadOpenAPI(prg *types.Program, data []byte) *openapi3.T {
return openAPIDocument
}

func readTool(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, targetToolName, defaultModel string) ([]types.Tool, error) {
func processMCP(ctx context.Context, tool []types.Tool, mcpLoader MCPLoader) (result []types.Tool, _ error) {
for _, t := range tool {
if t.IsMCP() {
mcpTools, err := mcpLoader.Load(ctx, t)
if err != nil {
return nil, fmt.Errorf("error loading MCP tools: %w", err)
}
result = append(result, mcpTools...)
} else {
result = append(result, t)
}
}

return result, nil
}

func readTool(ctx context.Context, cache *cache.Client, mcp MCPLoader, prg *types.Program, base *source, targetToolName, defaultModel string) ([]types.Tool, error) {
data := base.Content

var (
Expand Down Expand Up @@ -212,6 +229,11 @@ func readTool(ctx context.Context, cache *cache.Client, prg *types.Program, base
return nil, fmt.Errorf("no tools found in %s", base)
}

tools, err := processMCP(ctx, tools, mcp)
if err != nil {
return nil, err
}

var (
localTools = types.ToolSet{}
targetTools []types.Tool
Expand Down Expand Up @@ -279,17 +301,17 @@ func readTool(ctx context.Context, cache *cache.Client, prg *types.Program, base
localTools[strings.ToLower(tool.Name)] = tool
}

return linkAll(ctx, cache, prg, base, targetTools, localTools, defaultModel)
return linkAll(ctx, cache, mcp, prg, base, targetTools, localTools, defaultModel)
}

func linkAll(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, tools []types.Tool, localTools types.ToolSet, defaultModel string) (result []types.Tool, _ error) {
func linkAll(ctx context.Context, cache *cache.Client, mcp MCPLoader, prg *types.Program, base *source, tools []types.Tool, localTools types.ToolSet, defaultModel string) (result []types.Tool, _ error) {
localToolsMapping := make(map[string]string, len(tools))
for _, localTool := range localTools {
localToolsMapping[strings.ToLower(localTool.Name)] = localTool.ID
}

for _, tool := range tools {
tool, err := link(ctx, cache, prg, base, tool, localTools, localToolsMapping, defaultModel)
tool, err := link(ctx, cache, mcp, prg, base, tool, localTools, localToolsMapping, defaultModel)
if err != nil {
return nil, err
}
Expand All @@ -298,7 +320,7 @@ func linkAll(ctx context.Context, cache *cache.Client, prg *types.Program, base
return
}

func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, tool types.Tool, localTools types.ToolSet, localToolsMapping map[string]string, defaultModel string) (types.Tool, error) {
func link(ctx context.Context, cache *cache.Client, mcp MCPLoader, prg *types.Program, base *source, tool types.Tool, localTools types.ToolSet, localToolsMapping map[string]string, defaultModel string) (types.Tool, error) {
if existing, ok := prg.ToolSet[tool.ID]; ok {
return existing, nil
}
Expand All @@ -323,7 +345,7 @@ func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *so
linkedTool = existing
} else {
var err error
linkedTool, err = link(ctx, cache, prg, base, localTool, localTools, localToolsMapping, defaultModel)
linkedTool, err = link(ctx, cache, mcp, prg, base, localTool, localTools, localToolsMapping, defaultModel)
if err != nil {
return types.Tool{}, fmt.Errorf("failed linking %s at %s: %w", targetToolName, base, err)
}
Expand All @@ -333,7 +355,7 @@ func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *so
toolNames[targetToolName] = struct{}{}
} else {
toolName, subTool := types.SplitToolRef(targetToolName)
resolvedTools, err := resolve(ctx, cache, prg, base, toolName, subTool, defaultModel)
resolvedTools, err := resolve(ctx, cache, mcp, prg, base, toolName, subTool, defaultModel)
if err != nil {
return types.Tool{}, fmt.Errorf("failed resolving %s from %s: %w", targetToolName, base, err)
}
Expand Down Expand Up @@ -373,7 +395,7 @@ func ProgramFromSource(ctx context.Context, content, subToolName string, opts ..
prg := types.Program{
ToolSet: types.ToolSet{},
}
tools, err := readTool(ctx, opt.Cache, &prg, &source{
tools, err := readTool(ctx, opt.Cache, opt.MCPLoader, &prg, &source{
Content: []byte(content),
Path: locationPath,
Name: locationName,
Expand All @@ -390,13 +412,19 @@ type Options struct {
Cache *cache.Client
Location string
DefaultModel string
MCPLoader MCPLoader
}

type MCPLoader interface {
Load(ctx context.Context, tool types.Tool) ([]types.Tool, error)
}

func complete(opts ...Options) (result Options) {
for _, opt := range opts {
result.Cache = types.FirstSet(opt.Cache, result.Cache)
result.Location = types.FirstSet(opt.Location, result.Location)
result.DefaultModel = types.FirstSet(opt.DefaultModel, result.DefaultModel)
result.MCPLoader = types.FirstSet(opt.MCPLoader, result.MCPLoader)
}

if result.Location == "" {
Expand All @@ -407,6 +435,10 @@ func complete(opts ...Options) (result Options) {
result.DefaultModel = builtin.GetDefaultModel()
}

if result.MCPLoader == nil {
result.MCPLoader = mcp.DefaultLoader
}

return
}

Expand All @@ -430,15 +462,15 @@ func Program(ctx context.Context, name, subToolName string, opts ...Options) (ty
Name: name,
ToolSet: types.ToolSet{},
}
tools, err := resolve(ctx, opt.Cache, &prg, &source{}, name, subToolName, opt.DefaultModel)
tools, err := resolve(ctx, opt.Cache, opt.MCPLoader, &prg, &source{}, name, subToolName, opt.DefaultModel)
if err != nil {
return types.Program{}, err
}
prg.EntryToolID = tools[0].ID
return prg, nil
}

func resolve(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, name, subTool, defaultModel string) ([]types.Tool, error) {
func resolve(ctx context.Context, cache *cache.Client, mcp MCPLoader, prg *types.Program, base *source, name, subTool, defaultModel string) ([]types.Tool, error) {
if subTool == "" {
t, ok := builtin.DefaultModel(name, defaultModel)
if ok {
Expand All @@ -452,7 +484,7 @@ func resolve(ctx context.Context, cache *cache.Client, prg *types.Program, base
return nil, err
}

result, err := readTool(ctx, cache, prg, s, subTool, defaultModel)
result, err := readTool(ctx, cache, mcp, prg, s, subTool, defaultModel)
if err != nil {
return nil, err
}
Expand Down
Loading
Loading