Skip to content

Commit f29b9b7

Browse files
committed
wip: mcp server execute code
1 parent 4c41805 commit f29b9b7

3 files changed

Lines changed: 667 additions & 29 deletions

File tree

mcp/run.go

Lines changed: 206 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ package mcp
33
import (
44
"context"
55
_ "embed"
6+
"fmt"
7+
"io"
8+
"mokapi/js/faker"
9+
"mokapi/providers/openapi"
10+
"mokapi/providers/openapi/schema"
611
"mokapi/runtime"
12+
"mokapi/schema/json/generator"
13+
"net/http"
14+
"net/textproto"
715
"reflect"
816
"slices"
917
"strings"
@@ -122,6 +130,7 @@ type ApiSummary struct {
122130
func (m *mokapi) init(obj *goja.Object) {
123131
_ = obj.Set("getApis", m.getApis)
124132
_ = obj.Set("getApi", m.getApi)
133+
_ = obj.Set("fake", m.fake)
125134
}
126135

127136
func (m *mokapi) getApis() []ApiSummary {
@@ -146,20 +155,30 @@ func (m *mokapi) getApi(name string) any {
146155
for _, api := range m.app.ListHttp() {
147156
if api.Info.Name == name {
148157
return &OpenAPI{
149-
Name: name,
150-
Type: "http",
151-
info: api,
158+
Name: name,
159+
Type: "http",
160+
info: api,
161+
handler: api.Handler(m.app.Monitor.Http, m.app.Engine, m.app.Events),
152162
}
153163
}
154164
}
155165
return nil
156166
}
157167

168+
func (m *mokapi) fake(v goja.Value) (any, error) {
169+
js, err := faker.ToJsonSchema(v, m.vm)
170+
if err != nil {
171+
return nil, err
172+
}
173+
return generator.New(&generator.Request{Schema: js})
174+
}
175+
158176
type OpenAPI struct {
159177
Name string `json:"name"`
160178
Type string `json:"type"`
161179

162-
info *runtime.HttpInfo
180+
info *runtime.HttpInfo
181+
handler openapi.Handler
163182
}
164183

165184
type OperationSummary struct {
@@ -169,11 +188,41 @@ type OperationSummary struct {
169188
}
170189

171190
type OperationDetails struct {
172-
OperationId string `json:"operationId"`
173-
Method string `json:"method"`
174-
Path string `json:"path"`
175-
Summary string `json:"summary"`
176-
Description string `json:"description"`
191+
OperationId string `json:"operationId"`
192+
Method string `json:"method"`
193+
Path string `json:"path"`
194+
Summary string `json:"summary"`
195+
Description string `json:"description,omitempty"`
196+
Parameters []RequestParameters `json:"parameters,omitempty"`
197+
RequestBody RequestBody `json:"requestBody,omitempty"`
198+
199+
spec *openapi.Operation
200+
handler openapi.Handler
201+
}
202+
203+
type RequestParameters struct {
204+
Name string `json:"name"`
205+
In string `json:"in"`
206+
Required bool `json:"required"`
207+
Schema *schema.Schema
208+
Description string `json:"description,omitempty"`
209+
}
210+
211+
type RequestBody struct {
212+
Description string `json:"description,omitempty"`
213+
Required bool `json:"required"`
214+
Contents []Content `json:"contents"`
215+
}
216+
217+
type Content struct {
218+
ContentType string `json:"contentType"`
219+
Schema *schema.Schema `json:"schema"`
220+
}
221+
222+
type Response struct {
223+
StatusCode int `json:"statusCode"`
224+
Description string `json:"description,omitempty"`
225+
Content []Content `json:"content"`
177226
}
178227

179228
func (o *OpenAPI) GetOperations() []OperationSummary {
@@ -215,17 +264,164 @@ func (o *OpenAPI) GetOperationDetails(path, method string) *OperationDetails {
215264
if op == nil {
216265
continue
217266
}
218-
return &OperationDetails{
267+
r := &OperationDetails{
219268
OperationId: op.OperationId,
220269
Method: method,
221270
Path: p.Value.Path,
222271
Summary: op.Summary,
223272
Description: op.Description,
273+
spec: op,
274+
handler: o.handler,
275+
}
276+
for _, param := range op.Parameters {
277+
if param.Value == nil {
278+
continue
279+
}
280+
r.Parameters = append(r.Parameters, RequestParameters{
281+
Name: param.Value.Name,
282+
In: param.Value.Type.String(),
283+
Required: param.Value.Required,
284+
Schema: param.Value.Schema,
285+
Description: param.Value.Description,
286+
})
224287
}
288+
if op.RequestBody != nil && op.RequestBody.Value != nil {
289+
r.RequestBody = RequestBody{
290+
Description: op.RequestBody.Value.Description,
291+
Required: op.RequestBody.Value.Required,
292+
}
293+
for ct, content := range op.RequestBody.Value.Content {
294+
r.RequestBody.Contents = append(r.RequestBody.Contents, Content{
295+
ContentType: ct,
296+
Schema: content.Schema,
297+
})
298+
}
299+
}
300+
return r
225301
}
226302
return nil
227303
}
228304

305+
func (op *OperationDetails) GetResponseSchema(statusCode int) *Response {
306+
r := op.spec.Responses.GetResponse(statusCode)
307+
if r == nil {
308+
return nil
309+
}
310+
result := &Response{
311+
StatusCode: statusCode,
312+
Description: r.Description,
313+
}
314+
for ct, content := range r.Content {
315+
result.Content = append(result.Content, Content{
316+
ContentType: ct,
317+
Schema: content.Schema,
318+
})
319+
}
320+
return result
321+
}
322+
323+
type InvokeRequest struct {
324+
Path map[string]string `json:"path"`
325+
Query map[string]string `json:"query"`
326+
Header map[string][]string `json:"header"`
327+
Body string `json:"body"`
328+
}
329+
330+
type InvokeResponse struct {
331+
StatusCode int `json:"statusCode"`
332+
Headers map[string][]string `json:"headers"`
333+
Body string `json:"body"`
334+
}
335+
336+
func (op *OperationDetails) Invoke(req InvokeRequest) (InvokeResponse, error) {
337+
result := InvokeResponse{Headers: make(map[string][]string)}
338+
339+
var body io.Reader
340+
if req.Body != "" {
341+
body = strings.NewReader(req.Body)
342+
}
343+
344+
path := op.Path
345+
query := ""
346+
params := append(op.spec.Path.Parameters, op.spec.Parameters...)
347+
for _, p := range params {
348+
if p.Value == nil {
349+
continue
350+
}
351+
switch p.Value.Type {
352+
case openapi.ParameterPath:
353+
if req.Path == nil {
354+
return result, fmt.Errorf("invoke request %s %s failed: missing path parameter '%s'", op.Method, op.Path, p.Value.Name)
355+
}
356+
val, ok := req.Path[p.Value.Name]
357+
if !ok {
358+
return result, fmt.Errorf("invoke request %s %s failed: missing path parameter '%s'", op.Method, op.Path, p.Value.Name)
359+
}
360+
path = strings.ReplaceAll(path, fmt.Sprintf("{%s}", p.Value.Name), val)
361+
case openapi.ParameterQuery:
362+
if req.Query == nil && p.Value.Required {
363+
return result, fmt.Errorf("invoke request %s %s failed: missing query parameter '%s'", op.Method, op.Path, p.Value.Name)
364+
}
365+
val, ok := req.Query[p.Value.Name]
366+
if !ok {
367+
if !p.Value.Required {
368+
continue
369+
}
370+
return result, fmt.Errorf("invoke request %s %s failed: missing query parameter '%s'", op.Method, op.Path, p.Value.Name)
371+
}
372+
if query != "" {
373+
query += "&"
374+
}
375+
query += fmt.Sprintf("%s=%s", p.Value.Name, val)
376+
}
377+
}
378+
379+
if query != "" {
380+
path += "?" + query
381+
}
382+
383+
r, err := http.NewRequest(op.Method, path, body)
384+
if err != nil {
385+
return result, fmt.Errorf("error creating request: %w", err)
386+
}
387+
for _, p := range params {
388+
if p.Value == nil || p.Value.Type != openapi.ParameterHeader {
389+
continue
390+
}
391+
if req.Header == nil && p.Value.Required {
392+
return result, fmt.Errorf("invoke request %s %s failed: missing header parameter '%s'", op.Method, op.Path, p.Value.Name)
393+
}
394+
val, ok := req.Header[p.Value.Name]
395+
if !ok {
396+
if !p.Value.Required {
397+
continue
398+
}
399+
return result, fmt.Errorf("invoke request %s %s failed: missing header parameter '%s'", op.Method, op.Path, p.Value.Name)
400+
}
401+
r.Header[textproto.CanonicalMIMEHeaderKey(p.Value.Name)] = val
402+
}
403+
404+
he := op.handler.ServeHTTP(&result, r)
405+
if he != nil {
406+
result.StatusCode = he.StatusCode
407+
result.Body = he.Message
408+
}
409+
return result, nil
410+
}
411+
412+
func (r *InvokeResponse) Header() http.Header {
413+
return r.Headers
414+
}
415+
416+
func (r *InvokeResponse) WriteHeader(statusCode int) {
417+
r.StatusCode = statusCode
418+
}
419+
420+
func (r *InvokeResponse) Write(body []byte) (int, error) {
421+
r.Body = string(body)
422+
return len(body), nil
423+
}
424+
229425
type customFieldNameMapper struct {
230426
}
231427

0 commit comments

Comments
 (0)