@mcp-b/do-runtime 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3
4
+
5
+ ### Patch Changes
6
+
7
+ - e4b1be6: Close three input-lock laundering paths in HTTP bodies. `ReadableStream` async iteration, second-order `Blob` reads, and reader or stream lifecycle promises could previously resume outside the actor's input gate and make the next storage call throw "no input lock available in this context". They now route every surfaced continuation through the actor's gated I/O seam while preserving native stream cancellation and lock-release behavior.
8
+
9
+ ## 0.2.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 31cb2a6: Gate the streams `pipeThrough` and `pipeTo` produce. Native pipe machinery reads a gated body through internal spec operations and hands back a brand-new uninstrumented stream, so `res.body.pipeThrough(new TextDecoderStream()).getReader().read()` — the MCP SDK's SSE path — resumed foreign on every chunk and the next storage call threw "no input lock available in this context". `pipeThrough` now re-gates the readable it returns (recursively, so chains stay covered) and `pipeTo`'s settlement resumes gated.
14
+
3
15
  ## 0.2.1
4
16
 
5
17
  ### Patch Changes
package/dist/index.js CHANGED
@@ -3685,6 +3685,42 @@ var BODY_CONSUMERS = [
3685
3685
  "json",
3686
3686
  "text"
3687
3687
  ];
3688
+ /** A consumed Blob is a second-order body: every read it produces needs the same gate. */
3689
+ function gateBlob(ctx, blob) {
3690
+ const arrayBuffer = blob.arrayBuffer.bind(blob);
3691
+ const bytes = blob.bytes.bind(blob);
3692
+ const slice = blob.slice.bind(blob);
3693
+ const stream = blob.stream.bind(blob);
3694
+ const text = blob.text.bind(blob);
3695
+ Object.defineProperties(blob, {
3696
+ arrayBuffer: {
3697
+ configurable: true,
3698
+ writable: true,
3699
+ value: () => ctx.awaitIo(arrayBuffer())
3700
+ },
3701
+ bytes: {
3702
+ configurable: true,
3703
+ writable: true,
3704
+ value: () => ctx.awaitIo(bytes())
3705
+ },
3706
+ slice: {
3707
+ configurable: true,
3708
+ writable: true,
3709
+ value: (start, end, contentType) => gateBlob(ctx, slice(start, end, contentType))
3710
+ },
3711
+ stream: {
3712
+ configurable: true,
3713
+ writable: true,
3714
+ value: () => gateReadableStream(ctx, stream())
3715
+ },
3716
+ text: {
3717
+ configurable: true,
3718
+ writable: true,
3719
+ value: () => ctx.awaitIo(text())
3720
+ }
3721
+ });
3722
+ return blob;
3723
+ }
3688
3724
  /**
3689
3725
  * Wrap a `Request` or `Response` so every asynchronous step of reading it
3690
3726
  * resumes inside a gated slice.
@@ -3718,7 +3754,10 @@ function gateBody(ctx, value) {
3718
3754
  return clone;
3719
3755
  }
3720
3756
  if (BODY_CONSUMERS.includes(property)) {
3721
- const consume = (...args) => ctx.awaitIo(subject[property].apply(subject, args));
3757
+ const consume = (...args) => {
3758
+ const result = subject[property].apply(subject, args);
3759
+ return ctx.awaitIo(result, (value) => property === "blob" ? gateBlob(ctx, value) : value);
3760
+ };
3722
3761
  bound.set(property, consume);
3723
3762
  return consume;
3724
3763
  }
@@ -3748,9 +3787,45 @@ function gateResponseBody(ctx, response) {
3748
3787
  */
3749
3788
  var BYOB_READER_UNGATABLE_MESSAGE = "getReader({ mode: 'byob' }): a BYOB reader cannot be gated by this runtime, because `ReadableStream.getReader` is the only seam it has and a byte stream's `read(view)` returns the caller's own buffer. Read the body through `arrayBuffer()` or a default reader.";
