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
- Ensure requests flow through the middleware that registers the operation scope (withEncryptedBoxOperationScope)
- Don't retain or reuse the request context after the request completes; fetch it within the request lifetime
- 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
- Route all MCP requests through the scope-registering middleware
- Never cache the request-scoped context beyond the request lifetime
- Perform scoped encrypted-box work synchronously inside the handler
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
- encrypted notebook operation scope is closed
- Recorder has been disposed
- Cannot track a range in a destroyed Protyle instance
- View state service has been destroyed
- tool execution was cancelled before it started
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/111d848bef8301ba.
Report an issue: GitHub.