siyuan-note/siyuan · error

MCP request operation scope is closed

Error message

MCP request operation scope is closed

What it means

requestOperationContext looks up the per-request operation scope (a context.Context) registered by request ID in a sync.Map. The declared error errMCPRequestOperationScopeClosed is returned when no scope is registered for the request ID or the stored value is not a context.Context, meaning the scope was never created or already removed/closed.

Source

Thrown at kernel/mcp/request_scope.go:33

// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package mcp

import (
	"context"
	"errors"
	"net/http"
	"sync"

	"github.com/google/uuid"
	mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
	"github.com/siyuan-note/siyuan/kernel/model"
)

const requestOperationScopeHeader = "X-Siyuan-Mcp-Request-Scope"

var (
	errMCPRequestOperationScopeClosed = errors.New("MCP request operation scope is closed")
	requestOperationScopes            sync.Map // 键为请求 ID,值为 context.Context。
)

// withEncryptedBoxOperationScope 为当前 HTTP 响应注册加密笔记本操作作用域。
func withEncryptedBoxOperationScope(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		requestContext, release := model.WithEncryptedBoxOperationScope(request.Context())
		requestID := uuid.NewString()
		scopedRequest := request.WithContext(requestContext)
		scopedRequest.Header = request.Header.Clone()
		scopedRequest.Header.Set(requestOperationScopeHeader, requestID)
		requestOperationScopes.Store(requestID, requestContext)
		defer func() {
			requestOperationScopes.Delete(requestID)
			release()
		}()

		handler.ServeHTTP(writer, scopedRequest)

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Ensure requests flow through the middleware that registers the operation scope (withEncryptedBoxOperationScope)
  2. Don't retain or reuse the request context after the request completes; fetch it within the request lifetime
  3. If you need the scope in async work, capture values from the context before the scope closes instead of calling requestOperationContext later
Defensive patterns

Strategy: try-catch

Type guard

func scopeOpen(requestID string) bool {
    v, ok := requestOperationScopes.Load(requestID)
    _, isCtx := v.(context.Context)
    return ok && isCtx
}

Try / catch

ctx, err := requestOperationContext(requestID)
if err != nil {
    if errors.Is(err, errMCPRequestOperationScopeClosed) { /* skip scope work or re-register via middleware */ }
}

Prevention

When it happens

Trigger: An MCP handler (or TestRequestOperationScopeBridge) calls requestOperationContext with a request ID that has no entry in requestOperationScopes — e.g. the HTTP middleware withEncryptedBoxOperationScope did not run for that request, the scope was deleted after the request finished, or the stored value failed the type assertion.

Common situations: Calling encrypted-box scoped operations outside an MCP HTTP request (background jobs, direct tool invocation bypassing the middleware); a race where the scope was cleaned up before the handler finished.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/111d848bef8301ba. Report an issue: GitHub.