3750
3789
  function gateReadableStream(ctx, stream) {
3790
+ const cancel = stream.cancel.bind(stream);
3751
3791
  const getReader = stream.getReader.bind(stream);
3752
3792
  const tee = stream.tee.bind(stream);
3793
+ const pipeThrough = stream.pipeThrough.bind(stream);
3794
+ const pipeTo = stream.pipeTo.bind(stream);
3795
+ const values = async function* (options) {
3796
+ const reader = gateReader(ctx, getReader());
3797
+ let finished = false;
3798
+ try {
3799
+ for (;;) {
3800
+ let result;
3801
+ try {
3802
+ result = await reader.read();
3803
+ } catch (exception) {
3804
+ finished = true;
3805
+ throw exception;
3806
+ }
3807
+ if (result.done) {
3808
+ finished = true;
3809
+ return;
3810
+ }
3811
+ yield result.value;
3812
+ }
3813
+ } finally {
3814
+ try {
3815
+ if (!finished && options?.preventCancel !== true) await reader.cancel();
3816
+ } finally {
3817
+ reader.releaseLock();
3818
+ }
3819
+ }
3820
+ };
3753
3821
  Object.defineProperties(stream, {
3822
+ cancel: {
3823
+ configurable: true,
3824
+ writable: true,
3825
+ value(reason) {
3826
+ return ctx.awaitIo(cancel(reason));
3827
+ }
3828
+ },
3754
3829
  getReader: {
3755
3830
  configurable: true,
3756
3831
  writable: true,
@@ -3766,15 +3841,61 @@ function gateReadableStream(ctx, stream) {
3766
3841
  const [a, b] = tee();
3767
3842
  return [gateReadableStream(ctx, a), gateReadableStream(ctx, b)];
3768
3843
  }
3844
+ },
3845
+ pipeThrough: {
3846
+ configurable: true,
3847
+ writable: true,
3848
+ value(transform, options) {
3849
+ return gateReadableStream(ctx, pipeThrough(transform, options));
3850
+ }
3851
+ },
3852
+ pipeTo: {
3853
+ configurable: true,
3854
+ writable: true,
3855
+ value(destination, options) {
3856
+ return ctx.awaitIo(pipeTo(destination, options));
3857
+ }
3858
+ },
3859
+ values: {
3860
+ configurable: true,
3861
+ writable: true,
3862
+ value: values
3863
+ },
3864
+ [Symbol.asyncIterator]: {
3865
+ configurable: true,
3866
+ writable: true,
3867
+ value: values
3769
3868
  }
3770
3869
  });
3771
3870
  return stream;
3772
3871
  }
3773
3872
  function gateReader(ctx, reader) {
3873
+ const bound = /* @__PURE__ */ new Map();
3774
3874
  return new Proxy(reader, { get(subject, property) {
3775
- if (property === "read") return () => ctx.awaitIo(subject.read());
3875
+ const cached = bound.get(property);
3876
+ if (cached !== void 0) return cached;
3877
+ if (property === "closed") {
3878
+ const closed = ctx.awaitIo(subject.closed);
3879
+ bound.set(property, closed);
3880
+ return closed;
3881
+ }
3882
+ if (property === "read") {
3883
+ const read = () => ctx.awaitIo(subject.read());
3884
+ bound.set(property, read);
3885
+ return read;
3886
+ }
3887
+ if (property === "cancel") {
3888
+ const cancel = (reason) => ctx.awaitIo(subject.cancel(reason));
3889
+ bound.set(property, cancel);
3890
+ return cancel;
3891
+ }
3776
3892
  const value = Reflect.get(subject, property, subject);
3777
- return typeof value === "function" ? value.bind(subject) : value;
3893
+ if (typeof value === "function") {
3894
+ const method = value.bind(subject);
3895
+ bound.set(property, method);
3896
+ return method;
3897
+ }
3898
+ return value;
3778
3899
  } });
3779
3900
  }
3780
3901
  //#endregion