Go 1.27.1, and the code it can say more simply

Modules declare go 1.27.1 and the build images follow. go fix applied the
modernizers: any, max and range over int. omitzero is not applied, since it
changes what the JSON says.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-09-16 19:15:04 -07:00
co-authored by Hanzo Dev
parent 2270db4043
commit 6b380d62a4
15 changed files with 171 additions and 174 deletions
+5 -5
View File
@@ -24,9 +24,9 @@ func (pingSurface) Tools() []mcp.Tool {
return []mcp.Tool{{
Name: "ping",
Description: "returns pong",
InputSchema: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}},
Read: func(_ context.Context, _ map[string]interface{}) (interface{}, *mcp.ChainObservation, error) {
return map[string]interface{}{"pong": true}, nil, nil
InputSchema: map[string]any{"type": "object", "properties": map[string]any{}},
Read: func(_ context.Context, _ map[string]any) (any, *mcp.ChainObservation, error) {
return map[string]any{"pong": true}, nil, nil
},
}}
}
@@ -81,7 +81,7 @@ func TestTransportComposesMultipleSurfaces(t *testing.T) {
if err != nil {
t.Fatalf("dispatch ping: %v", err)
}
m, ok := res.(map[string]interface{})
m, ok := res.(map[string]any)
if !ok || m["pong"] != true {
t.Fatalf("ping result=%v, want {pong:true}", res)
}
@@ -90,7 +90,7 @@ func TestTransportComposesMultipleSurfaces(t *testing.T) {
// a tool whose error path is reached without a live chain read returning bad bytes:
// param_value with a malformed argument fails at argument validation (boundary), proving
// the governance tool was dispatched (not "unknown tool").
_, err = srv.CallTool(context.Background(), "param_value", map[string]interface{}{})
_, err = srv.CallTool(context.Background(), "param_value", map[string]any{})
if err == nil {
t.Fatal("expected param_value to fail on missing args (proving it dispatched), got nil")
}
+4 -4
View File
@@ -64,7 +64,7 @@ func NewContract(jsonABI string, addr common.Address) (*Contract, error) {
// Call packs `method`(args...), executes it as a read-only eth_call against the bound
// address at the latest block, and returns the decoded output values.
func (c *Contract) Call(ctx context.Context, ec Caller, method string, args ...interface{}) ([]interface{}, error) {
func (c *Contract) Call(ctx context.Context, ec Caller, method string, args ...any) ([]any, error) {
return c.CallAt(ctx, ec, nil, method, args...)
}
@@ -74,7 +74,7 @@ func (c *Contract) Call(ctx context.Context, ec Caller, method string, args ...i
// — a bond withdraw racing the tally — cannot produce an inconsistent snapshot. An
// in-process test backend may ignore the block arg (it has only latest state); the
// production *ethclient.Client honors it.
func (c *Contract) CallAt(ctx context.Context, ec Caller, block *big.Int, method string, args ...interface{}) ([]interface{}, error) {
func (c *Contract) CallAt(ctx context.Context, ec Caller, block *big.Int, method string, args ...any) ([]any, error) {
m, ok := c.abi.Methods[method]
if !ok {
return nil, fmt.Errorf("evmread: unknown method %q", method)
@@ -103,14 +103,14 @@ func (c *Contract) CallAt(ctx context.Context, ec Caller, block *big.Int, method
// (Arguments.copyAtomic). So we hand it a one-field wrapper whose field is T; geth then
// field-copies the tuple into it by index — which is exactly why T's field order MUST
// mirror the Solidity struct.
func CallStruct[T any](ctx context.Context, c *Contract, ec Caller, method string, args ...interface{}) (T, error) {
func CallStruct[T any](ctx context.Context, c *Contract, ec Caller, method string, args ...any) (T, error) {
return CallStructAt[T](ctx, c, ec, nil, method, args...)
}
// CallStructAt is CallStruct pinned to a specific block (nil = latest); see CallAt for
// why pinning matters. A quorum tally uses it to read getThought / getVerdicts at the
// same block it reads the bonds, for a consistent settle-equivalent snapshot.
func CallStructAt[T any](ctx context.Context, c *Contract, ec Caller, block *big.Int, method string, args ...interface{}) (T, error) {
func CallStructAt[T any](ctx context.Context, c *Contract, ec Caller, block *big.Int, method string, args ...any) (T, error) {
var wrap struct{ V T }
in, err := c.abi.Pack(method, args...)
if err != nil {
+2 -2
View File
@@ -53,7 +53,7 @@ func TestBoundedEnforcesCeiling(t *testing.T) {
cc := &countingCaller{ret: common.LeftPadBytes(big.NewInt(7).Bytes(), 32)}
b := NewBounded(cc, max)
for i := 0; i < max; i++ {
for i := range max {
if _, err := b.CallContract(context.Background(), gethereum.CallMsg{To: &common.Address{}}, nil); err != nil {
t.Fatalf("call %d under the ceiling errored: %v", i, err)
}
@@ -90,7 +90,7 @@ func TestBoundedEnforcesCeiling(t *testing.T) {
func TestBoundedZeroMaxDisablesCeiling(t *testing.T) {
cc := &countingCaller{ret: common.LeftPadBytes(big.NewInt(1).Bytes(), 32)}
b := NewBounded(cc, 0)
for i := 0; i < 1000; i++ {
for i := range 1000 {
if _, err := b.CallContract(context.Background(), gethereum.CallMsg{To: &common.Address{}}, nil); err != nil {
t.Fatalf("unbounded call %d errored: %v", i, err)
}
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/luxfi/mcp
go 1.26.4
go 1.27.1
require (
github.com/holiman/uint256 v1.3.2
+14 -14
View File
@@ -24,7 +24,7 @@ import (
const maxArgStringLen = 4096
// argString returns a required string argument, rejecting absurdly long inputs.
func argString(args map[string]interface{}, name string) (string, error) {
func argString(args map[string]any, name string) (string, error) {
v, ok := args[name]
if !ok {
return "", fmt.Errorf("%s: missing required argument %q", "args", name)
@@ -40,7 +40,7 @@ func argString(args map[string]interface{}, name string) (string, error) {
}
// argBytes32 parses a required 0x-hex bytes32 argument.
func argBytes32(args map[string]interface{}, name string) ([32]byte, error) {
func argBytes32(args map[string]any, name string) ([32]byte, error) {
var out [32]byte
s, err := argString(args, name)
if err != nil {
@@ -58,7 +58,7 @@ func argBytes32(args map[string]interface{}, name string) ([32]byte, error) {
}
// argAddress parses a required 0x-hex address argument.
func argAddress(args map[string]interface{}, name string) (common.Address, error) {
func argAddress(args map[string]any, name string) (common.Address, error) {
s, err := argString(args, name)
if err != nil {
return common.Address{}, err
@@ -72,7 +72,7 @@ func argAddress(args map[string]interface{}, name string) (common.Address, error
// argUint256 parses a required unsigned integer (used for task/round ids). Accepts a
// JSON number, a decimal string, or a 0x-hex string. Negative values are rejected.
func argUint256(args map[string]interface{}, name string) (*big.Int, error) {
func argUint256(args map[string]any, name string) (*big.Int, error) {
v, ok := args[name]
if !ok {
return nil, fmt.Errorf("args: missing required argument %q", name)
@@ -96,7 +96,7 @@ const maxLimit = 256
// argLimit reads an optional positive "limit" argument, falling back to def. A
// non-positive or absent limit yields def; anything over maxLimit is clamped to maxLimit
// so a caller cannot force an unbounded scan.
func argLimit(args map[string]interface{}, def int) int {
func argLimit(args map[string]any, def int) int {
v, ok := args["limit"]
if !ok {
return def
@@ -113,7 +113,7 @@ func argLimit(args map[string]interface{}, def int) int {
// argFromRound reads the optional "fromRound" argument; nil (use default) when
// absent or unparseable. Clamped to <= count-1 by the caller.
func argFromRound(args map[string]interface{}, count *big.Int) *big.Int {
func argFromRound(args map[string]any, count *big.Int) *big.Int {
v, ok := args["fromRound"]
if !ok {
return nil
@@ -127,7 +127,7 @@ func argFromRound(args map[string]interface{}, count *big.Int) *big.Int {
// toBigInt converts a decoded-JSON value (float64, json.Number, or string) to a
// *big.Int. Floats must be integral. Strings may be decimal or 0x-hex.
func toBigInt(v interface{}) (*big.Int, error) {
func toBigInt(v any) (*big.Int, error) {
switch t := v.(type) {
case float64:
if t != float64(int64(t)) {
@@ -176,11 +176,11 @@ func ensure0x(s string) string {
// JSON-schema helpers for the tool descriptors (tools/list inputSchema).
// ----------------------------------------------------------------------------
func objSchema(props map[string]interface{}, required []string) map[string]interface{} {
func objSchema(props map[string]any, required []string) map[string]any {
if props == nil {
props = map[string]interface{}{}
props = map[string]any{}
}
schema := map[string]interface{}{
schema := map[string]any{
"type": "object",
"properties": props,
}
@@ -190,10 +190,10 @@ func objSchema(props map[string]interface{}, required []string) map[string]inter
return schema
}
func strSchema(desc string) map[string]interface{} {
return map[string]interface{}{"type": "string", "description": desc}
func strSchema(desc string) map[string]any {
return map[string]any{"type": "string", "description": desc}
}
func intSchema(desc string) map[string]interface{} {
return map[string]interface{}{"type": "integer", "description": desc}
func intSchema(desc string) map[string]any {
return map[string]any{"type": "integer", "description": desc}
}
+7 -7
View File
@@ -80,7 +80,7 @@ func TestPerRequestCallCeiling(t *testing.T) {
// pending_operations with a large limit would normally scan many tasks. The ceiling must
// stop it well before taskCount.
_, err = srv.CallTool(context.Background(), toolPendingOperations, map[string]interface{}{"limit": 200})
_, err = srv.CallTool(context.Background(), toolPendingOperations, map[string]any{"limit": 200})
if err == nil {
t.Fatal("expected a per-request ceiling error, got nil")
}
@@ -101,28 +101,28 @@ func TestPerRequestCallCeiling(t *testing.T) {
func TestArgInputLengthCapped(t *testing.T) {
bigStr := strings.Repeat("9", maxArgStringLen+1)
// argString path (knobKey).
if _, err := argString(map[string]interface{}{"knobKey": bigStr}, "knobKey"); err == nil {
if _, err := argString(map[string]any{"knobKey": bigStr}, "knobKey"); err == nil {
t.Fatal("expected argString to reject an over-long string")
}
// toBigInt string path (task/round ids).
if _, err := argUint256(map[string]interface{}{"taskId": bigStr}, "taskId"); err == nil {
if _, err := argUint256(map[string]any{"taskId": bigStr}, "taskId"); err == nil {
t.Fatal("expected argUint256 to reject an over-long integer string")
}
// A normal value still works.
if _, err := argString(map[string]interface{}{"knobKey": "temperature"}, "knobKey"); err != nil {
if _, err := argString(map[string]any{"knobKey": "temperature"}, "knobKey"); err != nil {
t.Fatalf("normal knobKey wrongly rejected: %v", err)
}
}
// TestArgLimitClampedToMax is the MEDIUM-4 boundary test: limit is clamped to maxLimit.
func TestArgLimitClampedToMax(t *testing.T) {
if got := argLimit(map[string]interface{}{"limit": float64(1_000_000)}, 16); got != maxLimit {
if got := argLimit(map[string]any{"limit": float64(1_000_000)}, 16); got != maxLimit {
t.Fatalf("argLimit(1e6)=%d, want clamp to %d", got, maxLimit)
}
if got := argLimit(map[string]interface{}{}, 16); got != 16 {
if got := argLimit(map[string]any{}, 16); got != 16 {
t.Fatalf("argLimit(absent)=%d, want default 16", got)
}
if got := argLimit(map[string]interface{}{"limit": float64(10)}, 16); got != 10 {
if got := argLimit(map[string]any{"limit": float64(10)}, 16); got != 10 {
t.Fatalf("argLimit(10)=%d, want 10", got)
}
}
+6 -6
View File
@@ -207,7 +207,7 @@ func (c *evmChain) mine() {
// advanceBlocks mines `n` empty blocks (advances number + clock without a tx).
func (c *evmChain) advanceBlocks(n int) {
for i := 0; i < n; i++ {
for range n {
c.mine()
}
}
@@ -285,7 +285,7 @@ func (c *evmChain) headerTime(n uint64) uint64 {
// readStruct is the harness-side INDEPENDENT struct decode (full artifact ABI),
// using the same single-output wrapper geth requires (see callStruct in abi.go).
func readStruct[T any](t *testing.T, c *evmChain, b boundContract, method string, args ...interface{}) T {
func readStruct[T any](t *testing.T, c *evmChain, b boundContract, method string, args ...any) T {
t.Helper()
var wrap struct{ V T }
in, err := b.abi.Pack(method, args...)
@@ -303,7 +303,7 @@ func readStruct[T any](t *testing.T, c *evmChain, b boundContract, method string
}
// callViewValues unpacks into a []interface{} (for non-struct returns).
func (c *evmChain) callViewValues(b boundContract, method string, args ...interface{}) []interface{} {
func (c *evmChain) callViewValues(b boundContract, method string, args ...any) []any {
in, err := b.abi.Pack(method, args...)
if err != nil {
c.t.Fatalf("pack %s: %v", method, err)
@@ -364,7 +364,7 @@ func (e *chainEnv) deployAll() {
}
// deployWithCtor packs constructor args, appends to creation bytecode, and deploys.
func (e *chainEnv) deployWithCtor(a abi.ABI, code []byte, args ...interface{}) common.Address {
func (e *chainEnv) deployWithCtor(a abi.ABI, code []byte, args ...any) common.Address {
packed, err := a.Pack("", args...)
if err != nil {
e.t.Fatalf("pack ctor: %v", err)
@@ -451,7 +451,7 @@ func (e *chainEnv) settleRound(roundID *big.Int) {
e.c.sendTx(e.c.deployer, e.params.addr, in, nil)
}
func (e *chainEnv) callParams(method string, args ...interface{}) []interface{} {
func (e *chainEnv) callParams(method string, args ...any) []any {
return e.c.callViewValues(e.params, method, args...)
}
@@ -552,7 +552,7 @@ func specHash(label string) [32]byte {
func genKeys(t *testing.T, n int) []*ecdsa.PrivateKey {
t.Helper()
keys := make([]*ecdsa.PrivateKey, 0, n)
for i := 0; i < n; i++ {
for range n {
k, err := crypto.GenerateKey()
if err != nil {
t.Fatalf("gen key: %v", err)
+15 -15
View File
@@ -16,13 +16,13 @@ import (
// callTool runs a read tool and returns its result as a map (all tool results are
// JSON objects). Fails the test on tool error or wrong shape.
func callTool(t *testing.T, srv *mcp.Server, name string, args map[string]interface{}) map[string]interface{} {
func callTool(t *testing.T, srv *mcp.Server, name string, args map[string]any) map[string]any {
t.Helper()
res, err := srv.CallTool(context.Background(), name, args)
if err != nil {
t.Fatalf("tool %s: %v", name, err)
}
m, ok := res.(map[string]interface{})
m, ok := res.(map[string]any)
if !ok {
t.Fatalf("tool %s: result not a map: %T", name, res)
}
@@ -66,7 +66,7 @@ func TestLiveParamRoundSetsAIParamsValue(t *testing.T) {
// Before the round settles, the knob is not decided.
specPre := specHash("zen-nano-temperature")
pre := callTool(t, srv, toolParamValue, map[string]interface{}{
pre := callTool(t, srv, toolParamValue, map[string]any{
"modelSpecHash": common.BytesToHash(specPre[:]).Hex(),
"knobKey": "temperature",
})
@@ -77,7 +77,7 @@ func TestLiveParamRoundSetsAIParamsValue(t *testing.T) {
_, spec, knobKey := driveSettledRound(t, env, ops, value)
// MCP param_value must now reflect the decided value.
got := callTool(t, srv, toolParamValue, map[string]interface{}{
got := callTool(t, srv, toolParamValue, map[string]any{
"modelSpecHash": common.BytesToHash(spec[:]).Hex(),
"knobKey": knobKey,
})
@@ -109,7 +109,7 @@ func TestMCPParamHistoryMatchesAIParams(t *testing.T) {
_, spec, knobKey := driveSettledRound(t, env, ops, big.NewInt(777))
hist := callTool(t, srv, toolParamHistory, map[string]interface{}{})
hist := callTool(t, srv, toolParamHistory, map[string]any{})
// roundCount parity.
rcOut := env.callParams("roundCount")
@@ -118,21 +118,21 @@ func TestMCPParamHistoryMatchesAIParams(t *testing.T) {
t.Fatalf("param_history roundCount=%v, want %s", hist["roundCount"], wantCount)
}
rounds, ok := hist["rounds"].([]interface{})
rounds, ok := hist["rounds"].([]any)
if !ok || len(rounds) == 0 {
t.Fatalf("param_history returned no rounds: %v", hist["rounds"])
}
// The newest round (index 0, descending) is roundId wantCount-1. Field parity
// against the on-chain getRound.
first := rounds[0].(map[string]interface{})
first := rounds[0].(map[string]any)
roundID := new(big.Int).Sub(wantCount, big.NewInt(1))
if first["roundId"] != roundID.String() {
t.Fatalf("first round id=%v, want %s", first["roundId"], roundID)
}
onchain := readStruct[Round](t, env.c, env.params, "getRound", roundID)
rj := first["round"].(map[string]interface{})
rj := first["round"].(map[string]any)
if rj["knobKey"] != onchain.KnobKey || onchain.KnobKey != knobKey {
t.Fatalf("knobKey mismatch: mcp=%v onchain=%v want=%s", rj["knobKey"], onchain.KnobKey, knobKey)
}
@@ -151,12 +151,12 @@ func TestMCPParamHistoryMatchesAIParams(t *testing.T) {
// Proposals parity: count and each value/operator.
onProps := readStruct[[]Proposal](t, env.c, env.params, "getProposals", roundID)
mcpProps := first["proposals"].([]interface{})
mcpProps := first["proposals"].([]any)
if len(mcpProps) != len(onProps) {
t.Fatalf("proposals count mismatch: mcp=%d onchain=%d", len(mcpProps), len(onProps))
}
for i := range onProps {
p := mcpProps[i].(map[string]interface{})
p := mcpProps[i].(map[string]any)
if p["value"] != onProps[i].Value.String() {
t.Errorf("proposal[%d] value: mcp=%v onchain=%s", i, p["value"], onProps[i].Value)
}
@@ -183,7 +183,7 @@ func TestMCPThoughtStatusMatchesAIGovernor(t *testing.T) {
taskID := env.openThought(spec, knobKey, n)
// While Open: derived status must be "Open".
openRes := callTool(t, srv, toolThoughtStatus, map[string]interface{}{"taskId": taskID.String()})
openRes := callTool(t, srv, toolThoughtStatus, map[string]any{"taskId": taskID.String()})
if openRes["status"] != "Open" {
t.Fatalf("open thought status=%v, want Open", openRes["status"])
}
@@ -196,7 +196,7 @@ func TestMCPThoughtStatusMatchesAIGovernor(t *testing.T) {
env.c.advanceSeconds(2 * 3600) // past the 1h voting window
env.settleThought(taskID)
res := callTool(t, srv, toolThoughtStatus, map[string]interface{}{"taskId": taskID.String()})
res := callTool(t, srv, toolThoughtStatus, map[string]any{"taskId": taskID.String()})
onchain := readStruct[Thought](t, env.c, env.governor, "getThought", taskID)
@@ -235,7 +235,7 @@ func TestMCPThoughtStatusMatchesAIGovernor(t *testing.T) {
}
// quorum_status cross-check: unanimous YES means votesFor==n, quorumReached.
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
if got := toInt(t, q["votesFor"]); got != int(n) {
t.Fatalf("quorum votesFor=%d, want %d", got, n)
}
@@ -388,7 +388,7 @@ type mockVerdict struct {
// toUint8 coerces a JSON-decoded numeric (uint8 from the tool, or float64 if it
// round-tripped through JSON) to uint8.
func toUint8(t *testing.T, v interface{}) uint8 {
func toUint8(t *testing.T, v any) uint8 {
t.Helper()
switch n := v.(type) {
case uint8:
@@ -403,7 +403,7 @@ func toUint8(t *testing.T, v interface{}) uint8 {
}
}
func toInt(t *testing.T, v interface{}) int {
func toInt(t *testing.T, v any) int {
t.Helper()
switch n := v.(type) {
case int:
+8 -8
View File
@@ -35,13 +35,13 @@ func TestPendingOperationsAnnotatesDeadlineAndTruncation(t *testing.T) {
// Task 1: open it fresh (deadline in the future) -> Open, deadlinePassed false.
t1 := env.openThought(spec, "knob-1", 1)
res := callTool(t, srv, toolPendingOperations, map[string]interface{}{})
pending := res["pending"].([]interface{})
res := callTool(t, srv, toolPendingOperations, map[string]any{})
pending := res["pending"].([]any)
// Both tasks are Open, so both appear. Index them by taskId.
byID := map[string]map[string]interface{}{}
byID := map[string]map[string]any{}
for _, p := range pending {
m := p.(map[string]interface{})
m := p.(map[string]any)
byID[m["taskId"].(string)] = m
}
p0, ok := byID[t0.String()]
@@ -61,13 +61,13 @@ func TestPendingOperationsAnnotatesDeadlineAndTruncation(t *testing.T) {
// With a tiny limit and only the tail scanned, truncation must be reported honestly.
// Open several more tasks so taskCount exceeds a limit=1 window.
for i := 0; i < 3; i++ {
for range 3 {
env.openThought(spec, "knob-extra", 1)
}
// Pass limit as float64 to mirror JSON-decoded args (the real transport path).
small := callTool(t, srv, toolPendingOperations, map[string]interface{}{"limit": float64(1)})
small := callTool(t, srv, toolPendingOperations, map[string]any{"limit": float64(1)})
// We asked for at most 1 result; with many open tasks at the tail it returns 1.
if got := len(small["pending"].([]interface{})); got != 1 {
if got := len(small["pending"].([]any)); got != 1 {
t.Fatalf("limit=1 returned %d pending, want 1", got)
}
if small["truncated"] != true {
@@ -94,7 +94,7 @@ func TestPendingOperationsNotTruncatedWhenAllScanned(t *testing.T) {
env.openThought(spec, "a", 1)
env.openThought(spec, "b", 1)
res := callTool(t, srv, toolPendingOperations, map[string]interface{}{})
res := callTool(t, srv, toolPendingOperations, map[string]any{})
if res["truncated"] != false {
t.Fatalf("truncated=%v, want false (scan reached task 0)", res["truncated"])
}
+8 -8
View File
@@ -74,7 +74,7 @@ func TestQuorumStatusMatchesSettleAfterWithdrawRace(t *testing.T) {
// BEFORE any withdrawal, MCP must see a real quorum (sanity: the fix didn't break
// the honest case).
qBefore := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
qBefore := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
if qBefore["quorumReached"] != true {
t.Fatalf("pre-withdraw quorumReached=%v, want true", qBefore["quorumReached"])
}
@@ -92,7 +92,7 @@ func TestQuorumStatusMatchesSettleAfterWithdrawRace(t *testing.T) {
// MCP quorum_status must now report quorumReached=FALSE (matching settle), and
// account for the two dropped verdicts.
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
if q["quorumReached"] != false {
t.Fatalf("post-withdraw quorumReached=%v, want false (settle will drop unbonded verdicts)", q["quorumReached"])
}
@@ -118,7 +118,7 @@ func TestQuorumStatusMatchesSettleAfterWithdrawRace(t *testing.T) {
}
// And thought_status now reads NoQuorum, consistent with the prediction.
ts := callTool(t, srv, toolThoughtStatus, map[string]interface{}{"taskId": taskID.String()})
ts := callTool(t, srv, toolThoughtStatus, map[string]any{"taskId": taskID.String()})
if ts["status"] != "NoQuorum" {
t.Fatalf("thought_status=%v, want NoQuorum", ts["status"])
}
@@ -141,7 +141,7 @@ func TestQuorumStatusMatchesSettleAfterWithdrawRace(t *testing.T) {
env.submitVerdict(k, taskID, spec, voteYes, 10000)
}
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
if q["quorumReached"] != true {
t.Fatalf("quorumReached=%v, want true", q["quorumReached"])
}
@@ -183,7 +183,7 @@ func TestQuorumStatusMatchesSettleAfterWithdrawRace(t *testing.T) {
env.deregister(k)
}
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
if got := toInt(t, q["verdictsCounted"]); got != 3 {
t.Fatalf("verdictsCounted=%d, want 3 (deregistered-but-bonded verdicts still count)", got)
}
@@ -225,7 +225,7 @@ func TestQuorumStatusWinningVoteForNonApproval(t *testing.T) {
env.submitVerdict(ops[1], taskID, spec, voteNo, 10000)
env.submitVerdict(ops[2], taskID, spec, voteYes, 10000)
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
if q["quorumReached"] != true {
t.Fatalf("quorumReached=%v, want true (NO group of 2 >= threshold 2)", q["quorumReached"])
}
@@ -282,7 +282,7 @@ func TestQuorumStatusDeadlineGate(t *testing.T) {
}
// Mid-window: quorum reached, but the deadline has NOT passed -> not settleable.
q := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
q := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
if q["quorumReached"] != true {
t.Fatalf("quorumReached=%v, want true", q["quorumReached"])
}
@@ -295,7 +295,7 @@ func TestQuorumStatusDeadlineGate(t *testing.T) {
// Advance past the 1h voting window; now the quorum is settleable.
env.c.advanceSeconds(2 * 3600)
q2 := callTool(t, srv, toolQuorumStatus, map[string]interface{}{"taskId": taskID.String()})
q2 := callTool(t, srv, toolQuorumStatus, map[string]any{"taskId": taskID.String()})
if q2["deadlinePassed"] != true {
t.Fatalf("deadlinePassed=%v, want true after the window", q2["deadlinePassed"])
}
+16 -16
View File
@@ -48,8 +48,8 @@ func TestStdioServeRoundTrip(t *testing.T) {
if idNum(init["id"]) != 1 {
t.Fatalf("initialize response id=%v, want 1", init["id"])
}
result := init["result"].(map[string]interface{})
si := result["serverInfo"].(map[string]interface{})
result := init["result"].(map[string]any)
si := result["serverInfo"].(map[string]any)
if si["name"] != mcp.ServerName {
t.Fatalf("serverInfo.name=%v, want %s", si["name"], mcp.ServerName)
}
@@ -58,18 +58,18 @@ func TestStdioServeRoundTrip(t *testing.T) {
}
// 2) tools/list: exactly 8 tools.
list := resps[1]["result"].(map[string]interface{})
tools := list["tools"].([]interface{})
list := resps[1]["result"].(map[string]any)
tools := list["tools"].([]any)
if len(tools) != 8 {
t.Fatalf("tools/list returned %d tools, want 8", len(tools))
}
// Each tool descriptor must carry name + inputSchema (the MCP shape).
for _, tl := range tools {
td := tl.(map[string]interface{})
td := tl.(map[string]any)
if _, ok := td["name"].(string); !ok {
t.Fatalf("tool descriptor missing name: %v", td)
}
if _, ok := td["inputSchema"].(map[string]interface{}); !ok {
if _, ok := td["inputSchema"].(map[string]any); !ok {
t.Fatalf("tool descriptor missing inputSchema: %v", td)
}
}
@@ -77,20 +77,20 @@ func TestStdioServeRoundTrip(t *testing.T) {
// 3) tools/call(chain_state): content[0].text is a JSON object carrying the tool VALUE
// plus the verifiable observation. The chain_state fields live under "value"; the
// observation under "observation" (the MED-8 wiring — every result is bindable).
call := resps[2]["result"].(map[string]interface{})
content := call["content"].([]interface{})
call := resps[2]["result"].(map[string]any)
content := call["content"].([]any)
if len(content) != 1 {
t.Fatalf("tools/call content length=%d, want 1", len(content))
}
block := content[0].(map[string]interface{})
block := content[0].(map[string]any)
if block["type"] != "text" {
t.Fatalf("content type=%v, want text", block["type"])
}
var payload map[string]interface{}
var payload map[string]any
if err := json.Unmarshal([]byte(block["text"].(string)), &payload); err != nil {
t.Fatalf("chain_state text not JSON: %v", err)
}
state, ok := payload["value"].(map[string]interface{})
state, ok := payload["value"].(map[string]any)
if !ok {
t.Fatalf("tool result missing value object: %v", payload)
}
@@ -106,7 +106,7 @@ func TestStdioServeRoundTrip(t *testing.T) {
}
// The observation must be present and carry a binding hash + the tool name.
obs, ok := payload["observation"].(map[string]interface{})
obs, ok := payload["observation"].(map[string]any)
if !ok {
t.Fatalf("tool result missing observation object: %v", payload)
}
@@ -141,14 +141,14 @@ func TestStdioUnknownToolIsError(t *testing.T) {
}
// decodeLines parses newline-delimited JSON-RPC responses.
func decodeLines(t *testing.T, s string) []map[string]interface{} {
func decodeLines(t *testing.T, s string) []map[string]any {
t.Helper()
var out []map[string]interface{}
var out []map[string]any
r := bufio.NewReader(strings.NewReader(s))
for {
line, err := r.ReadString('\n')
if t := strings.TrimSpace(line); t != "" {
var m map[string]interface{}
var m map[string]any
if jerr := json.Unmarshal([]byte(t), &m); jerr != nil {
panic("bad response line: " + t)
}
@@ -165,7 +165,7 @@ func decodeLines(t *testing.T, s string) []map[string]interface{} {
}
// idNum extracts a numeric JSON-RPC id (decoded as float64).
func idNum(v interface{}) int {
func idNum(v any) int {
if f, ok := v.(float64); ok {
return int(f)
}
+44 -47
View File
@@ -85,8 +85,8 @@ func (g *Surface) Tools() []mcp.Tool {
// run executes a body against a fresh per-request bounded caller and builds the
// observation from the facts it returned (closing MED-8: every tool returns a
// ChainObservation of its exact reads). The ceiling lives in the bounded caller.
run := func(name string, body func(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error)) func(context.Context, map[string]interface{}) (interface{}, *mcp.ChainObservation, error) {
return func(ctx context.Context, args map[string]interface{}) (interface{}, *mcp.ChainObservation, error) {
run := func(name string, body func(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error)) func(context.Context, map[string]any) (any, *mcp.ChainObservation, error) {
return func(ctx context.Context, args map[string]any) (any, *mcp.ChainObservation, error) {
ec := g.bounded()
val, reads, err := body(ctx, ec, args)
if err != nil {
@@ -110,7 +110,7 @@ func (g *Surface) Tools() []mcp.Tool {
{
Name: toolParamValue,
Description: "Read a decided governance knob value from AIParams.valueOf(modelSpecHash, knobKey). Returns {value, decided}.",
InputSchema: objSchema(map[string]interface{}{
InputSchema: objSchema(map[string]any{
"modelSpecHash": strSchema("bytes32 model spec hash, 0x-hex"),
"knobKey": strSchema("knob key string"),
}, []string{"modelSpecHash", "knobKey"}),
@@ -119,7 +119,7 @@ func (g *Surface) Tools() []mcp.Tool {
{
Name: toolParamHistory,
Description: "AIParams round history (newest first): each round with its proposals. Optional limit (default 16) and fromRound.",
InputSchema: objSchema(map[string]interface{}{
InputSchema: objSchema(map[string]any{
"limit": intSchema("max rounds to return (default 16)"),
"fromRound": intSchema("highest round id to start from (default roundCount-1)"),
}, nil),
@@ -128,7 +128,7 @@ func (g *Surface) Tools() []mcp.Tool {
{
Name: toolThoughtStatus,
Description: "AIGovernor.getThought(taskId) fields plus a derived status (Open/Settled/NoQuorum) and taskCount.",
InputSchema: objSchema(map[string]interface{}{
InputSchema: objSchema(map[string]any{
"taskId": intSchema("task id"),
}, []string{"taskId"}),
Read: run(toolThoughtStatus, g.toolThoughtStatus),
@@ -136,7 +136,7 @@ func (g *Surface) Tools() []mcp.Tool {
{
Name: toolReceiptLookup,
Description: "AIThoughtRegistry receipt lookup by receiptId. Returns {exists, receipt, receiptCount}.",
InputSchema: objSchema(map[string]interface{}{
InputSchema: objSchema(map[string]any{
"receiptId": strSchema("bytes32 receipt id, 0x-hex"),
}, []string{"receiptId"}),
Read: run(toolReceiptLookup, g.toolReceiptLookup),
@@ -150,7 +150,7 @@ func (g *Surface) Tools() []mcp.Tool {
"winningIsApprove (winning vote == Yes), deadlinePassed and settleable " +
"(quorum reached AND deadline passed — settle reverts before the deadline). " +
"Also returns verdictsTotal/verdictsCounted/droppedUnbonded and observedBlock.",
InputSchema: objSchema(map[string]interface{}{
InputSchema: objSchema(map[string]any{
"taskId": intSchema("task id"),
}, []string{"taskId"}),
Read: run(toolQuorumStatus, g.toolQuorumStatus),
@@ -158,7 +158,7 @@ func (g *Surface) Tools() []mcp.Tool {
{
Name: toolOperatorReputation,
Description: "Operator standing: {isOperator, bond, weight, agreementRateBps, rep} from AIGovernor and AIReputation.",
InputSchema: objSchema(map[string]interface{}{
InputSchema: objSchema(map[string]any{
"operator": strSchema("operator address, 0x-hex"),
}, []string{"operator"}),
Read: run(toolOperatorReputation, g.toolOperatorReputation),
@@ -171,7 +171,7 @@ func (g *Surface) Tools() []mcp.Tool {
"inspected (range scannedFrom..taskCount-1). Each entry carries deadlinePassed " +
"(now >= deadline) so a still-Open task whose voting window has closed is visible as " +
"settle-ready. Also returns observedBlock.",
InputSchema: objSchema(map[string]interface{}{
InputSchema: objSchema(map[string]any{
"limit": intSchema("max open thoughts to return (default 16)"),
}, nil),
Read: run(toolPendingOperations, g.toolPendingOperations),
@@ -183,7 +183,7 @@ func (g *Surface) Tools() []mcp.Tool {
// 1. chain_state
// ----------------------------------------------------------------------------
func (g *Surface) toolChainState(ctx context.Context, ec evmread.Caller, _ map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
func (g *Surface) toolChainState(ctx context.Context, ec evmread.Caller, _ map[string]any) (any, []mcp.ObservedFact, error) {
chainID, err := ec.ChainID(ctx)
if err != nil {
return nil, nil, fmt.Errorf("chain_state: chainID: %w", err)
@@ -196,7 +196,7 @@ func (g *Surface) toolChainState(ctx context.Context, ec evmread.Caller, _ map[s
if err != nil {
return nil, nil, fmt.Errorf("chain_state: header: %w", err)
}
val := map[string]interface{}{
val := map[string]any{
"chainId": chainID.String(),
"blockNumber": bn,
"blockHash": hdr.Hash().Hex(),
@@ -215,7 +215,7 @@ func (g *Surface) toolChainState(ctx context.Context, ec evmread.Caller, _ map[s
// 2. param_value
// ----------------------------------------------------------------------------
func (g *Surface) toolParamValue(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
func (g *Surface) toolParamValue(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
spec, err := argBytes32(args, "modelSpecHash")
if err != nil {
return nil, nil, err
@@ -228,7 +228,7 @@ func (g *Surface) toolParamValue(ctx context.Context, ec evmread.Caller, args ma
if err != nil {
return nil, nil, err
}
val := map[string]interface{}{
val := map[string]any{
"value": value.String(),
"decided": decided,
}
@@ -264,7 +264,7 @@ func (g *Surface) readParamValue(ctx context.Context, ec evmread.Caller, spec [3
// 3. param_history
// ----------------------------------------------------------------------------
func (g *Surface) toolParamHistory(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
func (g *Surface) toolParamHistory(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
limit := argLimit(args, defaultLimit)
count, err := g.readRoundCount(ctx, ec)
if err != nil {
@@ -274,7 +274,7 @@ func (g *Surface) toolParamHistory(ctx context.Context, ec evmread.Caller, args
if err != nil {
return nil, nil, err
}
val := map[string]interface{}{
val := map[string]any{
"roundCount": count.String(),
"rounds": rounds,
}
@@ -300,8 +300,8 @@ func (g *Surface) readRoundCount(ctx context.Context, ec evmread.Caller) (*big.I
// readParamHistory walks rounds DESCENDING from `from` (inclusive), capped at limit,
// returning each round's fields and its proposals. `from` defaults to count-1 when not
// supplied by the caller; rounds beyond count-1 are clamped.
func (g *Surface) readParamHistory(ctx context.Context, ec evmread.Caller, count, from *big.Int, limit int) ([]interface{}, error) {
out := []interface{}{}
func (g *Surface) readParamHistory(ctx context.Context, ec evmread.Caller, count, from *big.Int, limit int) ([]any, error) {
out := []any{}
if count.Sign() == 0 {
return out, nil
}
@@ -319,7 +319,7 @@ func (g *Surface) readParamHistory(ctx context.Context, ec evmread.Caller, count
if err != nil {
return nil, err
}
out = append(out, map[string]interface{}{
out = append(out, map[string]any{
"roundId": new(big.Int).Set(i).String(),
"round": roundJSON(round),
"proposals": proposalsJSON(proposals),
@@ -344,7 +344,7 @@ func (g *Surface) readProposals(ctx context.Context, ec evmread.Caller, roundID
// 4. thought_status
// ----------------------------------------------------------------------------
func (g *Surface) toolThoughtStatus(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
func (g *Surface) toolThoughtStatus(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
taskID, err := argUint256(args, "taskId")
if err != nil {
return nil, nil, err
@@ -390,7 +390,7 @@ func derivedStatus(status uint8) string {
// 5. receipt_lookup
// ----------------------------------------------------------------------------
func (g *Surface) toolReceiptLookup(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
func (g *Surface) toolReceiptLookup(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
id, err := argBytes32(args, "receiptId")
if err != nil {
return nil, nil, err
@@ -411,7 +411,7 @@ func (g *Surface) toolReceiptLookup(ctx context.Context, ec evmread.Caller, args
if !ok {
return nil, nil, fmt.Errorf("receipt_lookup: receiptCount not *big.Int")
}
res := map[string]interface{}{
res := map[string]any{
"exists": exists,
"receiptCount": count.String(),
}
@@ -434,7 +434,7 @@ func (g *Surface) toolReceiptLookup(ctx context.Context, ec evmread.Caller, args
// 6. quorum_status
// ----------------------------------------------------------------------------
func (g *Surface) toolQuorumStatus(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
func (g *Surface) toolQuorumStatus(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
taskID, err := argUint256(args, "taskId")
if err != nil {
return nil, nil, err
@@ -474,7 +474,7 @@ func (g *Surface) toolQuorumStatus(ctx context.Context, ec evmread.Caller, args
// settle, the only gate — there is no full-committee early exit in the code path). So a
// quorum is only ACTUALLY settleable once the deadline has passed.
deadlinePassed := now >= t.Deadline
val := map[string]interface{}{
val := map[string]any{
"verdicts": verdictsJSON(verdicts),
"threshold": t.Threshold,
"deadline": t.Deadline,
@@ -649,7 +649,7 @@ func tallyQuorum(verdicts []Verdict, threshold uint8, bonded map[common.Address]
// 7. operator_reputation
// ----------------------------------------------------------------------------
func (g *Surface) toolOperatorReputation(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
func (g *Surface) toolOperatorReputation(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
op, err := argAddress(args, "operator")
if err != nil {
return nil, nil, err
@@ -690,7 +690,7 @@ func (g *Surface) toolOperatorReputation(ctx context.Context, ec evmread.Caller,
if err != nil {
return nil, nil, err
}
val := map[string]interface{}{
val := map[string]any{
"isOperator": isOp,
"bond": bond.String(),
"weight": weight,
@@ -711,7 +711,7 @@ func (g *Surface) toolOperatorReputation(ctx context.Context, ec evmread.Caller,
// 8. pending_operations
// ----------------------------------------------------------------------------
func (g *Surface) toolPendingOperations(ctx context.Context, ec evmread.Caller, args map[string]interface{}) (interface{}, []mcp.ObservedFact, error) {
func (g *Surface) toolPendingOperations(ctx context.Context, ec evmread.Caller, args map[string]any) (any, []mcp.ObservedFact, error) {
limit := argLimit(args, defaultLimit)
// Pin reads to one block so taskCount, each thought, and the "now" used for the
// deadlinePassed flag all reflect the same chain point.
@@ -731,7 +731,7 @@ func (g *Surface) toolPendingOperations(ctx context.Context, ec evmread.Caller,
if err != nil {
return nil, nil, err
}
val := map[string]interface{}{
val := map[string]any{
"taskCount": count.String(),
"pending": open,
// truncated is true when OLDER tasks below the scanned window were NOT inspected, so
@@ -759,16 +759,13 @@ func (g *Surface) toolPendingOperations(ctx context.Context, ec evmread.Caller,
// truncated=true when the scan stopped above task 0 (older tasks were not inspected), and
// the lowest task id scanned. The bounded window plus the per-request eth_call ceiling keep
// a huge taskCount from issuing an unbounded scan.
func (g *Surface) readPendingOperations(ctx context.Context, ec evmread.Caller, block, count *big.Int, limit int, now uint64) ([]interface{}, bool, *big.Int, error) {
out := []interface{}{}
func (g *Surface) readPendingOperations(ctx context.Context, ec evmread.Caller, block, count *big.Int, limit int, now uint64) ([]any, bool, *big.Int, error) {
out := []any{}
last := new(big.Int).Sub(count, big.NewInt(1))
if count.Sign() == 0 {
return out, false, big.NewInt(0), nil
}
scanWindow := int64(limit)
if scanWindow < pendingScanFloor {
scanWindow = pendingScanFloor
}
scanWindow := max(int64(limit), pendingScanFloor)
floor := new(big.Int).Sub(last, big.NewInt(scanWindow-1))
if floor.Sign() < 0 {
floor = big.NewInt(0)
@@ -814,8 +811,8 @@ func (g *Surface) readTaskCountAt(ctx context.Context, ec evmread.Caller, block
// JSON projections — stable, all-scalar renderings of the mirror structs.
// ----------------------------------------------------------------------------
func roundJSON(r *Round) map[string]interface{} {
return map[string]interface{}{
func roundJSON(r *Round) map[string]any {
return map[string]any{
"modelSpecHash": hexBytes32(r.ModelSpecHash),
"promptHash": hexBytes32(r.PromptHash),
"knobKey": r.KnobKey,
@@ -833,10 +830,10 @@ func roundJSON(r *Round) map[string]interface{} {
}
}
func proposalsJSON(ps []Proposal) []interface{} {
out := make([]interface{}, 0, len(ps))
func proposalsJSON(ps []Proposal) []any {
out := make([]any, 0, len(ps))
for i := range ps {
out = append(out, map[string]interface{}{
out = append(out, map[string]any{
"operator": ps[i].Operator.Hex(),
"value": bigString(ps[i].Value),
"confidenceBucket": ps[i].ConfidenceBucket,
@@ -847,8 +844,8 @@ func proposalsJSON(ps []Proposal) []interface{} {
return out
}
func thoughtJSON(t *Thought) map[string]interface{} {
return map[string]interface{}{
func thoughtJSON(t *Thought) map[string]any {
return map[string]any{
"modelSpecHash": hexBytes32(t.ModelSpecHash),
"promptHash": hexBytes32(t.PromptHash),
"evidenceHash": hexBytes32(t.EvidenceHash),
@@ -870,10 +867,10 @@ func thoughtJSON(t *Thought) map[string]interface{} {
}
}
func verdictsJSON(vs []Verdict) []interface{} {
out := make([]interface{}, 0, len(vs))
func verdictsJSON(vs []Verdict) []any {
out := make([]any, 0, len(vs))
for i := range vs {
out = append(out, map[string]interface{}{
out = append(out, map[string]any{
"operator": vs[i].Operator.Hex(),
"vote": vs[i].Vote,
"confidenceBucket": vs[i].ConfidenceBucket,
@@ -884,8 +881,8 @@ func verdictsJSON(vs []Verdict) []interface{} {
return out
}
func receiptJSON(rc *ThoughtReceipt) map[string]interface{} {
return map[string]interface{}{
func receiptJSON(rc *ThoughtReceipt) map[string]any {
return map[string]any{
"modelId": hexBytes32(rc.ModelId),
"promptHash": hexBytes32(rc.PromptHash),
"outputHash": hexBytes32(rc.OutputHash),
@@ -899,8 +896,8 @@ func receiptJSON(rc *ThoughtReceipt) map[string]interface{} {
}
}
func repJSON(r *Rep) map[string]interface{} {
return map[string]interface{}{
func repJSON(r *Rep) map[string]any {
return map[string]any{
"weightBps": r.WeightBps,
"participated": r.Participated,
"agreed": r.Agreed,
+1 -1
View File
@@ -105,7 +105,7 @@ func dedupLastWins(reads []ObservedFact) []ObservedFact {
return reads
}
out := reads[:0]
for i := 0; i < len(reads); i++ {
for i := range reads {
// Skip this entry if the NEXT entry has the same key (a later writer wins).
if i+1 < len(reads) && reads[i+1].Key == reads[i].Key {
continue
+25 -25
View File
@@ -72,8 +72,8 @@ const (
type Tool struct {
Name string
Description string
InputSchema map[string]interface{}
Read func(ctx context.Context, args map[string]interface{}) (interface{}, *ChainObservation, error)
InputSchema map[string]any
Read func(ctx context.Context, args map[string]any) (any, *ChainObservation, error)
}
// Surface is a domain's contribution to the transport: a set of read tools. governance/
@@ -159,7 +159,7 @@ type rpcRequest struct {
type rpcResponse struct {
JSONRPC string `json:"jsonrpc"`
ID json.RawMessage `json:"id,omitempty"`
Result interface{} `json:"result,omitempty"`
Result any `json:"result,omitempty"`
Error *rpcErr `json:"error,omitempty"`
}
@@ -180,9 +180,9 @@ const (
// toolDescriptor is the wire shape of a tool in tools/list (the MCP `inputSchema` key).
// It is projected from a Tool value so the handler closure is never serialized.
type toolDescriptor struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema map[string]interface{} `json:"inputSchema"`
Name string `json:"name"`
Description string `json:"description"`
InputSchema map[string]any `json:"inputSchema"`
}
// content is one MCP tool-result content block. Tool results are returned as a single
@@ -296,7 +296,7 @@ func (s *Server) handle(ctx context.Context, line []byte) *rpcResponse {
if isNotification {
return nil
}
return okResp(req.ID, map[string]interface{}{"tools": s.toolDescriptors()})
return okResp(req.ID, map[string]any{"tools": s.toolDescriptors()})
case "tools/call":
if isNotification {
return nil
@@ -310,13 +310,13 @@ func (s *Server) handle(ctx context.Context, line []byte) *rpcResponse {
}
}
func (s *Server) initializeResult() map[string]interface{} {
return map[string]interface{}{
func (s *Server) initializeResult() map[string]any {
return map[string]any{
"protocolVersion": ProtocolVersion,
"capabilities": map[string]interface{}{
"tools": map[string]interface{}{},
"capabilities": map[string]any{
"tools": map[string]any{},
},
"serverInfo": map[string]interface{}{
"serverInfo": map[string]any{
"name": ServerName,
"version": ServerVersion,
},
@@ -335,8 +335,8 @@ func (s *Server) toolDescriptors() []toolDescriptor {
// toolCallParams is the MCP tools/call params shape.
type toolCallParams struct {
Name string `json:"name"`
Arguments map[string]interface{} `json:"arguments"`
Name string `json:"name"`
Arguments map[string]any `json:"arguments"`
}
func (s *Server) handleToolsCall(ctx context.Context, id json.RawMessage, raw json.RawMessage) *rpcResponse {
@@ -353,7 +353,7 @@ func (s *Server) handleToolsCall(ctx context.Context, id json.RawMessage, raw js
// MCP response with isError=true so the client model sees the failure text (per
// MCP tool-result convention), not as a JSON-RPC protocol error, and the stdio
// loop keeps serving the next request.
return okResp(id, map[string]interface{}{
return okResp(id, map[string]any{
"content": []content{{Type: "text", Text: err.Error()}},
"isError": true,
})
@@ -361,7 +361,7 @@ func (s *Server) handleToolsCall(ctx context.Context, id json.RawMessage, raw js
// Embed the value AND (when present) the verifiable observation in the tool-result
// content, so a caller can read the value and independently re-derive/verify the
// observation hash against the chain state it claims.
payload := map[string]interface{}{"value": result}
payload := map[string]any{"value": result}
if obs != nil {
payload["observation"] = observationView(obs)
}
@@ -369,7 +369,7 @@ func (s *Server) handleToolsCall(ctx context.Context, id json.RawMessage, raw js
if merr != nil {
return errResp(id, codeInternalError, "encode result: "+merr.Error())
}
return okResp(id, map[string]interface{}{
return okResp(id, map[string]any{
"content": []content{{Type: "text", Text: string(encoded)}},
})
}
@@ -377,8 +377,8 @@ func (s *Server) handleToolsCall(ctx context.Context, id json.RawMessage, raw js
// observationView is the JSON shape of an observation embedded in a tool result: the
// block context, the tool name, the sorted/deduped reads, and the binding hash. A caller
// re-derives the hash from these reads via NewObservation/Hash to check the binding.
func observationView(o *ChainObservation) map[string]interface{} {
return map[string]interface{}{
func observationView(o *ChainObservation) map[string]any {
return map[string]any{
"chainId": bigString(o.ChainID),
"blockNumber": o.BlockNumber,
"blockHash": o.BlockHash.Hex(),
@@ -396,13 +396,13 @@ func observationView(o *ChainObservation) map[string]interface{} {
// the domain's Read closure, not here, so the transport stays chain-agnostic. Used by
// both the stdio path and CallTool, so the invariants hold no matter how a tool is
// invoked.
func (s *Server) dispatch(parent context.Context, name string, args map[string]interface{}) (result interface{}, obs *ChainObservation, err error) {
func (s *Server) dispatch(parent context.Context, name string, args map[string]any) (result any, obs *ChainObservation, err error) {
t, ok := s.tools[name]
if !ok {
return nil, nil, fmt.Errorf("mcp: unknown tool %q", name)
}
if args == nil {
args = map[string]interface{}{}
args = map[string]any{}
}
ctx := parent
var cancel context.CancelFunc
@@ -424,7 +424,7 @@ func (s *Server) dispatch(parent context.Context, name string, args map[string]i
// decoded result VALUE. Exposed for in-process callers and tests; it is read-only like
// every tool. The observation is dropped here (in-process callers that want it can build
// it from the same reads); CallToolObserved returns both. Unknown tool names error.
func (s *Server) CallTool(ctx context.Context, name string, args map[string]interface{}) (interface{}, error) {
func (s *Server) CallTool(ctx context.Context, name string, args map[string]any) (any, error) {
v, _, err := s.dispatch(ctx, name, args)
return v, err
}
@@ -432,14 +432,14 @@ func (s *Server) CallTool(ctx context.Context, name string, args map[string]inte
// CallToolObserved runs a read tool and returns BOTH its value and the observation it
// produced (nil when the tool observes nothing). Exposed for callers that bind the
// observation into a verdict.
func (s *Server) CallToolObserved(ctx context.Context, name string, args map[string]interface{}) (interface{}, *ChainObservation, error) {
func (s *Server) CallToolObserved(ctx context.Context, name string, args map[string]any) (any, *ChainObservation, error) {
return s.dispatch(ctx, name, args)
}
// Tools returns the descriptors for the registered read tools (the tools/list surface).
func (s *Server) Tools() []Tool { return s.descs }
func okResp(id json.RawMessage, result interface{}) *rpcResponse {
func okResp(id json.RawMessage, result any) *rpcResponse {
return &rpcResponse{JSONRPC: "2.0", ID: id, Result: result}
}
@@ -447,7 +447,7 @@ func errResp(id json.RawMessage, code int, msg string) *rpcResponse {
return &rpcResponse{JSONRPC: "2.0", ID: id, Error: &rpcErr{Code: code, Message: msg}}
}
func writeJSONLine(w io.Writer, v interface{}) error {
func writeJSONLine(w io.Writer, v any) error {
b, err := json.Marshal(v)
if err != nil {
return err
+15 -15
View File
@@ -25,9 +25,9 @@ func okTool(name string) Tool {
return Tool{
Name: name,
Description: name,
InputSchema: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}},
Read: func(_ context.Context, _ map[string]interface{}) (interface{}, *ChainObservation, error) {
return map[string]interface{}{"ok": true, "tool": name}, nil, nil
InputSchema: map[string]any{"type": "object", "properties": map[string]any{}},
Read: func(_ context.Context, _ map[string]any) (any, *ChainObservation, error) {
return map[string]any{"ok": true, "tool": name}, nil, nil
},
}
}
@@ -38,8 +38,8 @@ func blockingTool(name string) Tool {
return Tool{
Name: name,
Description: name,
InputSchema: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}},
Read: func(ctx context.Context, _ map[string]interface{}) (interface{}, *ChainObservation, error) {
InputSchema: map[string]any{"type": "object", "properties": map[string]any{}},
Read: func(ctx context.Context, _ map[string]any) (any, *ChainObservation, error) {
<-ctx.Done()
return nil, nil, ctx.Err()
},
@@ -51,8 +51,8 @@ func panicTool(name string) Tool {
return Tool{
Name: name,
Description: name,
InputSchema: map[string]interface{}{"type": "object", "properties": map[string]interface{}{}},
Read: func(_ context.Context, _ map[string]interface{}) (interface{}, *ChainObservation, error) {
InputSchema: map[string]any{"type": "object", "properties": map[string]any{}},
Read: func(_ context.Context, _ map[string]any) (any, *ChainObservation, error) {
panic("kaboom")
},
}
@@ -86,17 +86,17 @@ func TestPerCallTimeoutDoesNotWedgeServer(t *testing.T) {
}
// 1) The blocked call returns a tool error (isError=true) mentioning a deadline.
r1 := resps[0]["result"].(map[string]interface{})
r1 := resps[0]["result"].(map[string]any)
if r1["isError"] != true {
t.Fatalf("blocked call should be isError, got: %v", resps[0])
}
txt := r1["content"].([]interface{})[0].(map[string]interface{})["text"].(string)
txt := r1["content"].([]any)[0].(map[string]any)["text"].(string)
if !strings.Contains(strings.ToLower(txt), "deadline") && !strings.Contains(strings.ToLower(txt), "context") {
t.Fatalf("blocked call error %q does not look like a timeout", txt)
}
// 2) The SECOND request was answered — the server is still responsive.
r2 := resps[1]["result"].(map[string]interface{})
r2 := resps[1]["result"].(map[string]any)
if _, ok := r2["content"]; !ok {
t.Fatalf("second request not answered (server wedged?): %v", resps[1])
}
@@ -155,7 +155,7 @@ func TestOversizedLineIsRejectedAndLoopSurvives(t *testing.T) {
if resps[0]["error"] == nil {
t.Fatalf("oversized line should yield a parse error, got: %v", resps[0])
}
em := resps[0]["error"].(map[string]interface{})
em := resps[0]["error"].(map[string]any)
if int(em["code"].(float64)) != codeParseError {
t.Fatalf("oversized line error code=%v, want %d", em["code"], codeParseError)
}
@@ -216,21 +216,21 @@ func TestEmptyToolNameRejected(t *testing.T) {
}
func TestNilReadHandlerRejected(t *testing.T) {
bad := Tool{Name: "noread", Description: "noread", InputSchema: map[string]interface{}{"type": "object"}}
bad := Tool{Name: "noread", Description: "noread", InputSchema: map[string]any{"type": "object"}}
if _, err := NewServer(fakeSurface{tools: []Tool{bad}}); err == nil {
t.Fatal("expected a nil-Read-handler error, got nil")
}
}
// decodeLines parses newline-delimited JSON-RPC responses.
func decodeLines(t *testing.T, s string) []map[string]interface{} {
func decodeLines(t *testing.T, s string) []map[string]any {
t.Helper()
var out []map[string]interface{}
var out []map[string]any
r := bufio.NewReader(strings.NewReader(s))
for {
line, err := r.ReadString('\n')
if tl := strings.TrimSpace(line); tl != "" {
var m map[string]interface{}
var m map[string]any
if jerr := json.Unmarshal([]byte(tl), &m); jerr != nil {
t.Fatalf("bad response line: %s", tl)
}