managed

package module
v0.62.0 Latest Latest
Warning

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

Go to latest
Published: Nov 14, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package managed publishes a compiled Scope Program as an MCP tool backed by an existing Runtime. It is optional; a single ordinary tool uses mcp.Register and needs no run, writer or continuation token.

Register uses the Program's descriptor for name, description and input schema. Authorize must check the authenticated principal on every round and grant the caller's stable scoped StartID. An MCP request ID, untrusted header or diagnostic meta field alone is not authorization. If the SDK transport has no native session ID, the Host must also grant a trusted, stable dialog identity. Distinct principals and dialogs must not share it. A native session ID is bound independently whenever the transport exposes one. Rotating the secret resume key invalidates existing tokens. Replicas serving the same dialog must share the key, the exact Program and the durable start store.

These tools require the pinned SDK's 2026-07-28 per-request protocol and form capability. Authorization and negotiation are checked before a fresh run starts. Each call waits for committed output or a confirmed external wait in its tree. One flat form with string and boolean fields is published per round. The prompt must be a string, and unsupported schemas, including nested objects and references, fail explicitly. Numeric forms are refused: the pinned SDK decodes form numbers into float64, which cannot preserve arbitrary JSON integers or decimals. Projection never removes an unsupported constraint.

The caller receives an encrypted authenticated token holding a digest of the owner process, local wait key and expiry. Its size is independent of tree depth; committed run state resolves the process address, including reaped children recovered from history. It is bound to the authorized start, session, exact Program, tool and request arguments. Answers go through Handle.Answer; the Program alone owns decision semantics. Decline/cancel do not synthesize an answer or cancel the tree. Mismatched, expired and malformed tokens fail before mutation. Exact answer replay reuses the committed SignalAdmitted record; conflicting answers fail. After a caller disconnects, a fresh request with the same authorized StartID can inspect or rejoin the original run.

Registration requires a readable authoritative head. A node without the writer can render a confirmed pending or terminal state and reconcile a previously accepted answer read-only. A new answer requires routing to the owning writer; this adapter never starts another run or takes the writer epoch when handling a continuation. The Host owns release, retention, recovery and shutdown. Successful terminal output uses the host's pure projection and the ordinary MCP tool result encoder. Logical termination is an MCP tool error; transport, authorization, admission and runtime errors are protocol errors and do not reveal private diagnostics.

Index

Examples

Constants

View Source
const (
	CodeUnavailable         = -32051
	CodeDeclined            = -32052
	CodeCanceled            = -32053
	CodeUnauthorized        = -32054
	CodeInvalidContinuation = -32055
	CodeUnsupportedForm     = -32056
	CodeStartUnknown        = -32057
)

Codes distinguish transport and authorization outcomes from the run's own terminal status. Decline and cancel leave the pending wait unchanged.

Variables

This section is empty.

Functions

func Register

func Register[I, O any](server *sdk.Server, cfg Config[I, O]) error

Register exposes a Program's descriptor as an MCP tool. The projection only renders a committed successful result and must be deterministic and side-effect free.

Example
ctx := context.Background()
rt := must(run.New(ctx, run.Config{Build: "order-example", Store: run.NewMemoryStore()}))
defer rt.Close(ctx)
type order struct {
	ID string `json:"id"`
}
stage := must(workflow.Transform("lookup", func(_ context.Context, in order) (string, error) { return in.ID, nil }))
program := must(workflow.Compile[order, string](rt.Compiler(), workflow.Config{Name: "example.order", Description: "Return an order ID.", Stages: []workflow.Stage{stage}, Revision: "lookup.v1"}))
server := sdk.NewServer(&sdk.Implementation{Name: "orders", Version: "1"}, nil)
// The transport's authenticated middleware supplies the grant on every
// round. Use the same protected secret across replicas of a real service.
type verifiedGrantKey struct{}
var key [32]byte
_, _ = rand.Read(key[:])
err := managed.Register(server, managed.Config[order, string]{Runtime: rt, Program: program, ResumeKey: key, TokenLifetime: 15 * time.Minute,
	Authorize: func(ctx context.Context, _ *sdk.CallToolRequest) (managed.Grant, error) {
		grant, ok := ctx.Value(verifiedGrantKey{}).(managed.Grant)
		if !ok {
			return managed.Grant{}, errors.New("missing verified grant")
		}
		return grant, nil
	},
	Project: func(id string) (tool.Output, error) { return tool.NewTextOutput(id), nil }})
fmt.Println(err == nil)
Output:
true

Types

type Config

type Config[I, O any] struct {
	Runtime       *run.Runtime
	Program       run.Program[I, O]
	Authorize     func(context.Context, *sdk.CallToolRequest) (Grant, error)
	Project       func(O) (tool.Output, error)
	ResumeKey     [resumeKeyBytes]byte
	TokenLifetime time.Duration
}

func (Config[I, O]) Validate

func (c Config[I, O]) Validate() error

type Grant

type Grant struct {
	StartID run.StartID
	Session string
}

Grant is derived by the Host from authenticated request context on every round. Session identifies a trusted application conversation on sessionless transports. Native session IDs, when present, are bound independently.

Jump to

Keyboard shortcuts

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