Skip to content

MCP Apps (1/3): backend plumbing — tool _meta.ui, resources/read, app-resource endpoint - #911

Draft
nickmisasi wants to merge 10 commits into
masterfrom
cursor/mcp-apps-backend-a67d
Draft

MCP Apps (1/3): backend plumbing — tool _meta.ui, resources/read, app-resource endpoint#911
nickmisasi wants to merge 10 commits into
masterfrom
cursor/mcp-apps-backend-a67d

Conversation

@nickmisasi

@nickmisasi nickmisasi commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

First PR in a 3-PR stack adding MCP Apps (io.modelcontextprotocol/ui, spec 2026-01-26) host support to the Agents plugin. This PR is backend-only plumbing; nothing user-visible changes yet.

  • Bumps modelcontextprotocol/go-sdk to v1.6.1 and declares the io.modelcontextprotocol/ui extension (with required mimeTypes: ["text/html;profile=mcp-app"]) in client capabilities during MCP handshake.
  • Parses and preserves tool _meta.ui (canonical nested form; deprecated flat ui/resourceUri accepted as fallback) into llm.Tool, and plumbs it through llm.ToolCallconversation.ContentBlock (tool_use) → the postupdate websocket payload and GET /conversations/:id. Tools with visibility: ["app"] are hidden from the model. UI metadata is redacted for non-requesters until the result is shared.
  • Adds ReadAppResource to the MCP client layer: per-user, lazily connects only the target server, locked session access with single-flight reconnect, 4 MiB size cap, exact-URI match, UTF-8 and text/html;profile=mcp-app MIME validation.
  • New authenticated endpoint GET /mcp/app-resource?post_id=&tool_call_id=: derives the server origin and ui:// URI exclusively from the persisted tool_use block (unforgeable), enforces the authorization invariant — caller can read the conversation, result shared or caller is requester, caller has/establishes their own session to that server — and returns a spec-shaped MCP ReadResourceResult (contents[].uri/mimeType/text/_meta.ui). Error taxonomy: 401 mcp_auth_required (+ OAuth start URL), 403 forbidden, 404, 502 with fixed non-sensitive messages.
  • conversation.FindToolCallBlocks resolves tool calls scoped to the response owned by the anchoring post, rejecting ambiguous duplicate tool-call IDs.

Review process: implemented via a prescriptive plan, then two independent strict review passes with all blockers/majors remediated (session concurrency single-flight, lazy origin-targeted connect, response-owned turn spans, spec-shaped response contract, resource boundary hardening, error hygiene).

Testing: table-driven unit tests throughout (go test -race clean) — endpoint authorization matrix (20+ cases incl. cross-round duplicate-ID and needs-auth vs forbidden), meta parsing edge cases, concurrent reconnect races, live-401-during-read OAuth detection against real in-memory/HTTP go-sdk fixtures. End-to-end validation (browser demo with a real MCP App) is in the final PR of the stack.

Release Note

NONE
Open in Web Open in Cursor 

Summary by CodeRabbit

New Features

  • Added support for MCP Apps, allowing compatible tools to provide interactive HTML resources and UI metadata.
  • Added an authenticated endpoint for retrieving app resources.
  • MCP clients now advertise UI capabilities and can reconnect automatically when reading resources or executing tools.

Bug Fixes

  • Preserved tool UI metadata throughout conversations, streaming, persistence, and tool execution.
  • Improved privacy controls by hiding UI metadata and unshared tool details from non-requesters.
  • Added validation for resource formats, permissions, visibility, and duplicate tool calls.

cursoragent and others added 10 commits July 22, 2026 18:16
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
Collapse origin resolution into ClientManager.ReadUserAppResource
(ErrServerNotConfigured), connect only the requested enabled server,
map live resources/read 401s through oauthNeededError, and harden
ui:// URI/MIME/UTF-8/size validation (exact URI match, 4MB cap).

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
Centralize tool_use/tool_result ContentBlock scanning in conversation,
anchor to the given post_id span, and reject ambiguous duplicate
tool_call_id matches so reused IDs cannot cross-authorize.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
Return MCP ReadResourceResult wire JSON on success; gate D6(b) on
tool_use.Shared; map ErrServerNotConfigured/OAuth/invalid MIME via
typed errors with fixed non-sensitive 502 messages; use
FindToolCallBlocks for post-anchored lookup.

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
Close any map entry overwritten in connectToServer so concurrent cold
ConnectToRemoteServer calls cannot leak a session, and remove the
unused getClientForUser helper (tests now hit getOrCreateUserClientsShell).

