-
Notifications
You must be signed in to change notification settings - Fork 247
Expand file tree
/
Copy pathscript.go
More file actions
93 lines (80 loc) · 3.25 KB
/
Copy pathscript.go
File metadata and controls
93 lines (80 loc) · 3.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc.
// SPDX-License-Identifier: Apache-2.0
// Package script provides a Starlark-based script execution engine for
// orchestrating MCP tool calls. It allows agents to send scripts that call
// multiple tools and return aggregated results in a single round-trip.
package script
import (
"context"
"github.com/stacklok/toolhive-core/mcpcompat/mcp"
)
// DefaultStepLimit is the default maximum number of Starlark execution steps.
const DefaultStepLimit uint64 = 100_000
// Executor runs Starlark scripts and describes the virtual tool.
//
// Scripts can call any tool bound at construction time, use loops and
// conditionals, fan out with parallel(), and return aggregated results.
// The returned CallToolResult is ready for direct serialization into a
// JSON-RPC response by the middleware layer.
type Executor interface {
// Execute runs a Starlark script with optional named data arguments
// injected as top-level variables. Tools are already bound from
// construction and available as callable functions within the script.
Execute(ctx context.Context, script string, data map[string]interface{}) (*mcp.CallToolResult, error)
// ToolDescription returns the dynamic description for the
// execute_tool_script virtual tool definition, listing all available
// tools and their calling conventions.
ToolDescription() string
}
// Tool bundles an MCP tool's metadata with a callback for invoking it.
//
// The middleware layer constructs these with Call closures that route
// invocations through the middleware chain, ensuring authz and other
// policies are enforced on inner tool calls.
type Tool struct {
// Name is the MCP tool name (e.g., "github-fetch-prs").
Name string
// Description is the human-readable tool description.
Description string
// Call invokes the tool with the given arguments and returns the MCP result.
// Arguments are always a string-keyed map. When scripts use positional
// arguments, they are converted to "arg0", "arg1", etc.
//
// The caller is responsible for enforcing per-call timeouts (e.g., by
// wrapping ctx with context.WithTimeout in the closure). The engine
// passes the context through but does not apply additional deadlines.
Call func(ctx context.Context, arguments map[string]interface{}) (*mcp.CallToolResult, error)
}
// Config holds script execution parameters. A nil Config passed to New
// uses sensible defaults for all fields.
type Config struct {
// StepLimit is the maximum number of Starlark execution steps per script.
// Prevents infinite loops and runaway computation.
// Zero uses DefaultStepLimit (100,000).
StepLimit uint64
// ParallelMax is the maximum number of concurrent goroutines that
// parallel() can spawn. Zero means unlimited.
ParallelMax int
}
// New creates an Executor bound to the given tools and configuration.
// A nil cfg uses defaults (DefaultStepLimit, unlimited parallelism, no timeout).
func New(tools []Tool, cfg *Config) Executor {
c := resolveConfig(cfg)
return &executor{
tools: tools,
config: c,
}
}
func resolveConfig(cfg *Config) Config {
if cfg == nil {
return Config{StepLimit: DefaultStepLimit}
}
c := *cfg
if c.StepLimit == 0 {
c.StepLimit = DefaultStepLimit
}
if c.ParallelMax < 0 {
c.ParallelMax = 0
}
return c
}