portal

package
v1.131.2 Latest Latest
Warning

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

Go to latest
Published: Sep 12, 2026 License: Apache-2.0 Imports: 35 Imported by: 0

Documentation

Overview

Package portal provides the MCP toolkit for saving and managing AI-generated assets (JSX dashboards, HTML reports, SVG charts).

Index

Constants

View Source
const (
	// SaveToolName is the name of the asset-save tool.
	SaveToolName = "save_asset"
	// ManageToolName is the name of the asset-management tool.
	ManageToolName = "manage_asset"
	// ManageTableToolName is the name of the table-registration tool. It is
	// not asset-keyed: it acts on any stored file a reference names, which is
	// why it is its own tool rather than a set of manage_asset actions.
	ManageTableToolName = "manage_table"
	// ManageResourceToolName is the name of the managed-resource write tool
	// (#1487). Like manage_table it is not asset-keyed: a managed resource is
	// its own kind of record, with its own scopes and its own version trail.
	ManageResourceToolName = "manage_resource"
)

Tool names registered by the portal toolkit. Exported because pkg/platform needs the save tool's name to configure provenance harvesting.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	Name            string
	AssetStore      portal.AssetStore
	ShareStore      portal.ShareStore
	VersionStore    portal.VersionStore
	CollectionStore portal.CollectionStore
	ThreadStore     portal.ThreadStore
	S3Client        portal.S3Client
	S3Bucket        string
	S3Prefix        string
	BaseURL         string
	MaxContentSize  int // max asset content size in bytes (0 = no limit)

	// Embedder embeds search queries for the ranked `search` action. When nil
	// or the noop placeholder, search degrades to lexical-only ranking (the
	// store decides via embedding.EmbedForSearch).
	Embedder embedding.Provider

	// CaptureProvenance resolves which calls an asset write was built from
	// (#1320). Injected by the platform, which owns the audit log the capture
	// is read from. Nil leaves an asset recording its session and owner but no
	// calls — what a deployment without audit gets.
	CaptureProvenance portal.ProvenanceCapturer

	// Directory resolves a person's name to their address for the share
	// action (#1280). Nil (no database, so no directory) leaves sharing
	// working by email address only.
	Directory DirectoryReader
}

Config holds configuration for creating a portal toolkit.

type DirectoryReader added in v1.121.0

type DirectoryReader interface {
	List(ctx context.Context, filter user.Filter) ([]user.User, int, error)
}

DirectoryReader looks people up in the known-users directory. The share action only reads it, so this is the whole surface it takes, the same view the portal's share picker takes over the same store; pkg/user.Store implements it.

type RegisterOptions added in v1.126.5

type RegisterOptions struct {
	// Repair asks for a corrected version of the file to be saved and
	// registered when the file cannot be read as a table the way it is stored;
	// without it such a file is refused and the refusal says what is wrong
	// with it.
	Repair bool
	// Follow moves the table onto each new revision or version of the file
	// as it is written; off, the table is pinned to the version it was
	// registered over. It is the resolved choice: the tool defaults it to on
	// when the caller says nothing.
	Follow bool
}

RegisterOptions are the choices a registration is made with.

type ResourceHoldReader added in v1.131.0

type ResourceHoldReader interface {
	ResourceHolds(ctx context.Context, resourceID string) (ResourceHolds, error)
}

ResourceHoldReader answers what still points at a managed resource, so a delete can say what it would break before it breaks it.

It is a capability the toolkit asks for rather than one it implements: the two reverse lookups live in two different layers, and the toolkit must not learn what either of them is.

type ResourceHolds added in v1.131.0

type ResourceHolds struct {
	Assets  int `json:"assets"`
	Prompts int `json:"prompts"`
	// More says a count was cut at the bound rather than being the whole of
	// what points at the file. It is reported rather than left implicit
	// because a short count read as a complete one is the mistake this answer
	// exists to prevent.
	More bool `json:"more,omitempty"`
}

ResourceHolds counts the records still pointing at a managed resource (#1665): assets whose content references it, and prompts that attach it as reference material.

Neither is a foreign key -- deleting the file leaves the row behind so the thing that depended on it reports the material as missing -- which is why the count has to be gathered and put to whoever is deleting, before the delete rather than after it.

A knowledge page is deliberately not counted: the platform refuses an mcp:resource: citation on a shared page, because a resource is visibility-scoped, so no page can point at one.

It is counts and not names because each of those records carries an audience of its own and the person deleting the file is not necessarily in any of them. The portal's used-by panel resolves those audiences and names what its reader may open; this is the part that can be said to anybody who can see the file.

func (ResourceHolds) Any added in v1.131.0

func (h ResourceHolds) Any() bool

Any reports whether anything at all points at the file.

func (ResourceHolds) Describe added in v1.131.0

func (h ResourceHolds) Describe() []string

Describe renders the counts as the phrases a refusal lists, one per kind that has anything, in the order a reader cares about them: what serves the file to people, then what a procedure depends on.

type ResourceWriter added in v1.126.0

type ResourceWriter interface {
	// Create files new content as a managed resource, refusing a scope the
	// caller may not write to.
	Create(ctx context.Context, in resource.NewResource, claims resource.Claims) (*resource.Resource, error)
	// Replace records new content as the resource's next revision, keeping the
	// id, the canonical URI and the filename, and returns the version number
	// the content was recorded as.
	Replace(
		ctx context.Context, id string, up resource.RevisionUpload, claims resource.Claims,
	) (*resource.Resource, int, error)
	// Get reads a resource the caller may see. A replacement resolves the file
	// before it decodes anything, so a reference naming a file that is not
	// there is refused without the payload being read, and so type detection
	// is given the stored filename rather than a name the caller offered --
	// the filename is what a replacement must not change.
	Get(ctx context.Context, id string, claims resource.Claims) (*resource.Resource, error)
	// Locate reads the resource filed at an address, and reports the canonical
	// mcp:// URI that address names whether or not anything is filed there
	// (#1665). Nothing being there is an answer rather than an error: it is
	// what a create-or-replace decides on, and what a lookup of an empty
	// address reports.
	Locate(
		ctx context.Context, addr toolkit.ResourceAddress, claims resource.Claims,
	) (found *resource.Resource, uri string, err error)
	// List returns the resources filed under a folder that the caller may see,
	// newest first, with the total the page was cut from.
	List(
		ctx context.Context, q toolkit.ResourceQuery, claims resource.Claims,
	) (found []resource.Resource, total int, err error)
	// Delete removes a resource and the objects its content lives in,
	// returning the record that was removed. The authority is the authority to
	// change the file, the same rule replacing its content meets.
	Delete(ctx context.Context, id string, claims resource.Claims) (*resource.Resource, error)
}

ResourceWriter writes a managed resource on an agent's behalf (#1487).

A managed resource is the only kind of file an asset can reference, so without a writer the data half of a referencing asset can only be refreshed by a person at an upload form. This is the capability that makes an asset's numbers refreshable by the platform itself.

The acting caller is not a parameter, the same way it is not one on TableRegistrar: both methods take the resource claims this toolkit derives from the call's own identity, so the tool cannot present an identity the REST surface would refuse, and the scope boundary a tool call meets is the one an upload meets.

type TableRegistrar added in v1.125.0

type TableRegistrar interface {
	// Register makes the referenced file queryable, under the options the
	// caller chose for the registration.
	Register(ctx context.Context, reference, connection, tableName string, opts RegisterOptions) (*TableRegistration, error)
	Unregister(ctx context.Context, registrationID string) error
	Tables(ctx context.Context, reference string) ([]TableRegistration, error)
	// DropAssetTables removes every table registered over an asset. It is what
	// a delete calls: the asset is going, and a table over where its file used
	// to be would answer queries from a schema its owner can no longer see.
	// Best-effort by contract -- a delete must not fail because a scratch table
	// could not be dropped.
	DropAssetTables(ctx context.Context, assetID string)
	// DropResourceTables is DropAssetTables for a deleted managed resource
	// (#1665). The REST delete route already reaches this through the store's
	// own delete hook; a tool call does not cross that route, so the toolkit
	// calls it itself and both doors leave the same state behind.
	DropResourceTables(ctx context.Context, resourceID string)
	// FollowAssetTables and FollowResourceTables are what a write of a new
	// version calls (#1536): every following registration over the file is
	// moved onto the version written, and every registration over it is
	// reported -- followed, pinned and now behind, or not movable and why --
	// as the sentences the write's result carries. Best-effort by contract,
	// like the delete: the file changed, and that write succeeded whatever
	// happened to the tables over it.
	FollowAssetTables(ctx context.Context, assetID string, version int) []string
	FollowResourceTables(ctx context.Context, resourceID string, version int) []string
}

TableRegistrar makes a stored CSV readable as a query-engine table (#1327), whichever kind of stored file it is (#1428). The registrar seam satisfies it; the capability is declared here so the toolkit does not depend on the registrar, and does not learn what a managed resource is.

Every method is keyed by the canonical reference a caller already holds -- the string `search` emits and `fetch` dereferences -- rather than by the id of one kind of record. That is what lets one tool serve every kind: the kind travels inside the reference, so there is no per-kind argument and no second tool.

The acting caller is not a parameter. Every method resolves identity from the context exactly as this toolkit does, so the tool cannot present an identity the rest of the platform would refuse, and the connection boundary a tool call meets is the one a registration meets.

type TableRegistration added in v1.125.0

type TableRegistration struct {
	RegistrationID string   `json:"registration_id"`
	Connection     string   `json:"connection"`
	QueryTable     string   `json:"query_table"`
	Columns        []string `json:"columns,omitempty"`
	SampleSQL      string   `json:"sample_sql,omitempty"`
	RegisteredBy   string   `json:"registered_by,omitempty"`
	// Stale means the file has a newer revision or version than the one the
	// table points at, so the rows are the content that was current when it
	// was registered.
	Stale bool `json:"stale"`
	// Follow means the table is moved onto each new revision or version of
	// the file as it is written (#1536); without it the table is pinned to
	// the version it was registered over. FollowError is why the last follow
	// did not move it, empty while the table is where the file is.
	Follow      bool   `json:"follow"`
	FollowError string `json:"follow_error,omitempty"`
	// Repair means the table corrects its file: a new version carrying a
	// defect a reader cannot see past that the platform can correct is saved
	// corrected, as the file's next version, and the table is moved onto that
	// version (#1577). It is the choice made when the table was registered.
	Repair bool `json:"repair"`
	// Repaired says what a correction of the file changed before it could be
	// registered, and is empty when none was needed (#1441). The file itself
	// changed, so the person who asked for the registration is told so.
	Repaired string `json:"repaired,omitempty"`
	// TableChanges is what a replacing registration found about the OTHER
	// tables on the connection after its DROP ran (#1546): one sentence per
	// table that no longer exists. Empty when every other table is still
	// there.
	//
	// It is a change report, not an inventory, and it is named apart from the
	// `tables` a reader queries for that reason (#1666).
	TableChanges []string `json:"table_changes,omitempty"`
}

TableRegistration is one registration as the tool reports it.

type Toolkit

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

Toolkit implements the portal asset toolkit.

func New

func New(cfg Config) *Toolkit

New creates a new portal toolkit.

func (*Toolkit) Close

func (*Toolkit) Close() error

Close releases resources.

func (*Toolkit) Connection

func (*Toolkit) Connection() string

Connection returns the connection name for audit logging.

func (*Toolkit) FollowAssetTables added in v1.126.5

func (t *Toolkit) FollowAssetTables(ctx context.Context, assetID string, version int) []string

FollowAssetTables reports what a new version of an asset did to the tables registered over it. A deployment with no registrar has none.

It is exported for the composition root, which hands it to the script runner: a script's platform.export writes a version through its own store rather than through this toolkit, and reaches the registrar through the toolkit because the registrar is bound here after the runner is built.

func (*Toolkit) FollowResourceTables added in v1.131.0

func (t *Toolkit) FollowResourceTables(ctx context.Context, resourceID string, version int) []string

FollowResourceTables is FollowAssetTables for a replaced managed resource. It is exported for the same reason: a write path assembled before the registrar exists reaches it through the toolkit the registrar is bound onto. An export landing in a managed resource is such a path (#1663).

func (*Toolkit) Kind

func (*Toolkit) Kind() string

Kind returns the toolkit kind.

func (*Toolkit) LinkInsight added in v1.84.0

func (t *Toolkit) LinkInsight(ctx context.Context, threadIDs []string, insightID, actorID, actorEmail string) ([]string, error)

LinkInsight implements the knowledge ThreadLinker bridge with authorization. capture_insight calls this with the thread_ids an insight resolves. The agent surface must not be able to resolve a thread it could not resolve through resolve_thread, so each thread is gated through callerCanActOnThread (the same owns-or-edit / author / admin policy) using the caller identity in ctx. Threads the caller may not moderate, that are missing, empty, or duplicated are skipped and surface to the agent as unlinked_thread_ids. Authorized ids are delegated to the thread store, which performs the link transactionally.

func (*Toolkit) Name

func (t *Toolkit) Name() string

Name returns the toolkit instance name.

func (*Toolkit) PromptInfos added in v1.38.0

func (*Toolkit) PromptInfos() []registry.PromptInfo

PromptInfos returns metadata for prompts registered by the portal toolkit.

func (*Toolkit) RegisterTools

func (t *Toolkit) RegisterTools(s *mcp.Server)

RegisterTools registers save_asset, manage_asset, manage_table, manage_resource and manage_feedback with the MCP server.

func (*Toolkit) SetContentRefs added in v1.126.0

func (t *Toolkit) SetContentRefs(d *assetrefs.Declarer)

SetContentRefs binds the path that declares what an asset's content references (#1474, #1488).

It is installed after construction, like the feedback notifier below, because the reference store and the asset store are assembled with the portal layer. Left unbound, the `references` argument is refused with that explanation rather than accepted and silently dropped.

func (*Toolkit) SetFeedbackNotifications added in v1.114.0

func (t *Toolkit) SetFeedbackNotifications(notifier portal.Notifier, mentions portal.MentionResolver)

SetFeedbackNotifications installs the notification trigger and the mention resolver for agent-authored feedback replies. The composition root calls it once the notification substrate exists, which is later than toolkit construction; like the provider setters it must be called during composition, before the server starts serving.

That substrate (queue, send worker, SMTP) is owned by the HTTP composition root, so a stdio deployment leaves both nil: an agent's reply is stored and its mentions parsed, but nothing is mailed and no mention is recorded. The send worker belongs to a long-lived server rather than to a per-client stdio process, where every concurrent client would run its own.

func (*Toolkit) SetQueryProvider

func (t *Toolkit) SetQueryProvider(provider query.Provider)

SetQueryProvider sets the query execution provider.

func (*Toolkit) SetResourceHolds added in v1.131.0

func (t *Toolkit) SetResourceHolds(r ResourceHoldReader)

SetResourceHolds binds the reader behind the delete warning. Called by the composition root once the asset and prompt layers exist. Without it a delete cannot establish what depends on the file, and says so rather than reporting that nothing does.

func (*Toolkit) SetResourceWriter added in v1.126.0

func (t *Toolkit) SetResourceWriter(w ResourceWriter)

SetResourceWriter binds the writer behind manage_resource. Called by the composition root once the managed-resource layer exists, which is later than toolkit construction; without it the tool reports that the deployment has no managed-resource layer, which is what a deployment with no database or no blob storage can do.

func (*Toolkit) SetSemanticProvider

func (t *Toolkit) SetSemanticProvider(provider semantic.Provider)

SetSemanticProvider sets the semantic metadata provider.

func (*Toolkit) SetTableRegistrar added in v1.125.0

func (t *Toolkit) SetTableRegistrar(reg TableRegistrar)

SetTableRegistrar binds the registrar behind manage_table. Called by the composition root once the Trino toolkit and the registration store exist; without it the tool reports that the deployment cannot register tables, which is what a deployment with no Trino scratch connection can do.

func (*Toolkit) Tools

func (*Toolkit) Tools() []string

Tools returns the list of tool names provided by this toolkit.

Jump to

Keyboard shortcuts

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