1/* SerpWise WebMCP bridge 1.0.0 | https://serpwise.ai/docs/mcp-server */ 2(function () { 3 "use strict"; 4 5 if (typeof window === "undefined" || window.__swWebMcp) { return; } 6 7 var VERSION = "1.0.0"; 8 var PROTOCOL = "2025-06-18"; 9 var ENDPOINT = "/mcp"; 10 var BINDINGS = [{"n":"search_site","d":"Search this website. Returns ranked pages with title, URL and snippet.","p":"query","pd":"What to look for on this site, in natural language","t":"search_content","a":"query"},{"n":"get_page","d":"Read a page of this website as markdown, by its URL path.","p":"path","pd":"URL path of the page, for example /about or /blog/my-post","t":"get_page","a":"url_path"}]; 11 var TIMEOUT_MS = 20000; 12 var MAX_BYTES = 262144; 13 14 window.__swWebMcp = { version: VERSION, registered: 0 }; 15 16 var seq = 0; 17 var sessionId = ""; 18 var initialized = false; 19 20 function headers() { 21 var h = { 22 "Content-Type": "application/json", 23 "Accept": "application/json, text/event-stream", 24 "MCP-Protocol-Version": PROTOCOL 25 }; 26 if (sessionId) { h["Mcp-Session-Id"] = sessionId; } 27 return h; 28 } 29 30 function parseEnvelope(text) { 31 var body = (text || "").replace(/^\s+|\s+$/g, ""); 32 if (body && body.charAt(0) !== "{" && body.charAt(0) !== "[") { 33 var lines = body.split(/\r?\n/); 34 var joined = ""; 35 for (var i = 0; i < lines.length; i++) { 36 if (lines[i].indexOf("data:") === 0) { 37 joined += lines[i].slice(5).replace(/^\s+/, ""); 38 } 39 } 40 body = joined; 41 } 42 if (!body) { return null; } 43 try { return JSON.parse(body); } catch (e) { return null; } 44 } 45 46 function post(payload) { 47 var controller = typeof AbortController === "function" ? new AbortController() : null; 48 var timer = null; 49 if (controller) { 50 timer = setTimeout(function () { controller.abort(); }, TIMEOUT_MS); 51 } 52 function done() { 53 if (timer) { clearTimeout(timer); timer = null; } 54 } 55 var options = { 56 method: "POST", 57 headers: headers(), 58 credentials: "omit", 59 body: JSON.stringify(payload) 60 }; 61 if (controller) { options.signal = controller.signal; } 62 63 return fetch(ENDPOINT, options).then(function (res) { 64 var sid = res.headers.get("Mcp-Session-Id"); 65 if (sid) { sessionId = sid; } 66 var declared = parseInt(res.headers.get("Content-Length") || "", 10); 67 if (declared > MAX_BYTES) { 68 done(); 69 if (controller) { controller.abort(); } 70 return { status: res.status, envelope: null, tooLarge: true }; 71 } 72 return res.text().then(function (text) { 73 done(); 74 if (text.length > MAX_BYTES) { 75 return { status: res.status, envelope: null, tooLarge: true }; 76 } 77 return { status: res.status, envelope: parseEnvelope(text) }; 78 }, function (err) { done(); throw err; }); 79 }, function (err) { done(); throw err; }); 80 } 81 82 function rpc(method, params) { 83 return post({ jsonrpc: "2.0", id: ++seq, method: method, params: params }); 84 } 85 86 function needsInitialize(out) { 87 if (initialized) { return false; } 88 if (out.status === 400) { return true; } 89 var err = out.envelope && out.envelope.error; 90 var message = err && typeof err.message === "string" ? err.message.toLowerCase() : ""; 91 return message.indexOf("initial") >= 0; 92 } 93 94 function initialize() { 95 return rpc("initialize", { 96 protocolVersion: PROTOCOL, 97 capabilities: {}, 98 clientInfo: { name: "serpwise-webmcp", version: VERSION } 99 }).then(function () { 100 initialized = true; 101 return post({ jsonrpc: "2.0", method: "notifications/initialized" }).then( 102 function () { return true; }, 103 function () { return true; } 104 ); 105 }); 106 } 107 108 function textResult(message) { 109 return { content: [{ type: "text", text: message }], isError: true }; 110 } 111 112 function unwrap(out) { 113 if (out.tooLarge) { 114 return textResult("MCP response was too large to read."); 115 } 116 var envelope = out.envelope; 117 if (envelope && envelope.result) { return envelope.result; } 118 if (envelope && envelope.error && envelope.error.message) { 119 return textResult(envelope.error.message); 120 } 121 if (out.status === 429) {
122 return textResult("Rate limited by this site. Try again in a minute."); 123 } 124 if (out.status === 404) { 125 return textResult("This site has no MCP endpoint."); 126 } 127 return textResult("MCP endpoint returned status " + out.status + "."); 128 } 129 130 function callTool(tool, args) { 131 return rpc("tools/call", { name: tool, arguments: args }).then(function (out) { 132 if (!needsInitialize(out)) { return out; } 133 return initialize().then(function () { 134 return rpc("tools/call", { name: tool, arguments: args }); 135 }); 136 }).then(unwrap, function (err) { 137 var why = err && err.name === "AbortError" ? "timed out" : "failed"; 138 return textResult("MCP request " + why + "."); 139 }); 140 } 141 142 function descriptor(binding) { 143 var properties = {}; 144 properties[binding.p] = { type: "string", description: binding.pd }; 145 return { 146 name: binding.n, 147 description: binding.d, 148 inputSchema: { 149 type: "object", 150 properties: properties, 151 required: [binding.p] 152 }, 153 execute: function (args) { 154 var value = args && args[binding.p] != null ? String(args[binding.p]) : ""; 155 var payload = {}; 156 payload[binding.a] = value; 157 return callTool(binding.t, payload); 158 } 159 }; 160 } 161 162 function descriptors() { 163 var out = []; 164 for (var i = 0; i < BINDINGS.length; i++) {
165 out.push(descriptor(BINDINGS[i])); 166 } 167 return out; 168 } 169 170 function pageSummary() { 171 if (typeof document === "undefined") { return ""; } 172 var parts = []; 173 if (document.title) { parts.push(document.title); } 174 var meta = document.querySelector ? document.querySelector('meta[name="description"]') : null; 175 var description = meta && meta.getAttribute ? meta.getAttribute("content") : ""; 176 if (description) { parts.push(description); } 177 return parts.join(". "); 178 } 179 180 function register(tools) { 181 if (!tools.length) { return 0; } 182 183 var nav = typeof navigator !== "undefined" ? navigator.modelContext : null; 184 var doc = typeof document !== "undefined" ? document.modelContext : null; 185 var i; 186 187 if (nav && typeof nav.registerTool === "function") { 188 for (i = 0; i < tools.length; i++) { 189 navigator.modelContext.registerTool(tools[i]); 190 } 191 return tools.length; 192 } 193 if (doc && typeof doc.registerTool === "function") { 194 for (i = 0; i < tools.length; i++) { 195 document.modelContext.registerTool(tools[i]); 196 } 197 return tools.length; 198 } 199 200 if (nav && typeof nav.provideContext === "function") { 201 navigator.modelContext.provideContext({ tools: tools, description: pageSummary() }); 202 return tools.length; 203 } 204 if (doc && typeof doc.provideContext === "function") { 205 document.modelContext.provideContext({ tools: tools, description: pageSummary() }); 206 return tools.length; 207 } 208 209 return 0; 210 } 211 212 try { 213 window.__swWebMcp.registered = register(descriptors()); 214 } catch (e) { 215 window.__swWebMcp.error = String(e && e.message ? e.message : e); 216 } 217})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.