Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ require (
go.lumeweb.com/mcpplane v0.0.0-20260912095121-3753dd085aa8
go.lumeweb.com/oauth v0.1.6
go.lumeweb.com/opmesh v0.0.0-20260907112428-ade506e64ae9
go.lumeweb.com/pinner v0.0.0-20260912164012-1fb1c2629a68
go.lumeweb.com/pinner v0.0.0-20260912182741-cd1c1d950ad1
go.lumeweb.com/portal-sdk v0.1.72
go.lumeweb.com/queryutil v0.3.19
go.lumeweb.com/tunneler v0.0.0-20260907123602-56944ca7aeae
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -753,8 +753,10 @@ go.lumeweb.com/oauth v0.1.6 h1:6c6LrXxMwx5klbq3OshzXjkGwvYVjAQhjX2FxCAgqqg=
go.lumeweb.com/oauth v0.1.6/go.mod h1:Bfdxi1gkv+Ypj9yj9uOSmKnbZX8C7q7Pyn1OdPyuCbM=
go.lumeweb.com/opmesh v0.0.0-20260907112428-ade506e64ae9 h1:kb2adBZ8Ed11yuXoQw2I8LfmbPjpPkc0NPwyShKJ5KE=
go.lumeweb.com/opmesh v0.0.0-20260907112428-ade506e64ae9/go.mod h1:DbO27Lr6pBSzC+HogQMNNVEZMvkAHYwckL7i79aTETM=
go.lumeweb.com/pinner v0.0.0-20260912164012-1fb1c2629a68 h1:mdevpaq9foPQD4usfy88eUxQRqGWOF6WU8ATjDhtL2E=
go.lumeweb.com/pinner v0.0.0-20260912164012-1fb1c2629a68/go.mod h1:ni3gEeGNpTZ5xOwtO9p87G0yChjaEeSTqVId4n2b5pY=
go.lumeweb.com/pinner v0.0.0-20260912180402-b6294ce18ac9 h1:vs9Qyq/fxv2Bm11ZFBZXr0GMpTZWQWIyimAkTAc00SM=
go.lumeweb.com/pinner v0.0.0-20260912180402-b6294ce18ac9/go.mod h1:ni3gEeGNpTZ5xOwtO9p87G0yChjaEeSTqVId4n2b5pY=
go.lumeweb.com/pinner v0.0.0-20260912182741-cd1c1d950ad1 h1:HTSN3qZRHKd25UIni+crbBUTJrP9jwqYD6aHo+M91q4=
go.lumeweb.com/pinner v0.0.0-20260912182741-cd1c1d950ad1/go.mod h1:ni3gEeGNpTZ5xOwtO9p87G0yChjaEeSTqVId4n2b5pY=
go.lumeweb.com/portal v0.5.1 h1:l28uCNFmT+VewwRGhFwlmonYEwLxQfRh06hm7n0SQr8=
go.lumeweb.com/portal v0.5.1/go.mod h1:JXy/eHHlUdXMsPbXSbRyX2ZM7s/OGNfSNsXH7XRYWCk=
go.lumeweb.com/portal-middleware v0.3.7 h1:kq4SZq4T/uhauqHehw2JaTbNJpPpE8/EQAC3R737H7c=
Expand Down
7 changes: 4 additions & 3 deletions internal/mcp/adapter_tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"go.lumeweb.com/mcpplane/session"
mcptransfer "go.lumeweb.com/mcpplane/transfer"
oauthlib "go.lumeweb.com/oauth"
"go.lumeweb.com/pinner-cli/internal/mcp/apps"
"go.lumeweb.com/pinner-cli/internal/mcp/auth"
"go.lumeweb.com/pinner-cli/internal/mcp/core/handoff"
"go.lumeweb.com/pinner-cli/internal/mcp/core/ieo"
Expand Down Expand Up @@ -503,7 +504,7 @@ func serveHTTP(ctx context.Context, srv *sdk.Server, card *ServerCard, cmd *cli.
// already live; this covers the listing-level static default that most hosts
// actually use to build the iframe CSP.
if curlUpload != nil {
if err := sdk.SetAppResourceConnectDomains(srv, upload.IPFSUploadAppURI, curlUpload.ConnectOrigins()); err != nil {
if err := sdk.SetAppResourceConnectDomains(srv, apps.UploadManagerAppURI(), curlUpload.ConnectOrigins()); err != nil {
return err
}
}
Expand Down Expand Up @@ -734,7 +735,7 @@ func serveHTTP(ctx context.Context, srv *sdk.Server, card *ServerCard, cmd *cli.
// and captured HTML can be collected when this server is discarded,
// rather than accumulating for the process lifetime.
if curlUpload != nil {
_ = sdk.UnregisterAppResource(srv, upload.IPFSUploadAppURI)
_ = sdk.UnregisterAppResource(srv, apps.UploadManagerAppURI())
}
if vaultUpload != nil {
_ = sdk.UnregisterAppResource(srv, upload.VaultUploadAppURI)
Expand Down Expand Up @@ -802,7 +803,7 @@ func serveHTTP(ctx context.Context, srv *sdk.Server, card *ServerCard, cmd *cli.
// public origin, so re-bake the list-level connectDomains (last write
// wins over the base block above) to that tunnel origin.
if curlUpload != nil {
if err := sdk.SetAppResourceConnectDomains(srv, upload.IPFSUploadAppURI, curlUpload.ConnectOrigins()); err != nil {
if err := sdk.SetAppResourceConnectDomains(srv, apps.UploadManagerAppURI(), curlUpload.ConnectOrigins()); err != nil {
shutdown(context.Background())
return err
}
Expand Down
11 changes: 5 additions & 6 deletions internal/mcp/annotations_wire_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,11 @@ func TestToolDetailAlwaysEmitsBoolHints(t *testing.T) {
}

// TestIPFSAppHelpersWireContract pins the directory requirements on the
// app-only upload helpers: boolean annotations, openai/toolInvocation labels,
// and non-empty descriptions on every params property.
// app-only upload helpers: boolean annotations and non-empty descriptions on
// every params property. The helper DESCRIPTORS are module-owned
// (go.lumeweb.com/pinner/mcp/appswire.UploadManagerHelpers), so the
// openai/toolInvocation invocation labels the CLI-local copies used to carry
// are not part of this contract anymore.
func TestIPFSAppHelpersWireContract(t *testing.T) {
srv, _ := buildIPFSUploadAppServer(t)
cs := connectOfficialClient(t, srv)
Expand All @@ -316,10 +319,6 @@ func TestIPFSAppHelpersWireContract(t *testing.T) {
tool, ok := byName[name]
require.True(t, ok, "%s registered on the wire", name)
requireBoolHints(t, tool)
invocation, ok := tool.Meta["openai/toolInvocation"].(map[string]any)
require.True(t, ok, "%s: openai/toolInvocation metadata present", name)
require.NotEmpty(t, invocation["invoking"], "%s: present-tense invoking label", name)
require.NotEmpty(t, invocation["invoked"], "%s: past-tense invoked label", name)
}

// Every property of both helpers' input schemas carries a description.
Expand Down
63 changes: 63 additions & 0 deletions internal/mcp/apps/appswire_install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package apps

// This file adapts the shared module installers
// (go.lumeweb.com/pinner/mcp/appswire) onto the CLI's process-global registry
// adapter: appswire's InstallContext needs the *AppRegistry instance and a
// view-only render func — both composition-owned facts this package holds.

import (
mcpapps "go.lumeweb.com/mcpplane/apps"
sdk "go.lumeweb.com/mcpplane/sdk"
mcptransfer "go.lumeweb.com/mcpplane/transfer"

"go.lumeweb.com/pinner/canvas"
"go.lumeweb.com/pinner/mcp/appswire"

"go.lumeweb.com/pinner-cli/internal/mcpapp"
)

// GlobalRegistry exposes the process-global app registry so shared module
// installers write view + tool→view state into the SAME registry this
// package's registration API wraps (never a sibling instance that would split
// the association state).
func GlobalRegistry() *mcpapps.AppRegistry { return registry }

// RenderAppView adapts the CLI's RenderAppDoc to appswire's view-only
// RenderFunc shape. The document title is the shared table's ResourceTitle —
// the same canonical title this package's per-view render functions (and the
// hosted composition root) pass through, so CLI documents stay byte-identical
// across seams. An unknown view defers to RenderAppDoc's loud panic semantics.
func RenderAppView(view canvas.View) string {
for _, spec := range appswire.All() {
if spec.View == view {
return mcpapp.RenderAppDoc(view, spec.ResourceTitle)
}
}
return mcpapp.RenderAppDoc(view, string(view))
}

// UploadManagerAppURI returns the shared table's ui:// resource URI for the
// Upload to IPFS view — the single URI every connectDomains / unregister /
// resource-lookup call site uses, so the wire URI cannot drift from the table
// the launcher descriptor and view installer resolve against. The empty return
// covers a table that dropped the row (a build-time divergence).
func UploadManagerAppURI() string {
v, ok := appswire.SpecForLauncher(appswire.LauncherUploadManager)
if !ok {
return ""
}
return v.URI
}

// InstallUploadManagerApp wires the shared Upload to IPFS app seam
// (appswire.UploadManagerInstaller): the module owns the view assembly, the
// CSP connectDomains from the coordinator's origins, and the submit/status
// helper tools; the CLI supplies the server, catalog, registry, and renderer.
func InstallUploadManagerApp(srv *sdk.Server, catalog AppCatalog, hp *mcptransfer.Upload) error {
return appswire.UploadManagerInstaller(hp)(appswire.InstallContext{
Server: srv,
Catalog: catalog,
Registry: registry,
Render: RenderAppView,
})
}
38 changes: 0 additions & 38 deletions internal/mcp/core/transfer/presign_ttl.go

This file was deleted.

68 changes: 0 additions & 68 deletions internal/mcp/core/transfer/presign_ttl_test.go

This file was deleted.

14 changes: 8 additions & 6 deletions internal/mcp/core/transfer/upload_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (

"github.com/invopop/jsonschema"

pinnertransfer "go.lumeweb.com/pinner/transfer"

"go.lumeweb.com/pinner-cli/internal/mcp/core/ieo"
"go.lumeweb.com/pinner-cli/internal/mcp/mintcontract"
"go.lumeweb.com/pinner-cli/internal/mcp/schematext"
Expand Down Expand Up @@ -257,12 +259,12 @@ func newUploadFileDescriptor(features hostenv.FeatureSet, coLocated, tunnelOpenA
if name == "" {
name = DefaultUploadName
}
// The ONE shared presign-TTL parser (ParsePresignTTL below in
// this package): empty → default, non-positive → default,
// unparseable → the stable `invalid ttl "..."` wording. The
// coordinator-side clamp inside Prepare remains as defense in
// depth only.
ttl, terr := ParsePresignTTL(in.TTL)
// The ONE canonical presign-TTL parser
// (go.lumeweb.com/pinner/transfer.ParsePresignTTL): empty →
// default, non-positive → default, unparseable → the stable
// `invalid ttl "..."` wording. The coordinator-side clamp
// inside Prepare remains as defense in depth only.
ttl, terr := pinnertransfer.ParsePresignTTL(in.TTL)
if terr != nil {
return model.ToolResult{}, terr
}
Expand Down
4 changes: 3 additions & 1 deletion internal/mcp/custom_registration_inventory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import (
oobpkg "go.lumeweb.com/pinner-cli/internal/mcp/oob"
uploadpkg "go.lumeweb.com/pinner-cli/internal/mcp/upload"
vaultpkg "go.lumeweb.com/pinner-cli/internal/mcp/vault"

"go.lumeweb.com/pinner/mcp/appswire"
)

// stubUploadExec is a minimal-but-real UploadHandler (the shared shape behind
Expand Down Expand Up @@ -280,7 +282,7 @@ func openLauncherToolNames(t *testing.T) []string {
return []string{
apps.OpenPinCreatorToolName,
downloadpkg.OpenPinListToolName,
uploadpkg.OpenUploadManagerToolName,
appswire.LauncherUploadManager,
uploadpkg.OpenVaultManagerToolName,
auth.OpenSSOSigninToolName,
auth.OpenAccountToolName,
Expand Down
22 changes: 19 additions & 3 deletions internal/mcp/custom_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"go.lumeweb.com/pinner-cli/internal/mcp/toolforge"
"go.lumeweb.com/pinner-cli/internal/mcp/upload"
"go.lumeweb.com/pinner-cli/internal/mcp/vault"
pinnermcp "go.lumeweb.com/pinner/mcp"
"go.lumeweb.com/pinner/mcp/appswire"
)

Expand Down Expand Up @@ -583,8 +584,17 @@ func collectServerExtensions(deps customToolDeps) (*MaterializationPlan, error)
uploadFileSpec.roles = append(uploadFileSpec.roles, roleCatalogSearch)
}
if transfer.SinkDropReachable(deps.curlUpload != nil, deps.tunnelOpenAI) {
reg.add(appLauncherSpec(upload.NewOpenUploadManagerDescriptor(deps.curlUpload), func(srv *sdk.Server, catalog apps.AppCatalog) error {
return upload.RegisterIPFSUploadApp(srv, catalog, deps.curlUpload)
// Shared seam (go.lumeweb.com/pinner/mcp/appswire): the launcher
// descriptor and the dependency-bound installer are module-owned;
// the CLI supplies the render func and the process-global app
// registry. A descriptor build error is a wiring bug at this seam
// (same policy as addLauncher) and fails the assembly hard.
desc, err := appswire.UploadManagerDescriptor(deps.curlUpload)
if err != nil {
return nil, err
}
reg.add(appLauncherSpec(desc, func(srv *sdk.Server, catalog apps.AppCatalog) error {
return apps.InstallUploadManagerApp(srv, catalog, deps.curlUpload)
}))
}
reg.add(uploadFileSpec)
Expand All @@ -593,8 +603,14 @@ func collectServerExtensions(deps customToolDeps) (*MaterializationPlan, error)
// --- Async upload management tools (upload_status / upload_cancel / upload_list) ---
// These are search-only: the agent_guide upload flow names upload_status
// as a step, so an agent following the guide discovers it via search_tools.
// The descriptors are module-owned (go.lumeweb.com/pinner/mcp), so this
// surface cannot drift from the hosted assembly's. Descriptors without
// MCPTargets get the universal Fallback wrap in ToolCatalog.Add.
// upload_list is registered unconditionally here: this CLI server is a
// single-user, same-process manager — the per-principal-manager case the
// module's AsyncUploadList opt-in reserves the enumerator for.
if uploadOn && opts.uploadTasks != nil {
for _, desc := range upload.NewAsyncUploadTools(opts.uploadTasks) {
for _, desc := range pinnermcp.NewAsyncUploadTools(opts.uploadTasks) {
reg.add(searchableOnly(desc))
}
}
Expand Down
3 changes: 1 addition & 2 deletions internal/mcp/hosted.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"go.lumeweb.com/mcpplane/transfer"
"go.lumeweb.com/pinner-cli/internal/mcp/apps"
"go.lumeweb.com/pinner-cli/internal/mcp/core/ieo"
"go.lumeweb.com/pinner-cli/internal/mcp/upload"
)

// httpsOriginOf returns the exact HTTPS origin (scheme://host[:port], no
Expand Down Expand Up @@ -228,7 +227,7 @@ func buildHostedServer(cfg HostedServerConfig) (*sdk.Server, *ToolCatalog, *Host
// coordinator's origin to that resource's connectDomains (the resource URI is
// otherwise mounted as a static default that most hosts use for their CSP).
if hostedTransfer != nil && hostedTransfer.Upload != nil {
if err := sdk.SetAppResourceConnectDomains(srv, upload.IPFSUploadAppURI, hostedTransfer.Upload.ConnectOrigins()); err != nil {
if err := sdk.SetAppResourceConnectDomains(srv, apps.UploadManagerAppURI(), hostedTransfer.Upload.ConnectOrigins()); err != nil {
return nil, nil, nil, err
}
}
Expand Down
5 changes: 2 additions & 3 deletions internal/mcp/hosted_concurrency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"go.lumeweb.com/mcpplane/sdk"
mcptransfer "go.lumeweb.com/mcpplane/transfer"
"go.lumeweb.com/pinner-cli/internal/mcp/apps"
"go.lumeweb.com/pinner-cli/internal/mcp/upload"
)

// TestBuildHostedServerConcurrentViewDomainAssemblies is the concurrency
Expand Down Expand Up @@ -68,7 +67,7 @@ func TestBuildHostedServerConcurrentViewDomainAssemblies(t *testing.T) {
require.NoErrorf(t, err, "assembly %d list resources", i)
var uploadView *mcp.Resource
for _, r := range res.Resources {
if r.URI == upload.IPFSUploadAppURI {
if r.URI == apps.UploadManagerAppURI() {
uploadView = r
break
}
Expand Down Expand Up @@ -128,7 +127,7 @@ func TestBuildHostedServerEmptyOriginSerializedAgainstResolver(t *testing.T) {
res, err := cs.ListResources(context.Background(), nil)
require.NoErrorf(t, err, "assembly %d list resources (%s)", i, what)
for _, r := range res.Resources {
if r.URI == upload.IPFSUploadAppURI {
if r.URI == apps.UploadManagerAppURI() {
ui, ok := r.Meta["ui"].(map[string]any)
require.Truef(t, ok, "assembly %d upload view carries _meta.ui (%s)", i, what)
return ui
Expand Down
Loading
Loading