Co-authored-by: nick.misasi <nick.misasi@mattermost.com>
@github-actions

Copy link
Copy Markdown

🤖 LLM Evaluation Results

OpenAI

⚠️ Overall: 21/28 tests passed (75.0%)

Provider Total Passed Failed Pass Rate
⚠️ OPENAI 28 21 7 75.0%

❌ Failed Evaluations

Show 7 failures

OPENAI

1. TestReactEval/[openai]_react_cat_message

  • Score: 0.00
  • Rubric: The word/emoji is a cat emoji or a heart/love emoji
  • Reason: The output is the text string "heart_eyes_cat", not an actual cat emoji (e.g., 😺) or a heart/love emoji (e.g., ❤️).

2. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: is a list of bugs
  • Reason: The output does not provide any actual bug entries; it states inability to access a tracker and offers a blank table template. Therefore, it is not a list of bugs.

3. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: includes a description of each bug
  • Reason: The output does not include descriptions of any specific bugs; it only provides a blank table template and asks the user to paste bug titles/links so descriptions can be added later.

4. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: attributes each bug to a user
  • Reason: The output provides an empty table and asks the user to paste bug titles/links; it does not actually attribute any bugs to specific users (the 'Reported by' column is blank).

5. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: attributes the bug about trying to save without a color and the save button not doing anything to @maria.nunez
  • Reason: The output does not mention the specific bug about trying to save without a color, the save button doing nothing, or any attribution to @maria.nunez. It only states it cannot access the bug tracker and asks the user to paste bug reports.

6. TestConversationMentionHandling/[openai]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: the bug about the end user being able to change channel banner is attributed to @maria.nunez
  • Reason: The output does not mention the specific bug about an end user being able to change the channel banner, nor does it attribute that bug to @maria.nunez. It only provides a generic template and asks for more information.

7. TestDirectMessageConversations/[openai]_bot_dm_tool_introspection

  • Score: 0.00
  • Rubric: mentions Github and refers to the documentation
  • Reason: The output refers to documentation (Mattermost docs link) but does not mention GitHub anywhere. Since the rubric requires both mentioning GitHub and referring to the documentation, it fails.

Anthropic

⚠️ Overall: 22/28 tests passed (78.6%)

Provider Total Passed Failed Pass Rate
⚠️ ANTHROPIC 28 22 6 78.6%

❌ Failed Evaluations

Show 6 failures

ANTHROPIC

1. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: is a list of bugs
  • Reason: The output does not provide an actual list of bugs; it explains lack of access and suggests how to gather bug details, plus a blank table template. Therefore it is not a list of bugs.

2. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: includes a description of each bug
  • Reason: The output does not include descriptions of each bug; it explicitly states it cannot access or compile bug reports and only suggests how to provide data. No actual bug list/descriptions are present.

3. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: attributes each bug to a user
  • Reason: The output does not list any bugs or attribute them to specific users. It only states limitations and suggests a table format, but contains no per-bug 'Reported By' user attributions.

4. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: attributes the bug about trying to save without a color and the save button not doing anything to @maria.nunez
  • Reason: The output does not mention the specific bug (saving without a color and the save button doing nothing) nor does it attribute it to @maria.nunez; it only states inability to access bug trackers and suggests providing data.

5. TestConversationMentionHandling/[anthropic]_conversation_from_attribution_long_thread.json

  • Score: 0.00
  • Rubric: the bug about the end user being able to change channel banner is attributed to @maria.nunez
  • Reason: The output does not mention the specific bug about an end user being able to change the channel banner, nor does it attribute that bug to @maria.nunez. It only provides a generic statement about lacking access and suggests how to compile bugs.

6. TestDirectMessageConversations/[anthropic]_bot_dm_tool_introspection

  • Score: 0.00
  • Rubric: mentions Github and refers to the documentation
  • Reason: The output refers to documentation (docs.mattermost.com) but does not mention GitHub anywhere, so it does not satisfy the requirement to mention GitHub and refer to the documentation.

This comment was automatically generated by the eval CI pipeline.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds MCP Apps UI metadata support across tools and conversations, validated user-scoped app-resource retrieval with reconnect and OAuth handling, and an authenticated /mcp/app-resource endpoint with authorization and structured error responses.

Changes

MCP Apps UI support

