Skip to content

Commit 2deb4fb

Browse files
authored
Merge pull request #9 from selesy/feat/generate-method-param-lists
Feat/generate method param lists
2 parents 42c4a83 + 455a630 commit 2deb4fb

File tree

80 files changed

+1244
-1303
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+1244
-1303
lines changed

gen/go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ require (
77
github.com/google/go-github/v74 v74.0.0
88
github.com/invopop/yaml v0.3.1
99
github.com/lmittmann/tint v1.1.2
10-
github.com/selesy/ethereum-mcp v0.1.0
10+
github.com/selesy/jsonschema v0.14.0-rc1
1111
github.com/stretchr/testify v1.11.0
12+
github.com/wk8/go-ordered-map/v2 v2.1.8
13+
gotest.tools/v3 v3.5.2
1214
)
1315

1416
require (
1517
github.com/bahlo/generic-list-go v0.2.0 // indirect
1618
github.com/buger/jsonparser v1.1.1 // indirect
1719
github.com/davecgh/go-spew v1.1.1 // indirect
20+
github.com/google/go-cmp v0.7.0 // indirect
1821
github.com/google/go-querystring v1.1.0 // indirect
1922
github.com/mailru/easyjson v0.7.7 // indirect
2023
github.com/pmezard/go-difflib v1.0.0 // indirect
21-
github.com/selesy/jsonschema v0.14.0-rc1 // indirect
22-
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
2324
gopkg.in/yaml.v3 v3.0.1 // indirect
2425
)

gen/go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
2222
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
2323
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2424
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
25-
github.com/selesy/ethereum-mcp v0.1.0 h1:R4iMYgEQ+qg6k6KZzRjZIQaNUt+OG+lvmYvjb+1g6oY=
26-
github.com/selesy/ethereum-mcp v0.1.0/go.mod h1:sfYXFegKyqhjuk3atQmYUIkfIAnA2D/6mEEB6dMox0E=
2725
github.com/selesy/jsonschema v0.14.0-rc1 h1:rGdpQeWFk76uxpmOx0lrA9Lwb+eVTBPZ6io5IC7rVIk=
2826
github.com/selesy/jsonschema v0.14.0-rc1/go.mod h1:QgbdOu2y8GVkNvmK+npB2dvXlbdEL0eQ2LrRcHRTcKU=
2927
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=

gen/internal/generator/generator.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
"github.com/dave/jennifer/jen"
1414

15-
"github.com/selesy/ethereum-mcp/pkg/openrpc"
15+
"github.com/selesy/ethereum-mcp/gen/internal/openrpc"
1616
)
1717

