UNPKG

@modelcontextprotocol/sdk

Version:

Model Context Protocol implementation for TypeScript

243 lines 8.86 kB
"use strict"; // Claude-authored implementation of RFC 6570 URI Templates Object.defineProperty(exports, "__esModule", { value: true }); exports.UriTemplate = void 0; const MAX_TEMPLATE_LENGTH = 1000000; // 1MB const MAX_VARIABLE_LENGTH = 1000000; // 1MB const MAX_TEMPLATE_EXPRESSIONS = 10000; const MAX_REGEX_LENGTH = 1000000; // 1MB class UriTemplate { /** * Returns true if the given string contains any URI template expressions. * A template expression is a sequence of characters enclosed in curly braces, * like {foo} or {?bar}. */ static isTemplate(str) { // Look for any sequence of characters between curly braces // that isn't just whitespace return /\{[^}\s]+\}/.test(str); } static validateLength(str, max, context) { if (str.length > max) { throw new Error(`${context} exceeds maximum length of ${max} characters (got ${str.length})`); } } get variableNames() { return this.parts.flatMap(part => (typeof part === 'string' ? [] : part.names)); } constructor(template) { UriTemplate.validateLength(template, MAX_TEMPLATE_LENGTH, 'Template'); this.template = template; this.parts = this.parse(template); } toString() { return this.template; } parse(template) { const parts = []; let currentText = ''; let i = 0; let expressionCount = 0; while (i < template.length) { if (template[i] === '{') { if (currentText) { parts.push(currentText); currentText = ''; } const end = template.indexOf('}', i); if (end === -1) throw new Error('Unclosed template expression'); expressionCount++; if (expressionCount > MAX_TEMPLATE_EXPRESSIONS) { throw new Error(`Template contains too many expressions (max ${MAX_TEMPLATE_EXPRESSIONS})`); } const expr = template.slice(i + 1, end); const operator = this.getOperator(expr); const exploded = expr.includes('*'); const names = this.getNames(expr); const name = names[0]; // Validate variable name length for (const name of names) { UriTemplate.validateLength(name, MAX_VARIABLE_LENGTH, 'Variable name'); } parts.push({ name, operator, names, exploded }); i = end + 1; } else { currentText += template[i]; i++; } } if (currentText) { parts.push(currentText); } return parts; } getOperator(expr) { const operators = ['+', '#', '.', '/', '?', '&']; return operators.find(op => expr.startsWith(op)) || ''; } getNames(expr) { const operator = this.getOperator(expr); return expr .slice(operator.length) .split(',') .map(name => name.replace('*', '').trim()) .filter(name => name.length > 0); } encodeValue(value, operator) { UriTemplate.validateLength(value, MAX_VARIABLE_LENGTH, 'Variable value'); if (operator === '+' || operator === '#') { return encodeURI(value); } return encodeURIComponent(value); } expandPart(part, variables) { if (part.operator === '?' || part.operator === '&') { const pairs = part.names .map(name => { const value = variables[name]; if (value === undefined) return ''; const encoded = Array.isArray(value) ? value.map(v => this.encodeValue(v, part.operator)).join(',') : this.encodeValue(value.toString(), part.operator); return `${name}=${encoded}`; }) .filter(pair => pair.length > 0); if (pairs.length === 0) return ''; const separator = part.operator === '?' ? '?' : '&'; return separator + pairs.join('&'); } if (part.names.length > 1) { const values = part.names.map(name => variables[name]).filter(v => v !== undefined); if (values.length === 0) return ''; return values.map(v => (Array.isArray(v) ? v[0] : v)).join(','); } const value = variables[part.name]; if (value === undefined) return ''; const values = Array.isArray(value) ? value : [value]; const encoded = values.map(v => this.encodeValue(v, part.operator)); switch (part.operator) { case '': return encoded.join(','); case '+': return encoded.join(','); case '#': return '#' + encoded.join(','); case '.': return '.' + encoded.join('.'); case '/': return '/' + encoded.join('/'); default: return encoded.join(','); } } expand(variables) { let result = ''; let hasQueryParam = false; for (const part of this.parts) { if (typeof part === 'string') { result += part; continue; } const expanded = this.expandPart(part, variables); if (!expanded) continue; // Convert ? to & if we already have a query parameter if ((part.operator === '?' || part.operator === '&') && hasQueryParam) { result += expanded.replace('?', '&'); } else { result += expanded; } if (part.operator === '?' || part.operator === '&') { hasQueryParam = true; } } return result; } escapeRegExp(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } partToRegExp(part) { const patterns = []; // Validate variable name length for matching for (const name of part.names) { UriTemplate.validateLength(name, MAX_VARIABLE_LENGTH, 'Variable name'); } if (part.operator === '?' || part.operator === '&') { for (let i = 0; i < part.names.length; i++) { const name = part.names[i]; const prefix = i === 0 ? '\\' + part.operator : '&'; patterns.push({ pattern: prefix + this.escapeRegExp(name) + '=([^&]+)', name }); } return patterns; } let pattern; const name = part.name; switch (part.operator) { case '': pattern = part.exploded ? '([^/]+(?:,[^/]+)*)' : '([^/,]+)'; break; case '+': case '#': pattern = '(.+)'; break; case '.': pattern = '\\.([^/,]+)'; break; case '/': pattern = '/' + (part.exploded ? '([^/]+(?:,[^/]+)*)' : '([^/,]+)'); break; default: pattern = '([^/]+)'; } patterns.push({ pattern, name }); return patterns; } match(uri) { UriTemplate.validateLength(uri, MAX_TEMPLATE_LENGTH, 'URI'); let pattern = '^'; const names = []; for (const part of this.parts) { if (typeof part === 'string') { pattern += this.escapeRegExp(part); } else { const patterns = this.partToRegExp(part); for (const { pattern: partPattern, name } of patterns) { pattern += partPattern; names.push({ name, exploded: part.exploded }); } } } pattern += '$'; UriTemplate.validateLength(pattern, MAX_REGEX_LENGTH, 'Generated regex pattern'); const regex = new RegExp(pattern); const match = uri.match(regex); if (!match) return null; const result = {}; for (let i = 0; i < names.length; i++) { const { name, exploded } = names[i]; const value = match[i + 1]; const cleanName = name.replace('*', ''); if (exploded && value.includes(',')) { result[cleanName] = value.split(','); } else { result[cleanName] = value; } } return result; } } exports.UriTemplate = UriTemplate; //# sourceMappingURL=uriTemplate.js.map