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

mcp

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package mcp defines immutable, SDK-independent Model Context Protocol tools. It owns request-time capability availability, typed decoding and input validation; protocol adapters own transport, recovery, and emitted-output validation. Applications own authentication and resource authorization. Availability runs before decoding, not during construction, and does not replace resource checks inside handlers. Published tools can be shared across goroutines when policies, handlers and captured dependencies are concurrency-safe. Optional CLI presentation travels with ToolInfo, never in a second catalog. Example arguments are checked by typed decoding without executing input validation rules. Custom JSON unmarshaling methods can run during that construction-time check; this is not a guarantee that no user code executes. CLI roles describe diagnostic meaning, not command shortcuts or policy. ToolFailure explicitly approves structured diagnostics; ToolError approves text only. Adapters must keep policy failures private. Schemas are captured as owned JSON bytes; retained drafts and Describe reports cannot change them. Input schema violations wrap ErrInvalidInput, while operational Check errors preserve their cause under ErrInputSchema. Neither schema execution nor output encoding causes approve client-facing diagnostics, even when they wrap ToolError or ToolFailure. Typed integers and raw JSON fields retain number precision; decoding any uses float64 semantics.

Example:

tool := mcp.NewTool("status", func(ctx context.Context, in mcp.NoInput) (struct{}, error) {
	return struct{}{}, nil
})
result, err := tool.Call(context.Background(), nil)
if err != nil {
	panic(err)
}
fmt.Println(string(result)) // {}

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrAvailability = errors.New("mcp: availability evaluation failed")

ErrAvailability classifies a failed capability-policy evaluation. Its wrapped cause is for local diagnostics only, including causes marked with ToolError. Adapters must not expose policy diagnostics as client-safe handler messages.

Example: if errors.Is(err, mcp.ErrAvailability) { logger.Error("policy failed", "error", err) }

View Source
var ErrInputSchema = errors.New("mcp: input schema execution failed")

ErrInputSchema classifies an operational error from input schema execution, not a validation violation. Its original cause remains available locally; adapters must redact it even when it contains a ToolError or ToolFailure. A schema's Check should use validate.Fail for a caller-correctable violation.

Example:

_, err := tool.Call(ctx, arguments)
if errors.Is(err, mcp.ErrInputSchema) {
	logger.Error("input schema failed", "error", err)
}
View Source
var ErrInvalidInput = errors.New("mcp: invalid input")

ErrInvalidInput classifies argument decoding and input-schema violations. Its wrapped cause remains available through errors.Is and errors.As. An operational Check failure instead wraps ErrInputSchema, not ErrInvalidInput.

Example: if errors.Is(err, mcp.ErrInvalidInput) { return err }

View Source
var ErrInvalidOutput = errors.New("mcp: invalid output")

ErrInvalidOutput classifies encoding and nonobject output failures. Output-schema enforcement belongs to the protocol adapter, not Call.

Example: if errors.Is(err, mcp.ErrInvalidOutput) { return err }

View Source
var ErrInvalidTool = errors.New("mcp: invalid tool")

ErrInvalidTool classifies malformed declarations, including a zero Tool. The wrapped cause describes the declaration defect; never expose it blindly.

Example: if errors.Is(tool.Err(), mcp.ErrInvalidTool) { return tool.Err() }

View Source
var ErrUnavailable = errors.New("mcp: tool unavailable")

ErrUnavailable means the capability policy explicitly declined this request. No arguments were parsed and no handler ran. It does not classify policy execution failures, which instead wrap ErrAvailability.

Example: if errors.Is(err, mcp.ErrUnavailable) { return nil }

Functions

func ToolError

func ToolError(message string) error

ToolError marks message as safe for the protocol adapter to disclose. Only explicitly approved, nonsecret text belongs here. Wrappers can retain internal context; ToolErrorMessage extracts only the original message. Core retains the complete message; an adapter may bound its wire length. This marker approves handler failures, not framework failures: availability, input decoding/schema execution, and output encoding classifications take precedence over markers nested inside their diagnostic causes.

