@j0hanz/fetch-url-mcp 1.11.4 → 1.11.5
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/dist/lib/http.d.ts.map +1 -1
- package/dist/lib/http.js +77 -112
- package/package.json +1 -1
package/dist/lib/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/lib/http.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAQhD,OAAO,EAAE,KAAK,EAAmB,MAAM,QAAQ,CAAC;AAehD,OAAO,EAOL,KAAK,eAAe,EAGrB,MAAM,UAAU,CAAC;AAsElB,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EACrB,SAAS,SAAQ,GAChB,MAAM,CAIR;AAkBD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,cAAc,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,IAAI,CA6CN;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/lib/http.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAQhD,OAAO,EAAE,KAAK,EAAmB,MAAM,QAAQ,CAAC;AAehD,OAAO,EAOL,KAAK,eAAe,EAGrB,MAAM,UAAU,CAAC;AAsElB,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EACrB,SAAS,SAAQ,GAChB,MAAM,CAIR;AAkBD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,cAAc,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,IAAI,CA6CN;AAkvCD,UAAU,qBAAqB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoKD,UAAU,YAAY;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AA6ND,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE/C;AACD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAEA;AACD,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEjE;AACD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAE9D;AACD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAExD;AACD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,qBAAqB,CAEvB;AACD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,qBAAqB,EAC9B,QAAQ,EAAE,QAAQ,EAClB,WAAW,CAAC,EAAE,MAAM,GACnB,IAAI,CAEN;AACD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,qBAAqB,EAC9B,KAAK,EAAE,OAAO,EACd,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAEN;AACD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC,CAE7D;AACD,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,EACpB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CASzC;AACD,wBAAsB,kBAAkB,CACtC,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,MAAM,CAAC,CAEjB;AACD,wBAAsB,wBAAwB,CAC5C,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC;IACT,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAED"}
|
package/dist/lib/http.js
CHANGED
|
@@ -3,7 +3,7 @@ import { hash, randomUUID } from 'node:crypto';
|
|
|
3
3
|
import diagnosticsChannel from 'node:diagnostics_channel';
|
|
4
4
|
import {} from 'node:http';
|
|
5
5
|
import { posix as pathPosix } from 'node:path';
|
|
6
|
-
import { PassThrough, Readable
|
|
6
|
+
import { PassThrough, Readable } from 'node:stream';
|
|
7
7
|
import { finished, pipeline } from 'node:stream/promises';
|
|
8
8
|
import {} from 'node:stream/web';
|
|
9
9
|
import tls from 'node:tls';
|
|
@@ -671,13 +671,14 @@ function createUnsupportedContentEncodingError(url, encodingHeader) {
|
|
|
671
671
|
});
|
|
672
672
|
}
|
|
673
673
|
function createDecompressor(encoding) {
|
|
674
|
+
const options = { chunkSize: 64 * 1024 };
|
|
674
675
|
switch (encoding) {
|
|
675
676
|
case 'gzip':
|
|
676
|
-
return createGunzip();
|
|
677
|
+
return createGunzip(options);
|
|
677
678
|
case 'deflate':
|
|
678
|
-
return createInflate();
|
|
679
|
+
return createInflate(options);
|
|
679
680
|
case 'br':
|
|
680
|
-
return createBrotliDecompress();
|
|
681
|
+
return createBrotliDecompress(options);
|
|
681
682
|
}
|
|
682
683
|
}
|
|
683
684
|
function createPumpedStream(initialChunk, reader) {
|
|
@@ -800,86 +801,6 @@ async function decodeResponseIfNeeded(response, url, signal) {
|
|
|
800
801
|
// ═══════════════════════════════════════════════════════════════════
|
|
801
802
|
// RESPONSE READING
|
|
802
803
|
// ═══════════════════════════════════════════════════════════════════
|
|
803
|
-
function concatUint8Arrays(chunks, totalLength) {
|
|
804
|
-
const result = new Uint8Array(totalLength);
|
|
805
|
-
let offset = 0;
|
|
806
|
-
for (const chunk of chunks) {
|
|
807
|
-
result.set(chunk, offset);
|
|
808
|
-
offset += chunk.byteLength;
|
|
809
|
-
}
|
|
810
|
-
return result;
|
|
811
|
-
}
|
|
812
|
-
class BoundedBufferTransform extends Transform {
|
|
813
|
-
byteLimit;
|
|
814
|
-
captureChunks;
|
|
815
|
-
url;
|
|
816
|
-
declaredEncoding;
|
|
817
|
-
total = 0;
|
|
818
|
-
chunks = [];
|
|
819
|
-
effectiveEncoding;
|
|
820
|
-
encodingResolved = false;
|
|
821
|
-
firstChunk = true;
|
|
822
|
-
constructor(byteLimit, captureChunks, url, declaredEncoding) {
|
|
823
|
-
super();
|
|
824
|
-
this.byteLimit = byteLimit;
|
|
825
|
-
this.captureChunks = captureChunks;
|
|
826
|
-
this.url = url;
|
|
827
|
-
this.declaredEncoding = declaredEncoding;
|
|
828
|
-
this.effectiveEncoding = declaredEncoding ?? 'utf-8';
|
|
829
|
-
}
|
|
830
|
-
_transform(chunk, _encoding, callback) {
|
|
831
|
-
try {
|
|
832
|
-
const buf = chunk instanceof Uint8Array
|
|
833
|
-
? chunk
|
|
834
|
-
: new Uint8Array(chunk);
|
|
835
|
-
this.resolveChunkEncoding(buf);
|
|
836
|
-
const binaryError = this.detectBinaryContent(buf);
|
|
837
|
-
if (binaryError) {
|
|
838
|
-
callback(binaryError);
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
this.enforceByteLimit(buf, callback);
|
|
842
|
-
}
|
|
843
|
-
catch (error) {
|
|
844
|
-
callback(toError(error));
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
resolveChunkEncoding(buf) {
|
|
848
|
-
if (this.encodingResolved)
|
|
849
|
-
return;
|
|
850
|
-
this.encodingResolved = true;
|
|
851
|
-
this.effectiveEncoding = resolveEncoding(this.declaredEncoding, buf);
|
|
852
|
-
}
|
|
853
|
-
detectBinaryContent(buf) {
|
|
854
|
-
const isFirst = this.firstChunk;
|
|
855
|
-
this.firstChunk = false;
|
|
856
|
-
if ((isFirst && hasBinarySignature(buf)) ||
|
|
857
|
-
(!isUnicodeWideEncoding(this.effectiveEncoding) &&
|
|
858
|
-
hasNullByte(buf, BINARY_NULL_CHECK_LIMIT))) {
|
|
859
|
-
return createBinaryContentError(this.url);
|
|
860
|
-
}
|
|
861
|
-
return null;
|
|
862
|
-
}
|
|
863
|
-
enforceByteLimit(buf, callback) {
|
|
864
|
-
const newTotal = this.total + buf.length;
|
|
865
|
-
if (newTotal > this.byteLimit) {
|
|
866
|
-
const remaining = this.byteLimit - this.total;
|
|
867
|
-
if (remaining > 0) {
|
|
868
|
-
const slice = buf.subarray(0, remaining);
|
|
869
|
-
this.total += remaining;
|
|
870
|
-
if (this.captureChunks)
|
|
871
|
-
this.chunks.push(slice);
|
|
872
|
-
this.push(slice);
|
|
873
|
-
}
|
|
874
|
-
callback(new MaxBytesError());
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
this.total = newTotal;
|
|
878
|
-
if (this.captureChunks)
|
|
879
|
-
this.chunks.push(buf);
|
|
880
|
-
callback(null, buf);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
804
|
function assertNonBinaryContent(buffer, encoding, url) {
|
|
884
805
|
if (isBinaryContent(buffer, encoding)) {
|
|
885
806
|
throw createBinaryContentError(url);
|
|
@@ -935,20 +856,53 @@ class ResponseTextReader {
|
|
|
935
856
|
const byteLimit = maxBytes <= 0 ? Number.POSITIVE_INFINITY : maxBytes;
|
|
936
857
|
const captureChunks = byteLimit !== Number.POSITIVE_INFINITY;
|
|
937
858
|
const source = Readable.fromWeb(toNodeReadableStream(stream, url, 'response:read-stream-buffer'));
|
|
938
|
-
const guard = new BoundedBufferTransform(byteLimit, captureChunks, url, encoding);
|
|
939
859
|
const chunks = [];
|
|
860
|
+
let total = 0;
|
|
861
|
+
let effectiveEncoding = encoding ?? 'utf-8';
|
|
862
|
+
let encodingResolved = false;
|
|
863
|
+
let firstChunk = true;
|
|
864
|
+
async function* guard(sourceIterable) {
|
|
865
|
+
for await (const chunk of sourceIterable) {
|
|
866
|
+
const buf = chunk instanceof Uint8Array
|
|
867
|
+
? chunk
|
|
868
|
+
: new Uint8Array(chunk);
|
|
869
|
+
if (!encodingResolved) {
|
|
870
|
+
encodingResolved = true;
|
|
871
|
+
effectiveEncoding = resolveEncoding(encoding, buf);
|
|
872
|
+
}
|
|
873
|
+
if ((firstChunk && hasBinarySignature(buf)) ||
|
|
874
|
+
(!isUnicodeWideEncoding(effectiveEncoding) &&
|
|
875
|
+
hasNullByte(buf, BINARY_NULL_CHECK_LIMIT))) {
|
|
876
|
+
throw createBinaryContentError(url);
|
|
877
|
+
}
|
|
878
|
+
firstChunk = false;
|
|
879
|
+
const newTotal = total + buf.length;
|
|
880
|
+
if (newTotal > byteLimit) {
|
|
881
|
+
const remaining = byteLimit - total;
|
|
882
|
+
if (remaining > 0) {
|
|
883
|
+
const slice = buf.subarray(0, remaining);
|
|
884
|
+
total += remaining;
|
|
885
|
+
yield slice;
|
|
886
|
+
}
|
|
887
|
+
throw new MaxBytesError();
|
|
888
|
+
}
|
|
889
|
+
total = newTotal;
|
|
890
|
+
yield buf;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
940
893
|
try {
|
|
941
894
|
await pipeline(source, guard, async (iterable) => {
|
|
942
895
|
for await (const chunk of iterable) {
|
|
943
|
-
|
|
896
|
+
if (captureChunks)
|
|
897
|
+
chunks.push(chunk);
|
|
944
898
|
}
|
|
945
899
|
},
|
|
946
900
|
// Only pass `{ signal }` if signal exists to avoid type errors with exactOptionalPropertyTypes
|
|
947
901
|
...(signal ? [{ signal }] : []));
|
|
948
902
|
return {
|
|
949
|
-
buffer:
|
|
950
|
-
encoding:
|
|
951
|
-
size:
|
|
903
|
+
buffer: Buffer.concat(chunks, total),
|
|
904
|
+
encoding: effectiveEncoding,
|
|
905
|
+
size: total,
|
|
952
906
|
truncated: false,
|
|
953
907
|
};
|
|
954
908
|
}
|
|
@@ -960,9 +914,9 @@ class ResponseTextReader {
|
|
|
960
914
|
throw error;
|
|
961
915
|
if (error instanceof MaxBytesError) {
|
|
962
916
|
return {
|
|
963
|
-
buffer:
|
|
964
|
-
encoding:
|
|
965
|
-
size:
|
|
917
|
+
buffer: Buffer.concat(chunks, total),
|
|
918
|
+
encoding: effectiveEncoding,
|
|
919
|
+
size: total,
|
|
966
920
|
truncated: true,
|
|
967
921
|
};
|
|
968
922
|
}
|
|
@@ -1024,7 +978,7 @@ function toWebReadableStream(stream, url, stage) {
|
|
|
1024
978
|
assertReadableStreamLike(converted, url, stage);
|
|
1025
979
|
return converted;
|
|
1026
980
|
}
|
|
1027
|
-
const
|
|
981
|
+
const fetchChannels = diagnosticsChannel.tracingChannel('fetch-url-mcp.fetch');
|
|
1028
982
|
const SLOW_REQUEST_THRESHOLD_MS = 5000;
|
|
1029
983
|
class FetchTelemetry {
|
|
1030
984
|
logger;
|
|
@@ -1061,14 +1015,21 @@ class FetchTelemetry {
|
|
|
1061
1015
|
if (operationId)
|
|
1062
1016
|
ctx.operationId = operationId;
|
|
1063
1017
|
const fields = this.contextFields(ctx);
|
|
1064
|
-
|
|
1018
|
+
const event = {
|
|
1065
1019
|
v: 1,
|
|
1066
|
-
type: 'start',
|
|
1067
1020
|
requestId: ctx.requestId,
|
|
1068
1021
|
method: ctx.method,
|
|
1069
1022
|
url: ctx.url,
|
|
1070
1023
|
...fields,
|
|
1071
|
-
}
|
|
1024
|
+
};
|
|
1025
|
+
if (fetchChannels.hasSubscribers) {
|
|
1026
|
+
try {
|
|
1027
|
+
fetchChannels.start.publish(event);
|
|
1028
|
+
}
|
|
1029
|
+
catch {
|
|
1030
|
+
// Best-effort telemetry; never crash request path.
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1072
1033
|
this.logger.debug('HTTP Request', {
|
|
1073
1034
|
requestId: ctx.requestId,
|
|
1074
1035
|
method: ctx.method,
|
|
@@ -1081,14 +1042,21 @@ class FetchTelemetry {
|
|
|
1081
1042
|
const duration = performance.now() - context.startTime;
|
|
1082
1043
|
const durationLabel = `${Math.round(duration)}ms`;
|
|
1083
1044
|
const fields = this.contextFields(context);
|
|
1084
|
-
|
|
1045
|
+
const event = {
|
|
1085
1046
|
v: 1,
|
|
1086
|
-
type: 'end',
|
|
1087
1047
|
requestId: context.requestId,
|
|
1088
1048
|
status: response.status,
|
|
1089
1049
|
duration,
|
|
1090
1050
|
...fields,
|
|
1091
|
-
}
|
|
1051
|
+
};
|
|
1052
|
+
if (fetchChannels.hasSubscribers) {
|
|
1053
|
+
try {
|
|
1054
|
+
fetchChannels.end.publish(event);
|
|
1055
|
+
}
|
|
1056
|
+
catch {
|
|
1057
|
+
// Best-effort telemetry; never crash request path.
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1092
1060
|
const contentType = response.headers.get('content-type') ?? undefined;
|
|
1093
1061
|
const contentLengthHeader = response.headers.get('content-length');
|
|
1094
1062
|
const size = contentLengthHeader ??
|
|
@@ -1116,9 +1084,8 @@ class FetchTelemetry {
|
|
|
1116
1084
|
const err = toError(error);
|
|
1117
1085
|
const code = isSystemError(err) ? err.code : undefined;
|
|
1118
1086
|
const fields = this.contextFields(context);
|
|
1119
|
-
|
|
1087
|
+
const event = {
|
|
1120
1088
|
v: 1,
|
|
1121
|
-
type: 'error',
|
|
1122
1089
|
requestId: context.requestId,
|
|
1123
1090
|
url: context.url,
|
|
1124
1091
|
error: err.message,
|
|
@@ -1126,7 +1093,15 @@ class FetchTelemetry {
|
|
|
1126
1093
|
...(code !== undefined ? { code } : {}),
|
|
1127
1094
|
...(status !== undefined ? { status } : {}),
|
|
1128
1095
|
...fields,
|
|
1129
|
-
}
|
|
1096
|
+
};
|
|
1097
|
+
if (fetchChannels.hasSubscribers) {
|
|
1098
|
+
try {
|
|
1099
|
+
fetchChannels.error.publish(event);
|
|
1100
|
+
}
|
|
1101
|
+
catch {
|
|
1102
|
+
// Best-effort telemetry; never crash request path.
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1130
1105
|
const logData = {
|
|
1131
1106
|
requestId: context.requestId,
|
|
1132
1107
|
url: context.url,
|
|
@@ -1141,16 +1116,6 @@ class FetchTelemetry {
|
|
|
1141
1116
|
}
|
|
1142
1117
|
this.logger.error('HTTP Request Error', logData);
|
|
1143
1118
|
}
|
|
1144
|
-
publish(event) {
|
|
1145
|
-
if (!fetchChannel.hasSubscribers)
|
|
1146
|
-
return;
|
|
1147
|
-
try {
|
|
1148
|
-
fetchChannel.publish(event);
|
|
1149
|
-
}
|
|
1150
|
-
catch {
|
|
1151
|
-
// Best-effort telemetry; never crash request path.
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
1119
|
}
|
|
1155
1120
|
const defaultLogger = {
|
|
1156
1121
|
debug: logDebug,
|
package/package.json
CHANGED