Skip to content

Commit b4df1de

Browse files
authored
Merge pull request #331 from smart-mcp-proxy/033-typescript-code-execution
feat: add TypeScript support to code_execution
2 parents 93cc912 + 587ab4a commit b4df1de

29 files changed

Lines changed: 1803 additions & 64 deletions

File tree

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,8 @@ See `docs/prerelease-builds.md` for download instructions.
633633
- BBolt database (`~/.mcpproxy/config.db`) — new `agent_tokens` bucket (028-agent-tokens)
634634
- 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)
635635
- BBolt database (`~/.mcpproxy/config.db`) - new buckets for users, sessions, user servers (024-teams-multiuser-oauth)
636+
- 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)
637+
- N/A (no new storage requirements) (033-typescript-code-execution)
636638

637639
## Recent Changes
638640
- 001-update-version-display: Added Go 1.24 (toolchain go1.24.10)

cmd/mcpproxy/code_cmd.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,19 @@ import (
2828
var (
2929
codeCmd = &cobra.Command{
3030
Use: "code",
31-
Short: "JavaScript code execution for multi-tool orchestration",
32-
Long: "Execute JavaScript code that orchestrates multiple upstream MCP tools in a single request",
31+
Short: "JavaScript/TypeScript code execution for multi-tool orchestration",
32+
Long: "Execute JavaScript or TypeScript code that orchestrates multiple upstream MCP tools in a single request",
3333
}
3434

3535
codeExecCmd = &cobra.Command{
3636
Use: "exec",
37-
Short: "Execute JavaScript code",
38-
Long: `Execute JavaScript code that can orchestrate multiple upstream MCP tools.
37+
Short: "Execute JavaScript or TypeScript code",
38+
Long: `Execute JavaScript or TypeScript code that can orchestrate multiple upstream MCP tools.
3939
40-
The JavaScript code has access to:
40+
Use --language typescript to write TypeScript code with type annotations,
41+
interfaces, enums, and generics. Types are automatically stripped before execution.
42+
43+
The code has access to:
4144
- input: Global variable containing the input data (from --input or --input-file)
4245
- call_tool(serverName, toolName, args): Function to invoke upstream MCP tools
4346
@@ -63,6 +66,7 @@ Exit codes:
6366
codeAllowedSrvs []string
6467
codeLogLevel string
6568
codeConfigPath string
69+
codeLanguage string
6670
)
6771

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

8893
// Add examples
8994
codeExecCmd.Example = ` # Execute inline code with input
9095
mcpproxy code exec --code="({ result: input.value * 2 })" --input='{"value": 21}'
9196
97+
# Execute TypeScript code
98+
mcpproxy code exec --language typescript --code="const x: number = 42; ({ result: x })"
99+
92100
# Execute code from file
93101
mcpproxy code exec --file=script.js --input-file=params.json
94102
103+
# Execute TypeScript from file
104+
mcpproxy code exec --language typescript --file=script.ts --input-file=params.json
105+
95106
# Call upstream tools
96107
mcpproxy code exec --code="call_tool('github', 'get_user', {username: input.user})" --input='{"user":"octocat"}'
97108
@@ -196,6 +207,7 @@ func runCodeExecClientMode(dataDir, code string, input map[string]interface{}, l
196207
codeTimeout,
197208
codeMaxToolCalls,
198209
codeAllowedSrvs,
210+
cliclient.CodeExecOptions{Language: codeLanguage},
199211
)
200212
if err != nil {
201213
// T029: Use formatErrorWithRequestID to include request_id in error output
@@ -274,6 +286,11 @@ func runCodeExecStandalone(globalConfig *config.Config, code string, input map[s
274286
},
275287
}
276288

289+
// Pass language if not the default
290+
if codeLanguage != "" && codeLanguage != "javascript" {
291+
args["language"] = codeLanguage
292+
}
293+
277294
// Call the code_execution tool
278295
result, err := mcpProxy.CallBuiltInTool(ctx, "code_execution", args)
279296
if err != nil {
@@ -330,6 +347,10 @@ func validateOptions() error {
330347
fmt.Fprintf(os.Stderr, "Error: max-tool-calls cannot be negative\n")
331348
return fmt.Errorf("invalid max-tool-calls")
332349
}
350+
if codeLanguage != "javascript" && codeLanguage != "typescript" {
351+
fmt.Fprintf(os.Stderr, "Error: unsupported language %q. Supported languages: javascript, typescript\n", codeLanguage)
352+
return fmt.Errorf("invalid language")
353+
}
333354
return nil
334355
}
335356

docs/code_execution/api-reference.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# JavaScript Code Execution - API Reference
1+
# Code Execution - API Reference
22

3-
Complete reference for the `code_execution` MCP tool.
3+
Complete reference for the `code_execution` MCP tool (JavaScript and TypeScript).
44

55
## Table of Contents
66

@@ -27,11 +27,17 @@ Complete reference for the `code_execution` MCP tool.
2727
"properties": {
2828
"code": {
2929
"type": "string",
30-
"description": "JavaScript source code (ES2020+) to execute..."
30+
"description": "JavaScript or TypeScript source code (ES2020+) to execute..."
31+
},
32+
"language": {
33+
"type": "string",
34+
"description": "Source code language. When set to 'typescript', the code is automatically transpiled to JavaScript before execution.",
35+
"enum": ["javascript", "typescript"],
36+
"default": "javascript"
3137
},
3238
"input": {
3339
"type": "object",
34-
"description": "Input data accessible as global `input` variable in JavaScript code",
40+
"description": "Input data accessible as global `input` variable in code",
3541
"default": {}
3642
},
3743
"options": {
@@ -77,6 +83,16 @@ Complete reference for the `code_execution` MCP tool.
7783
}
7884
```
7985

86+
### TypeScript Request
87+
88+
```json
89+
{
90+
"code": "const x: number = 42; const msg: string = 'hello'; ({ result: x, message: msg })",
91+
"language": "typescript",
92+
"input": {}
93+
}
94+
```
95+
8096
### Full Request with Options
8197

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

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

docs/code_execution/overview.md

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
# JavaScript Code Execution - Overview
1+
# Code Execution - Overview
22

33
## What is Code Execution?
44

5-
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.
5+
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.
6+
7+
**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).
68

79
## When to Use Code Execution
810

@@ -213,9 +215,12 @@ mcpproxy serve
213215
### 3. Test with CLI
214216

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

221+
# TypeScript test
222+
mcpproxy code exec --language typescript --code="const x: number = 42; ({ result: x })"
223+
219224
# Call upstream tool
220225
mcpproxy code exec --code="call_tool('github', 'get_user', {username: input.user})" --input='{"user":"octocat"}'
221226
```
@@ -373,6 +378,59 @@ code_execution({
373378
// Returns: {ok: false, error: {code: "MAX_TOOL_CALLS_EXCEEDED", message: "..."}}
374379
```
375380
381+
## TypeScript Support
382+
383+
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.
384+
385+
### Supported TypeScript Features
386+
387+
- Type annotations: `const x: number = 42`
388+
- Interfaces: `interface User { name: string; age: number; }`
389+
- Type aliases: `type StringOrNumber = string | number`
390+
- Generics: `function identity<T>(arg: T): T { return arg; }`
391+
- Enums: `enum Direction { Up = "UP", Down = "DOWN" }`
392+
- Namespaces: `namespace MyLib { export const value = 42; }`
393+
- Type assertions: `const x = value as string`
394+
395+
### TypeScript via MCP Tool
396+
397+
```json
398+
{
399+
"name": "code_execution",
400+
"arguments": {
401+
"code": "interface User { name: string; }\nconst user: User = { name: input.username };\n({ greeting: 'Hello ' + user.name })",
402+
"language": "typescript",
403+
"input": {"username": "Alice"}
404+
}
405+
}
406+
```
407+
408+
### TypeScript via REST API
409+
410+
```bash
411+
curl -X POST http://127.0.0.1:8080/api/v1/code/exec \
412+
-H "Content-Type: application/json" \
413+
-H "X-API-Key: your-key" \
414+
-d '{
415+
"code": "const x: number = 42; ({ result: x })",
416+
"language": "typescript"
417+
}'
418+
```
419+
420+
### TypeScript via CLI
421+
422+
```bash
423+
mcpproxy code exec --language typescript \
424+
--code="const x: number = 42; ({ result: x })"
425+
```
426+
427+
### Important Notes
428+
429+
- TypeScript support uses type-stripping only (no type checking or semantic validation)
430+
- Valid JavaScript is also valid TypeScript, so you can always use `language: "typescript"` even for plain JS
431+
- The transpiled output runs in the same ES5.1+ goja sandbox with all existing capabilities
432+
- Transpilation errors return the `TRANSPILE_ERROR` error code with line/column information
433+
376434
## Best Practices
377435
378436
### 1. Keep Code Simple

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ require (
8282
github.com/dlclark/regexp2 v1.11.4 // indirect
8383
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
8484
github.com/esiqveland/notify v0.13.3 // indirect
85+
github.com/evanw/esbuild v0.27.3 // indirect
8586
github.com/fsnotify/fsnotify v1.8.0 // indirect
8687
github.com/go-logr/logr v1.4.3 // indirect
8788
github.com/go-logr/stdr v1.2.2 // indirect

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
9898
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
9999
github.com/esiqveland/notify v0.13.3 h1:QCMw6o1n+6rl+oLUfg8P1IIDSFsDEb2WlXvVvIJbI/o=
100100
github.com/esiqveland/notify v0.13.3/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE=
101+
github.com/evanw/esbuild v0.27.3 h1:dH/to9tBKybig6hl25hg4SKIWP7U8COdJKbGEwnUkmU=
102+
github.com/evanw/esbuild v0.27.3/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
101103
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
102104
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
103105
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
@@ -318,6 +320,7 @@ golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
318320
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
319321
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
320322
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
323+
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
321324
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
322325
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
323326
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=

internal/cliclient/client.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,20 @@ func parseAPIError(errorMsg, requestID string) error {
147147
return &APIError{Message: errorMsg, RequestID: requestID}
148148
}
149149

150-
// CodeExec executes JavaScript code via the daemon API.
150+
// CodeExecOptions contains optional parameters for code execution via the daemon API.
151+
type CodeExecOptions struct {
152+
Language string // Source language: "javascript" (default) or "typescript"
153+
}
154+
155+
// CodeExec executes JavaScript or TypeScript code via the daemon API.
151156
func (c *Client) CodeExec(
152157
ctx context.Context,
153158
code string,
154159
input map[string]interface{},
155160
timeoutMS int,
156161
maxToolCalls int,
157162
allowedServers []string,
163+
opts ...CodeExecOptions,
158164
) (*CodeExecResult, error) {
159165
// Build request body
160166
reqBody := map[string]interface{}{
@@ -167,6 +173,11 @@ func (c *Client) CodeExec(
167173
},
168174
}
169175

176+
// Apply optional language parameter
177+
if len(opts) > 0 && opts[0].Language != "" && opts[0].Language != "javascript" {
178+
reqBody["language"] = opts[0].Language
179+
}
180+
170181
bodyBytes, err := json.Marshal(reqBody)
171182
if err != nil {
172183
return nil, fmt.Errorf("failed to marshal request: %w", err)

internal/httpapi/code_exec.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import (
1515

1616
// CodeExecRequest represents the request body for code execution.
1717
type CodeExecRequest struct {
18-
Code string `json:"code"`
19-
Input map[string]interface{} `json:"input"`
20-
Options CodeExecOptions `json:"options"`
18+
Code string `json:"code"`
19+
Language string `json:"language,omitempty"` // "javascript" (default) or "typescript"
20+
Input map[string]interface{} `json:"input"`
21+
Options CodeExecOptions `json:"options"`
2122
}
2223

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

79+
// Validate language if provided
80+
if req.Language != "" && req.Language != "javascript" && req.Language != "typescript" {
81+
h.writeError(w, r, http.StatusBadRequest, "INVALID_LANGUAGE",
82+
fmt.Sprintf("Unsupported language %q. Supported languages: javascript, typescript", req.Language))
83+
return
84+
}
85+
7886
// Set defaults
7987
if req.Input == nil {
8088
req.Input = make(map[string]interface{})
@@ -99,6 +107,11 @@ func (h *CodeExecHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
99107
},
100108
}
101109

110+
// Pass language if specified
111+
if req.Language != "" {
112+
args["language"] = req.Language
113+
}
114+
102115
// Call the code_execution built-in tool
103116
result, err := h.toolCaller.CallTool(ctx, "code_execution", args)
104117
if err != nil {

0 commit comments

Comments
 (0)