Example: return mcp.ToolError("document is not available")

Example

ExampleToolError exercises the error interface and approved-message extraction through Call. Private wrapping remains available locally, outside the marker.

package main

import (
	"context"
	"errors"
	"fmt"

	"arkoren.dev/core/mcp"
)

func main() {
	cause := mcp.ToolError("document is not available")
	tool := mcp.NewTool("lookup", func(context.Context, mcp.NoInput) (mcp.NoInput, error) {
		return mcp.NoInput{}, fmt.Errorf("private lookup context: %w", cause)
	})

	_, err := tool.Call(context.Background(), nil)
	message, ok := mcp.ToolErrorMessage(err)
	fmt.Println(message, ok)
	fmt.Println(errors.Is(err, cause))
	fmt.Println(cause.Error())
}
Output:
document is not available true
true
document is not available

func ToolErrorMessage

func ToolErrorMessage(err error) (string, bool)

ToolErrorMessage extracts approved text through ordinary error wrapping. It returns ("", false) for nil or unmarked errors. An approved empty message returns ("", true); adapters must not substitute the surrounding error text.

Example:

if message, ok := mcp.ToolErrorMessage(err); ok {
	fmt.Println(message)
}

func ToolFailure

func ToolFailure(code, message string, details jsontext.Value) error

ToolFailure marks code, message, and details as safe for clients. Details must be nil or a valid JSON object, and code must be nonempty; invalid declarations return an unmarked error that adapters redact. Bytes are copied immediately. Framework input/output failures and policy errors override this marker and remain private at the adapter boundary; only handler failures approve details.

Example:

return mcp.ToolFailure("unavailable", "capture is disabled", nil)

Types

type Annotations

type Annotations struct {
	// ReadOnly reports that a tool does not mutate its environment.
	// Example: if tool.Describe().Annotations.ReadOnly { fmt.Println("read only") }
	ReadOnly bool
	// Destructive reports possible destructive updates, unless ReadOnly applies.
	// Example: if tool.Describe().Annotations.Destructive { fmt.Println("confirm") }
	Destructive bool
	// Idempotent reports repeatable effects for the same arguments.
	// Example: fmt.Println(tool.Describe().Annotations.Idempotent)
	Idempotent bool
	// OpenWorld reports possible interaction with an unbounded external world.
	// Example: fmt.Println(tool.Describe().Annotations.OpenWorld)
	OpenWorld bool
}

Annotations are detached protocol hints, not authorization guarantees. Their conservative defaults are false, true, false, true respectively.

Example: hints := tool.Describe().Annotations; fmt.Println(hints.ReadOnly)

type CLIInfo

type CLIInfo struct {
	// Group is the CLI namespace; protocol names remain unchanged.
	//
	// Example:
	//
	//	if info := tool.Describe().CLI; info != nil {
	//		fmt.Println(info.Group)
	//	}
	Group string `json:"group"`
	// Operation is the command within Group.
	//
	// Example:
	//
	//	if info := tool.Describe().CLI; info != nil {
	//		fmt.Println(info.Operation)
	//	}
	Operation string `json:"operation"`
	// Summary optionally describes the group in help output.
	//
	// Example:
	//
	//	if info := tool.Describe().CLI; info != nil {
	//		fmt.Println(info.Summary)
	//	}
	Summary string `json:"summary,omitempty"`
	// Role optionally labels a diagnostic meaning, such as identity or health.
	// Empty means unspecified; a role neither authorizes use nor promises safety.
	//
	// Example:
	//
	//	if info := tool.Describe().CLI; info != nil {
	//		fmt.Println(info.Role)
	//	}
	Role string `json:"role,omitempty"`
	// Cursor advertises follow through repeated bounded tool calls. The handler
	// still owns cursor arguments and results; this field installs no behavior.
	//
	// Example:
	//
	//	if info := tool.Describe().CLI; info != nil {
	//		fmt.Println(info.Cursor)
	//	}
	Cursor bool `json:"cursor,omitempty"`
	// Positional lists input property names in command-line argument order.
	//
	// Example:
	//
	//	if info := tool.Describe().CLI; info != nil {
	//		fmt.Println(info.Positional)
	//	}
	Positional []string `json:"positional,omitempty"`
	// Examples contains detached, typed-decodable argument objects, or nil when
	// none were declared. Reading a report does not repeat their decoding hooks.
	//
	// Example:
	//
	//	if info := tool.Describe().CLI; info != nil {
	//		for _, sample := range info.Examples {
	//			fmt.Println(sample.Description, string(sample.Input))
	//		}
	//	}
	Examples []Example `json:"examples,omitempty"`
}