1818
// Generator writes JSONSchema files and Go code for each OpenRPC method
@@ -40,11 +40,11 @@ func (g *Generator) Run(ctx context.Context, methods []openrpc.Method) error {
4040
schemaFile.Anon("embed")
4141
schemaDict := jen.Dict{}
4242

43-
if err := os.Mkdir("mcp", 0o750); err != nil && !os.IsExist(err) {
43+
if err := os.Mkdir("tool", 0o750); err != nil && !os.IsExist(err) {
4444
return err
4545
}
4646

47-
toolFile := jen.NewFile("mcp")
47+
toolFile := jen.NewFile("tool")
4848
toolFile.HeaderComment("Code generated by /gen. DO NOT EDIT.")
4949
toolDict := jen.Dict{}
5050

@@ -61,7 +61,7 @@ func (g *Generator) Run(ctx context.Context, methods []openrpc.Method) error {
6161
goDescr := goDescription(method.Description())
6262

6363
// Save the method schemas as formatted JSON files
64-
data, err := json.MarshalIndent(method, "", " ")
64+
data, err := json.MarshalIndent(method.Schema(), "", " ")
6565
if err != nil {
6666
return err
6767
}
@@ -92,11 +92,19 @@ func (g *Generator) Run(ctx context.Context, methods []openrpc.Method) error {
9292
toolDict[jen.Lit(method.Name())] = jen.Id(goName + "Tool")
9393

9494
// Add the tool parameters - in order - to the lookup table
95-
paramNames := make([]jen.Code, 0, len(method.Params()))
95+
paramOrder := make([]jen.Code, 0, len(method.Params()))
96+
paramRequired := jen.Dict{}
97+
9698
for _, param := range method.Params() {
97-
paramNames = append(paramNames, jen.Lit(param.Name()))
99+
paramOrder = append(paramOrder, jen.Lit(param.Name()))
100+
if param.Required() {
101+
paramRequired[jen.Lit(param.Name())] = jen.Struct().Block()
102+
}
98103
}
99-
paramsDict[jen.Lit(method.Name())] = jen.Index().String().Values(paramNames...)
104+
paramsDict[jen.Lit(method.Name())] = jen.Id("").Values(jen.Dict{
105+
jen.Id("order"): jen.Index().String().Values(paramOrder...),
106+
jen.Id("required"): jen.Map(jen.String()).Struct().Values(paramRequired),
107+
})
100108
}
101109

102110
schemaFile.Comment("Schemas returns a map relating method names to the associated JSONSchema.")
@@ -115,15 +123,15 @@ func (g *Generator) Run(ctx context.Context, methods []openrpc.Method) error {
115123
jen.Return(jen.Map(jen.String()).Qual("github.com/mark3labs/mcp-go/mcp", "Tool").Values(toolDict)),
116124
)
117125

118-
if err := toolFile.Save(filepath.Join("mcp", "tool_gen.go")); err != nil {
126+
if err := toolFile.Save(filepath.Join("tool", "tool_gen.go")); err != nil {
119127
return err
120128
}
121129

122130
g.log.Info("Generated mcp/tool_gen.go file")
123131

124-
paramsFile.Comment("Parameters returns a map relating method names to the associated (ordered) list of parameters.")
125-
paramsFile.Func().Id("Parameters").Params().Map(jen.String()).Index().String().Block(
126-
jen.Return(jen.Map(jen.String()).Index().String().Values(paramsDict)),
132+
paramsFile.Comment("ParamsSpecs returns a map relating method names to the associated (ordered) list of parameters.")
133+
paramsFile.Func().Id("ParamsSpecs").Params().Map(jen.String()).Id("ParamsSpec").Block(
134+
jen.Return(jen.Map(jen.String()).Id("ParamsSpec").Values(paramsDict)),
127135
)
128136

129137
if err := paramsFile.Save(filepath.Join("proxy", "params_gen.go")); err != nil {
Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/stretchr/testify/require"
1212
"gotest.tools/v3/golden"
1313

14-
"github.com/selesy/ethereum-mcp/pkg/openrpc"
14+
"github.com/selesy/ethereum-mcp/gen/internal/openrpc"
1515
)
1616

1717
func TestMerge(t *testing.T) {
@@ -65,28 +65,33 @@ func TestDefinitions_Filter(t *testing.T) {
6565
"A2": {},
6666
"A3": {"deprecated":true},
6767
"B1": {},
68-
"B2": {},
69-
"B3": {}
68+
"B2": {
69+
"title": "C2 or C4",
70+
"oneOf": [
71+
{"$ref":"#/components/schemas/C2"},
72+
{"$ref":"#/components/schemas/C4"}
73+
]
74+
},
75+
"B3": {},
76+
"C1": {},
77+
"C2": {},
78+
"C3": {},
79+
"C4": {}
7080
}`
7181

7282
var defs openrpc.Definitions
7383

7484
require.NoError(t, json.Unmarshal([]byte(in), &defs))
75-
assert.Equal(t, 6, defs.Len())
76-
77-
assert.True(t, defs.Contains("A1"))
78-
assert.True(t, defs.Contains("A2"))
79-
assert.True(t, defs.Contains("A3"))
80-
assert.True(t, defs.Contains("B1"))
81-
assert.True(t, defs.Contains("B2"))
82-
assert.True(t, defs.Contains("B3"))
85+
require.Equal(t, 10, defs.Len())
8386

8487
defs, err := defs.Filter("A3", "B2")
8588
require.NoError(t, err)
86-
assert.Equal(t, 2, defs.Len())
89+
assert.Equal(t, 4, defs.Len())
8790

8891
assert.True(t, defs.Contains("A3"))
8992
assert.True(t, defs.Contains("B2"))
93+
assert.True(t, defs.Contains("C2"))
94+
assert.True(t, defs.Contains("C4"))
9095

9196
assert.True(t, defs.Get("A3").Deprecated)
9297
assert.False(t, defs.Get("B2").Deprecated)
Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import (
66
"strings"
77

88
"github.com/selesy/jsonschema"
9+
orderedmap "github.com/wk8/go-ordered-map/v2"
910
)
1011

11-
// Method is an idempotent representation of an OpenRPC method. Only the
12+
var _ json.Unmarshaler = (*Method)(nil)
13+
14+
// Method is an immutable representation of an OpenRPC method. Only the
1215
// fields needed to create a "raw schema" for an MCP tool are defined and
1316
// processed while unmarshaling the JSON. See [the specification] for
1417
// more details on the available fields when updating or expanding this
@@ -19,6 +22,10 @@ type Method struct {
1922
method method
2023
}
2124

25+
// WithDefs returns a deep copy of the receiver with the provided
26+
// Definitions replacing any previously set definitions. This
27+
// method is integral to merging the OpenRPC schemas and definitions
28+
// into "complete" JSONSchema types.
2229
func (m *Method) WithDefs(defs Definitions) *Method {
2330

2431
return &Method{
@@ -60,9 +67,30 @@ func (m *Method) Defs() Definitions {
6067
return m.method.Defs
6168
}
6269

63-
// MarshalJSON implements the json.Marshaler interface.
64-
func (m Method) MarshalJSON() ([]byte, error) {
65-
return json.Marshal(m.method)
70+
func (m *Method) Schema() jsonschema.Schema {
71+
s := jsonschema.Schema{
72+
ID: jsonschema.ID("https://github.com/selesy/ethereum-mcp/" + m.Name() + ".json"),
73+
Version: "https://json-schema.org/draft/2020-12/schema",
74+
Title: m.Name(),
75+
Description: m.Description(),
76+
Type: "object",
77+
Properties: orderedmap.New[string, *jsonschema.Schema](),
78+
Definitions: m.method.Defs.GetAll(),
79+
}
80+
81+
required := []string{}
82+
for _, param := range m.Params() {
83+
s.Properties.Store(param.Name(), param.Schema())
84+
if param.Required() {
85+
required = append(required, param.Name())
86+
}
87+
}
88+
89+
if len(required) > 0 {
90+
s.Required = required
91+
}
92+
93+
return s
6694
}
6795

6896
// UnmarshalJSON implements the json.Unmarshaler interface.
@@ -89,8 +117,11 @@ type method struct {
89117
Params []Param `json:"params"`
90118
Refs []string `json:"-"`
91119
Defs Definitions `json:"defs"`
120+
Type string `json:"type"`
92121
}
93122

123+
var _ json.Unmarshaler = (*Param)(nil)
124+
94125
// Param is an idempotent representation of an OpenRPC method parameter.
95126
// For the Ethereum RPC, this is always a ContentDescriptorObject and all
96127
// fields are decoded. See [the specification] for more details.
@@ -110,9 +141,13 @@ func (p *Param) Description() string {
110141
return p.param.description()
111142
}
112143

113-
// Schema returns the schema of the parameter.
114-
func (p *Param) Schema() jsonschema.Schema {
115-
return p.param.Schema
144+
// Schema returns the JSONSchema representation of the parameter.
145+
func (p *Param) Schema() *jsonschema.Schema {
146+
s := &p.param.Schema
147+
s.Title = p.Name()
148+
s.Description = p.Description()
149+
150+
return s
116151
}
117152

118153
// Required returns whether the parameter is required.
@@ -125,11 +160,6 @@ func (p *Param) Deprecated() bool {
125160
return p.param.Deprecated
126161
}
127162

128-
// MarshalJSON implements the json.Marshaler interface.
129-
func (p Param) MarshalJSON() ([]byte, error) {
130-
return json.Marshal(p.param)
131-
}
132-
133163
// UnmarshalJSON implements the json.Unmarshaler interface.
134164
func (p *Param) UnmarshalJSON(data []byte) error {
135165
var err error
Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,41 @@ import (
99
"github.com/stretchr/testify/require"
1010
"gotest.tools/v3/golden"
1111

12-
"github.com/selesy/ethereum-mcp/pkg/openrpc"
12+
"github.com/selesy/ethereum-mcp/gen/internal/openrpc"
1313
)
1414

15+
func TestMethod_Schema(t *testing.T) {
16+
t.Parallel()
17+
18+
data := golden.Get(t, "transaction.yaml")
19+
assert.NotNil(t, data)
20+
21+
data, err := yaml.YAMLToJSON(data)
22+
require.NoError(t, err)
23+
24+
var methods []openrpc.Method
25+
26+
require.NoError(t, json.Unmarshal(data, &methods))
27+
28+
for _, method := range methods {
29+
// t.Log(method.Schema())
30+
31+
data, err := json.MarshalIndent(method.Schema(), "", " ")
32+
require.NoError(t, err)
33+
34+
_ = data
35+
// t.Log(string(data))
36+
}
37+
38+
// t.Fail()
39+
}
40+
1541
func TestMethod_UnmarshalJSON(t *testing.T) {
1642
t.Parallel()
1743

1844
t.Run("with execution-api example", func(t *testing.T) {
1945
t.Parallel()
46+
2047
data := golden.Get(t, "transaction.yaml")
2148
assert.NotNil(t, data)
2249

File renamed without changes.

0 commit comments

Comments
 (0)