Help us shape the future of Go! Take the 2026 Go Developer Survey

tasks

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

README

MCP Tasks for Go

Go Reference License

Go support for the official io.modelcontextprotocol/tasks extension, 2026-07-28. This module targets the dated specification, not the draft or the incompatible 2025-11-25 Tasks API. Core MCP schemas remain in mcp-protocol; transports remain in viant/mcp. This module supplies extension types, policy/state interfaces, a reference durable store, an opt-in RPC decorator, and polling/streaming clients.

Quick start

Requires Go 1.25.5 or later.

go get github.com/viant/mcp-tasks

An existing runtime implements the small tasks.Backend interface. It creates an owned handle only after its native execution is durably admitted, projects that execution on Get, and delegates Update / Cancel to its ordinary control APIs. The optional reference implementation is useful for a small service or for testing a new adapter:

storage, err := store.Open("./task-state", 1000)
if err != nil { return err }
backend, err := reference.New(storage, reference.Options{})
if err != nil { return err }

caps := schema.ClientCapabilities{}
tasks.Advertise(&caps)
owner := tasks.Owner{Tenant: "my-service", Subject: authenticatedSubject}
handle, err := backend.Create(ctx, owner, caps, tasks.CreateOptions{
    Method: tasks.MethodCallTool,
})
// Return handle only after err == nil. Dispatch work through your existing
// executor; persist its original MCP result with backend.Complete(...).

Imports for this example are github.com/viant/mcp-tasks (alias tasks), github.com/viant/mcp-tasks/reference, github.com/viant/mcp-tasks/store, and github.com/viant/mcp-protocol/schema. A complete runnable example is in example_test.go.

An opt-in client handles either an ordinary result or a task handle:

caller, err := rpc.NewHTTPCaller(endpoint, authenticatedHTTPClient)
if err != nil { return err }
requester, err := client.New(caller, client.Options{
    Tasks: true,
    Info: schema.Implementation{Name: "my-client", Version: "1.0"},
})
if err != nil { return err }
outcome, err := requester.CallTool(ctx, &schema.CallToolRequestParams{
    Name: "generate_report",
})
if err != nil { return err }
if outcome.Task != nil {
    // Persist taskId and the input cursor to recover observation after restart.
    result, err := requester.Wait(ctx, outcome.Task.TaskID, client.WaitOptions{
        Initial: &outcome.Task.Task,
    })
    _ = result
    return err
}
// outcome.Result contains the unchanged regular or MRTR result.

Wait returns an InputRequiredError unless an explicit Input callback is provided. That callback must use the same sampling, elicitation, consent and approval policy as normal MCP requests. SaveCursor records input attempt state before side effects; a lost acknowledgement is not permission to retry it. Cancel explicitly acknowledges cancellation intent. Cancelling the observation context only stops polling and leaves native work running.

Design

A task is a durable view of an existing execution, not an execution scheduler. Store adapters may map the task directly onto an application's canonical run store. reference.Service maintains the reference task state; it does not start workers, retry operations, replay original tool calls, or cancel work on disconnect. The supplied file store is a small reference backend, not a replacement for Agently's existing durable execution engine.

The server decides whether to create a task for each tool request. The client must declare the extension in that request's _meta capabilities; declarations on another request or an older initialization do not authorize this response. Calls that do not use task augmentation keep their existing result and behavior. CreateTaskResult carries a flat Task seed, which can already have any status. Status-specific result/error/input payloads are fetched as DetailedTask through tasks/get or full-state notifications; seed decoding does not require them. The creator commits the owned record and verifies that it can be read before exposing its handle. Hosts that create work through the reference backend must schedule it only after this durable barrier; adapters may instead capture an already-admitted canonical native run.

Task IDs use 192 bits of random entropy, but are not treated as authorization. The caller supplies an authenticated tenant/subject identity from trusted request context. Every get, update and cancellation checks it; callers cannot select owners through wire parameters. Foreign and missing IDs have the same outcome. Input requests use lifetime-unique keys, durable response acceptance, and the same host consent/elicitation/sampling policy as their non-task equivalents. Cancellation records intent; its acknowledgement does not prove work stopped.

Packages and contract

  • Root tasks: wire types, negotiation and backend/storage contracts.
  • reference: optional lifecycle/state implementation, with no scheduler.
  • store: file-backed durable reference implementation with process-safe locks, atomic replacement, file/directory sync, optimistic revisions and explicit expiry cleanup. It starts no executions.
  • rpc: transport.Handler decorator, per-request authentication/negotiation, raw result dispatch, and a bounded HTTP JSON-RPC caller with task routing headers.
  • client: task-aware tool results, polling, explicit update/cancel and optional input handling/checkpoints. Transport errors never retry a mutation or the original tool call; unknown input outcomes require reconciliation.
Surface Behavior
tools/call Either the normal result or flat resultType: "task" handle
tasks/get resultType: "complete", full current state and status payload
tasks/update Partial keyed input acceptance; complete empty acknowledgement
tasks/cancel Durable cooperative intent; complete empty acknowledgement
working, input_required Progress and all outstanding requests
completed Original result, including tool-level isError: true
failed JSON-RPC error only
notifications/tasks Full owned canonical state on an acknowledged subscription stream
subscriptions/listen task filters Explicit bounded task IDs, acknowledgement-first ordering, per-owner validation and optional native event hints
HTTP Mcp-Method plus Mcp-Name: taskId on get/update/cancel
Legacy API No tasks/list, tasks/result, task-preference parameter or legacy task capability claim

Store.Create and Store.CompareAndSwap must be atomic and durable before success. A failed/uncertain commit must not be repeated automatically. CAS conflicts may reread state and retry only the pure state mutation, never a worker or input-handler side effect. Host integrations own execution policy, quotas, job dispatch, approval, durable input delivery and recovery of uncertain work.

Integration boundary

The RPC decorator can wrap an existing JSON-RPC handler. Its owner resolver must perform the same authentication and authorization as the host; interception must not bypass a sibling handler's private authorization pipeline. Non-task methods delegate unchanged. A tool decision callback is read-only planning and policy, not worker dispatch. Discovery advertises Tasks only for a configured service. Optional subscription streams are not silently acknowledged.

Minimal native viant/mcp hooks needed for seamless high-level integration:

  1. Extension dispatch after normal protocol/request-context/auth preparation, before its closed method switch, with raw JSON result support.
  2. Preserve the tools/call result discriminator before decoding the fixed CallToolResult shape; opt-in callers can expose a handle or poll to completion.
  3. Route Tasks requests by taskId in the Streamable HTTP Mcp-Name header and attach July per-request metadata. A raw transport supplied to this module can already carry extension requests without changing core schemas.
  4. Tool-schema x-mcp-header discovery, validation and argument mirroring belong to the host/high-level MCP client. This raw Tasks client does not fetch/cache all tool schemas or claim that complete core client surface. A custom caller can provide those headers without replaying a call.
  5. The supplied decorator already streams owned taskIds through its actual configured notifier. A native viant/mcp integration may compose this with its existing subscription hub. Mixed Tasks/base filters are rejected by the standalone decorator instead of silently dropping them.

For Agently, adapt these hooks to existing runs, deferred inputs, approvals, resume/cancel APIs and AG-UI projections; do not create another native task engine. Keep native causal links in the adapter's private record, not caller-supplied IDs. Existing async tool pairs (run/status/cancel), operation selectors, wait/detach and skill fork remain the native APIs and execution modes. MCP Tasks adapts them for interoperable clients; it does not deprecate or replace them.

Existing execution mode Required adapter behavior / acceptance
Foreground Preserve ordinary results for non-negotiated requests; same final tool result under Tasks
Skill fork Parent still waits for the canonical child result; retain parent/child and invocation links
Detached child Child outlives parent/HTTP observer disconnect; task polling observes the same owned child
Approval / input Surface existing requests once, preserve keys and policy, resume the original run
Cancellation Call existing scoped cancellation; acknowledgement remains distinct from the terminal outcome
Recovery Recover canonical run/task IDs and input cursor; never replay execution to reconstruct a handle
AG-UI Project canonical native events; Tasks polling must not become a competing execution owner

Existing native fork/detach tests alone do not prove standard Tasks interoperability. The consuming adapter needs tests for each row over a real MCP connection, including lost responses, restart, foreign-owner polling, partial inputs and cancellation races. The Agently Core source adapter lives in sdk/mcp_tasks.go, with an opt-in transport wrapper in protocol/mcp/expose/tasks.go. It projects existing owned AG-UI/native executions and persists aliases/control receipts in the existing native store. Enabling it requires the host's trusted capture, exported-tool policy and owner resolver; importing this module does not automatically enable an endpoint or authorize arbitrary tools. No runtime/UI deployment is performed by this module.

Task subscription streams

Configure the actual notification transport before accepting taskIds:

handler := &rpc.Handler{
    Next: ordinaryMCPHandler,
    Service: nativeBackend,
    Owner: resolveAuthenticatedOwner,
    Notifier: requestScopedNotifier,
    Subscriptions: &rpc.SubscriptionOptions{
        ReconcileInterval: time.Second,
        MaxDuration: 5 * time.Minute,
    },
}

rpc.HTTPHandler supplies a request-scoped SSE notifier from a Build factory. Host middleware must prepare authentication/context before its owner resolver. The adapter rejects present Origins unless AllowOrigin explicitly admits them. It validates July routing headers before admission, decodes the standard Base64 sentinel for Mcp-Name, and returns HTTP 400/404 with correlated protocol errors. Callers preserve those error bodies; they do not interpret them as permission to retry a mutation or fall back to an unauthenticated transport. protocolVersion and clientCapabilities are required per request; clientInfo is optional. Authentication never derives from self-reported client metadata. Other transports can provide their existing transport.Notifier; stdio notifications/cancelled ends only the matching owned subscription. The HTTP client generates positive integer stream IDs; servers also accept valid MCP string/integer IDs. These IDs are present in every acknowledgement, notification and graceful result under _meta["io.modelcontextprotocol/subscriptionId"].

A backend may implement tasks.ChangeSource for bounded wakeup hints from its existing native event bus. The stream attaches hints before reading state, then re-reads Backend.Get after hints and periodically. Hints never carry MCP payloads. Dropped/overflowed events and process-local bus loss are recovered by canonical rereads; reconnect creates a fresh subscription and initial full-state snapshot. There is no execution replay or unbounded durable event backfill.

The typed client supports bounded HTTP observation and reconnect:

err := requester.Subscribe(ctx, []string{taskID}, client.SubscriptionOptions{
    MaxReconnects: 3,
    OnTask: func(state tasks.DetailedTask) error {
        renderOwnedTaskState(state)
        return nil
    },
})

Subscribe verifies acknowledgement order, agreed IDs, subscription correlation, state shape (including required ttlMs) and declared input capabilities. It only observes: input responses and task cancellation still require their explicit APIs and ordinary policy. Unexpected disconnects refresh canonical state and reconnect within the supplied budget; protocol errors and callback failures are not retried. Cancelling ctx closes the observer and leaves the task running.

Frames, selected IDs, per-handler active subscriptions, stream duration and reconnect count are bounded. Hosts own aggregate connection quotas when their factory creates a handler per request. Frame bytes are bounded independently of stream lifetime. Slow HTTP writes have a deadline; request cancellation cleans up event subscriptions. Mixed task/base filters, unrequested notification methods, unknown/foreign task IDs, missing Tasks capability, and input notifications without the corresponding client capabilities are rejected. No task list or cross-owner stream exists.

The dated core subscription contract and dated Tasks extension define these wire rules. Draft behavior is not enabled.

Testing and status

go test ./...
go test -race ./...
go vet ./...

Tests cover flat wire decoding, required/null fields, per-request capability negotiation, missing capability error -32021, owner/tenant isolation, atomic cross-instance storage, durable reopen, partial/deduplicated input, unique keys, TTL, tool versus protocol errors, cooperative cancellation and competing terminal transitions. Integration tests use the real viant/mcp handler and ordinary high-level client, a task-aware raw client, and HTTP routing headers. Streaming tests cover real HTTP acknowledgement and full states, bounded reconnect with canonical refresh, stdio observer cancellation, owner/tenant rejection, input-capability checks after acknowledgement, malformed or unrequested frames, and frame budgets independent of stream lifetime. No live service, application account, credentials or UI is required.