CLIInfo reports optional CLI presentation in tools/list metadata. It does not rename the protocol tool or grant permissions. Cursor indicates ordinary bounded calls with cursor arguments, not a separate streaming transport. Roles label diagnostic meaning for clients, not command aliases or policy. Field examples below inspect a published Tool; its CLI report can be nil.

Example: if info := tool.Describe(); info.CLI != nil { fmt.Println(info.CLI.Operation) }

type Example

type Example struct {
	// Description explains the example's purpose, not its result.
	// Example: fmt.Println(sample.Description)
	Description string `json:"description"`
	// Input is a JSON argument object compatible with the tool's Go input type.
	// Example: fmt.Println(string(sample.Input))
	Input jsontext.Value `json:"input"`
}

Example is a documented argument object, never an invocation. Input is copied when captured and reported; examples do not execute schema rules or handlers. Publication checks typed JSON decoding, which may invoke custom unmarshaling methods. Those application methods must be safe to run during construction.

Example: sample := mcp.Example{Description: "Recent", Input: jsontext.Value(`{"limit":10}`)}

type Failure

type Failure struct {
	// Code is the stable application error classification, such as unavailable.
	// Example: fmt.Println(failure.Code)
	Code string `json:"code"`
	// Message is approved client-facing text, without internal context.
	// Example: fmt.Println(failure.Message)
	Message string `json:"message"`
	// Details contains copied, explicitly approved fields, or nil when omitted.
	// Example: fmt.Println(string(failure.Details))
	Details jsontext.Value `json:"details,omitempty"`
}

Failure is an explicitly approved, detached client diagnostic. Details is an optional JSON object; neither wrapping context nor arbitrary errors are safe.

Example: if failure, ok := mcp.ToolFailureInfo(err); ok { fmt.Println(failure.Code) }

func ToolFailureInfo

func ToolFailureInfo(err error) (Failure, bool)

ToolFailureInfo extracts an approved record through ordinary error wrapping. Missing markers return the zero record and false. The returned details bytes are independent of both the original caller's input and previous reports.

Example:

if failure, ok := mcp.ToolFailureInfo(err); ok {
	fmt.Println(failure.Code, failure.Message)
}
Example

ExampleToolFailureInfo obtains approved structured diagnostics from a failed public call. The error interface contains only the approved message.

package main

import (
	"context"
	"encoding/json/jsontext"
	"fmt"

	"arkoren.dev/core/mcp"
)

func main() {
	cause := mcp.ToolFailure("unavailable", "capture is disabled", jsontext.Value(`{"retry":false}`))
	tool := mcp.NewTool("capture", func(context.Context, mcp.NoInput) (mcp.NoInput, error) {
		return mcp.NoInput{}, fmt.Errorf("private capture context: %w", cause)
	})

	_, err := tool.Call(context.Background(), nil)
	failure, ok := mcp.ToolFailureInfo(err)

	if !ok {
		fmt.Println(err)

		return
	}

	fmt.Println(failure.Code, failure.Message)
	fmt.Println(string(failure.Details))
	fmt.Println(cause.Error())
}
Output:
unavailable capture is disabled
{"retry":false}
capture is disabled