Layer / File(s) Summary
UI metadata contracts and propagation
llm/*, mcp/apps_meta.go, mcp/user_clients.go, conversation/*, streaming/*, toolrunner/*
Adds UI metadata types, visibility filtering, MCP capability advertisement, metadata parsing, persistence, redaction, and propagation through tool and conversation pipelines.
App resource client retrieval
mcp/client.go, mcp/client_manager.go, mcp/resources.go, mcp/user_clients.go, mcp/oauth_manager.go, go.mod
Adds validated ui:// resource reads, targeted user-server connections, reconnect coordination, OAuth mapping, and MCP SDK updates.
Authenticated app-resource API
api/api.go, api/api_mcp_app_resource.go, conversation/find_tool_call.go, api/*_test.go, conversation/find_tool_call_test.go
Registers /mcp/app-resource, validates tool-call ownership and sharing, retrieves resources, and returns structured success or error responses.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AppClient
  participant APIHandler
  participant MCPClientManager
  participant MCPServer
  AppClient->>APIHandler: GET /mcp/app-resource
  APIHandler->>MCPClientManager: ReadUserAppResource(userID, serverOrigin, uri)
  MCPClientManager->>MCPServer: resources/read(uri)
  MCPServer-->>MCPClientManager: App resource content
  MCPClientManager-->>APIHandler: AppResource or mapped error
  APIHandler-->>AppClient: JSON response
Loading

Suggested labels: Setup Cloud Test Server

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main backend MCP Apps plumbing changes, including tool _meta.ui handling, resources/read support, and the app-resource endpoint.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/mcp-apps-backend-a67d

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
go.mod

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
conversation/convert.go (1)

83-97: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Clear UIMeta on the redaction path. Arguments and MCPBareName are stripped when redactToolUse is true, but block.UIMeta still gets copied through, leaving app metadata on unshared tool calls. That matches neither FilterForNonRequester nor redactToolCalls, both of which clear UIMeta.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@conversation/convert.go` around lines 83 - 97, The redactToolUse branch in
the tool-call conversion must also clear toolCall.UIMeta, matching the existing
redaction behavior for MCPBareName and Arguments and the conventions in
FilterForNonRequester and redactToolCalls. Keep UIMeta intact on the
non-redacted enrichment path.
🧹 Nitpick comments (2)
mcp/user_clients.go (1)

180-188: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Move existing.Close() out from under clientsMu. Client.Close() synchronously tears down the MCP session, so holding the write lock during that call blocks concurrent GetTools/hasClient readers until shutdown completes. Capture the old client, swap it in the map, unlock, then close the replaced client.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp/user_clients.go` around lines 180 - 188, Update the client replacement
logic in the Client connection flow to capture the existing client, assign
serverClient under clientsMu, then release the lock before calling
existing.Close(). Preserve replacement behavior and only close the captured
client after unlocking, so GetTools and hasClient are not blocked during
shutdown.
conversation/find_tool_call_test.go (1)

15-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a table-driven structure for the four scenarios.

As per path instructions, **/*_test.go: "Go tests must be table-driven when they contain more than one case." This test has four distinct cases implemented as separate t.Run blocks rather than a []struct{...} + range loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@conversation/find_tool_call_test.go` around lines 15 - 98, Refactor
TestFindToolCallBlocksPostAnchored into a table-driven test with a []struct of
four cases and a range over t.Run. Keep the shared turns, mustBlocks helper,
per-case inputs, expected blocks/error assertions, and scenario coverage
unchanged while consolidating the duplicated subtests.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/api_mcp_app_resource.go`:
- Around line 140-162: Add a bounded per-call timeout around the
ReadUserAppResource invocation, deriving the context from c.Request.Context()
and passing the timed context instead of the uncancelled request context. Ensure
the timeout covers both remote connection and resources/read operations while
preserving the existing error handling and response behavior.

In `@go.mod`:
- Line 19: Enable CrossOriginProtection explicitly in every
NewStreamableHTTPHandler call within mcpserver/http_server.go,
mcpserver/plugin_handlers.go, and external/pluginmcp/server.go, configuring the
handler options so the /mcp endpoints reject cross-site requests.

---

Outside diff comments:
In `@conversation/convert.go`:
- Around line 83-97: The redactToolUse branch in the tool-call conversion must
also clear toolCall.UIMeta, matching the existing redaction behavior for
MCPBareName and Arguments and the conventions in FilterForNonRequester and
redactToolCalls. Keep UIMeta intact on the non-redacted enrichment path.

---