The dated Tasks request/poll/update/cancel contract is implemented and tested. Task subscription streaming is implemented behind explicit transport enablement. The decorator rejects subscription requests unless both Subscriptions options and a real Notifier are configured. It emits acknowledgement first, full state only, and a correlated graceful completion; it never forwards task progress or logging events. Mixed task/base subscriptions require host composition and are rejected explicitly by this standalone adapter. The older 2025-11-25 API is deliberately separate. Production store retention, quotas, process shutdown, request authentication, native scheduling and recovery are host responsibilities. Tests do not certify an application's deployment.

License

Apache License 2.0. See LICENSE and NOTICE.

Documentation

Overview

Package tasks implements the io.modelcontextprotocol/tasks extension pinned to 2026-07-28. It defines extension wire types and durable lifecycle interfaces, not an execution scheduler. A host can adapt its existing canonical run store.

Example
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/viant/mcp-protocol/schema"

	tasks "github.com/viant/mcp-tasks"
	"github.com/viant/mcp-tasks/reference"
	"github.com/viant/mcp-tasks/store"
	"os"
)

func main() {
	// Existing runtimes can implement tasks.Backend directly. This optional
	// reference example stores state only; it never schedules a worker.
	directory, err := os.MkdirTemp("", "mcp-tasks-example-")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(directory)
	storage, err := store.Open(directory, 100)
	if err != nil {
		panic(err)
	}
	backend, err := reference.New(storage, reference.Options{})
	if err != nil {
		panic(err)
	}
	capabilities := schema.ClientCapabilities{}
	tasks.Advertise(&capabilities)
	owner := tasks.Owner{Tenant: "example", Subject: "authenticated-user"}
	ctx := context.Background()
	created, err := backend.Create(ctx, owner, capabilities, tasks.CreateOptions{Method: tasks.MethodCallTool})
	if err != nil {
		panic(err)
	}
	// A real host completes this from its original canonical executor result.
	err = backend.Complete(ctx, owner, created.TaskID, json.RawMessage(`{"resultType":"complete","content":[{"type":"text","text":"done"}]}`))
	if err != nil {
		panic(err)
	}
	result, err := backend.Get(ctx, owner, created.TaskID)
	if err != nil {
		panic(err)
	}
	fmt.Println(created.ResultType, result.ResultType, result.Status)
}
Output:
task complete completed

Index

Examples

Constants

View Source
const (
	MethodListen             = "subscriptions/listen"
	MethodAcknowledged       = "notifications/subscriptions/acknowledged"
	SubscriptionIDMeta       = "io.modelcontextprotocol/subscriptionId"
	MaximumSubscriptionTasks = 64
)
View Source
const (
	Extension             = "io.modelcontextprotocol/tasks"
	ProtocolVersion       = "2026-07-28"
	MethodCallTool        = "tools/call"
	MethodGet             = "tasks/get"
	MethodUpdate          = "tasks/update"
	MethodCancel          = "tasks/cancel"
	MethodNotification    = "notifications/tasks"
	ResultTask            = "task"
	ResultComplete        = "complete"
	MissingCapabilityCode = -32021
	MaxJSONBytes          = 4 << 20
)
View Source
const ClientCapabilitiesMeta = "io.modelcontextprotocol/clientCapabilities"

Variables

View Source
var (
	ErrNotFound   = errors.New("task unavailable")
	ErrConflict   = errors.New("task revision conflict")
	ErrTerminal   = errors.New("task is terminal")
	ErrKeyReuse   = errors.New("task input key cannot be reused")
	ErrDurability = errors.New("task creation was not durably visible")
)

Functions

func Advertise(caps *schema.ClientCapabilities)

Advertise preserves unrelated capabilities. Old capabilities.tasks is never used.

func AdvertiseServer

func AdvertiseServer(caps *schema.ServerCapabilities)

func RequestCapabilities

func RequestCapabilities(params json.RawMessage) (schema.ClientCapabilities, error)

RequestCapabilities reads this request only; there is no initialize fallback.

func ServerSupports

func ServerSupports(caps schema.ServerCapabilities) bool

func Supports

func Supports(caps schema.ClientCapabilities) bool

func SupportsInput

func SupportsInput(caps schema.ClientCapabilities, method string) bool

func ValidID

func ValidID(id string) bool

func ValidObject

func ValidObject(raw json.RawMessage) bool