type NoInput

type NoInput struct{}

NoInput describes an empty argument object, not JSON null. It is useful for tools that obtain all their data from dependencies.

Example:

tool := mcp.NewTool("status", func(context.Context, mcp.NoInput) (mcp.NoInput, error) {
	return mcp.NoInput{}, nil
})
result, err := tool.Call(context.Background(), nil)
if err != nil {
	return err
}
fmt.Println(string(result)) // {}

type Tool

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

Tool is an immutable typed handler erased for heterogeneous registration. Its zero value is invalid. Closure state is shared, not deep-copied.

Example:

tool := mcp.NewTool("status", func(context.Context, mcp.NoInput) (mcp.NoInput, error) {
	return mcp.NoInput{}, nil
})
tools := []mcp.Tool{tool}
result, err := tools[0].Call(context.Background(), nil)
if err != nil {
	return err
}
fmt.Println(string(result)) // {}

func NewTool

func NewTool[In, Out any](
	name string,
	fn func(context.Context, In) (Out, error),
	configure ...ToolBuilderFunc[In, Out],
) Tool

NewTool publishes an immutable tool without invoking fn or evaluating its availability policy. It performs no protocol I/O of its own. Construction callbacks and JSON methods used by schema export or CLI example decoding are application code and must be safe to run during construction. Names contain 1–128 ASCII letters, digits, underscores, dots or hyphens. Unspecified schemas are inferred; both must describe JSON objects. Invalid declarations retain their first error for Err and Call. Every callback runs once in order on one draft, including invalid declarations; nil callbacks and callback panics propagate. Nil fn is a declaration error, not a panic.

Example:

tool := mcp.NewTool("status", func(ctx context.Context, in mcp.NoInput) (struct{}, error) {
	return struct{}{}, nil
}, func(action *mcp.ToolBuilder[mcp.NoInput, struct{}]) {
	action.Description("Read status").ReadOnly(true)
})
if err := tool.Err(); err != nil {
	panic(err)
}
Example

ExampleNewTool demonstrates a transport-free call with inferred object schemas. Real adapters additionally validate the emitted result against OutputSchema.

package main

import (
	"context"
	"encoding/json/jsontext"
	"fmt"

	"arkoren.dev/core/mcp"
)

func main() {
	type Document struct {
		// ID is the document identifier echoed by this example.
		ID string `json:"id"`
	}
	tool := mcp.NewTool("documents.echo", func(_ context.Context, input Document) (Document, error) {
		return input, nil
	}, func(action *mcp.ToolBuilder[Document, Document]) {
		action.Description("Echo a document identifier").ReadOnly(true).OpenWorld(false)
	})

	result, err := tool.Call(context.Background(), jsontext.Value(`{"id":"doc-42"}`))

	if err != nil {
		panic(err)
	}

	fmt.Println(string(result))
}
Output:
{"id":"doc-42"}

func (Tool) Available

func (tool Tool) Available(ctx context.Context) (bool, error)

Available evaluates the request's capability policy without parsing input or invoking the handler. No policy means true. Invalid declarations and canceled contexts fail before policy execution. Policy errors preserve their cause and wrap ErrAvailability, even when that cause is a client-safe ToolError. Results are never cached; captures are shared and must be concurrency-safe. This does not replace resource authorization in the handler. Panics propagate.

Example:

offered, err := tool.Available(ctx)
if err != nil {
	return err
}
if offered {
	fmt.Println(tool.Name())
}

func (Tool) Call

func (tool Tool) Call(ctx context.Context, arguments jsontext.Value) (jsontext.Value, error)