Nitpick comments:
In `@conversation/find_tool_call_test.go`:
- Around line 15-98: Refactor TestFindToolCallBlocksPostAnchored into a
table-driven test with a []struct of four cases and a range over t.Run. Keep the
shared turns, mustBlocks helper, per-case inputs, expected blocks/error
assertions, and scenario coverage unchanged while consolidating the duplicated
subtests.

In `@mcp/user_clients.go`:
- Around line 180-188: Update the client replacement logic in the Client
connection flow to capture the existing client, assign serverClient under
clientsMu, then release the lock before calling existing.Close(). Preserve
replacement behavior and only close the captured client after unlocking, so
GetTools and hasClient are not blocked during shutdown.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: f4324647-74ec-4ff1-aaf3-0a907219f7de

📥 Commits

Reviewing files that changed from the base of the PR and between 076aa4e and 99040b1.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (32)
  • api/api.go
  • api/api_conversation_test.go
  • api/api_mcp_app_resource.go
  • api/api_mcp_app_resource_test.go
  • api/api_test.go
  • conversation/content_block.go
  • conversation/content_block_test.go
  • conversation/convert.go
  • conversation/convert_test.go
  • conversation/find_tool_call.go
  • conversation/find_tool_call_test.go
  • conversation/helpers.go
  • conversation/helpers_test.go
  • go.mod
  • llm/tools.go
  • llm/tools_test.go
  • llm/tools_ui.go
  • mcp/apps_meta.go
  • mcp/apps_meta_test.go
  • mcp/client.go
  • mcp/client_manager.go
  • mcp/client_manager_test.go
  • mcp/client_test.go
  • mcp/oauth_manager.go
  • mcp/resources.go
  • mcp/resources_test.go
  • mcp/user_clients.go
  • mcp/user_clients_test.go
  • streaming/streaming.go
  • streaming/turn_persistence_test.go
  • toolrunner/toolrunner.go
  • toolrunner/toolrunner_test.go