func ValidateResponses

func ValidateResponses(responses InputResponses) error

Types

type Acknowledgement

type Acknowledgement struct {
	ResultType string                     `json:"resultType"`
	Meta       map[string]json.RawMessage `json:"_meta,omitempty"`
}

type Backend

Backend lets an existing native runtime expose the wire lifecycle directly, without adopting the reference Service or maintaining a second task engine.

type CancelParams

type CancelParams = GetParams

type ChangeSource

type ChangeSource interface {
	WatchTasks(context.Context, Owner, []string) (Wakeup, error)
}

type CreateOptions

type CreateOptions struct {
	Method string
	Origin json.RawMessage
	// Nil TTL uses the service default; Unlimited explicitly selects null.
	TTLMS         *int64
	Unlimited     bool
	StatusMessage string
}

type CreateTaskResult

type CreateTaskResult struct {
	ResultType string `json:"resultType"`
	// DetailedTask remains embedded for source compatibility. Creation carries
	// only the Task seed on the wire; status-specific payloads come from Get.
	DetailedTask
	Meta map[string]json.RawMessage `json:"_meta,omitempty"`
}

func DecodeCreate

func DecodeCreate(raw json.RawMessage) (CreateTaskResult, error)

func (CreateTaskResult) MarshalJSON

func (r CreateTaskResult) MarshalJSON() ([]byte, error)

type DetailedTask

type DetailedTask struct {
	Task
	InputRequests InputRequests   `json:"inputRequests,omitempty"`
	Result        json.RawMessage `json:"result,omitempty"`
	Error         *RPCError       `json:"error,omitempty"`
}

func (DetailedTask) Validate

func (t DetailedTask) Validate() error

type GetParams

type GetParams struct {
	TaskID string                     `json:"taskId"`
	Meta   map[string]json.RawMessage `json:"_meta,omitempty"`
}

type GetTaskResult

type GetTaskResult struct {
	ResultType string `json:"resultType"`
	DetailedTask
	Meta map[string]json.RawMessage `json:"_meta,omitempty"`
}

func DecodeGet

func DecodeGet(raw json.RawMessage) (GetTaskResult, error)

DecodeGet checks mandatory fields, including required-but-null ttlMs.

type InputRequest

type InputRequest struct {
	Method string          `json:"method"`
	Params json.RawMessage `json:"params,omitempty"`
}

InputRequest is an MRTR request object, without a JSON-RPC request ID. Hosts must apply their ordinary request-specific trust and consent policy.

func (InputRequest) Validate

func (r InputRequest) Validate() error

type InputRequests

type InputRequests map[string]InputRequest

type InputResponses

type InputResponses map[string]json.RawMessage

type ListenParams

type ListenParams struct {
	Notifications SubscriptionFilter         `json:"notifications"`
	Meta          map[string]json.RawMessage `json:"_meta,omitempty"`
}

type Owner

type Owner struct {
	Tenant  string `json:"tenant"`
	Subject string `json:"subject"`
}

Owner is authenticated host authority, not a wire field. Both components are required, even for a single-tenant host (use a fixed tenant identifier).

func (Owner) Valid

func (o Owner) Valid() bool

type RPCError

type RPCError struct {
	Code    int             `json:"code"`
	Message string          `json:"message"`
	Data    json.RawMessage `json:"data,omitempty"`
}

RPCError is the JSON-RPC error object retained on a failed task.

func MissingCapability

func MissingCapability() *RPCError

func (*RPCError) Error

func (e *RPCError) Error() string

type Record

type Record struct {
	Owner              Owner                     `json:"owner"`
	Revision           uint64                    `json:"revision"`
	OriginMethod       string                    `json:"originMethod"`
	Origin             json.RawMessage           `json:"origin,omitempty"`
	ClientCapabilities schema.ClientCapabilities `json:"clientCapabilities"`
	State              DetailedTask              `json:"state"`
	Issued             InputRequests             `json:"issued"`
	Responses          InputResponses            `json:"responses"`
	CancelRequested    bool                      `json:"cancelRequested"`
}

Record is private adapter state. Origin can identify an existing native run; no origin metadata, capability snapshot or accepted inputs is sent to clients.

type Status

