Skip to content

Commit ea1f503

Browse files
chore: add MCP support
1 parent 7e668d5 commit ea1f503

File tree

13 files changed

+784
-20
lines changed

13 files changed

+784
-20
lines changed

go.mod

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ require (
1818
github.com/gptscript-ai/chat-completion-client v0.0.0-20250224164718-139cb4507b1d
1919
github.com/gptscript-ai/cmd v0.0.0-20240802230653-326b7baf6fcb
2020
github.com/gptscript-ai/go-gptscript v0.9.6-0.20250204133419-744b25b84a61
21-
github.com/gptscript-ai/tui v0.0.0-20250204145344-33cd15de4cee
21+
github.com/gptscript-ai/tui v0.0.0-20250419050840-5e79e16786c9
2222
github.com/hexops/autogold/v2 v2.2.1
2323
github.com/hexops/valast v1.4.4
2424
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
25+
github.com/mark3labs/mcp-go v0.21.1
2526
github.com/mholt/archives v0.1.0
2627
github.com/pkoukk/tiktoken-go v0.1.7
2728
github.com/pkoukk/tiktoken-go-loader v0.0.2-0.20240522064338-c17e8bc0f699
@@ -122,6 +123,7 @@ require (
122123
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
123124
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
124125
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
126+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
125127
github.com/yuin/goldmark v1.5.4 // indirect
126128
github.com/yuin/goldmark-emoji v1.0.2 // indirect
127129
go4.org v0.0.0-20230225012048-214862532bf5 // indirect

go.sum

+6-2
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ github.com/gptscript-ai/cmd v0.0.0-20240802230653-326b7baf6fcb h1:ky2J2CzBOskC7J
203203
github.com/gptscript-ai/cmd v0.0.0-20240802230653-326b7baf6fcb/go.mod h1:DJAo1xTht1LDkNYFNydVjTHd576TC7MlpsVRl3oloVw=
204204
github.com/gptscript-ai/go-gptscript v0.9.6-0.20250204133419-744b25b84a61 h1:QxLjsLOYlsVLPwuRkP0Q8EcAoZT1s8vU2ZBSX0+R6CI=
205205
github.com/gptscript-ai/go-gptscript v0.9.6-0.20250204133419-744b25b84a61/go.mod h1:/FVuLwhz+sIfsWUgUHWKi32qT0i6+IXlUlzs70KKt/Q=
206-
github.com/gptscript-ai/tui v0.0.0-20250204145344-33cd15de4cee h1:70PHW6Xw70yNNZ5aX936XqcMLwNmfMZpCV3FCOGKpxE=
207-
github.com/gptscript-ai/tui v0.0.0-20250204145344-33cd15de4cee/go.mod h1:iwHxuueg2paOak7zIg0ESBWx7A0wIHGopAratbgaPNY=
206+
github.com/gptscript-ai/tui v0.0.0-20250419050840-5e79e16786c9 h1:wQC8sKyeGA50WnCEG+Jo5FNRIkuX3HX8d3ubyWCCoI8=
207+
github.com/gptscript-ai/tui v0.0.0-20250419050840-5e79e16786c9/go.mod h1:iwHxuueg2paOak7zIg0ESBWx7A0wIHGopAratbgaPNY=
208208
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
209209
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
210210
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -270,6 +270,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
270270
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
271271
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
272272
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
273+
github.com/mark3labs/mcp-go v0.21.1 h1:7Ek6KPIIbMhEYHRiRIg6K6UAgNZCJaHKQp926MNr6V0=
274+
github.com/mark3labs/mcp-go v0.21.1/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
273275
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
274276
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
275277
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -406,6 +408,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
406408
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
407409
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
408410
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
411+
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
412+
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
409413
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
410414
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
411415
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=

pkg/cli/gptscript.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ func (r *GPTScript) listTools(ctx context.Context, gptScript *gptscript.GPTScrip
215215
// Don't print instructions
216216
tool.Instructions = ""
217217

218-
lines = append(lines, tool.String())
218+
lines = append(lines, tool.Print())
219219
}
220220
fmt.Println(strings.Join(lines, "\n---\n"))
221221
return nil

pkg/engine/engine.go

+26
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"sync"
1212

1313
"github.com/gptscript-ai/gptscript/pkg/counter"
14+
"github.com/gptscript-ai/gptscript/pkg/mcp"
1415
"github.com/gptscript-ai/gptscript/pkg/types"
1516
"github.com/gptscript-ai/gptscript/pkg/version"
1617
)
@@ -41,6 +42,11 @@ type Engine struct {
4142
RuntimeManager RuntimeManager
4243
Env []string
4344
Progress chan<- types.CompletionStatus
45+
MCPRunner MCPRunner
46+
}
47+
48+
type MCPRunner interface {
49+
Run(ctx context.Context, progress chan<- types.CompletionStatus, tool types.Tool, input string) (string, error)
4450
}
4551

4652
type State struct {
@@ -307,6 +313,21 @@ func populateMessageParams(ctx Context, completion *types.CompletionRequest, too
307313
return nil
308314
}
309315

316+
func (e *Engine) runMCPInvoke(ctx Context, tool types.Tool, input string) (*Return, error) {
317+
runner := e.MCPRunner
318+
if runner == nil {
319+
runner = mcp.DefaultRunner
320+
}
321+
output, err := runner.Run(ctx.Ctx, e.Progress, tool, input)
322+
if err != nil {
323+
return nil, fmt.Errorf("failed to run MCP invoke: %w", err)
324+
}
325+
326+
return &Return{
327+
Result: &output,
328+
}, nil
329+
}
330+
310331
func (e *Engine) runCommandTools(ctx Context, tool types.Tool, input string) (*Return, error) {
311332
if tool.IsHTTP() {
312333
return e.runHTTP(ctx, tool, input)
@@ -342,6 +363,10 @@ func (e *Engine) Start(ctx Context, input string) (ret *Return, err error) {
342363
}
343364
}()
344365

366+
if tool.IsMCPInvoke() {
367+
return e.runMCPInvoke(ctx, tool, input)
368+
}
369+
345370
if tool.IsCommand() {
346371
return e.runCommandTools(ctx, tool, input)
347372
}
@@ -378,6 +403,7 @@ func addUpdateSystem(ctx Context, tool types.Tool, msgs []types.CompletionMessag
378403
instructions = append(instructions, context.Content)
379404
}
380405

406+
tool.Instructions = strings.TrimPrefix(tool.Instructions, types.PromptPrefix)
381407
if tool.Instructions != "" {
382408
instructions = append(instructions, tool.Instructions)
383409
}

pkg/loader/loader.go

+43-11
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/gptscript-ai/gptscript/pkg/builtin"
2121
"github.com/gptscript-ai/gptscript/pkg/cache"
2222
"github.com/gptscript-ai/gptscript/pkg/hash"
23+
"github.com/gptscript-ai/gptscript/pkg/mcp"
2324
"github.com/gptscript-ai/gptscript/pkg/openapi"
2425
"github.com/gptscript-ai/gptscript/pkg/parser"
2526
"github.com/gptscript-ai/gptscript/pkg/system"
@@ -155,7 +156,23 @@ func loadOpenAPI(prg *types.Program, data []byte) *openapi3.T {
155156
return openAPIDocument
156157
}
157158

158-
func readTool(ctx context.Context, cache *cache.Client, prg *types.Program, base *source, targetToolName, defaultModel string) ([]types.Tool, error) {
159+
func processMCP(ctx context.Context, tool []types.Tool, mcpLoader MCPLoader) (result []types.Tool, _ error) {
160+
for _, t := range tool {
161+
if t.IsMCP() {
162+
mcpTools, err := mcpLoader.Load(ctx, t)
163+
if err != nil {
164+
return nil, fmt.Errorf("error loading MCP tools: %w", err)
165+
}
166+
result = append(result, mcpTools...)
167+
} else {
168+
result = append(result, t)
169+
}
170+
}
171+
172+
return result, nil
173+
}
174+
175+
func readTool(ctx context.Context, cache *cache.Client, mcp MCPLoader, prg *types.Program, base *source, targetToolName, defaultModel string) ([]types.Tool, error) {
159176
data := base.Content
160177

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

232+
tools, err := processMCP(ctx, tools, mcp)
233+
if err != nil {
234+
return nil, err
235+
}
236+
215237
var (
216238
localTools = types.ToolSet{}
217239
targetTools []types.Tool
@@ -279,17 +301,17 @@ func readTool(ctx context.Context, cache *cache.Client, prg *types.Program, base
279301
localTools[strings.ToLower(tool.Name)] = tool
280302
}
281303

282-
return linkAll(ctx, cache, prg, base, targetTools, localTools, defaultModel)
304+
return linkAll(ctx, cache, mcp, prg, base, targetTools, localTools, defaultModel)
283305
}
284306

285-
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) {
307+
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) {
286308
localToolsMapping := make(map[string]string, len(tools))
287309
for _, localTool := range localTools {
288310
localToolsMapping[strings.ToLower(localTool.Name)] = localTool.ID
289311
}
290312

291313
for _, tool := range tools {
292-
tool, err := link(ctx, cache, prg, base, tool, localTools, localToolsMapping, defaultModel)
314+
tool, err := link(ctx, cache, mcp, prg, base, tool, localTools, localToolsMapping, defaultModel)
293315
if err != nil {
294316
return nil, err
295317
}
@@ -298,7 +320,7 @@ func linkAll(ctx context.Context, cache *cache.Client, prg *types.Program, base
298320
return
299321
}
300322

301-
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) {
323+
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) {
302324
if existing, ok := prg.ToolSet[tool.ID]; ok {
303325
return existing, nil
304326
}
@@ -323,7 +345,7 @@ func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *so
323345
linkedTool = existing
324346
} else {
325347
var err error
326-
linkedTool, err = link(ctx, cache, prg, base, localTool, localTools, localToolsMapping, defaultModel)
348+
linkedTool, err = link(ctx, cache, mcp, prg, base, localTool, localTools, localToolsMapping, defaultModel)
327349
if err != nil {
328350
return types.Tool{}, fmt.Errorf("failed linking %s at %s: %w", targetToolName, base, err)
329351
}
@@ -333,7 +355,7 @@ func link(ctx context.Context, cache *cache.Client, prg *types.Program, base *so
333355
toolNames[targetToolName] = struct{}{}
334356
} else {
335357
toolName, subTool := types.SplitToolRef(targetToolName)
336-
resolvedTools, err := resolve(ctx, cache, prg, base, toolName, subTool, defaultModel)
358+
resolvedTools, err := resolve(ctx, cache, mcp, prg, base, toolName, subTool, defaultModel)
337359
if err != nil {
338360
return types.Tool{}, fmt.Errorf("failed resolving %s from %s: %w", targetToolName, base, err)
339361
}
@@ -373,7 +395,7 @@ func ProgramFromSource(ctx context.Context, content, subToolName string, opts ..
373395
prg := types.Program{
374396
ToolSet: types.ToolSet{},
375397
}
376-
tools, err := readTool(ctx, opt.Cache, &prg, &source{
398+
tools, err := readTool(ctx, opt.Cache, opt.MCPLoader, &prg, &source{
377399
Content: []byte(content),
378400
Path: locationPath,
379401
Name: locationName,
@@ -390,13 +412,19 @@ type Options struct {
390412
Cache *cache.Client
391413
Location string
392414
DefaultModel string
415+
MCPLoader MCPLoader
416+
}
417+
418+
type MCPLoader interface {
419+
Load(ctx context.Context, tool types.Tool) ([]types.Tool, error)
393420
}
394421

395422
func complete(opts ...Options) (result Options) {
396423
for _, opt := range opts {
397424
result.Cache = types.FirstSet(opt.Cache, result.Cache)
398425
result.Location = types.FirstSet(opt.Location, result.Location)
399426
result.DefaultModel = types.FirstSet(opt.DefaultModel, result.DefaultModel)
427+
result.MCPLoader = types.FirstSet(opt.MCPLoader, result.MCPLoader)
400428
}
401429

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

438+
if result.MCPLoader == nil {
439+
result.MCPLoader = mcp.DefaultLoader
440+
}
441+
410442
return
411443
}
412444

@@ -430,15 +462,15 @@ func Program(ctx context.Context, name, subToolName string, opts ...Options) (ty
430462
Name: name,
431463
ToolSet: types.ToolSet{},
432464
}
433-
tools, err := resolve(ctx, opt.Cache, &prg, &source{}, name, subToolName, opt.DefaultModel)
465+
tools, err := resolve(ctx, opt.Cache, opt.MCPLoader, &prg, &source{}, name, subToolName, opt.DefaultModel)
434466
if err != nil {
435467
return types.Program{}, err
436468
}
437469
prg.EntryToolID = tools[0].ID
438470
return prg, nil
439471
}
440472

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

455-
result, err := readTool(ctx, cache, prg, s, subTool, defaultModel)
487+
result, err := readTool(ctx, cache, mcp, prg, s, subTool, defaultModel)
456488
if err != nil {
457489
return nil, err
458490
}

0 commit comments

Comments
 (0)