Summary
reference/mcp/tools-and-resources.md (§ Custom mcpTools opt-in, ~line 109) says:
… Authentication is "is the user logged in" only — finer-grained gating is the method's responsibility.
The "is the user logged in" only part is inaccurate: a custom mcpTool is invocable by a fully anonymous / unauthenticated MCP session. There is no login gate.
Evidence (live, harper@5.1.15, mcp: { application: {} })
Component: a @table @export Widget + a Resource with
static mcpTools = [{ name: 'echo_docs', method: 'echoDocs', inputSchema: { type:'object', properties:{ message:{type:'string'} } } }];
async echoDocs(args, context) { return { echoed: args.message, sawUser: context?.user?.username ?? '(anonymous)' }; }
Driving POST /mcp with no Authorization header:
initialize → 200, session issued.
tools/list → returns echo_docs (the auto verb tools get_widget/search_widget are RBAC-hidden from the anonymous user).
tools/call echo_docs → 200, {"echoed":"…","sawUser":""} — the method executed with an empty user.
This matches the implementation: dist/components/mcp/tools/application.js:646 registers custom tools with visibleTo: () => true, the route mounts { after: 'authentication' } with no auth requirement (dist/components/mcp/index.js:110), and there is no 401/WWW-Authenticate path anywhere in components/mcp/. The application.d.ts comment ("the MCP layer does not invent new ACLs for these") is the accurate description.
Suggested wording
Custom mcpTools are exposed to any MCP session, including anonymous / unauthenticated ones — the MCP layer performs no authentication or ACL check for them. RBAC is entirely the method's responsibility (the method still runs through the transactional() envelope, so per-record allow* predicates and audit logging apply). To restrict a tool to logged-in users, the method must enforce that itself.
Note: there is a related server-behaviour question — initialize advertises resources.subscribe: true while ~line 123 says "no resources/subscribe in v1" — filed separately as HarperFast/harper#1611.
Summary
reference/mcp/tools-and-resources.md(§ CustommcpToolsopt-in, ~line 109) says:The "is the user logged in" only part is inaccurate: a custom
mcpToolis invocable by a fully anonymous / unauthenticated MCP session. There is no login gate.Evidence (live,
harper@5.1.15,mcp: { application: {} })Component: a
@table @export Widget+ a Resource withDriving
POST /mcpwith noAuthorizationheader:initialize→ 200, session issued.tools/list→ returnsecho_docs(the auto verb toolsget_widget/search_widgetare RBAC-hidden from the anonymous user).tools/call echo_docs→ 200,{"echoed":"…","sawUser":""}— the method executed with an empty user.This matches the implementation:
dist/components/mcp/tools/application.js:646registers custom tools withvisibleTo: () => true, the route mounts{ after: 'authentication' }with no auth requirement (dist/components/mcp/index.js:110), and there is no401/WWW-Authenticatepath anywhere incomponents/mcp/. Theapplication.d.tscomment ("the MCP layer does not invent new ACLs for these") is the accurate description.Suggested wording