Describe the bug
MemoryEventStore.After(ctx, sessionID, streamID, index) panics with slice bounds out of range when index is greater than the highest stored event index for the stream.
In mcp/event.go, After computes start := index + 1 and guards only the lower bound:
start := index + 1
if dl.first > start { // too old → ErrEventsPurged
return nil, fmt.Errorf(... ErrEventsPurged)
}
return slices.Clone(dl.data[start-dl.first:]), nil
There is no upper-bound guard, so when start-dl.first > len(dl.data) the final slice expression panics. The boundary start-dl.first == len(dl.data) (resume from the last event seen) is a valid empty replay, so only strictly-greater indices fault.
This is reachable from untrusted input on the streamable HTTP server path: parseEventID (mcp/streamable.go) accepts any non-negative index from the client-supplied Last-Event-ID header, and serveGET passes it straight to eventStore.After. A client sending Last-Event-ID: <stream>_<N> with N beyond any event the server ever sent triggers the panic. Direct callers of the exported EventStore.After API crash outright.
To Reproduce
ctx := context.Background()
s := mcp.NewMemoryEventStore(nil)
s.Open(ctx, "sess", "st")
s.Append(ctx, "sess", "st", []byte("a")) // only index 0 exists
for _, err := range s.After(ctx, "sess", "st", 5) { // index past the end
_ = err
}
// panic: runtime error: slice bounds out of range [6:1]
Expected behavior
An index at or beyond the latest stored event should yield an empty replay (there is nothing after it), consistent with the already-valid index == last case — not a panic. (ErrEventsPurged would be misleading here, since the events were never sent rather than purged.)
Version
main (reproduced against the current mcp package).
Describe the bug
MemoryEventStore.After(ctx, sessionID, streamID, index)panics withslice bounds out of rangewhenindexis greater than the highest stored event index for the stream.In
mcp/event.go,Aftercomputesstart := index + 1and guards only the lower bound:There is no upper-bound guard, so when
start-dl.first > len(dl.data)the final slice expression panics. The boundarystart-dl.first == len(dl.data)(resume from the last event seen) is a valid empty replay, so only strictly-greater indices fault.This is reachable from untrusted input on the streamable HTTP server path:
parseEventID(mcp/streamable.go) accepts any non-negative index from the client-suppliedLast-Event-IDheader, andserveGETpasses it straight toeventStore.After. A client sendingLast-Event-ID: <stream>_<N>withNbeyond any event the server ever sent triggers the panic. Direct callers of the exportedEventStore.AfterAPI crash outright.To Reproduce
Expected behavior
An index at or beyond the latest stored event should yield an empty replay (there is nothing after it), consistent with the already-valid
index == lastcase — not a panic. (ErrEventsPurgedwould be misleading here, since the events were never sent rather than purged.)Version
main(reproduced against the currentmcppackage).