mirror of
https://github.com/hanzoai/mcp.git
synced 2026-10-28 22:35:17 +00:00
* feat: cloud Mount() per HIP-0106 Adds pkg/mcp with the canonical Mount(*zip.App, cloud.Deps) entry so the unified cloud binary can serve the HIP-0300 tool surface under /v1/mcp. The route layer is pure-Go: it defines an Invoker contract and dispatches GET /v1/mcp/tools, POST /v1/mcp/tools/:name, plus the standard search and fetch shortcuts. The CGO-backed hanzomcp package implements Invoker; a build without CGO mounts the routes and returns 503 until SetInvoker registers a runtime. Adds schema/mcp.zap describing the typed surface. * refactor: collapse pkg/go/ → root + flatten cloud import Repo IS the package. Mount() at root. Consumers use `github.com/hanzoai/mcp/go` (no /pkg/go/ nesting).
75 lines
1.9 KiB
Go
75 lines
1.9 KiB
Go
package mcp
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"sync"
|
|
)
|
|
|
|
// Invoker is the contract between mcp's HTTP routes and the underlying
|
|
// tool runtime. The CGO-backed hanzomcp package implements this; a
|
|
// pure-Go cloud build with no MCP backend registers nil and the routes
|
|
// return 503.
|
|
type Invoker interface {
|
|
// CallTool dispatches a tool by name with JSON-encoded params and
|
|
// returns the JSON-encoded result envelope.
|
|
CallTool(ctx context.Context, name string, paramsJSON []byte) ([]byte, error)
|
|
|
|
// ListTools returns JSON-encoded definitions for the available tools.
|
|
ListTools(ctx context.Context) ([]byte, error)
|
|
}
|
|
|
|
// ErrNoInvoker is returned when the route layer is mounted but no Invoker
|
|
// has been registered. Callers see HTTP 503.
|
|
var ErrNoInvoker = errors.New("mcp: no invoker registered (build without MCP runtime)")
|
|
|
|
var (
|
|
mu sync.RWMutex
|
|
current Invoker
|
|
)
|
|
|
|
// SetInvoker registers the active Invoker. Pass nil to deactivate
|
|
// (routes return 503). Safe for concurrent use.
|
|
func SetInvoker(inv Invoker) {
|
|
mu.Lock()
|
|
current = inv
|
|
mu.Unlock()
|
|
}
|
|
|
|
// get returns the active Invoker or the null implementation if none
|
|
// registered.
|
|
func get() Invoker {
|
|
mu.RLock()
|
|
inv := current
|
|
mu.RUnlock()
|
|
if inv == nil {
|
|
return nullInvoker{}
|
|
}
|
|
return inv
|
|
}
|
|
|
|
// nullInvoker is the standby when no real backend is wired. Returns
|
|
// ErrNoInvoker so route handlers respond with 503.
|
|
type nullInvoker struct{}
|
|
|
|
func (nullInvoker) CallTool(context.Context, string, []byte) ([]byte, error) {
|
|
return nil, ErrNoInvoker
|
|
}
|
|
|
|
func (nullInvoker) ListTools(context.Context) ([]byte, error) {
|
|
return nil, ErrNoInvoker
|
|
}
|
|
|
|
// errorEnvelope is the JSON shape returned when the runtime fails or
|
|
// no invoker is registered.
|
|
type errorEnvelope struct {
|
|
Success bool `json:"success"`
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
func encodeError(msg string) []byte {
|
|
b, _ := json.Marshal(errorEnvelope{Success: false, Error: msg})
|
|
return b
|
|
}
|