webops

package
v0.0.0-...-f5f4912 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package webops is a mobile-first web executor for Model Context Protocol servers. It connects to an MCP server (typically cli-mcp projecting a urfave/cli command tree, but anything that speaks MCP), enumerates the server's tools, and presents annotated favorites as one-push mobile buttons. Tool input forms are generated from the MCP tool's JSON Schema; output streams back over Server-Sent Events.

Threat model: this is a CLI-to-RCE bridge by construction. Default posture refuses to bind any non-Tailscale interface; `DangerouslySkipTailscale=true` opts out for localhost-only development. See README threat-model section.

Architecture (intentional):

browser ── HTTP ── webops.Server ── MCP (stdio) ── MCP server ── tools

webops never invokes a *cli.Command directly. Every action goes through the MCP server, which is the trust boundary.

Index

Examples

Constants

View Source
const (
	MDKeyFavorite = "webops.favorite"
	MDKeyLabel    = "webops.label"
	MDKeyGroup    = "webops.group"
)

MDKey* are the well-known tool.Meta keys that cli-mcp stamps onto MCP Tool entries for webops to consume. The contract lives in cli-mcp's README under "Extension-namespace annotations".

Variables

This section is empty.

Functions

func ListenAndServe

func ListenAndServe(ctx context.Context, target MCPTarget, opts Options) error

ListenAndServe is the package-level convenience: NewServer + Run.

func TestOnlyConnectAndList

func TestOnlyConnectAndList(ctx context.Context, s *Server) error

TestOnlyConnectAndList is a test helper that runs the MCP-connection + ListTools half of Server.Run without binding a listener. Useful for mounting the Handler under httptest.NewServer.

Do not call from production code.

Types

type Auth

type Auth interface {
	// Middleware wraps the protected handler. Implementations may also
	// mount their own auth endpoints onto the inner mux (registration,
	// login) by routing through pattern matching on r.URL.Path before
	// delegating to inner.
	Middleware(inner http.Handler) http.Handler
}

Auth is the authentication backend interface. Implementations gate every HTTP request before it reaches the executor. The Middleware shape lets drivers expose their own challenge/response endpoints (e.g. WebAuthn registration and login) alongside the protected routes.

Built-in drivers:

  • DangerouslyAllowAllAuth: STUB. Allows every request. Never deploy. The prefix follows the OpenClaw convention: any name that disables a load-bearing safety property carries the `Dangerously` prefix so call sites cannot pretend they did not know.

Planned (see #2):

  • WebAuthn + TOTP: asymmetric-key challenge with a TOTP second factor. Phone holds the private key, server holds the registered public key and a per-user TOTP seed.

type DangerouslyAllowAllAuth

type DangerouslyAllowAllAuth struct{}

DangerouslyAllowAllAuth lets every request through with no authentication. Useful for the DangerouslySkipTailscale=true example and unit tests. NEVER deploy with this.

func (DangerouslyAllowAllAuth) Middleware

func (DangerouslyAllowAllAuth) Middleware(inner http.Handler) http.Handler

Middleware implements Auth.

type Field

type Field struct {
	Name        string
	Type        string // "text" "number" "checkbox"
	Required    bool
	Description string
	Default     string
	Enum        []string
}

Field is one rendered input on a tool's form.

type MCPTarget

type MCPTarget struct {
	// Command launches an MCP server as a subprocess and speaks stdio.
	// Path-style: ["myapp", "--mcp-mode"] (first element is the binary).
	Command []string

	// Env is appended to the subprocess environment if Command is set.
	Env []string

	// HTTP is reserved for the planned HTTP/SSE transport. Setting it
	// currently returns an error.
	HTTP string
}

MCPTarget describes how the server connects to its upstream MCP server. Exactly one of Command or HTTP must be set.

type Options

type Options struct {
	// Addr is the TCP address to bind. Required.
	Addr string

	// Auth is the authentication backend. Required. Stub:
	// DangerouslyAllowAllAuth{} (never deploy with that).
	Auth Auth

	// DangerouslySkipTailscale bypasses the Tailscale-interface bind gate.
	// The listen address must still resolve to a loopback (127.0.0.1, ::1)
	// when this is set; webops refuses to bind anywhere else even with
	// the gate off. For development on a machine that has Tailscale
	// installed but is not currently logged in, or in CI.
	//
	// Naming: the cli-web-ops convention follows OpenClaw - any flag that
	// turns a load-bearing safety property off carries the `Dangerously`
	// prefix so call sites read as the alarm they are.
	DangerouslySkipTailscale bool

	// DangerouslyBindAnywhere additionally bypasses the loopback check
	// that DangerouslySkipTailscale leaves in place. Setting this lets
	// webops bind 0.0.0.0 or any routable interface. There is no
	// legitimate reason to set this in production; it exists so an
	// integration test can bind a non-loopback inside an isolated
	// network namespace.
	DangerouslyBindAnywhere bool

	// ShowAllTools exposes every tool, not just webops.favorite ones, on
	// the home screen.
	ShowAllTools bool

	// CommandTimeout caps how long a single tool call may take before the
	// SSE stream is closed and the upstream call is cancelled.
	CommandTimeout time.Duration

	// TailscaleInterface overrides the interface name(s) considered
	// Tailscale-managed.
	TailscaleInterface []string

	// MCPClientName / MCPClientVersion identify webops to the MCP server.
	// Sensible defaults are used when empty.
	MCPClientName    string
	MCPClientVersion string
}

Options configure the web server.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the webops HTTP server.

func NewServer

func NewServer(target MCPTarget, opts Options) (*Server, error)

NewServer prepares a Server. Does not open the MCP connection (that happens in Run).

Example

Build a webops.Server that connects to an MCP server subprocess over stdio. The Tailscale-binding gate is enforced unless DangerouslySkipTailscale is set. The MCP server is the trust boundary - webops never invokes a CLI directly.

package main

import (
	"fmt"

	"github.com/coilysiren/cli-web-ops/webops"
)

func main() {
	srv, err := webops.NewServer(
		webops.MCPTarget{
			Command: []string{"cli-mcp-binary"},
		},
		webops.Options{
			Addr:                     "127.0.0.1:8443",
			Auth:                     webops.DangerouslyAllowAllAuth{}, // STUB - do not deploy
			DangerouslySkipTailscale: true,                             // dev only
		},
	)
	fmt.Println("ok:", srv != nil && err == nil)
}
Output:
ok: true

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the http.Handler with auth middleware applied. Use when embedding webops behind another listener that already enforces the network discipline.

func (*Server) Run

func (s *Server) Run(ctx context.Context) error

Run opens the MCP session, performs ListTools, and starts the HTTP server. Returns when ctx is cancelled or the listener closes. The MCP subprocess is torn down on return.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL