- Go 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .github | ||
| cmd/demo | ||
| .golangci.yml | ||
| execute.go | ||
| execute_test.go | ||
| generator.go | ||
| generator_test.go | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| normalize.go | ||
| README.md | ||
| schema_types.go | ||
| Taskfile.yml | ||
| tool_definition.go | ||
| tool_search.go | ||
| tool_search_test.go | ||
codemode
Search tool definitions, generate a TypeScript API surface for them, and execute small JavaScript programs against caller-provided tool callbacks.
go get github.com/ralscha/codemode
The module targets the Go version declared in go.mod. The root package depends directly on QuickJS and the MCP SDK.
Tool Search
NewToolIndex(...) builds a searchable index over many tool definitions. The
index accepts either []codemode.ToolDefinition or []mcp.Tool and searches
by name, description, input schema, and output schema.
definitions := []codemode.ToolDefinition{
codemode.NewToolDefinition("search-products").
WithDescription("Search the product catalog").
WithInputSchema(codemode.NewObjectSchema().
WithProperty("query", codemode.NewStringSchema().WithDescription("Search query")).
WithRequired("query")).
WithOutputSchema(codemode.NewObjectSchema().
WithProperty("items", codemode.NewArraySchema(codemode.NewObjectSchema().
WithProperty("id", codemode.NewStringSchema().WithDescription("Product identifier")).
WithProperty("title", codemode.NewStringSchema().WithDescription("Product title")).
WithRequired("id", "title")).WithDescription("Matched items")).
WithRequired("items")).
Build(),
}
index, err := codemode.NewToolIndex(definitions)
if err != nil {
return err
}
results := index.Query("matched product items", codemode.WithToolSearchLimit(3))
for _, result := range results {
fmt.Println(result.Definition.Name, result.Score, result.Matches)
}
Query(...) combines BM25-style lexical ranking with exact, normalized, and
fuzzy name boosts. Schema property names and descriptions carry more weight
than raw schema JSON, so queries like matched product items can find a tool
from its output shape. Tools with no positive relevance score are omitted.
QueryRegex(...) provides deterministic filtering:
results, err := index.QueryRegex(`github.*issues`)
Generate API
GenerateFromDefinitions(...) and GenerateFromMCPTools(...) generate
TypeScript API declarations. The output uses sanitized method names,
generated input and output types, and JSDoc from tool and schema descriptions.
Object inputs with no required properties are optional, while tools without an
input schema are generated as zero-argument functions.
api, err := codemode.GenerateFromDefinitions(definitions)
if err != nil {
return err
}
fmt.Println(api)
The generator produces declarations like:
declare const tools : {
/**
* Create a new project
* @param name - Project name
* @returns id - Project identifier
*/
create_project: (input: { name: string; }) => { id: string; name: string; };
}
Supported inputs:
GenerateFromDefinitions(...): normalizedToolDefinitionvaluesGenerateFromMCPTools(...): MCP SDKmcp.Toolvalues
The schema converter supports objects, arrays, JSON Schema 2020-12
prefixItems tuples, unions and intersections, enums, constants, local
$ref values, nullable values, and boolean schemas.
ToolDefinition supports both input and output schemas. The builder APIs keep
nested JSON schema values readable:
definition := codemode.NewToolDefinition("create-project").
WithDescription("Create a new project").
WithInputSchema(codemode.NewObjectSchema().
WithProperty("name", codemode.NewStringSchema().WithDescription("Project name")).
WithRequired("name")).
WithOutputSchema(codemode.NewObjectSchema().
WithProperty("id", codemode.NewStringSchema().WithDescription("Project identifier")).
WithProperty("name", codemode.NewStringSchema().WithDescription("Project name")).
WithRequired("id", "name")).
Build()
GenerateFromMCPTools(...) works directly with tool definitions from an MCP
server:
api, err := codemode.GenerateFromMCPTools(mcpTools, codemode.WithNamespace("githubTools"))
By default the output starts with declare const tools : { ... }.
WithNamespace(...) changes the object name:
githubAPI, _ := codemode.GenerateFromMCPTools(githubTools, codemode.WithNamespace("githubTools"))
stripeAPI, _ := codemode.GenerateFromMCPTools(stripeTools, codemode.WithNamespace("stripeTools"))
Execute
Execute(...) runs JavaScript returned by an LLM. The code executes inside a
QuickJS VM with a memory limit and timeout. The caller provides the callback
namespaces, so execution stays independent of HTTP clients, databases, or any
other tool runtime.
Execution is synchronous. Tool callbacks are exposed as regular JavaScript
functions, so generated code should not use await, Promise.all(...), dynamic
import(...), or other async-only patterns.
Execute(...) honors context cancellation in addition to its VM evaluation
timeout. A canceled context interrupts running JavaScript and is returned as a
wrapped context.Canceled or context.DeadlineExceeded error.
Tool callback inputs are JSON objects. Calling a tool without an argument uses
an empty object; passing primitive values such as false, 0, or a string is
reported as an argument parsing error instead of being silently coerced.
Before evaluation, code is normalized for common LLM output shapes:
- markdown code fences are stripped
- bare expressions and final expressions are returned automatically
- sync arrow functions and simple
async () => { ... }wrappers are unwrapped
type WeatherInput struct {
City string `json:"city"`
}
type WeatherOutput struct {
City string `json:"city"`
Temperature string `json:"temperature"`
}
result, err := codemode.Execute(ctx, `
const weather = tools.get_weather({ city: "Zurich" });
return { text: "Weather in " + weather.city + ": " + weather.temperature };
`, []codemode.ToolCallbackNamespace{
{
Callbacks: []codemode.ToolCallbackDefinition{
codemode.NewToolCallback("get-weather", func(ctx context.Context, input WeatherInput) (WeatherOutput, error) {
return WeatherOutput{City: input.City, Temperature: "12 C"}, nil
}),
},
},
})
if err != nil {
return err
}
fmt.Println(result.Value)
Sandboxed code can write diagnostic output with console.log, console.warn,
and console.error. Messages are captured in ExecuteResult.Logs; warnings and
errors are prefixed with [warn] and [error]. Logs captured before a
JavaScript error are still returned with the result value.
The JavaScript API uses the same sanitized names as the generated declarations.
For example, get-weather becomes tools.get_weather(...).
WithNamespace(...) changes the object name for both generation and execution.
api, _ := codemode.GenerateFromDefinitions(definitions, codemode.WithNamespace("sdk"))
result, _ := codemode.Execute(ctx, `return sdk.search_docs({ query: "install" });`, []codemode.ToolCallbackNamespace{
{Callbacks: callbacks},
}, codemode.WithNamespace("sdk"))
Multiple namespace entries expose helper objects from multiple generated namespaces to the same JavaScript program.
result, err := codemode.Execute(ctx, `
const repos = githubTools.list_repos({ owner: "ralscha" }).items;
const customers = stripeTools.list_customers({ limit: 2 }).items;
return { repos: repos.length, customers: customers.length };
`, []codemode.ToolCallbackNamespace{
{Namespace: "githubTools", Callbacks: githubCallbacks},
{Namespace: "stripeTools", Callbacks: stripeCallbacks},
})
Execution options:
WithEvalTimeout(...): limit JavaScript runtime, default10sWithMemoryLimit(...): limit QuickJS memory, default32 MiB
Namespace and method names are converted to valid JavaScript identifiers.
Namespace names that would shadow execution-runtime globals receive a trailing
underscore; for example, console becomes console_ in both generated and
executed APIs.
Development
Run the local checks with:
go vet ./...
go test ./...
The GitHub Actions workflow reads the Go version from go.mod, runs go vet,
and then runs the test suite. Dependabot is configured for Go modules and
GitHub Actions updates.
License
MIT License. See LICENSE for details.