1(function () { 2 'use strict'; 3 function readConfig() { 4 var element = document.currentScript; 5 var dataset = element && element.dataset ? element.dataset : {}; 6 var packsRaw = typeof dataset.packs === 'string' && dataset.packs.length > 0 ? dataset.packs : 'site,page'; 7 var packs = packsRaw.split(',').map(function (part) { return part.trim(); }).filter(function (part) { return part.length > 0; }); 8 var mcpUrl = typeof dataset.mcpUrl === 'string' && dataset.mcpUrl.length > 0 ? dataset.mcpUrl : '/mcp'; 9 return { packs: packs, mcpUrl: mcpUrl }; 10 } 11 function textResult(payload) { 12 return { content: [{ type: 'text', text: JSON.stringify(payload) }] }; 13 } 14 function head(limit, value) { 15 return (value || '').trim().slice(0, limit); 16 } 17 function collectHeadings() { 18 return Array.prototype.slice.call(document.querySelectorAll('h1, h2'), 0, 10).map(function (node) { 19 return { level: node.tagName.toLowerCase(), text: head(200, node.textContent) }; 20 }); 21 } 22 function collectArticleLinks() { 23 return Array.prototype.slice.call(document.querySelectorAll('a[href]'), 0, 200) 24 .map(function (node) { return { title: head(120, node.textContent), href: node.getAttribute('href') || '' }; }) 25 .filter(function (link) { return link.title.length > 0 && link.href.length > 0 && link.href.charAt(0) === '/'; }) 26 .slice(0, 20); 27 } 28 function pageMetadata() { 29 function meta(name) { 30 var node = document.querySelector('meta[name="' + name + '"]'); 31 return node ? node.getAttribute('content') || '' : ''; 32 } 33 var canonical = document.querySelector('link[rel="canonical"]'); 34 return { 35 title: head(200, document.title), 36 canonical: canonical ? canonical.getAttribute('href') || '' : '', 37 description: head(300, meta('description')), 38 headings: collectHeadings(), 39 articleLinks: collectArticleLinks(), 40 }; 41 } 42 function pageImages() { 43 var nodes = document.images ? document.images : []; 44 var images = Array.prototype.slice.call(nodes, 0, 20).map(function (image) { 45 return { src: image.currentSrc || image.src || '', alt: head(200, image.alt) }; 46 }); 47 return { imageCount: nodes.length, images: images, signatureVerified: false }; 48 } 49 var config = readConfig(); 50 var surface = document.modelContext; 51 if (!surface || typeof surface.registerTool !== 'function') return; 52 function safeRegister(tool) { 53 try { surface.registerTool(tool); } catch (error) { /* one bad tool never breaks the page */ } 54 } 55 var rpcId = 0; 56 function nextId() { rpcId += 1; return rpcId; } 57 function postRpc(payload) { 58 return fetch(config.mcpUrl, { 59 method: 'POST', 60 credentials: 'same-origin', 61 headers: { 'content-type': 'application/json', accept: 'application/json' }, 62 body: JSON.stringify(payload), 63 }).then(function (response) { return response.json(); }); 64 } 65 if (config.packs.indexOf('page') !== -1) { 66 safeRegister({ 67 name: 'get_page_metadata', 68 description: 'Read this page title, canonical URL, meta description, headings, and article links. Runs entirely in the browser with no server round trip.', 69 inputSchema: { type: 'object', properties: {}, additionalProperties: false }, 70 execute: async function () { return textResult(pageMetadata()); }, 71 }); 72 safeRegister({ 73 name: 'list_page_images', 74 description: 'List up to 20 images on this page with their current source and alt text. Reports markup only, never fetches or verifies pixels.', 75 inputSchema: { type: 'object', properties: {}, additionalProperties: false }, 76 execute: async function () { return textResult(pageImages()); }, 77 }); 78 } 79 if (config.packs.indexOf('site') !== -1) { 80 postRpc({ jsonrpc: '2.0', id: nextId(), method: 'tools/list', params: {} }).then(function (data) { 81 var tools = data && data.result && Array.isArray(data.result.tools) ? data.result.tools : []; 82 tools.forEach(function (tool) { 83 safeRegister({ 84 name: tool.name, 85 description: tool.description, 86 inputSchema: tool.inputSchema, 87 execute: async function (args) { 88 var response = await postRpc({ jsonrpc: '2.0', id: nextId(), method: 'tools/call', params: { name: tool.name, arguments: args || {} } }); 89 if (response.error) throw new Error(response.error.message || 'mcp_error'); 90 return response.result; 91 }, 92 }); 93 }); 94 }).catch(function () { /* MCP server unreachable: the page pack still works */ }); 95 } 96})();
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.