type Status string
const (
	Working       Status = "working"
	InputRequired Status = "input_required"
	Completed     Status = "completed"
	Cancelled     Status = "cancelled"
	Failed        Status = "failed"
)

func (Status) Terminal

func (s Status) Terminal() bool

func (Status) Valid

func (s Status) Valid() bool

type Store

type Store interface {
	Create(context.Context, Record) error
	Get(context.Context, Owner, string) (Record, error)
	CompareAndSwap(context.Context, Owner, string, uint64, Record) error
}

Store success means committed and immediately visible to Get. CAS must be atomic across every writer; callers never retry a failed/uncertain write. Implementations isolate owners and return ErrNotFound for foreign IDs too.

type SubscriptionAcknowledged

type SubscriptionAcknowledged struct {
	JSONRPC string                         `json:"jsonrpc"`
	Method  string                         `json:"method"`
	Params  SubscriptionAcknowledgedParams `json:"params"`
}

type SubscriptionAcknowledgedParams

type SubscriptionAcknowledgedParams struct {
	Notifications SubscriptionFilter         `json:"notifications"`
	Meta          map[string]json.RawMessage `json:"_meta"`
}

type SubscriptionFilter

type SubscriptionFilter struct {
	TaskIDs               []string `json:"taskIds,omitempty"`
	ToolsListChanged      bool     `json:"toolsListChanged,omitempty"`
	PromptsListChanged    bool     `json:"promptsListChanged,omitempty"`
	ResourcesListChanged  bool     `json:"resourcesListChanged,omitempty"`
	ResourceSubscriptions []string `json:"resourceSubscriptions,omitempty"`
}

SubscriptionFilter embeds the extension's taskIds field. The base fields remain typed for explicit composition/rejection, never silently discarded.

func (SubscriptionFilter) Mixed

func (f SubscriptionFilter) Mixed() bool

func (SubscriptionFilter) Validate

func (f SubscriptionFilter) Validate() error

type SubscriptionResult

type SubscriptionResult struct {
	ResultType string                     `json:"resultType"`
	Meta       map[string]json.RawMessage `json:"_meta"`
}

type Task

type Task struct {
	TaskID         string    `json:"taskId"`
	Status         Status    `json:"status"`
	StatusMessage  string    `json:"statusMessage,omitempty"`
	CreatedAt      time.Time `json:"createdAt"`
	LastUpdatedAt  time.Time `json:"lastUpdatedAt"`
	TTLMS          *int64    `json:"ttlMs"`
	PollIntervalMS *int64    `json:"pollIntervalMs,omitempty"`
}

Task fields are flat on the wire. TTLMS is required and nil means unlimited.

func (Task) Expired

func (t Task) Expired(now time.Time) bool

func (Task) Validate

func (t Task) Validate() error

type TaskNotificationParams

type TaskNotificationParams struct {
	DetailedTask
	Meta map[string]json.RawMessage `json:"_meta,omitempty"`
}

func DecodeNotificationParams

func DecodeNotificationParams(raw json.RawMessage) (TaskNotificationParams, error)

DecodeNotificationParams validates the same required Task fields as Get. In particular a missing ttlMs must not silently become unlimited retention.

type TaskStatusNotification

type TaskStatusNotification struct {
	JSONRPC string                 `json:"jsonrpc"`
	Method  string                 `json:"method"`
	Params  TaskNotificationParams `json:"params"`
}

type UpdateParams

type UpdateParams struct {
	TaskID         string                     `json:"taskId"`
	InputResponses InputResponses             `json:"inputResponses"`
	Meta           map[string]json.RawMessage `json:"_meta,omitempty"`
}

type Wakeup

type Wakeup interface {
	C() <-chan struct{}
	Close() error
}

Wakeup contains only invalidation hints. Subscribers re-read canonical owned state after every hint/overflow and on reconnect; this is not an event queue.

Directories

Path Synopsis
Package client provides opt-in task-aware calls.
Package client provides opt-in task-aware calls.
Package reference supplies an optional durable state implementation.
Package reference supplies an optional durable state implementation.
Package rpc contains opt-in JSON-RPC integration.
Package rpc contains opt-in JSON-RPC integration.
Package store provides a durable reference backend.
Package store provides a durable reference backend.

Jump to

Keyboard shortcuts

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