mcp

package
v0.36.2 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package mcp implements the Model Context Protocol JSON-RPC 2.0 transport that LLM clients (Claude Desktop, Cursor, Claude.ai web) call to discover and invoke wick connectors as tools.

Surface:

POST /mcp                                  — JSON-RPC requests
GET  /.well-known/oauth-protected-resource — auth metadata (RFC 9728)

Methods served (server-side, JSON-RPC 2.0):

initialize      — protocol handshake, capability negotiation
tools/list      — enumerate the caller's accessible connector ops
tools/call      — invoke one op, dispatching to connectors.Service.Execute
notifications/* — accepted as no-ops (we don't push from the server yet)

Auth model:

Static bearer (wick_pat_...) — PAT path, decoded via accesstoken.Service
OAuth opaque token           — OAuth path, decoded via oauth.Service
Anything else                — 401 with WWW-Authenticate pointing at
                                /.well-known/oauth-protected-resource

The auth middleware resolves a user_id and the user's filter-tag IDs onto the request context; everything below it (tools/list, tools/call) sees only the connectors that user can access.

Index

Constants

View Source
const InternalAgentUserID = "wick-agent-internal"

InternalAgentUserID is the id of the synthetic admin principal the per-boot internal token maps to. Agent spawns authenticate with that token, so this id shows up as the caller's user id for agent MCP calls that don't resolve a session owner — per-owner gates (data tables) treat it as an unrestricted system principal rather than a real user.

View Source
const ScopedTokenPrefix = "wick_sub_"

ScopedTokenPrefix marks tokens minted here. Distinct from the PAT prefix so resolveToken never routes one to the wrong validator.

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthMiddleware

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

AuthMiddleware extracts the Authorization: Bearer header, routes the token to the right validator (PAT vs OAuth), loads the user record + filter-tag IDs, and stamps both onto the request context using login.WithUser so downstream code reads identity exactly the way it does for cookie-authed requests.

Unauth requests get a 401 with WWW-Authenticate pointing at the resource-metadata document (RFC 9728), so spec-compliant MCP clients can discover the OAuth server and run the auth dance.

func NewAuthMiddleware

func NewAuthMiddleware(tokens *accesstoken.Service, users userResolver, oauth oauthValidator, resourceMetaURL string) *AuthMiddleware

NewAuthMiddleware wires the bearer middleware. Pass nil oauth to run PAT-only (Phase B); attach later by replacing the field.

func (*AuthMiddleware) WithInternalToken added in v0.15.1

func (m *AuthMiddleware) WithInternalToken(token string) *AuthMiddleware

WithInternalToken sets the per-boot internal MCP secret agent spawns send as their Bearer token to reach the live MCP server over loopback.

func (*AuthMiddleware) WithScopedTokens added in v0.36.0

func (m *AuthMiddleware) WithScopedTokens(s *ScopedTokens) *AuthMiddleware

WithScopedTokens attaches the sub-agent token issuer.

func (*AuthMiddleware) Wrap

func (m *AuthMiddleware) Wrap(next http.Handler) http.Handler

Wrap returns the http.Handler middleware. Apply to /mcp.

type Handler

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

Handler wires the MCP JSON-RPC surface — tools/list, tools/call, initialize. Bearer auth is applied by AuthMiddleware before this handler runs.

Tool surface uses a meta-tool pattern: three stable tools (wick_list, wick_search, wick_execute) let the LLM discover connectors at runtime. Adding/removing connectors never changes the cached tool list.

func NewHandler

func NewHandler(c *connectors.Service) *Handler

func (*Handler) AgentToolDescriptors added in v0.34.0

func (h *Handler) AgentToolDescriptors(ctx context.Context) []handlers.ToolDescriptor

AgentToolDescriptors returns the tool catalog the in-process agent may call, as the synthetic admin principal (full visibility, like the CLI agents' loopback MCP calls). Includes the dynamic wickmanager tools.

func (*Handler) CallAgentTool added in v0.34.0

func (h *Handler) CallAgentTool(ctx context.Context, name string, args map[string]any, sessionID string) (string, bool)

CallAgentTool dispatches one tool call in-process and returns the tool result text + isError, reusing dispatchTool (identical routing to the HTTP transport). sessionID is threaded via the X-Wick-Session-Id header so session-aware tools (ask_user, wick_session_*) resolve correctly.

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*Handler) ServeStdio added in v0.5.1

func (h *Handler) ServeStdio(ctx context.Context, r io.Reader, w io.Writer)

ServeStdio runs the MCP JSON-RPC server over r/w for local clients (Claude Desktop, Cursor, etc.). Each line from r is one JSON-RPC message; the response is written as one JSON line to w.

No auth middleware — the caller pre-populates ctx with login.WithUser (typically a synthetic local admin) before calling. Logs go to stderr so the protocol stream stays clean.

func (*Handler) ServeStdioOS added in v0.5.1

func (h *Handler) ServeStdioOS(ctx context.Context)

ServeStdioOS is the process-level entrypoint: reads os.Stdin, writes os.Stdout. Thin wrapper around ServeStdio for production use.

func (*Handler) WithAppURL added in v0.6.0

func (h *Handler) WithAppURL(get func() string) *Handler

func (*Handler) WithAskUser added in v0.9.0

func (h *Handler) WithAskUser(m askuser.Asker) *Handler

func (*Handler) WithAskUserPolicy added in v0.13.0

func (h *Handler) WithAskUserPolicy(fn func(sessionID string) (bool, string)) *Handler

func (*Handler) WithBuildInfo added in v0.5.1

func (h *Handler) WithBuildInfo(version, commit, buildTime string) *Handler

func (*Handler) WithDB added in v0.14.2

func (h *Handler) WithDB(db *gorm.DB) *Handler

func (*Handler) WithLayout added in v0.17.0

func (h *Handler) WithLayout(l agentconfig.Layout) *Handler

WithLayout wires the agents storage layout without a pool — stdio mode needs it for the session-scoped tools (wick_session_info, wick_set_title, wick_session_workspace) even though no agent pool runs in that process.

func (*Handler) WithPool added in v0.13.4

func (h *Handler) WithPool(p *agentpool.Pool, layout agentconfig.Layout) *Handler

func (*Handler) WithRefreshSession added in v0.16.13

func (h *Handler) WithRefreshSession(fn func(id string) error) *Handler

WithRefreshSession wires the registry-refresh callback used by wick_set_title to keep the dashboard's in-memory session cache in sync after the title is written to disk.

func (*Handler) WithSchedule added in v0.29.0

func (h *Handler) WithSchedule(s *schedule.Store) *Handler

WithSchedule wires the scheduled-message store that backs wick_schedule_message. nil (stdio/tests) disables the tool.

func (*Handler) WithWickRoot added in v0.6.0

func (h *Handler) WithWickRoot(root string) *Handler

type ScopedTokens added in v0.36.0

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

ScopedTokens issues short-lived, in-memory bearer tokens that authenticate a spawned SUB-AGENT to the loopback MCP server as the human who triggered it, with an explicitly narrowed tag set.

Why this exists

Normal agent spawns authenticate with the per-boot internal token, which maps to a synthetic ADMIN principal. Admin bypasses tag filtering entirely. If a sub-agent were handed that same token, a profile's "allowed tags" would be decorative: the child would see every tool in the system regardless of what the profile said, and regardless of what its triggering user was allowed to reach.

So a sub-agent gets one of these instead. The token carries:

  • the REAL triggering user (so per-owner gates behave normally and the audit trail names a human, not a synthetic admin), and
  • a precomputed tag slice that is already the intersection of the user's tags with the profile's optional narrowing list.

Tokens are process-local by design. They are only ever sent over loopback to this same process, they must die with the run, and persisting them would create a durable credential where a transient one suffices.

func NewScopedTokens added in v0.36.0

func NewScopedTokens() *ScopedTokens

NewScopedTokens builds an empty issuer.

func (*ScopedTokens) Issue added in v0.36.0

func (s *ScopedTokens) Issue(userID string, tagIDs []string) (string, error)

Issue mints a token bound to userID with exactly tagIDs.

The caller is responsible for having already intersected tagIDs down to what the user may reach — this type stores what it is given and never widens it, but it also cannot verify the intersection for you.

func (*ScopedTokens) Lookup added in v0.36.0

func (s *ScopedTokens) Lookup(token string) (userID string, tagIDs []string, ok bool)

Lookup resolves a token to its principal. ok is false for unknown or expired tokens.

func (*ScopedTokens) Revoke added in v0.36.0

func (s *ScopedTokens) Revoke(token string)

Revoke drops a token. Called when a delegation reaches a terminal state so a finished child cannot keep calling tools.

func (*ScopedTokens) Sweep added in v0.36.0

func (s *ScopedTokens) Sweep()

Sweep drops expired grants. Cheap; safe to call periodically.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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