@lousy-agents/mcp 5.22.17 → 5.22.19

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.
Files changed (2) hide show
  1. package/dist/mcp-server.js +170 -21
  2. package/package.json +1 -1
@@ -61621,7 +61621,8 @@ const lockFiles = [
61621
61621
  "npm-shrinkwrap.json",
61622
61622
  "bun.lockb",
61623
61623
  "bun.lock",
61624
- "deno.lock"
61624
+ "deno.lock",
61625
+ "upm.lock"
61625
61626
  ];
61626
61627
  const packageFiles = [
61627
61628
  "package.json",
@@ -61855,11 +61856,27 @@ async function readGitConfig(dir, opts) {
61855
61856
  async function writeGitConfig(path, config) {
61856
61857
  await writeFile(path, stringifyGitConfig(config));
61857
61858
  }
61859
+ const _unescapeGitSubsection = (name) => name.replaceAll(/\\(.)/g, "$1");
61860
+ const _escapeGitSubsection = (name) => name.replaceAll(/(["\\])/g, "\\$1");
61861
+ const _escapeINISection = (name) => name.replaceAll("\\", String.raw`\\`).replaceAll(".", String.raw`\.`);
61862
+ const _encodeINISection = (name) => encodeURIComponent(name).replaceAll(".", "%2E");
61863
+ const _decodeINISection = (name) => decodeURIComponent(name);
61864
+ const _isSubsection = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
61858
61865
  function parseGitConfig(ini) {
61859
- return parseINI(ini.replaceAll(/^\[(\w+) "(.+)"\]$/gm, "[$1.$2]"));
61866
+ return parseINI(ini.replaceAll(/^\[(\w+) "(.*)"\]$/gm, (_, section, subsection) => `[${section}.${_escapeINISection(_unescapeGitSubsection(subsection))}]`));
61860
61867
  }
61861
61868
  function stringifyGitConfig(config) {
61862
- return stringifyINI(config).replaceAll(/^\[(\w+)\.(\w+)\]$/gm, "[$1 \"$2\"]");
61869
+ const encoded = {};
61870
+ for (const [section, value] of Object.entries(config)) {
61871
+ if (!_isSubsection(value)) {
61872
+ encoded[section] = value;
61873
+ continue;
61874
+ }
61875
+ const entries = {};
61876
+ for (const [key, subValue] of Object.entries(value)) entries[_isSubsection(subValue) ? _encodeINISection(key) : key] = subValue;
61877
+ encoded[section] = entries;
61878
+ }
61879
+ return stringifyINI(encoded).replaceAll(/^\[(\w+)\.(.*)\]$/gm, (_, section, subsection) => `[${section} "${_escapeGitSubsection(_decodeINISection(subsection))}"]`);
61863
61880
  }
61864
61881
 
61865
61882
 
@@ -99968,22 +99985,30 @@ module.exports = __rspack_createRequire_require("zlib");
99968
99985
  4800(module, __unused_rspack_exports, __webpack_require__) {
99969
99986
 
99970
99987
 
99971
- const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = __webpack_require__(5365)
99988
+ const { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = __webpack_require__(5365)
99972
99989
  const { SCHEMES, getSchemeHandler } = __webpack_require__(3956)
99973
99990
 
99974
99991
  const VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u
99975
99992
  const MALFORMED_SCHEME_ERROR = 'URI scheme is malformed.'
99993
+ const MALFORMED_HOST_ERROR = 'URI host is malformed.'
99994
+ // ASCII characters outside the RFC 3986 reg-name set (unreserved, sub-delims
99995
+ // and "%" for pct-encoded): C0 controls, space, DEL, and "#/:<>?@[\]^`{|}.
99996
+ const FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u
99976
99997
 
99977
99998
  /**
99999
+ * RFC 3986 section 3.1 does not allow percent-encoding in a scheme, so the
100000
+ * scheme is validated as-is and never decoded. Decoding would let inert input
100001
+ * such as `%6Aavascript:` become a live `javascript:` scheme.
100002
+ *
99978
100003
  * @param {string} scheme
99979
100004
  * @returns {string}
99980
100005
  */
99981
- function decodeValidScheme (scheme) {
99982
- const decodedScheme = unescape(String(scheme))
99983
- if (!VALID_SCHEME.test(decodedScheme)) {
100006
+ function validateScheme (scheme) {
100007
+ const stringScheme = String(scheme)
100008
+ if (!VALID_SCHEME.test(stringScheme)) {
99984
100009
  throw new TypeError(MALFORMED_SCHEME_ERROR)
99985
100010
  }
99986
- return decodedScheme
100011
+ return stringScheme
99987
100012
  }
99988
100013
 
99989
100014
  /**
@@ -100044,6 +100069,12 @@ function resolve (baseURI, relativeURI, options) {
100044
100069
  const resolvedHost = resolved.host
100045
100070
  const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== '' &&
100046
100071
  (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6)
100072
+ // Inputs are parsed scheme-neutrally, so delimiters and controls in a domain
100073
+ // host are only detected here, once the final scheme is known. They always
100074
+ // fail closed, including in percent-encoded hosts.
100075
+ if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
100076
+ throw new Error(MALFORMED_HOST_ERROR)
100077
+ }
100047
100078
  canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)
100048
100079
  // Percent escapes in an ASCII reg-name are encoded data. The WHATWG hostname
100049
100080
  // parser can reject them even though fast-uri preserves them safely as RFC
@@ -100101,14 +100132,17 @@ function resolveComponent (base, relative, options, skipNormalization) {
100101
100132
  if (relative.path[0] === '/') {
100102
100133
  target.path = removeDotSegments(relative.path)
100103
100134
  } else {
100104
- if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) {
100135
+ const baseHasAuthority = base.userinfo !== undefined || base.host !== undefined || base.port !== undefined
100136
+ if (baseHasAuthority && !base.path) {
100105
100137
  target.path = '/' + relative.path
100106
100138
  } else if (!base.path) {
100107
100139
  target.path = relative.path
100108
100140
  } else {
100109
100141
  target.path = base.path.slice(0, base.path.lastIndexOf('/') + 1) + relative.path
100110
100142
  }
100111
- target.path = removeDotSegments(target.path)
100143
+ target.path = base.scheme === undefined && !baseHasAuthority && target.path[0] !== '/'
100144
+ ? removeDotSegmentsRelative(target.path)
100145
+ : removeDotSegments(target.path)
100112
100146
  }
100113
100147
  target.query = relative.query
100114
100148
  }
@@ -100164,7 +100198,7 @@ function serialize (cmpts, opts) {
100164
100198
  const uriTokens = []
100165
100199
 
100166
100200
  if (component.scheme) {
100167
- component.scheme = decodeValidScheme(component.scheme)
100201
+ component.scheme = validateScheme(component.scheme)
100168
100202
  }
100169
100203
 
100170
100204
  // find scheme handler
@@ -100174,7 +100208,11 @@ function serialize (cmpts, opts) {
100174
100208
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options)
100175
100209
 
100176
100210
  const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined
100177
- const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority
100211
+ // A reference with no emitted scheme is path-noscheme: a bare ':' in its
100212
+ // first path segment would reparse as a scheme (RFC 3986 3.3/4.2). Enforced
100213
+ // under skipEscape too (resolve()), so the reapply below gates on this flag.
100214
+ const schemelessReference = (component.scheme === undefined || options.reference === 'suffix') && !hasAuthority
100215
+ const pathNoScheme = !options.skipEscape && schemelessReference
100178
100216
 
100179
100217
  if (component.path !== undefined) {
100180
100218
  if (!options.skipEscape) {
@@ -100186,7 +100224,7 @@ function serialize (cmpts, opts) {
100186
100224
 
100187
100225
  if (options.reference !== 'suffix' && component.scheme) {
100188
100226
  // Scheme handlers may replace the scheme during serialization.
100189
- component.scheme = decodeValidScheme(component.scheme)
100227
+ component.scheme = validateScheme(component.scheme)
100190
100228
  uriTokens.push(component.scheme, ':')
100191
100229
  }
100192
100230
 
@@ -100206,13 +100244,18 @@ function serialize (cmpts, opts) {
100206
100244
  let s = component.path
100207
100245
 
100208
100246
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
100209
- s = removeDotSegments(s)
100247
+ // A relative-path reference keeps its unresolvable leading ".." segments
100248
+ // and must never become root-relative (RFC 3986 section 5.2.4 applies to
100249
+ // merged target paths, not to the reference itself).
100250
+ s = schemelessReference && s[0] !== '/'
100251
+ ? removeDotSegmentsRelative(s)
100252
+ : removeDotSegments(s)
100210
100253
  }
100211
100254
 
100212
100255
  // Dot-segment removal can expose a colon that was not originally in the
100213
100256
  // first segment (for example, "./a:b"). Reapply path-noscheme encoding so
100214
100257
  // the serialized relative reference cannot be reparsed as a URI scheme.
100215
- if (pathNoScheme) {
100258
+ if (schemelessReference) {
100216
100259
  s = serializePathEncoding(s, true)
100217
100260
  }
100218
100261
 
@@ -100327,6 +100370,11 @@ function hasMalformedComponentPercentEncoding (matches) {
100327
100370
  * @returns {boolean} whether host conversion failed
100328
100371
  */
100329
100372
  function canonicalizeHost (parsed, options, schemeHandler, isIP) {
100373
+ if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
100374
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR
100375
+ return true
100376
+ }
100377
+
100330
100378
  if (
100331
100379
  !options.unicodeSupport &&
100332
100380
  (!schemeHandler || !schemeHandler.unicodeSupport) &&
@@ -100336,16 +100384,62 @@ function canonicalizeHost (parsed, options, schemeHandler, isIP) {
100336
100384
  isIP === false &&
100337
100385
  nonSimpleDomain(parsed.host)
100338
100386
  ) {
100387
+ let url
100339
100388
  try {
100340
- parsed.host = new URL('http://' + parsed.host).hostname
100389
+ url = new URL('http://' + parsed.host)
100341
100390
  } catch (e) {
100342
100391
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
100343
100392
  return true
100344
100393
  }
100394
+ // The WHATWG parser must have read the whole input as a host. Anything
100395
+ // else means it found a delimiter fast-uri did not, so its hostname would
100396
+ // describe a different authority.
100397
+ if (!isHostOnlyURL(url)) {
100398
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR
100399
+ return true
100400
+ }
100401
+ parsed.host = url.hostname
100345
100402
  }
100346
100403
  return false
100347
100404
  }
100348
100405
 
100406
+ /**
100407
+ * Whether a domain host contains an ASCII character that RFC 3986 does not
100408
+ * allow in a reg-name (unreserved, sub-delims, or pct-encoded). Such a host is
100409
+ * rejected instead of being handed to the WHATWG URL parser, which would treat
100410
+ * "@" as a userinfo delimiter (silently dropping the data before it) and strip
100411
+ * TAB, LF, and CR. Non-ASCII characters remain allowed for IDNs.
100412
+ *
100413
+ * @param {import('./types/index').URIComponent} parsed
100414
+ * @param {import('./types/index').Options} options
100415
+ * @param {{ domainHost?: boolean }|undefined} schemeHandler
100416
+ * @param {boolean} isIP
100417
+ * @returns {boolean}
100418
+ */
100419
+ function hasForbiddenDomainHost (parsed, options, schemeHandler, isIP) {
100420
+ return Boolean(
100421
+ parsed.host &&
100422
+ isIP === false &&
100423
+ !isIPLiteral(parsed.host) &&
100424
+ (options.domainHost || (schemeHandler && schemeHandler.domainHost)) &&
100425
+ FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host)
100426
+ )
100427
+ }
100428
+
100429
+ /**
100430
+ * @param {URL} url
100431
+ * @returns {boolean}
100432
+ */
100433
+ function isHostOnlyURL (url) {
100434
+ return url.username === '' &&
100435
+ url.password === '' &&
100436
+ url.port === '' &&
100437
+ url.pathname === '/' &&
100438
+ url.search === '' &&
100439
+ url.hash === '' &&
100440
+ url.hostname !== ''
100441
+ }
100442
+
100349
100443
  /**
100350
100444
  * @param {string} uri
100351
100445
  * @param {import('./types/index').Options} [opts]
@@ -100428,9 +100522,8 @@ function parseWithStatus (uri, opts) {
100428
100522
  parsed.fragment = matches[8]
100429
100523
 
100430
100524
  if (parsed.scheme !== undefined) {
100431
- const decodedScheme = unescape(parsed.scheme)
100432
- if (VALID_SCHEME.test(decodedScheme)) {
100433
- parsed.scheme = decodedScheme.toLowerCase()
100525
+ if (VALID_SCHEME.test(parsed.scheme)) {
100526
+ parsed.scheme = parsed.scheme.toLowerCase()
100434
100527
  } else {
100435
100528
  parsed.error = parsed.error || MALFORMED_SCHEME_ERROR
100436
100529
  malformedScheme = true
@@ -100464,7 +100557,7 @@ function parseWithStatus (uri, opts) {
100464
100557
  parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase()
100465
100558
 
100466
100559
  if (malformedIPLiteral) {
100467
- parsed.error = parsed.error || 'URI host is malformed.'
100560
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR
100468
100561
  malformedAuthorityOrPort = true
100469
100562
  }
100470
100563
  } else {
@@ -100501,7 +100594,11 @@ function parseWithStatus (uri, opts) {
100501
100594
  // pass only restores uppercase hex in escapes that remain encoded.
100502
100595
  host = normalizePercentEncoding(host.toLowerCase())
100503
100596
  }
100504
- parsed.host = reescapeHostDelimiters(host, isIP)
100597
+ // Resolution parses both inputs with the internal `null` scheme before
100598
+ // the effective scheme is known. Keep raw host delimiters for that step
100599
+ // so resolve() can reject them for domain-host schemes; the resolved
100600
+ // authority is encoded when it is serialized.
100601
+ parsed.host = isIP || options.scheme !== 'null' ? reescapeHostDelimiters(host, isIP) : host
100505
100602
  }
100506
100603
 
100507
100604
  if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {
@@ -101224,6 +101321,57 @@ function removeDotSegments (path) {
101224
101321
  return output.join('')
101225
101322
  }
101226
101323
 
101324
+ /**
101325
+ * Removes dot segments from the path of a relative reference (a path that does
101326
+ * not start with "/") without changing what it resolves to against any base.
101327
+ * Unlike `removeDotSegments`, leading ".." segments that cannot be collapsed
101328
+ * are kept, the result never becomes root-relative, and a result that would be
101329
+ * empty or start with an empty segment is prefixed with "." (an empty path
101330
+ * means "same document" and a leading "//" would introduce an authority).
101331
+ *
101332
+ * @param {string} path
101333
+ * @returns {string}
101334
+ */
101335
+ function removeDotSegmentsRelative (path) {
101336
+ if (path === '' || path[0] === '/') {
101337
+ return removeDotSegments(path)
101338
+ }
101339
+
101340
+ const segments = path.split('/')
101341
+ const last = segments.length - 1
101342
+ /** @type {string[]} */
101343
+ const output = []
101344
+ let trailingDirectory = false
101345
+
101346
+ for (let i = 0; i <= last; i++) {
101347
+ const segment = segments[i]
101348
+ if (segment === '.' || segment === '..') {
101349
+ if (segment === '..') {
101350
+ if (output.length !== 0 && output[output.length - 1] !== '..') {
101351
+ output.pop()
101352
+ } else {
101353
+ output.push('..')
101354
+ }
101355
+ }
101356
+ trailingDirectory = i === last
101357
+ } else {
101358
+ output.push(segment)
101359
+ }
101360
+ }
101361
+
101362
+ if (trailingDirectory && output[output.length - 1] !== '..') {
101363
+ output.push('')
101364
+ }
101365
+ if (output.length === 0 || output[0] === '' || output[0].includes(':')) {
101366
+ // An empty path, a leading empty segment ("//"), or a first segment
101367
+ // containing ":" (which would be mistaken for a scheme, RFC 3986 section
101368
+ // 4.2) must stay behind a "." segment so the reference keeps its meaning.
101369
+ output.unshift('.')
101370
+ }
101371
+
101372
+ return output.join('/')
101373
+ }
101374
+
101227
101375
  /**
101228
101376
  * Re-escape RFC 3986 gen-delims that must not appear literally in the host.
101229
101377
  * After the URI regex parses, these characters cannot be literal in the host
@@ -101619,6 +101767,7 @@ module.exports = {
101619
101767
  encodeFragment,
101620
101768
  escapePreservingEscapes,
101621
101769
  removeDotSegments,
101770
+ removeDotSegmentsRelative,
101622
101771
  isIPv4,
101623
101772
  isUUID,
101624
101773
  normalizeIPv6,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/mcp",
3
- "version": "5.22.17",
3
+ "version": "5.22.19",
4
4
  "description": "MCP server for lousy-agents - provides AI coding assistant tools via the Model Context Protocol",
5
5
  "type": "module",
6
6
  "repository": {