1(function () { 2 var modelContext = document.modelContext || navigator.modelContext; 3 4 if (!modelContext || typeof modelContext.registerTool !== 'function' || window.__dongjunkeWebMCP) { 5 return; 6 } 7 8 window.__dongjunkeWebMCP = true; 9 10 var searchEntriesPromise; 11 12 function result(value) { 13 return JSON.stringify(value); 14 } 15 16 function articleTitle() { 17 var title = document.querySelector('.post-content > h1'); 18 return title ? title.innerText.trim() : document.title; 19 } 20 21 function pageType() { 22 var path = window.location.pathname; 23 24 if (path === '/' || path === '') { 25 return 'home'; 26 } 27 28 if (document.querySelector('.post-content .markdown-body')) { 29 return 'article'; 30 } 31 32 if (/^\/archives/.test(path)) { 33 return 'archives'; 34 } 35 36 if (/^\/categories/.test(path)) { 37 return 'categories'; 38 } 39 40 if (/^\/tags/.test(path)) { 41 return 'tags'; 42 } 43 44 return 'page'; 45 } 46 47 function htmlToText(html) { 48 var wrapper = document.createElement('div'); 49 wrapper.innerHTML = html; 50 return (wrapper.textContent || '').replace(/\s+/g, ' ').trim(); 51 } 52 53 function loadSearchEntries() { 54 if (!searchEntriesPromise) { 55 searchEntriesPromise = fetch('/local-search.xml', { 56 credentials: 'same-origin' 57 }).then(function (response) { 58 if (!response.ok) { 59 throw new Error('æ æ³è¯»åå客æç´¢ç´¢å¼'); 60 } 61 return response.text(); 62 }).then(function (xml) { 63 var documentNode = new DOMParser().parseFromString(xml, 'text/xml'); 64 65 return Array.prototype.map.call(documentNode.querySelectorAll('entry'), function (entry) { 66 var title = entry.querySelector('title'); 67 var url = entry.querySelector('url'); 68 var link = entry.querySelector('link'); 69 var content = entry.querySelector('content'); 70 var categories = Array.prototype.map.call(entry.querySelectorAll('category'), function (category) { 71 return category.textContent.trim(); 72 }); 73 74 return { 75 title: title ? title.textContent.trim() : '', 76 url: url ? url.textContent.trim() : (link ? link.getAttribute('href') : ''), 77 content: content ? htmlToText(content.textContent) : '', 78 categories: categories 79 }; 80 }); 81 }); 82 } 83 84 return searchEntriesPromise; 85 } 86 87 function register(tool) { 88 modelContext.registerTool(tool).catch(function (error) { 89 console.warn('[dongjunke-webmcp] tool registration failed', error); 90 }); 91 } 92 93 register({ 94 name: 'get_site_context', 95 description: 'è·åä¸è¯è¥¿å°±å½å页é¢çæ é¢ãå°åå页é¢ç±»å', 96 inputSchema: { 97 type: 'object', 98 properties: {} 99 }, 100 execute: async function () { 101 return result({ 102 site: 'ä¸è¯è¥¿å°±', 103 url: window.location.href, 104 title: document.title, 105 pageType: pageType(), 106 description: (document.querySelector('meta[name="description"]') || {}).content || '' 107 }); 108 } 109 }); 110 111 register({ 112 name: 'read_current_article', 113 description: '读åä¸è¯è¥¿å°±å½åæç« çæ é¢ãå°å忣æ', 114 inputSchema: { 115 type: 'object', 116 properties: {} 117 }, 118 execute: async function () { 119 var content = document.querySelector('.post-content .markdown-body'); 120 121 return result({ 122 title: articleTitle(), 123 url: window.location.href, 124 content: content ? content.innerText.trim().slice(0, 20000) : '', 125 available: Boolean(content) 126 }); 127 } 128 }); 129 130 register({ 131 name: 'search_posts', 132 description: 'æå ³é®è¯æç´¢ä¸è¯è¥¿å°±çæç« ï¼æ¯æå¤ä¸ªå ³é®è¯åæ¶å¹é ', 133 inputSchema: { 134 type: 'object', 135 properties: { 136 query: { 137 type: 'string', 138 description: 'è¦æç´¢çå ³é®è¯ï¼å¯ä»¥ç¨ç©ºæ ¼åéå¤ä¸ªå ³é®è¯' 139 }, 140 limit: { 141 type: 'integer', 142 description: 'æå¤è¿åå¤å°ç¯æç« ï¼é»è®¤ 10ï¼æå¤ 20' 143 } 144 }, 145 required: ['query'] 146 }, 147 execute: async function (input) { 148 var query = String((input || {}).query || '').trim().toLowerCase(); 149 var limit = Math.min(Math.max(Number((input || {}).limit) || 10, 1), 20); 150 151 if (!query) { 152 return result({ query: '', results: [] }); 153 } 154
155 var terms = query.split(/\s+/).filter(Boolean); 156 var entries = await loadSearchEntries(); 157 var results = entries.map(function (entry) { 158 var searchable = (entry.title + ' ' + entry.content + ' ' + entry.categories.join(' ')).toLowerCase(); 159 var matches = terms.filter(function (term) { 160 return searchable.indexOf(term) !== -1; 161 }).length; 162 163 return { 164 entry: entry, 165 matches: matches, 166 titleMatch: entry.title.toLowerCase().indexOf(query) !== -1 167 }; 168 }).filter(function (item) { 169 return item.matches === terms.length; 170 }).sort(function (left, right) { 171 return Number(right.titleMatch) - Number(left.titleMatch) || right.matches - left.matches; 172 }).slice(0, limit).map(function (item) { 173 return { 174 title: item.entry.title, 175 url: new URL(item.entry.url, window.location.origin).href, 176 categories: item.entry.categories, 177 excerpt: item.entry.content.slice(0, 400) 178 }; 179 }); 180 181 return result({ query: query, results: results }); 182 } 183 }); 184 185 register({ 186 name: 'list_recent_posts', 187 description: 'ååºä¸è¯è¥¿å°±æè¿åå¸çæç« ', 188 inputSchema: { 189 type: 'object', 190 properties: { 191 limit: { 192 type: 'integer', 193 description: 'è¿åæ°éï¼é»è®¤ 10ï¼æå¤ 20' 194 } 195 } 196 }, 197 execute: async function (input) { 198 var limit = Math.min(Math.max(Number((input || {}).limit) || 10, 1), 20); 199 var entries = await loadSearchEntries(); 200 201 return result(entries.slice(0, limit).map(function (entry) { 202 return { 203 title: entry.title, 204 url: new URL(entry.url, window.location.origin).href, 205 categories: entry.categories, 206 excerpt: entry.content.slice(0, 240) 207 }; 208 })); 209 } 210 }); 211 212 register({ 213 name: 'list_categories', 214 description: 'ååºä¸è¯è¥¿å°±çæç« åç±»åæç« æ°é', 215 inputSchema: { 216 type: 'object', 217 properties: {} 218 }, 219 execute: async function () { 220 var entries = await loadSearchEntries(); 221 var counts = {}; 222 223 entries.forEach(function (entry) { 224 entry.categories.forEach(function (category) { 225 counts[category] = (counts[category] || 0) + 1; 226 }); 227 }); 228 229 return result(Object.keys(counts).sort().map(function (name) { 230 return { 231 name: name, 232 count: counts[name], 233 url: new URL('/categories/' + encodeURIComponent(name) + '/', window.location.origin).href 234 }; 235 })); 236 } 237 }); 238 239 register({ 240 name: 'navigate_site', 241 description: '跳转å°ä¸è¯è¥¿å°±ç«å 页颿æç« ï¼ä¸å 许跳转å°å¤é¨ç½ç«', 242 inputSchema: { 243 type: 'object', 244 properties: { 245 path: { 246 type: 'string', 247 description: 'ç«å è·¯å¾ï¼ä¾å¦ /about ææç« è·¯å¾' 248 } 249 }, 250 required: ['path'] 251 }, 252 execute: async function (input) { 253 var target = new URL(String((input || {}).path || ''), window.location.origin); 254 255 if (target.origin !== window.location.origin) { 256 throw new Error('åªè½è·³è½¬å°ä¸è¯è¥¿å°±ç«å 页é¢'); 257 } 258 259 window.location.assign(target.href); 260 return result({ url: target.href }); 261 } 262 }); 263 264 console.info('[dongjunke-webmcp] tools registered'); 265})();
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.