A live-data workflow built without a single custom operator. Two parallel
HTTPGetOp calls hit Yahoo Finance for a quote and a news headline.
JSONExtractOp pulls fields out of the responses. AIParseNumberOp
coerces stringly-typed numerics to float64. SubFloatOp computes
the price change. AIScoreOp turns the headline into a sentiment score. A
chain of StringConcatOp assembles the final analysis prompt, and
AIComputeStringToStringOp emits the Buy/Hold/Sell recommendation.
What this example teaches
RegisterConst + StringConcatOp chainsHTTPGetOp + JSONExtractOp for two independent fetchesAIParseNumberOp as a fallback when the source returns stringly-typed numericsSubFloatOp) with AI scoring (AIScoreOp) in one graphprovider: "gemini"RegisterConst + StringConcatOpRegisterConst turns a fixed Go value into a
registered op. Combine with the ticker context-val and two StringConcatOp
vertices to build a per-run URL without any custom code.
library.RegisterConst("quote_prefix", "https://query2.finance.yahoo.com/v8/finance/chart/")
library.RegisterConst("quote_suffix", "?interval=1d&range=1d")
b.Vertex("q_join_1").Op("StringConcatOp").
Input("A", "q_pre").Input("B", "ticker").Output("Result", "q_mid")
b.Vertex("quote_url").Op("StringConcatOp").
Input("A", "q_mid").Input("B", "q_suf").Output("Result", "quote_url")
Two independent HTTPGetOp vertices. The
engine sees they share no upstream dependency and runs them concurrently. Three
JSONExtractOp vertices then pull the three numeric/string fields the
downstream stages need.
b.Vertex("fetch_quote").Op("HTTPGetOp").Input("URL", "quote_url").Output("Body", "quote_json")
b.Vertex("fetch_news").Op("HTTPGetOp").Input("URL", "news_url").Output("Body", "news_json")
b.Vertex("extract_price").Op("JSONExtractOp").
Input("JSON", "quote_json").Input("Path", "p_path").
Output("Value", "price_raw")
Once the price floats are parsed, the change is one
SubFloatOp. The headline's sentiment is one AIScoreOp. Two ops,
one a pure function and one a typed AI call, both produce float64 wires the
downstream prompt-assembly stage stringifies and concatenates.
b.Vertex("calc_change").Op("SubFloatOp").
Input("A", "price").Input("B", "prev_close").Output("Result", "change")
b.Vertex("sentiment").Op("AIScoreOp").
Params(map[string]string{
"provider": "gemini",
"model": "gemini-3-flash-preview",
"criterion": "The headline indicates a positive/bullish outlook",
}).
Input("Input", "headline").Output("Result", "sentiment_score")
export GEMINI_API_KEY=<your key>
# default ticker is AAPL
go run ./examples/stock-analyzer
# or specify your own
go run ./examples/stock-analyzer --ticker MSFT
The same binary is dual-mode. Pass --mcp and instead of running once and exiting it speaks the Model Context Protocol over stdin/stdout, exposing the entire workflow as a single MCP tool, analyze_stock. The Go SDK derives the tool's input schema from the workflow's UserInput struct and validates every tools/call request against it, so an agent runs the whole quote + news → sentiment → recommendation pipeline with one tool call.
# Speak MCP over stdin/stdout instead of running once
go run ./examples/stock-analyzer --mcp
Register it with any MCP client by pointing the client at that command:
{
"mcpServers": {
"stock-analyzer": {
"command": "go",
"args": ["run", "./examples/stock-analyzer", "--mcp"]
}
}
}