Call checks availability, decodes one object, parses input once, and invokes the handler once. A false policy returns ErrUnavailable before even decoding arguments; policy failures preserve ErrAvailability and their original cause. Nil arguments mean {}. Null, unknown struct members, and nonobjects fail with ErrInvalidInput. Canceled contexts prevent handler invocation. Output must encode as an object; output Parse is deliberately never called, so adapters must validate the emitted bytes against the published OutputSchema. Handler errors retain identity; panics propagate for adapter-level recovery. Operational input Check failures retain their cause under ErrInputSchema, not ErrInvalidInput; adapters must not disclose their error markers. Integer fields and jsontext.Value retain numeric precision; interface-held numbers follow encoding/json/v2's float64 decoding semantics. Use raw JSON fields instead of any when opaque numbers must remain exact.

Example:

result, err := tool.Call(context.Background(), jsontext.Value(`{}`))
if err != nil {
	return err
}
fmt.Println(string(result))

func (Tool) Describe

func (tool Tool) Describe() ToolInfo

Describe returns metadata with independent copies of both schema documents. Check Err before registering; invalid tools may have incomplete metadata.

Example: info := tool.Describe(); fmt.Println(info.Description)

func (Tool) Err

func (tool Tool) Err() error

Err reports the first declaration defect, or nil for a usable tool. A zero Tool fails with ErrInvalidTool; callbacks still run on invalid drafts.

Example: if err := tool.Err(); err != nil { return err }

func (Tool) Name

func (tool Tool) Name() string

Name returns the exact declared identifier, even for an invalid declaration. It does not validate or normalize the name.

Example: fmt.Println(tool.Name())

func (Tool) WithAvailability

func (tool Tool) WithAvailability(policy func(context.Context) (bool, error)) Tool

WithAvailability returns a guarded copy without evaluating either policy. Both the existing policy and policy must permit each request, in that order; denial short-circuits. Nil policy records an invalid declaration. Captures remain shared and must be concurrency-safe; the original tool is unchanged.

Example:

guarded := tool.WithAvailability(func(ctx context.Context) (bool, error) {
	return permissions.Allowed(ctx)
})

type ToolBuilder

type ToolBuilder[In, Out any] struct {
	// contains filtered or unexported fields
}

ToolBuilder holds construction-only edits for NewTool. Retaining it does not grant mutation of a published tool. Schemas remain shared immutable dependencies; functions and their captures stay application-owned and must support concurrent use. Schema bytes are copied during publication.

Example:

// status is the application's func(context.Context, mcp.NoInput) (Status, error).
tool := mcp.NewTool("status", status, func(action *mcp.ToolBuilder[mcp.NoInput, Status]) {
	action.Description("Read current status").ReadOnly(true)
})
if err := tool.Err(); err != nil {
	return err
}

func (*ToolBuilder[In, Out]) Available

func (action *ToolBuilder[In, Out]) Available(policy func(context.Context) (bool, error)) *ToolBuilder[In, Out]

Available replaces the request-time capability policy. Omitting it offers the tool to everyone; explicitly passing nil records a sticky declaration error. The callback must be observational and concurrency-safe. It may run for both listing and calling with the same context, and its captures remain shared. Use it for roles or plans, not argument-dependent resource authorization: handlers must still authorize the resources they access. Panics propagate in core and are recovered by protocol adapters, like handler panics.

Example:

action.Available(func(ctx context.Context) (bool, error) {
	return plans.CanExport(ctx)
})

func (*ToolBuilder[In, Out]) CLI

func (action *ToolBuilder[In, Out]) CLI(group, operation string) *ToolBuilder[In, Out]

CLI sets the command namespace and operation without changing Tool.Name. Both must be nonempty protocol-style identifiers; final checks run after all callbacks, so settings can be declared in any order.

Example: action.CLI("telemetry", "traces")

func (*ToolBuilder[In, Out]) Cursor

func (action *ToolBuilder[In, Out]) Cursor(cursor bool) *ToolBuilder[In, Out]

Cursor advertises follow by repeated bounded calls using ordinary arguments. It installs no handler, goroutine, or transport and defaults to false.

Example: action.Cursor(true)

func (*ToolBuilder[In, Out]) Description