Comment on lines +140 to +162
res, err := a.mcpClientManager.ReadUserAppResource(c.Request.Context(), userID, toolUse.ServerOrigin, toolUse.UIMeta.ResourceURI)
if err != nil {
var oauthErr *mcp.OAuthNeededError
if errors.As(err, &oauthErr) {
writeAppResourceError(c, http.StatusUnauthorized, appResourceErrAuthRequired, "MCP authentication required", oauthErr.AuthURL())
return
}
if errors.Is(err, mcp.ErrServerNotConfigured) {
writeAppResourceError(c, http.StatusNotFound, appResourceErrNotFound, "server no longer configured", "")
return
}
var invalidErr *mcp.InvalidAppResourceError
if errors.As(err, &invalidErr) {
a.pluginAPI.Log.Error("Invalid MCP app resource",
"userID", userID, "serverOrigin", toolUse.ServerOrigin, "uri", toolUse.UIMeta.ResourceURI, "error", invalidErr)
writeAppResourceError(c, http.StatusBadGateway, appResourceErrInvalidResourceMime, "invalid app resource", "")
return
}
a.pluginAPI.Log.Error("MCP app resource upstream failure",
"userID", userID, "serverOrigin", toolUse.ServerOrigin, "uri", toolUse.UIMeta.ResourceURI, "error", err)
writeAppResourceError(c, http.StatusBadGateway, appResourceErrUpstreamUnreachable, "MCP server unreachable", "")
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -A5 -B5 'context.WithTimeout|context.WithDeadline' mcp/resources.go mcp/client.go

Repository: mattermost/mattermost-plugin-agents

Length of output: 173


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file list ==\n'
git ls-files | rg '(^|/)(mcp|api)/.*\.(go|ts|tsx)$|^mcp/|^api/api_mcp_app_resource\.go$'

printf '\n== locate ReadUserAppResource ==\n'
rg -n 'ReadUserAppResource|WithTimeout|WithDeadline|context\.Background|context\.TODO' api mcp

printf '\n== map candidate files ==\n'
for f in api/api_mcp_app_resource.go mcp/resources.go mcp/client.go; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    ast-grep outline "$f" --view compact || true
  fi
done

printf '\n== relevant slices ==\n'
for f in mcp/resources.go mcp/client.go api/api_mcp_app_resource.go; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    nl -ba "$f" | sed -n '1,260p'
  fi
done

Repository: mattermost/mattermost-plugin-agents

Length of output: 18718


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== ReadUserAppResource implementation =='
sed -n '230,320p' mcp/resources.go

echo
echo '== client manager timeout-related path =='
sed -n '130,190p' mcp/user_clients.go

echo
echo '== client manager wrapper =='
sed -n '1,120p' mcp/client_manager.go

echo
echo '== client read path =='
sed -n '1,260p' mcp/client.go | sed -n '1,260p'

Repository: mattermost/mattermost-plugin-agents

Length of output: 17777


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== user_clients remote connect =='
sed -n '1,140p' mcp/user_clients.go
echo
sed -n '140,240p' mcp/user_clients.go

echo
echo '== client session setup =='
sed -n '260,520p' mcp/client.go

echo
echo '== search for connect/read timeouts in mcp package =='
rg -n 'WithTimeout|WithDeadline|Deadline|timeout' mcp/client.go mcp/user_clients.go mcp/http_client.go mcp/plugin_roundtripper.go mcp/oauth_transport.go mcp/dcrp.go

Repository: mattermost/mattermost-plugin-agents

Length of output: 15630


Add a bounded timeout to the remote MCP app-resource path

ReadUserAppResource passes cacheableContext(ctx) into ConnectToRemoteServer(...), which drops request cancellation. If the MCP server hangs during connect or resources/read, this request can block indefinitely; use a per-call timeout here instead of relying on the client connection.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/api_mcp_app_resource.go` around lines 140 - 162, Add a bounded per-call
timeout around the ReadUserAppResource invocation, deriving the context from
c.Request.Context() and passing the timed context instead of the uncancelled
request context. Ensure the timeout covers both remote connection and
resources/read operations while preserving the existing error handling and
response behavior.

Comment thread go.mod
github.com/mattermost/testcontainers-mattermost-go v0.1.0
github.com/maximhq/bifrost/core v1.5.18
github.com/modelcontextprotocol/go-sdk v1.4.1
github.com/modelcontextprotocol/go-sdk v1.6.1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate where the go-sdk StreamableHTTPHandler is constructed and check for explicit CrossOriginProtection config.
rg -n -A5 -B5 'NewStreamableHTTPHandler|StreamableHTTPOptions' --type=go

Repository: mattermost/mattermost-plugin-agents

Length of output: 173


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== go.mod ==\n'
cat -n go.mod | sed -n '1,80p'

printf '\n== sdk and MCP references ==\n'
rg -n -A4 -B4 'modelcontextprotocol/go-sdk|CrossOriginProtection|enableoriginverification|StreamableHTTP|mcpServerGroup.Any\("/mcp"|mcpAuthMiddleware' --type=go .

Repository: mattermost/mattermost-plugin-agents

Length of output: 34595


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file list ==\n'
git ls-files | rg '(^|/)(go\.mod|.*\.go)$'

printf '\n== candidate file outlines ==\n'
for f in $(git ls-files | rg '(^|/)(go\.mod|.*\.go)$'); do
  case "$f" in
    *go.mod|*go.sum) continue ;;
  esac
done

Repository: mattermost/mattermost-plugin-agents

Length of output: 13100


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== go.mod line refs ==\n'
cat -n go.mod | sed -n '1,60p'

printf '\n== MCP-related Go files ==\n'
rg -l --type=go 'mcp|StreamableHTTP|CrossOriginProtection|modelcontextprotocol/go-sdk' .

printf '\n== likely handler construction snippets ==\n'
for f in $(rg -l --type=go 'mcp|StreamableHTTP|CrossOriginProtection|modelcontextprotocol/go-sdk' .); do
  echo "--- $f ---"
  rg -n -A8 -B8 'mcp|StreamableHTTP|CrossOriginProtection|modelcontextprotocol/go-sdk' "$f"
done

Repository: mattermost/mattermost-plugin-agents

Length of output: 50391


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== direct imports / module references ==\n'
rg -n 'github.com/modelcontextprotocol/go-sdk|modelcontextprotocol/go-sdk' .

Repository: mattermost/mattermost-plugin-agents

Length of output: 3495


🌐 Web query:

modelcontextprotocol go-sdk v1.6.1 StreamableHTTPHandler CrossOriginProtection release notes

💡 Result:

There are no public, dedicated release notes for version 1.6.1 of the Model Context Protocol (MCP) Go SDK. The version v1.6.1 was released on May 22, 2026 [1]. Regarding the technical components mentioned in your query: StreamableHTTPHandler The StreamableHTTPHandler is a core component within the MCP Go SDK (found in the mcp package) designed to serve streamable MCP sessions over HTTP, conforming to the MCP specification [2][3]. It manages HTTP requests for MCP sessions, including session lifecycle and transport handling [2][4]. CrossOriginProtection The CrossOriginProtection field is a configuration option available within StreamableHTTPOptions used by the StreamableHTTPHandler [2]. In the implementation, if CrossOriginProtection is not explicitly set and a specific internal flag (enableoriginverification) is enabled, the handler initializes it with a default http.CrossOriginProtection instance [2]. This functionality is part of the SDK's effort to provide secure transport primitives, including support for cross-origin verification [2]. For detailed changes in specific versions, you can review the official repository's commit history or issues tracker, where features like these are typically discussed as proposals or pull requests [5][6][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== origin-protection references ==\n'
rg -n 'CrossOriginProtection|enableoriginverification' .

printf '\n== mcpserver/http_server.go ==\n'
cat -n mcpserver/http_server.go | sed -n '88,112p'

printf '\n== mcpserver/plugin_handlers.go ==\n'
cat -n mcpserver/plugin_handlers.go | sed -n '88,110p'

printf '\n== external/pluginmcp/server.go ==\n'
cat -n external/pluginmcp/server.go | sed -n '1,120p'

printf '\n== api/mcp_handlers.go ==\n'
cat -n api/mcp_handlers.go | sed -n '1,120p'

Repository: mattermost/mattermost-plugin-agents

Length of output: 209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== StreamableHTTPHandler call sites in production code ==\n'
rg -n -A3 -B3 'NewStreamableHTTPHandler\(' api mcp mcpserver external/pluginmcp server

printf '\n== files that mention CrossOriginProtection in go-sdk imports or docs ==\n'
rg -n 'CrossOriginProtection' api mcp mcpserver external/pluginmcp server

Repository: mattermost/mattermost-plugin-agents

Length of output: 4401


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== mcpserver/http_server.go (90-106) ==\n'
cat -n mcpserver/http_server.go | sed -n '90,106p'

printf '\n== mcpserver/plugin_handlers.go (90-110) ==\n'
cat -n mcpserver/plugin_handlers.go | sed -n '90,110p'

printf '\n== external/pluginmcp/server.go (1-120) ==\n'
cat -n external/pluginmcp/server.go | sed -n '1,120p'

printf '\n== api/api.go (286-296) ==\n'
cat -n api/api.go | sed -n '286,296p'

Repository: mattermost/mattermost-plugin-agents

Length of output: 4056


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== mcpserver/http_server.go (1-140) ==\n'
cat -n mcpserver/http_server.go | sed -n '1,140p'

printf '\n== mcpserver/plugin_handlers.go (1-140) ==\n'
cat -n mcpserver/plugin_handlers.go | sed -n '1,140p'

Repository: mattermost/mattermost-plugin-agents

Length of output: 11094


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== external/pluginmcp/server.go (1-120) ==\n'
cat -n external/pluginmcp/server.go | sed -n '1,120p'

printf '\n== mcpserver/http_server.go (88-120) ==\n'
cat -n mcpserver/http_server.go | sed -n '88,120p'

printf '\n== mcpserver/plugin_handlers.go (88-120) ==\n'
cat -n mcpserver/plugin_handlers.go | sed -n '88,120p'

Repository: mattermost/mattermost-plugin-agents

Length of output: 4788


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== external/pluginmcp/server.go ServeHTTP and handler setup ==\n'
rg -n -A20 -B10 'func \(s \*Server\) ServeHTTP|NewStreamableHTTPHandler|StreamableHTTPOptions' external/pluginmcp/server.go

printf '\n== mcpserver/http_server.go streamable setup ==\n'
rg -n -A10 -B5 'NewStreamableHTTPHandler|StreamableHTTPOptions' mcpserver/http_server.go

printf '\n== mcpserver/plugin_handlers.go streamable setup ==\n'
rg -n -A10 -B5 'NewStreamableHTTPHandler|StreamableHTTPOptions' mcpserver/plugin_handlers.go

Repository: mattermost/mattermost-plugin-agents

Length of output: 3222


Enable CrossOriginProtection on the Streamable HTTP handlers here. go-sdk v1.6.1 no longer enables it by default, and mcpserver/http_server.go, mcpserver/plugin_handlers.go, and external/pluginmcp/server.go all call NewStreamableHTTPHandler(...) without it, leaving /mcp open to cross-site requests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` at line 19, Enable CrossOriginProtection explicitly in every
NewStreamableHTTPHandler call within mcpserver/http_server.go,
mcpserver/plugin_handlers.go, and external/pluginmcp/server.go, configuring the
handler options so the /mcp endpoints reject cross-site requests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants