mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
- Rewrite package doc, struct comments, log line, and route-registration comment so they describe the MCP server as a first-class feature rather than a pre-release prototype. The "Usage" strings on MCPCommands / MCPServeCommand were updated separately and stay unchanged. - Drop "in this prototype" from the edition-advisory warning emitted by list_config_keys; the message now reads "edition filtering is advisory; results come from the current <edition> build metadata". - Rewrite internal/mcp/README.md: new "Scope" section with in-scope/out-of-scope invariants, corrected authorization summary (admin + API client roles + manager in Pro/Portal, anonymous in public mode for the registered read-only tools), and a clearer "Current Capabilities" block. Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
16fc758f54
commit
4f14ed90c0
8 changed files with 30 additions and 29 deletions
|
|
@ -53,7 +53,7 @@ func ServeMCP(router *gin.RouterGroup) {
|
|||
// 401/403/429 response if the session is invalid. In public mode,
|
||||
// Session() returns the default public session so the currently
|
||||
// registered read-only tools are reachable without a token — this
|
||||
// is intentional so the prototype can be showcased on
|
||||
// is intentional so the MCP server can be showcased on
|
||||
// demo.photoprism.app. Any future tool that touches per-user state,
|
||||
// the database, or mutates anything MUST NOT be registered on this
|
||||
// server without an additional per-tool check (see internal/mcp
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func TestServeMCP(t *testing.T) {
|
|||
// the currently registered MCP tools only surface static reference
|
||||
// data. Anonymous callers must therefore be able to initialize an
|
||||
// MCP session and call both tools without a token — this is what
|
||||
// lets the prototype run on demo.photoprism.app. Guard the policy
|
||||
// lets the MCP server run on demo.photoprism.app. Guard the policy
|
||||
// here so it regresses loudly if a future change tightens it.
|
||||
app, router, _ := NewApiTest()
|
||||
conf := prepareMCPTest(t)
|
||||
|
|
|
|||
|
|
@ -11,19 +11,19 @@ import (
|
|||
internalmcp "github.com/photoprism/photoprism/internal/mcp"
|
||||
)
|
||||
|
||||
// MCPCommands configures the MCP prototype command group.
|
||||
// MCPCommands configures the Model Context Protocol (MCP) command group.
|
||||
var MCPCommands = &cli.Command{
|
||||
Name: "mcp",
|
||||
Usage: "Runs the internal read-only MCP prototype",
|
||||
Usage: "Shows the Model Context Protocol (MCP) server subcommands",
|
||||
Subcommands: []*cli.Command{
|
||||
MCPServeCommand,
|
||||
},
|
||||
}
|
||||
|
||||
// MCPServeCommand starts the MCP prototype over stdio.
|
||||
// MCPServeCommand starts the MCP server over the stdio transport.
|
||||
var MCPServeCommand = &cli.Command{
|
||||
Name: "serve",
|
||||
Usage: "Starts the internal read-only MCP prototype over stdio",
|
||||
Usage: "Starts the internal MCP server via stdio for development and testing",
|
||||
Action: mcpServeAction,
|
||||
}
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ func mcpServeAction(ctx *cli.Context) error {
|
|||
}
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
||||
|
||||
logger.Info("starting mcp prototype", "transport", "stdio", "tools", 2, "resources", 2)
|
||||
logger.Info("starting mcp server", "transport", "stdio", "tools", 2, "resources", 2)
|
||||
|
||||
edition := mcpAppMetadata(ctx, "Edition", "unknown")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
## PhotoPrism MCP Prototype
|
||||
## PhotoPrism MCP Server
|
||||
|
||||
**Last Updated:** April 13, 2026
|
||||
|
||||
> See `specs/platform/mcp.md` for the canonical specification, including the rationale for the user-access policy and the role/grant matrix per edition.
|
||||
|
||||
### Current capabilities
|
||||
### Current Capabilities
|
||||
|
||||
- **Transports:**
|
||||
- CLI: `photoprism mcp serve` (stdio, no auth)
|
||||
- CLI: `photoprism mcp serve` (stdio, no auth; development and testing)
|
||||
- HTTP: `POST/GET/DELETE /api/v1/mcp` (Streamable HTTP, authenticated)
|
||||
- **Authentication:** HTTP endpoint requires admin role via `ResourceMCP` ACL
|
||||
- **Feature gate:** HTTP endpoint requires `--experimental` flag
|
||||
- **Authorization:** HTTP endpoint enforces the `ResourceMCP` ACL (admin plus the API client roles in every edition, manager in Pro/Portal); anonymous access is permitted in public mode for the currently registered read-only tools.
|
||||
- **Feature gate:** HTTP endpoint requires the `--experimental` flag; absent that flag `/api/v1/mcp` returns 404.
|
||||
- Read-only resources:
|
||||
- `photoprism://config-options`
|
||||
- `photoprism://search-filters`
|
||||
|
|
@ -27,21 +27,20 @@
|
|||
| `internal/commands/mcp.go` | CLI command (`photoprism mcp serve`) using stdio transport |
|
||||
| `internal/auth/acl/` | `ResourceMCP` constant and ACL grant rules (`GrantFullAccess` for admin; `GrantSearchAll` for manager in Pro/Portal and for the API client roles: client, instance, service, portal) |
|
||||
|
||||
### Goals and non-goals
|
||||
### Scope
|
||||
|
||||
Goals:
|
||||
In scope for the current server:
|
||||
|
||||
- Prove the MCP model works end-to-end inside the PhotoPrism codebase
|
||||
- Reuse internal reference data instead of maintaining a separate copy
|
||||
- Keep outputs concise enough for LLM use
|
||||
- Provide authenticated remote access via Streamable HTTP transport
|
||||
- Reuse existing internal reference data (`config.Flags`, `config.OptionsReportSections`, `form.Report(&form.SearchPhotos{})`) instead of maintaining a parallel dataset.
|
||||
- Keep outputs compact enough for LLM consumption.
|
||||
- Authenticated remote access via the Streamable HTTP transport, plus a stdio transport for local development and testing.
|
||||
|
||||
Non-Goals:
|
||||
Out of scope for the current server (must not regress without additional per-tool gates):
|
||||
|
||||
- No write-capable tools
|
||||
- No direct database access
|
||||
- No live PhotoPrism instance or API queries
|
||||
- No non-admin access
|
||||
- Write-capable tools.
|
||||
- Direct database access.
|
||||
- Live PhotoPrism instance or API queries.
|
||||
- Per-user state (albums, photos, sessions, settings).
|
||||
|
||||
### Internal data sources
|
||||
|
||||
|
|
@ -221,7 +220,7 @@ Whichever path you pick, **add a test in `internal/mcp/server_test.go` that fail
|
|||
|
||||
### How Users Get Access
|
||||
|
||||
Regular user accounts (`RoleUser`, `RoleViewer`, etc.) are intentionally **not** in the `ResourceMCP` ACL. Regular users typically don't have shell access to the server, so they can't run the CLI commands themselves — and the prototype's tools only return static reference data, so there's no per-user information to authorize against. Access is therefore granted through admin-issued client tokens.
|
||||
Regular user accounts (`RoleUser`, `RoleViewer`, etc.) are intentionally **not** in the `ResourceMCP` ACL. Regular users typically don't have shell access to the server, so they can't run the CLI commands themselves — and the currently registered tools only return static reference data, so there's no per-user information to authorize against. Access is therefore granted through admin-issued client tokens.
|
||||
|
||||
To onboard a user (or a CI job, IDE, etc.), an administrator runs the following on the PhotoPrism server:
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const (
|
|||
searchFiltersURI = "photoprism://search-filters"
|
||||
)
|
||||
|
||||
// Dataset caches the static MCP prototype data returned by resources and tools.
|
||||
// Dataset caches the static MCP data returned by resources and tools.
|
||||
type Dataset struct {
|
||||
CurrentEdition string
|
||||
ConfigOptions []ConfigOption
|
||||
|
|
@ -50,7 +50,7 @@ type SearchFiltersResource struct {
|
|||
Items []SearchFilter `json:"items"`
|
||||
}
|
||||
|
||||
// NewDataset builds the static MCP prototype dataset for the current build.
|
||||
// NewDataset builds the static MCP dataset for the current build.
|
||||
func NewDataset(currentEdition string) *Dataset {
|
||||
return &Dataset{
|
||||
CurrentEdition: normalizeEdition(currentEdition),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
/*
|
||||
Package mcp provides a small read-only MCP prototype for internal evaluation.
|
||||
Package mcp implements the PhotoPrism Model Context Protocol (MCP) server,
|
||||
which exposes read-only reference data (config options and search filters)
|
||||
to MCP-aware clients over both stdio and authenticated Streamable HTTP.
|
||||
|
||||
Copyright (c) 2018 - 2026 PhotoPrism UG. All rights reserved.
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ func listConfigKeys(_ context.Context, _ *sdkmcp.CallToolRequest, input ListConf
|
|||
|
||||
if edition != "all" && edition != data.CurrentEdition {
|
||||
result.Warnings = []string{
|
||||
fmt.Sprintf("edition filtering is advisory in this prototype; results come from the current %s build metadata", data.CurrentEdition),
|
||||
fmt.Sprintf("edition filtering is advisory; results come from the current %s build metadata", data.CurrentEdition),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ func registerRoutes(router *gin.Engine, conf *config.Config) {
|
|||
api.ClusterMetrics(APIv1)
|
||||
api.ClusterHealth(APIv1)
|
||||
|
||||
// MCP Prototype.
|
||||
// Model Context Protocol (MCP).
|
||||
api.ServeMCP(APIv1)
|
||||
|
||||
// Technical Endpoints.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue