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
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,8 @@ See `docs/prerelease-builds.md` for download instructions.
- BBolt database (`~/.mcpproxy/config.db`) — new `agent_tokens` bucket (028-agent-tokens)
- Go 1.24 (toolchain go1.24.10) + TypeScript 5.9 / Vue 3.5 + Chi router, BBolt, Zap logging, mcp-go, golang-jwt/jwt/v5, Vue 3, Pinia, DaisyUI (024-teams-multiuser-oauth)
- BBolt database (`~/.mcpproxy/config.db`) - new buckets for users, sessions, user servers (024-teams-multiuser-oauth)
- Go 1.24 (toolchain go1.24.10) + `github.com/dop251/goja` (existing JS sandbox), `github.com/evanw/esbuild` (new - TypeScript transpilation), `github.com/mark3labs/mcp-go` (MCP protocol), `github.com/spf13/cobra` (CLI) (033-typescript-code-execution)
- N/A (no new storage requirements) (033-typescript-code-execution)

## Recent Changes
- 001-update-version-display: Added Go 1.24 (toolchain go1.24.10)
31 changes: 26 additions & 5 deletions cmd/mcpproxy/code_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,19 @@ import (
var (
codeCmd = &cobra.Command{
Use: "code",
Short: "JavaScript code execution for multi-tool orchestration",
Long: "Execute JavaScript code that orchestrates multiple upstream MCP tools in a single request",
Short: "JavaScript/TypeScript code execution for multi-tool orchestration",
Long: "Execute JavaScript or TypeScript code that orchestrates multiple upstream MCP tools in a single request",
}

codeExecCmd = &cobra.Command{
Use: "exec",
Short: "Execute JavaScript code",
Long: `Execute JavaScript code that can orchestrate multiple upstream MCP tools.
Short: "Execute JavaScript or TypeScript code",
Long: `Execute JavaScript or TypeScript code that can orchestrate multiple upstream MCP tools.

The JavaScript code has access to:
Use --language typescript to write TypeScript code with type annotations,
interfaces, enums, and generics. Types are automatically stripped before execution.

The code has access to:
- input: Global variable containing the input data (from --input or --input-file)
- call_tool(serverName, toolName, args): Function to invoke upstream MCP tools

Expand All @@ -63,6 +66,7 @@ Exit codes:
codeAllowedSrvs []string
codeLogLevel string
codeConfigPath string
codeLanguage string
)

// GetCodeCommand returns the code command for adding to the root command
Expand All @@ -84,14 +88,21 @@ func init() {
codeExecCmd.Flags().StringSliceVar(&codeAllowedSrvs, "allowed-servers", []string{}, "Comma-separated list of allowed server names (empty = all allowed)")
codeExecCmd.Flags().StringVarP(&codeLogLevel, "log-level", "l", "info", "Log level (trace, debug, info, warn, error)")
codeExecCmd.Flags().StringVarP(&codeConfigPath, "config", "c", "", "Path to MCP configuration file (default: ~/.mcpproxy/mcp_config.json)")
codeExecCmd.Flags().StringVar(&codeLanguage, "language", "javascript", "Source code language: javascript, typescript")

// Add examples
codeExecCmd.Example = ` # Execute inline code with input
mcpproxy code exec --code="({ result: input.value * 2 })" --input='{"value": 21}'

# Execute TypeScript code
mcpproxy code exec --language typescript --code="const x: number = 42; ({ result: x })"

# Execute code from file
mcpproxy code exec --file=script.js --input-file=params.json

# Execute TypeScript from file
mcpproxy code exec --language typescript --file=script.ts --input-file=params.json

# Call upstream tools
mcpproxy code exec --code="call_tool('github', 'get_user', {username: input.user})" --input='{"user":"octocat"}'

Expand Down Expand Up @@ -196,6 +207,7 @@ func runCodeExecClientMode(dataDir, code string, input map[string]interface{}, l
codeTimeout,
codeMaxToolCalls,
codeAllowedSrvs,
cliclient.CodeExecOptions{Language: codeLanguage},
)
if err != nil {
// T029: Use formatErrorWithRequestID to include request_id in error output
Expand Down Expand Up @@ -274,6 +286,11 @@ func runCodeExecStandalone(globalConfig *config.Config, code string, input map[s
},
}

// Pass language if not the default
if codeLanguage != "" && codeLanguage != "javascript" {
args["language"] = codeLanguage
}

// Call the code_execution tool
result, err := mcpProxy.CallBuiltInTool(ctx, "code_execution", args)
if err != nil {
Expand Down Expand Up @@ -330,6 +347,10 @@ func validateOptions() error {
fmt.Fprintf(os.Stderr, "Error: max-tool-calls cannot be negative\n")
return fmt.Errorf("invalid max-tool-calls")
}
if codeLanguage != "javascript" && codeLanguage != "typescript" {
fmt.Fprintf(os.Stderr, "Error: unsupported language %q. Supported languages: javascript, typescript\n", codeLanguage)
return fmt.Errorf("invalid language")
}
return nil
}

Expand Down
27 changes: 22 additions & 5 deletions docs/code_execution/api-reference.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# JavaScript Code Execution - API Reference
# Code Execution - API Reference

Complete reference for the `code_execution` MCP tool.
Complete reference for the `code_execution` MCP tool (JavaScript and TypeScript).

## Table of Contents

Expand All @@ -27,11 +27,17 @@ Complete reference for the `code_execution` MCP tool.
"properties": {
"code": {
"type": "string",
"description": "JavaScript source code (ES2020+) to execute..."
"description": "JavaScript or TypeScript source code (ES2020+) to execute..."
},
"language": {
"type": "string",
"description": "Source code language. When set to 'typescript', the code is automatically transpiled to JavaScript before execution.",
"enum": ["javascript", "typescript"],
"default": "javascript"
},
"input": {
"type": "object",
"description": "Input data accessible as global `input` variable in JavaScript code",
"description": "Input data accessible as global `input` variable in code",
"default": {}
},
"options": {
Expand Down Expand Up @@ -77,6 +83,16 @@ Complete reference for the `code_execution` MCP tool.
}
```

### TypeScript Request

```json
{
"code": "const x: number = 42; const msg: string = 'hello'; ({ result: x, message: msg })",
"language": "typescript",
"input": {}
}
```

### Full Request with Options

```json
Expand All @@ -97,7 +113,8 @@ Complete reference for the `code_execution` MCP tool.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `code` | string | **Yes** | JavaScript source code to execute (ES2020+ syntax supported) |
| `code` | string | **Yes** | JavaScript or TypeScript source code to execute (ES2020+ syntax supported) |
| `language` | string | No | Source language: `"javascript"` (default) or `"typescript"` |
| `input` | object | No | Input data accessible as `input` global variable (default: `{}`) |
| `options` | object | No | Execution options (see below) |

Expand Down
64 changes: 61 additions & 3 deletions docs/code_execution/overview.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# JavaScript Code Execution - Overview
# Code Execution - Overview

## What is Code Execution?

The `code_execution` tool enables LLM agents to orchestrate multiple upstream MCP tools in a single request using JavaScript. Instead of making multiple round-trips to the model, you can execute complex multi-step workflows with conditional logic, loops, and data transformations—all within a single execution context.
The `code_execution` tool enables LLM agents to orchestrate multiple upstream MCP tools in a single request using JavaScript or TypeScript. Instead of making multiple round-trips to the model, you can execute complex multi-step workflows with conditional logic, loops, and data transformations—all within a single execution context.

**TypeScript support**: Set `language: "typescript"` to write code with type annotations, interfaces, enums, and generics. Types are automatically stripped before execution with near-zero overhead (<5ms).

## When to Use Code Execution

Expand Down Expand Up @@ -213,9 +215,12 @@ mcpproxy serve
### 3. Test with CLI

```bash
# Simple test
# Simple JavaScript test
mcpproxy code exec --code="({ result: input.value * 2 })" --input='{"value": 21}'

# TypeScript test
mcpproxy code exec --language typescript --code="const x: number = 42; ({ result: x })"

# Call upstream tool
mcpproxy code exec --code="call_tool('github', 'get_user', {username: input.user})" --input='{"user":"octocat"}'
```
Expand Down Expand Up @@ -373,6 +378,59 @@ code_execution({
// Returns: {ok: false, error: {code: "MAX_TOOL_CALLS_EXCEEDED", message: "..."}}
```

## TypeScript Support

You can write code execution scripts in TypeScript by setting the `language` parameter to `"typescript"`. TypeScript types are automatically stripped before execution using esbuild, with near-zero transpilation overhead.

### Supported TypeScript Features

- Type annotations: `const x: number = 42`
- Interfaces: `interface User { name: string; age: number; }`
- Type aliases: `type StringOrNumber = string | number`
- Generics: `function identity<T>(arg: T): T { return arg; }`
- Enums: `enum Direction { Up = "UP", Down = "DOWN" }`
- Namespaces: `namespace MyLib { export const value = 42; }`
- Type assertions: `const x = value as string`

### TypeScript via MCP Tool

```json
{
"name": "code_execution",
"arguments": {
"code": "interface User { name: string; }\nconst user: User = { name: input.username };\n({ greeting: 'Hello ' + user.name })",
"language": "typescript",
"input": {"username": "Alice"}
}
}
```

### TypeScript via REST API

```bash
curl -X POST http://127.0.0.1:8080/api/v1/code/exec \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"code": "const x: number = 42; ({ result: x })",
"language": "typescript"
}'
```

### TypeScript via CLI

```bash
mcpproxy code exec --language typescript \
--code="const x: number = 42; ({ result: x })"
```

### Important Notes

- TypeScript support uses type-stripping only (no type checking or semantic validation)
- Valid JavaScript is also valid TypeScript, so you can always use `language: "typescript"` even for plain JS
- The transpiled output runs in the same ES5.1+ goja sandbox with all existing capabilities
- Transpilation errors return the `TRANSPILE_ERROR` error code with line/column information

## Best Practices

### 1. Keep Code Simple
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ require (
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/esiqveland/notify v0.13.3 // indirect
github.com/evanw/esbuild v0.27.3 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/esiqveland/notify v0.13.3 h1:QCMw6o1n+6rl+oLUfg8P1IIDSFsDEb2WlXvVvIJbI/o=
github.com/esiqveland/notify v0.13.3/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE=
github.com/evanw/esbuild v0.27.3 h1:dH/to9tBKybig6hl25hg4SKIWP7U8COdJKbGEwnUkmU=
github.com/evanw/esbuild v0.27.3/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
Expand Down Expand Up @@ -318,6 +320,7 @@ golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
Expand Down
13 changes: 12 additions & 1 deletion internal/cliclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,20 @@ func parseAPIError(errorMsg, requestID string) error {
return &APIError{Message: errorMsg, RequestID: requestID}
}

// CodeExec executes JavaScript code via the daemon API.
// CodeExecOptions contains optional parameters for code execution via the daemon API.
type CodeExecOptions struct {
Language string // Source language: "javascript" (default) or "typescript"
}

// CodeExec executes JavaScript or TypeScript code via the daemon API.
func (c *Client) CodeExec(
ctx context.Context,
code string,
input map[string]interface{},
timeoutMS int,
maxToolCalls int,
allowedServers []string,
opts ...CodeExecOptions,
) (*CodeExecResult, error) {
// Build request body
reqBody := map[string]interface{}{
Expand All @@ -167,6 +173,11 @@ func (c *Client) CodeExec(
},
}

// Apply optional language parameter
if len(opts) > 0 && opts[0].Language != "" && opts[0].Language != "javascript" {
reqBody["language"] = opts[0].Language
}

bodyBytes, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
Expand Down
19 changes: 16 additions & 3 deletions internal/httpapi/code_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import (

// CodeExecRequest represents the request body for code execution.
type CodeExecRequest struct {
Code string `json:"code"`
Input map[string]interface{} `json:"input"`
Options CodeExecOptions `json:"options"`
Code string `json:"code"`
Language string `json:"language,omitempty"` // "javascript" (default) or "typescript"
Input map[string]interface{} `json:"input"`
Options CodeExecOptions `json:"options"`
}

// CodeExecOptions represents execution options.
Expand Down Expand Up @@ -75,6 +76,13 @@ func (h *CodeExecHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

// Validate language if provided
if req.Language != "" && req.Language != "javascript" && req.Language != "typescript" {
h.writeError(w, r, http.StatusBadRequest, "INVALID_LANGUAGE",
fmt.Sprintf("Unsupported language %q. Supported languages: javascript, typescript", req.Language))
return
}

// Set defaults
if req.Input == nil {
req.Input = make(map[string]interface{})
Expand All @@ -99,6 +107,11 @@ func (h *CodeExecHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
},
}

// Pass language if specified
if req.Language != "" {
args["language"] = req.Language
}

// Call the code_execution built-in tool
result, err := h.toolCaller.CallTool(ctx, "code_execution", args)
if err != nil {
Expand Down
Loading