Skip to content

Commit 613ab98

Browse files
committed
feat(mcp): improve tools collection
reduced number of tools add tool name prefix mokapi
1 parent 5fefb13 commit 613ab98

12 files changed

Lines changed: 196 additions & 76 deletions

mcp/generate_http_mock_response.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func (s *Service) registerGenerateHttpMockResponseTool(server *mcp.Server) {
5555
"default": "application/json",
5656
},
5757
},
58+
"required": []string{"apiName", "path", "method", "statusCode"},
5859
}
5960

6061
outputSchema := map[string]any{
@@ -76,7 +77,7 @@ func (s *Service) registerGenerateHttpMockResponseTool(server *mcp.Server) {
7677
}
7778

7879
registerTool(server, &mcp.Tool{
79-
Name: "generate_http_mock_response",
80+
Name: "mokapi_generate_http_mock_response",
8081
Description: `Generate a valid HTTP response for a specific API endpoint.
8182
8283
This tool returns a complete response object that already conforms to the OpenAPI specification.

mcp/get_api_spec.go

Lines changed: 150 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,67 +5,191 @@ import (
55
"fmt"
66

77
"github.com/modelcontextprotocol/go-sdk/mcp"
8+
log "github.com/sirupsen/logrus"
89
)
910

10-
type GetSpecInput struct {
11+
type GetApiSpecInput struct {
1112
Name string `json:"name"`
1213
Type string `json:"type"`
1314
}
1415

16+
type GetApiSpecOutput struct {
17+
Apis []ApiSpec `json:"apis"`
18+
}
19+
20+
type ApiSpec struct {
21+
Name string `json:"name"`
22+
Type string `json:"type"`
23+
Spec any `json:"spec"`
24+
}
25+
1526
func (s *Service) registerGetSpecTool(server *mcp.Server) {
1627
inputSchema := map[string]any{
1728
"type": "object",
1829
"properties": map[string]any{
1930
"name": map[string]any{
2031
"type": "string",
21-
"description": "The exact name of the API as returned by 'get_api_list'",
32+
"description": "The exact name of the API",
33+
"optional": true,
2234
},
2335
"type": map[string]any{
2436
"type": "string",
25-
"description": "The type of the API as returned by 'get_api_list'",
37+
"description": "Filter APIs by type. Use 'http' for REST/OpenAPI APIs, 'kafka' for AsyncAPI topics, 'ldap' for directory services, or 'mail' for SMTP/IMAP.",
2638
"enum": []string{"http", "kafka", "ldap", "mail"},
39+
"optional": true,
40+
},
41+
},
42+
}
43+
44+
outputSchema := map[string]any{
45+
"type": "object",
46+
"properties": map[string]any{
47+
"apis": map[string]any{
48+
"type": "array",
49+
"description": "The list of mocked apis",
50+
"items": map[string]any{
51+
"type": "object",
52+
"properties": map[string]any{
53+
"name": map[string]any{
54+
"type": "string",
55+
"description": "The name of the API",
56+
},
57+
"type": map[string]any{
58+
"type": "string",
59+
"description": "The type of the API",
60+
"enum": []string{"http", "kafka", "ldap", "mail"},
61+
},
62+
"spec": map[string]any{
63+
"type": "any",
64+
"description": "The specification of the API (e.g. OpenAPI or AsyncAPI",
65+
},
66+
},
67+
},
2768
},
2869
},
2970
}
3071

3172
registerTool(server, &mcp.Tool{
32-
Name: "get_api_spec",
33-
Description: `Get the full API specification for a specific API.
73+
Name: "mokapi_get_api_spec",
74+
Description: `Retrieve API specifications from Mokapi.
3475
35-
This tool should be used AFTER calling 'get_api_list' to find available APIs, then call this tool with the exact 'name' and 'type'.
76+
- DISCOVERY: Call without 'name' to get an overview of all available APIs (names and types).
77+
- DETAILS: Call with a specific 'name' and 'type' to get the full specification (OpenAPI, AsyncAPI, etc.) including endpoints, schemas, and operations.
3678
37-
Returns the complete specification including endpoints, operations, and schemas.`,
38-
InputSchema: inputSchema,
79+
Use discovery first if you are unsure which APIs are currently mocked.`,
80+
InputSchema: inputSchema,
81+
OutputSchema: outputSchema,
3982
}, s.GetApiSpec)
4083
}
4184

42-
func (s *Service) GetApiSpec(_ context.Context, in GetSpecInput) (any, error) {
85+
func (s *Service) GetApiSpec(_ context.Context, in GetApiSpecInput) (GetApiSpecOutput, error) {
86+
var result []ApiSpec
87+
4388
switch in.Type {
44-
case "http":
89+
case "", "http", "kafka", "ldap", "mail":
90+
break
91+
default:
92+
return GetApiSpecOutput{}, fmt.Errorf("unknown type: %s", in.Type)
93+
}
94+
95+
if in.Name == "" {
96+
if in.Type == "http" || len(in.Type) == 0 {
97+
for _, api := range s.app.ListHttp() {
98+
if api.Info.Name == "" {
99+
log.Warnf("mcp tool get_api_list: skip empty HTTTP API name")
100+
continue
101+
}
102+
result = append(result, ApiSpec{
103+
Name: api.Info.Name,
104+
Type: "http",
105+
})
106+
}
107+
}
108+
109+
if in.Type == "kafka" || len(in.Type) == 0 {
110+
for _, api := range s.app.Kafka.List() {
111+
if api.Info.Name == "" {
112+
log.Warnf("mcp tool get_api_list: skip empty Kafka API name")
113+
continue
114+
}
115+
result = append(result, ApiSpec{
116+
Name: api.Info.Name,
117+
Type: "kafka",
118+
})
119+
}
120+
}
121+
122+
if in.Type == "ldap" || len(in.Type) == 0 {
123+
for _, api := range s.app.Ldap.List() {
124+
if api.Info.Name == "" {
125+
log.Warnf("mcp tool get_api_list: skip empty LDAP API name")
126+
continue
127+
}
128+
result = append(result, ApiSpec{
129+
Name: api.Info.Name,
130+
Type: "ldap",
131+
})
132+
}
133+
}
134+
135+
if in.Type == "mail" || len(in.Type) == 0 {
136+
for _, api := range s.app.Mail.List() {
137+
if api.Info.Name == "" {
138+
log.Warnf("mcp tool get_api_list: skip empty Mail API name")
139+
continue
140+
}
141+
result = append(result, ApiSpec{
142+
Name: api.Info.Name,
143+
Type: "mail",
144+
})
145+
}
146+
}
147+
return GetApiSpecOutput{Apis: result}, nil
148+
}
149+
150+
if in.Type == "http" || len(in.Type) == 0 {
45151
info := s.app.GetHttp(in.Name)
46-
if info == nil {
47-
return nil, fmt.Errorf("http api spec not found")
152+
if info != nil {
153+
result = append(result, ApiSpec{
154+
Name: in.Name,
155+
Type: "http",
156+
Spec: info.Config,
157+
})
48158
}
49-
return info.Config, nil
50-
case "kafka":
159+
}
160+
161+
if in.Type == "kafka" || len(in.Type) == 0 {
51162
info := s.app.Kafka.Get(in.Name)
52-
if info == nil {
53-
return nil, fmt.Errorf("kafka api spec not found")
163+
if info != nil {
164+
result = append(result, ApiSpec{
165+
Name: in.Name,
166+
Type: "kafka",
167+
Spec: info.Config,
168+
})
54169
}
55-
return info.Config, nil
56-
case "ldap":
170+
}
171+
172+
if in.Type == "ldap" || len(in.Type) == 0 {
57173
info := s.app.Ldap.Get(in.Name)
58-
if info == nil {
59-
return nil, fmt.Errorf("ldap api spec not found")
174+
if info != nil {
175+
result = append(result, ApiSpec{
176+
Name: in.Name,
177+
Type: "ldap",
178+
Spec: info.Config,
179+
})
60180
}
61-
return info.Config, nil
62-
case "mail":
181+
}
182+
183+
if in.Type == "mail" || len(in.Type) == 0 {
63184
info := s.app.Mail.Get(in.Name)
64-
if info == nil {
65-
return nil, fmt.Errorf("mail api spec not found")
185+
if info != nil {
186+
result = append(result, ApiSpec{
187+
Name: in.Name,
188+
Type: "mail",
189+
Spec: info.Config,
190+
})
66191
}
67-
return info.Config, nil
68192
}
69193

70-
return nil, fmt.Errorf("invalid type: %s", in.Type)
194+
return GetApiSpecOutput{Apis: result}, nil
71195
}
Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"github.com/stretchr/testify/require"
1717
)
1818

19-
func TestService(t *testing.T) {
19+
func TestService_GetApiSpec(t *testing.T) {
2020
testcases := []struct {
2121
name string
2222
app *runtime.App
@@ -28,7 +28,7 @@ func TestService(t *testing.T) {
2828
openapitest.NewConfig("3.1.0"),
2929
),
3030
test: func(t *testing.T, s *mcp.Service) {
31-
r, err := s.ListApis(context.Background(), mcp.ListApisInput{})
31+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{})
3232
require.NoError(t, err)
3333
require.Len(t, r.Apis, 0)
3434
},
@@ -41,7 +41,7 @@ func TestService(t *testing.T) {
4141
),
4242
),
4343
test: func(t *testing.T, s *mcp.Service) {
44-
r, err := s.ListApis(context.Background(), mcp.ListApisInput{})
44+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{})
4545
require.NoError(t, err)
4646
require.Len(t, r.Apis, 1)
4747
require.Equal(t, "foo", r.Apis[0].Name)
@@ -56,7 +56,7 @@ func TestService(t *testing.T) {
5656
)),
5757
),
5858
test: func(t *testing.T, s *mcp.Service) {
59-
r, err := s.ListApis(context.Background(), mcp.ListApisInput{})
59+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{})
6060
require.NoError(t, err)
6161
require.Len(t, r.Apis, 1)
6262
require.Equal(t, "foo", r.Apis[0].Name)
@@ -74,7 +74,7 @@ func TestService(t *testing.T) {
7474
)),
7575
),
7676
test: func(t *testing.T, s *mcp.Service) {
77-
r, err := s.ListApis(context.Background(), mcp.ListApisInput{})
77+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{})
7878
require.NoError(t, err)
7979
require.Len(t, r.Apis, 2)
8080
require.Equal(t, "foo", r.Apis[0].Name)
@@ -94,7 +94,7 @@ func TestService(t *testing.T) {
9494
)),
9595
),
9696
test: func(t *testing.T, s *mcp.Service) {
97-
r, err := s.ListApis(context.Background(), mcp.ListApisInput{Type: "kafka"})
97+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{Type: "kafka"})
9898
require.NoError(t, err)
9999
require.Len(t, r.Apis, 1)
100100
require.Equal(t, "bar", r.Apis[0].Name)
@@ -109,7 +109,7 @@ func TestService(t *testing.T) {
109109
}),
110110
),
111111
test: func(t *testing.T, s *mcp.Service) {
112-
r, err := s.ListApis(context.Background(), mcp.ListApisInput{})
112+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{})
113113
require.NoError(t, err)
114114
require.Len(t, r.Apis, 1)
115115
require.Equal(t, "foo", r.Apis[0].Name)
@@ -124,7 +124,7 @@ func TestService(t *testing.T) {
124124
}),
125125
),
126126
test: func(t *testing.T, s *mcp.Service) {
127-
r, err := s.ListApis(context.Background(), mcp.ListApisInput{})
127+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{})
128128
require.NoError(t, err)
129129
require.Len(t, r.Apis, 1)
130130
require.Equal(t, "foo", r.Apis[0].Name)
@@ -139,10 +139,10 @@ func TestService(t *testing.T) {
139139
),
140140
),
141141
test: func(t *testing.T, s *mcp.Service) {
142-
r, err := s.GetApiSpec(context.Background(), mcp.GetSpecInput{Name: "foo", Type: "http"})
142+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{Name: "foo", Type: "http"})
143143
require.NoError(t, err)
144-
require.IsType(t, &openapi.Config{}, r)
145-
require.Equal(t, "foo", r.(*openapi.Config).Info.Name)
144+
require.IsType(t, &openapi.Config{}, r.Apis[0].Spec)
145+
require.Equal(t, "foo", r.Apis[0].Spec.(*openapi.Config).Info.Name)
146146
},
147147
},
148148
{
@@ -153,8 +153,9 @@ func TestService(t *testing.T) {
153153
),
154154
),
155155
test: func(t *testing.T, s *mcp.Service) {
156-
_, err := s.GetApiSpec(context.Background(), mcp.GetSpecInput{Name: "bar", Type: "http"})
157-
require.EqualError(t, err, "http api spec not found")
156+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{Name: "bar", Type: "http"})
157+
require.NoError(t, err)
158+
require.Len(t, r.Apis, 0)
158159
},
159160
},
160161
{
@@ -165,10 +166,10 @@ func TestService(t *testing.T) {
165166
)),
166167
),
167168
test: func(t *testing.T, s *mcp.Service) {
168-
r, err := s.GetApiSpec(context.Background(), mcp.GetSpecInput{Name: "foo", Type: "kafka"})
169+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{Name: "foo", Type: "kafka"})
169170
require.NoError(t, err)
170-
require.IsType(t, &asyncapi3.Config{}, r)
171-
require.Equal(t, "foo", r.(*asyncapi3.Config).Info.Name)
171+
require.IsType(t, &asyncapi3.Config{}, r.Apis[0].Spec)
172+
require.Equal(t, "foo", r.Apis[0].Spec.(*asyncapi3.Config).Info.Name)
172173
},
173174
},
174175
{
@@ -177,10 +178,10 @@ func TestService(t *testing.T) {
177178
runtimetest.WithLdap(&directory.Config{Info: directory.Info{Name: "foo"}}),
178179
),
179180
test: func(t *testing.T, s *mcp.Service) {
180-
r, err := s.GetApiSpec(context.Background(), mcp.GetSpecInput{Name: "foo", Type: "ldap"})
181+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{Name: "foo", Type: "ldap"})
181182
require.NoError(t, err)
182-
require.IsType(t, &directory.Config{}, r)
183-
require.Equal(t, "foo", r.(*directory.Config).Info.Name)
183+
require.IsType(t, &directory.Config{}, r.Apis[0].Spec)
184+
require.Equal(t, "foo", r.Apis[0].Spec.(*directory.Config).Info.Name)
184185
},
185186
},
186187
{
@@ -189,10 +190,10 @@ func TestService(t *testing.T) {
189190
runtimetest.WithMail(&mail.Config{Info: mail.Info{Name: "foo"}}),
190191
),
191192
test: func(t *testing.T, s *mcp.Service) {
192-
r, err := s.GetApiSpec(context.Background(), mcp.GetSpecInput{Name: "foo", Type: "mail"})
193+
r, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{Name: "foo", Type: "mail"})
193194
require.NoError(t, err)
194-
require.IsType(t, &mail.Config{}, r)
195-
require.Equal(t, "foo", r.(*mail.Config).Info.Name)
195+
require.IsType(t, &mail.Config{}, r.Apis[0].Spec)
196+
require.Equal(t, "foo", r.Apis[0].Spec.(*mail.Config).Info.Name)
196197
},
197198
},
198199
{
@@ -203,8 +204,8 @@ func TestService(t *testing.T) {
203204
),
204205
),
205206
test: func(t *testing.T, s *mcp.Service) {
206-
_, err := s.GetApiSpec(context.Background(), mcp.GetSpecInput{Name: "bar", Type: "unknown"})
207-
require.EqualError(t, err, "invalid type: unknown")
207+
_, err := s.GetApiSpec(context.Background(), mcp.GetApiSpecInput{Name: "bar", Type: "unknown"})
208+
require.EqualError(t, err, "unknown type: unknown")
208209
},
209210
},
210211
}

mcp/get_events.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func (s *Service) registerGetEvents(server *mcp.Server) {
8282
}
8383

8484
registerTool(server, &mcp.Tool{
85-
Name: "get_events",
85+
Name: "mokapi_get_events",
8686
Description: `Returns recorded events from Mokapi including HTTP requests/responses and Kafka messages.
8787
8888
Use this tool when the user asks:

0 commit comments

Comments
 (0)