An open API service indexing awesome lists of open source software.

https://github.com/tinywasm/mcp

implementation of the Model Context Protocol (MCP)
https://github.com/tinywasm/mcp

Last synced: 2 months ago
JSON representation

implementation of the Model Context Protocol (MCP)

Awesome Lists containing this project

README

          

# tinywasm/mcp

Lean Go implementation of the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) over JSON-RPC 2.0. Protocol-only, WASM-safe, minimal public API.

## Installation

```bash
go get github.com/tinywasm/mcp
```

### ormc (code generation)

Tools use `ormc` for automatic `Schema()`, `Pointers()`, and `Validate()` generation:

```bash
go install github.com/tinywasm/orm/cmd/ormc@latest
```

## Quickstart

### 1. Define tool arguments with validation (using ormc)

```go
// Tool arguments are plain structs with validation tags
// ormc generates Schema(), Pointers(), Validate() automatically
type SearchArgs struct {
Query string `input:"required,min=1,max=255"`
Limit int64 `input:"min=1,max=100"`
}
```

### 2. Write the handler

```go
func handleSearch(ctx *context.Context, req mcp.Request) (*mcp.Result, error) {
var args SearchArgs
if err := req.Bind(&args); err != nil {
return nil, err // server wraps as JSON-RPC error
}
return mcp.Text("found 3 results"), nil
}
```

### 3. Register and serve

```go
srv, err := mcp.NewServer(mcp.Config{
Name: "my-server",
Version: "1.0.0",
Auth: mcp.NewTokenAuthorizer("my-secret-key"),
}, nil)
if err != nil {
log.Fatal(err)
}

srv.AddTool(mcp.Tool{
Name: "search",
Description: "Search items by query",
InputSchema: new(SearchArgs).Schema(),
Resource: "items",
Action: 'r',
Execute: handleSearch,
})

// Consumer owns HTTP routing — use HandleMessage directly
http.HandleFunc("/mcp", func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
ctx := context.New()
ctx.Set(mcp.CtxKeyAuthToken, r.Header.Get("Authorization"))
resp := srv.HandleMessage(&ctx, body)
// encode resp as JSON and write to w
})
http.ListenAndServe(":3030", nil)
```

### 4. Tool results

```go
return mcp.Text("operation completed"), nil // text
return mcp.JSON(&MyData{Name: "test"}) // JSON (Fielder)
text, err := mcp.GetText(result) // extract text
```

---

## Authorizer

mcp provides built-in implementations and accepts custom ones:

```go
type Authorizer interface {
Authorize(token string) (userID string, err error)
Can(userID, resource string, action byte) bool
}

// Built-in:
auth := mcp.NewTokenAuthorizer("my-api-key") // token-based, Can() always true
auth := mcp.OpenAuthorizer() // no auth, Can() always true

// Custom (e.g. from tinywasm/user):
auth := userModule.MCPAuthorizer() // satisfies mcp.Authorizer
```

Auth is required — `NewServer` returns error if `Config.Auth == nil`. Use `mcp.OpenAuthorizer()` for open access.

---

## SSE (Streamable HTTP)

Optional streaming via `SSEPublisher` interface. The consumer creates the SSE server and injects it:

```go
import "github.com/tinywasm/sse"

sseServer := sse.New()

srv, err := mcp.NewServer(mcp.Config{
Name: "my-server",
Version: "1.0.0",
Auth: mcp.NewTokenAuthorizer("secret"),
SSE: sseServer, // *sse.SSEServer satisfies mcp.SSEPublisher
}, providers)
```

When SSE is present, tool list changes and notifications are published automatically. When nil, no streaming occurs.

---

## ToolProvider

Group related tools and their dependencies in a provider:

```go
type CatalogProvider struct {
db *postgres.DB
}

type CatalogSearchArgs struct {
Query string `input:"required,min=1,max=255"`
Category string `input:"max=50"`
}

type CatalogUpdateArgs struct {
ProductID string `input:"required"`
Price float64 `input:"required,min=0"`
}

func (p *CatalogProvider) Tools() []mcp.Tool {
return []mcp.Tool{
{
Name: "catalog_search",
Description: "Search product catalog",
InputSchema: new(CatalogSearchArgs).Schema(),
Resource: "catalog",
Action: 'r',
Execute: p.handleSearch,
},
{
Name: "catalog_update",
Description: "Update product price",
InputSchema: new(CatalogUpdateArgs).Schema(),
Resource: "catalog",
Action: 'u',
Execute: p.handleUpdate,
},
}
}

func (p *CatalogProvider) handleSearch(ctx *context.Context, req mcp.Request) (*mcp.Result, error) {
var args CatalogSearchArgs
if err := req.Bind(&args); err != nil {
return nil, err
}
// ... query p.db
return mcp.Text("found 3 products"), nil
}

// Pass providers to NewServer
srv, err := mcp.NewServer(config, []mcp.ToolProvider{&CatalogProvider{db: db}})
```

---

## WASM / Browser

The protocol core compiles with TinyGo. Server-only files (`//go:build !wasm`)
are excluded automatically.

In browser mode, call the handler directly — no HTTP server needed:

```go
srv, err := mcp.NewServer(config, providers)
response := srv.HandleMessage(&ctx, message)
```

---

## API Reference

| Symbol | Description |
|--------|-------------|
| `NewServer(config, providers)` | Create MCP server — returns `(*Server, error)` |
| `Server.AddTool(tool)` | Register a single tool |
| `Server.HandleMessage(ctx, msg)` | Process JSON-RPC message (WASM-safe) |
| `Tool{Name, Description, InputSchema, Resource, Action, Execute}` | Tool definition |
| `ToolProvider` | Interface: `Tools() []Tool` |
| `Authorizer` | Interface: `Authorize(token) (userID, error)`, `Can(userID, resource, action) bool` |
| `SSEPublisher` | Interface: `Publish(data, channel)` (build `!wasm`) |
| `NewTokenAuthorizer(apiKey)` | Token-based authorizer |
| `OpenAuthorizer()` | Open access authorizer |
| `Request` | Incoming tool call |
| `Request.Bind(target)` | Decode + validate arguments |
| `Result` | Tool call result |
| `Text(s)` | Create text result |
| `JSON(data)` | Create JSON result |
| `GetText(result)` | Extract text from result |
| `Config` | Server configuration: `Name`, `Version`, `Auth`, `SSE` |
| `CtxKeySessionID` | Context key for session ID |
| `CtxKeyUserID` | Context key for authenticated user ID |
| `CtxKeyAuthToken` | Context key for auth token |

---

See [docs/WHY_ARQ.md](docs/WHY_ARQ.md) for architecture decisions and trade-offs.