func (action *ToolBuilder[In, Out]) Description(text string) *ToolBuilder[In, Out]

Description replaces the human-readable description; empty is permitted. It returns the same construction draft and never edits a published Tool.

Example: action.Description("Look up a document by its identifier")

func (*ToolBuilder[In, Out]) Destructive

func (action *ToolBuilder[In, Out]) Destructive(value bool) *ToolBuilder[In, Out]

Destructive sets the destructive-effects hint, which defaults to true. It is relevant when ReadOnly is false and does not trigger confirmation.

Example: action.Destructive(false)

func (*ToolBuilder[In, Out]) Example

func (action *ToolBuilder[In, Out]) Example(description, input string) *ToolBuilder[In, Out]

Example appends a documented JSON argument object, retaining its own bytes. Final validation checks strict typed decoding only, never contextual schema rules or handlers. Examples are illustrative, not authorization guarantees. Custom JSON unmarshaling methods can run during NewTool publication, so they must be safe at construction time. Their errors become declaration errors. Availability guards calls, not these publication-time hooks. Custom methods remain responsible for their own decoding semantics and side effects.

Example: action.Example("Recent traces", `{"limit":10}`)

func (*ToolBuilder[In, Out]) GroupSummary

func (action *ToolBuilder[In, Out]) GroupSummary(summary string) *ToolBuilder[In, Out]

GroupSummary replaces optional group help text. Empty removes the summary; it is presentation only and does not affect the tool description.

Example: action.GroupSummary("Captured telemetry")

func (*ToolBuilder[In, Out]) Idempotent

func (action *ToolBuilder[In, Out]) Idempotent(value bool) *ToolBuilder[In, Out]

Idempotent sets the repeatable-effects hint, which defaults to false. It does not cause retries or deduplication during Call.

Example: action.Idempotent(true)

func (*ToolBuilder[In, Out]) Input

func (action *ToolBuilder[In, Out]) Input(schema validate.Schema[In]) *ToolBuilder[In, Out]

Input replaces the typed input parser and snapshots its object schema. A nil, invalid or nonobject schema retains a sticky construction error. Parse runs once per permitted call. Rule violations wrap ErrInvalidInput; operational Check failures wrap ErrInputSchema and must remain private.

Example: action.Input(validate.Struct[Request]())

func (*ToolBuilder[In, Out]) OpenWorld

func (action *ToolBuilder[In, Out]) OpenWorld(value bool) *ToolBuilder[In, Out]

OpenWorld sets the unbounded-external-interaction hint, defaulting to true. False describes a closed domain, not an enforced network restriction.

Example: action.OpenWorld(false)

func (*ToolBuilder[In, Out]) Output

func (action *ToolBuilder[In, Out]) Output(schema validate.Schema[Out]) *ToolBuilder[In, Out]

Output replaces the published object schema without adding output parsing. Only JSON-expressible rules reach the adapter; normalizers and custom Check functions are not executed on output. Invalid settings retain a sticky error.

Example: action.Output(validate.Struct[Result]())

func (*ToolBuilder[In, Out]) Positional

func (action *ToolBuilder[In, Out]) Positional(names ...string) *ToolBuilder[In, Out]

Positional replaces the ordered input property names and copies the slice. Names must be unique properties of the final input schema; empty clears them.

Example: action.Positional("id")

func (*ToolBuilder[In, Out]) ReadOnly

func (action *ToolBuilder[In, Out]) ReadOnly(value bool) *ToolBuilder[In, Out]

ReadOnly sets the nonmutating hint, which defaults to false. Hints are descriptive and never enforce authorization or execution policy.

Example: action.ReadOnly(true)

func (*ToolBuilder[In, Out]) Role

func (action *ToolBuilder[In, Out]) Role(role string) *ToolBuilder[In, Out]

Role labels optional diagnostic meaning for clients. Accepted values are identity, health, routes, traces, trace, logs, metrics, or empty to omit it. It does not rename the command, register a shortcut, authorize a call, or certify that the handler is read-only; those remain separate responsibilities.

