enhancement
major
minor
defect
critical
major
minor
major
#30252
tl-ai-mcp-server: tools/list is not filtered by the caller's tool access, and /mcp/ with a trailing slash answers 404
tl-ai-mcp-server 8.0.0-alpha12.819, application E-Home (haumacher/ehome #129).
1. Tool list per caller
tools/list is answered from the global tool set of the MCP Java SDK, the same for every authenticated caller (the article ai/security/mcp-endpoint names it under "What it does not do"). In E-Home an ehome.Viewer holds model.read and data.read only, yet the viewer's MCP client lists the writing tools of data.write (updateObject, createObject, deleteObject, editText) and is refused only when it calls one. An agent plans with tools it cannot use and learns that only by failing; the list also discloses what other roles may do.
Expected: tools/list contains the tools the caller's tool access admits (as the chat offers only the permitted tools, ToolPermissionFilter), or the endpoint filters the list per session.
2. Trailing slash
<context>/mcp/ answers 404, <context>/mcp works: the transport (HttpServletStreamableServerTransportProvider) matches the end of the path against /mcp. MCP clients are configured by hand with a URL; a slash is a common way to write it.
Expected: both forms reach the endpoint (or the slash form redirects).
Workaround in E-Home
None for the list (the test asserts the refusal of a call); doc/ai.md names the address without the slash.
Solution
- MCP Java SDK 2.1.0: The SDK version (mcp.sdk.version in tl-bom) is raised from 1.1.5 to 2.1.0, for mcp-core and mcp-json-jackson3. From 2.1.0 on, the server builder accepts per-request filters for the tool list (addToolFilter); 1.x answers tools/list from the global tool set without a hook. The MCP client of tl-ai-core and the resources of tl-ai-mcp-server use the builders of the 2.x schema classes.
- Tool list per caller: MCPServerService registers a tool filter (McpToolAdapter.visibilityFilter) that shows a published tool only if it passes ToolPermissionFilter for the caller's background run (ToolContext.forBackgroundRun) - the same gate that authorizes a tools/call, background allowlist included, so the list and the call cannot disagree. The filter runs on the request thread (the server is built with immediateExecution(true)), where McpAuthenticationFilter has established the caller. A request without an account and a failing check hide the tool instead of failing the whole listing.
- Trailing slash: MCPTransportServlet serves <context>/mcp/ like <context>/mcp (the request reaches the transport without the trailing slash); other paths below the endpoint stay unserved. No redirect: not every client repeats a POST body and its Authorization header after one.
- Sessions: The session sweep SDK 2.x performs by default is switched off: it removes a session from the transport without notice, so the session's owner would stay registered (McpSessionPrincipals). McpSessionKeeper continues to ping, remove idle sessions and release their owners.
- MCP client: The input schema of a tool of an external MCP server reaches the model with all its keywords (e.g. $defs); before, only type, properties, required and additionalProperties: false were kept. A missing type is still object, missing properties an empty object.
- Resource templates: A configured title of a ConfigurableResourceTemplate is published as the template's title; it overwrote the template's name before.
- The article ai/security/mcp-endpoint, the boot log of MCPServerService and the documentation of McpToolAdapter and of exposed-tools describe the filtered list; the article names both forms of the endpoint URL.
Tests
- TestMcpServerProtocol (over the real transport): <context>/mcp/ opens and serves a session, <context>/mcp/other is not served, a request without an account is listed no tool.
- SecDemoMcpEndpointTest (real accounts of the security test bed): a tool withheld by the background allowlist and an external tool are not listed, a callable tool is; for three accounts and five tools, a tool is listed exactly when its call passes the gate, and an unlisted tool's call is refused with the gate's reason.