Example: action.Role("health")

Example

ExampleToolBuilder_Role publishes diagnostic meaning without adding a command alias or granting access. The independent availability policy still denies use.

package main

import (
	"context"
	"errors"
	"fmt"

	"arkoren.dev/core/mcp"
)

func main() {
	tool := mcp.NewTool("service.health", func(context.Context, mcp.NoInput) (mcp.NoInput, error) {
		return mcp.NoInput{}, nil
	}, func(action *mcp.ToolBuilder[mcp.NoInput, mcp.NoInput]) {
		action.CLI("service", "observe").Role("health")
		action.Available(func(context.Context) (bool, error) {
			return false, nil
		})
	})

	if err := tool.Err(); err != nil {
		fmt.Println(err)

		return
	}

	fmt.Println(tool.Describe().CLI.Role)

	_, err := tool.Call(context.Background(), nil)
	fmt.Println(errors.Is(err, mcp.ErrUnavailable))
}
Output:
health
true

type ToolBuilderFunc

type ToolBuilderFunc[In, Out any] = func(*ToolBuilder[In, Out])

ToolBuilderFunc configures a single construction draft synchronously. NewTool invokes callbacks once in order; nil entries panic as caller misuse.

Example:

policy := mcp.ToolBuilderFunc[mcp.NoInput, mcp.NoInput](
	func(action *mcp.ToolBuilder[mcp.NoInput, mcp.NoInput]) {
		action.ReadOnly(true)
	},
)
tool := mcp.NewTool("status", func(context.Context, mcp.NoInput) (mcp.NoInput, error) {
	return mcp.NoInput{}, nil
}, policy)
if err := tool.Err(); err != nil {
	return err
}
fmt.Println(tool.Name())
Example

ExampleToolBuilderFunc uses the public callback alias directly at publication. The resulting tool owns the captured settings, not the construction draft.

package main

import (
	"context"
	"fmt"

	"arkoren.dev/core/mcp"
)

func main() {
	policy := mcp.ToolBuilderFunc[mcp.NoInput, mcp.NoInput](
		func(action *mcp.ToolBuilder[mcp.NoInput, mcp.NoInput]) {
			action.ReadOnly(true)
		},
	)

	tool := mcp.NewTool("status", func(context.Context, mcp.NoInput) (mcp.NoInput, error) {
		return mcp.NoInput{}, nil
	}, policy)

	if err := tool.Err(); err != nil {
		fmt.Println(err)

		return
	}

	fmt.Println(tool.Name(), tool.Describe().Annotations.ReadOnly)
}
Output:
status true

type ToolInfo

type ToolInfo struct {
	// Name is the case-sensitive protocol identifier.
	// Example: fmt.Println(tool.Describe().Name)
	Name string
	// Description explains the tool to a client; it may be empty.
	// Example: fmt.Println(tool.Describe().Description)
	Description string
	// InputSchema is an owned JSON Schema document with object root type.
	// Example: fmt.Println(string(tool.Describe().InputSchema))
	InputSchema jsontext.Value
	// OutputSchema describes the emitted object; adapters validate emitted bytes.
	// Example: fmt.Println(string(tool.Describe().OutputSchema))
	OutputSchema jsontext.Value
	// Annotations contains application-declared hints, not security policy.
	// Example: fmt.Println(tool.Describe().Annotations.ReadOnly)
	Annotations Annotations
	// CLI is optional command presentation, independent of the protocol name.
	// The returned pointer and its containers are detached on every Describe.
	// Example: if info := tool.Describe(); info.CLI != nil { fmt.Println(info.CLI.Group) }
	CLI *CLIInfo
}

ToolInfo is a detached report suitable for protocol registration. Editing its fields or schema bytes cannot change the published Tool.

Example: info := tool.Describe(); fmt.Println(info.Name, string(info.InputSchema))

Jump to

Keyboard shortcuts

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