@saccolabs/tars 1.37.0 → 1.37.1
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.
- package/dist/channels/tui/tui-channel.js +1 -1
- package/dist/channels/tui/tui-channel.js.map +1 -1
- package/dist/cli/commands/refresh.js +7 -1
- package/dist/cli/commands/refresh.js.map +1 -1
- package/dist/supervisor/bootstrap.js +7 -1
- package/dist/supervisor/bootstrap.js.map +1 -1
- package/extensions/search/dist/search-helper.js +43 -0
- package/extensions/search/dist/server.js +111 -0
- package/extensions/search/package-lock.json +1485 -0
- package/extensions/search/package.json +21 -0
- package/extensions/search/src/search-helper.ts +62 -0
- package/extensions/search/src/server.ts +127 -0
- package/extensions/search/tars-extension.json +11 -0
- package/extensions/search/tsconfig.json +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tars-search",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Web search and page fetching MCP server for Tars",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"start": "node dist/server.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
12
|
+
"duck-duck-scrape": "^2.2.5",
|
|
13
|
+
"linkedom": "^0.18.5",
|
|
14
|
+
"turndown": "^7.2.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^22.10.2",
|
|
18
|
+
"@types/turndown": "^5.0.5",
|
|
19
|
+
"typescript": "^5.7.2"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { search } from 'duck-duck-scrape';
|
|
2
|
+
import { parseHTML } from 'linkedom';
|
|
3
|
+
import TurndownService from 'turndown';
|
|
4
|
+
|
|
5
|
+
export interface SearchResult {
|
|
6
|
+
title: string;
|
|
7
|
+
url: string;
|
|
8
|
+
snippet: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Executes a DuckDuckGo search query and returns mapped search results.
|
|
13
|
+
*/
|
|
14
|
+
export async function performWebSearch(query: string, limit: number = 5): Promise<SearchResult[]> {
|
|
15
|
+
const response = await search(query);
|
|
16
|
+
const rawResults = response.results || [];
|
|
17
|
+
return rawResults.slice(0, limit).map((r: any) => ({
|
|
18
|
+
title: r.title || '',
|
|
19
|
+
url: r.url || '',
|
|
20
|
+
snippet: r.description || r.snippet || ''
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Fetches HTML from a URL, removes noise/boilerplate, and converts it to clean Markdown.
|
|
26
|
+
*/
|
|
27
|
+
export async function fetchWebPage(url: string, maxLength: number = 15000): Promise<string> {
|
|
28
|
+
const res = await fetch(url, {
|
|
29
|
+
headers: {
|
|
30
|
+
'User-Agent':
|
|
31
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
throw new Error(`Failed to fetch page: HTTP ${res.status} ${res.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const html = await res.text();
|
|
40
|
+
const { document } = parseHTML(html);
|
|
41
|
+
|
|
42
|
+
// Strip boilerplate, navigation, and script/style tags
|
|
43
|
+
const elementsToRemove = document.querySelectorAll(
|
|
44
|
+
'script, style, noscript, iframe, nav, header, footer, aside, svg, img, form, button'
|
|
45
|
+
);
|
|
46
|
+
elementsToRemove.forEach((el) => el.remove());
|
|
47
|
+
|
|
48
|
+
const bodyHtml = document.body ? document.body.innerHTML : document.documentElement.innerHTML;
|
|
49
|
+
|
|
50
|
+
const turndown = new TurndownService({
|
|
51
|
+
headingStyle: 'atx',
|
|
52
|
+
codeBlockStyle: 'fenced'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
let markdown = turndown.turndown(bodyHtml).trim();
|
|
56
|
+
|
|
57
|
+
if (markdown.length > maxLength) {
|
|
58
|
+
markdown = markdown.substring(0, maxLength) + '\n\n...[Content truncated for brevity]...';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return markdown;
|
|
62
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { performWebSearch, fetchWebPage } from './search-helper.js';
|
|
5
|
+
|
|
6
|
+
const server = new Server(
|
|
7
|
+
{
|
|
8
|
+
name: 'tars-search',
|
|
9
|
+
version: '1.0.0'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
capabilities: {
|
|
13
|
+
tools: {}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Tool Definitions
|
|
20
|
+
*/
|
|
21
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
22
|
+
return {
|
|
23
|
+
tools: [
|
|
24
|
+
{
|
|
25
|
+
name: 'web_search',
|
|
26
|
+
description:
|
|
27
|
+
'Search the web using DuckDuckGo (free, keyless) and return a list of matches.',
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
query: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description:
|
|
34
|
+
'The search query to execute (e.g. "latest version of Node.js")'
|
|
35
|
+
},
|
|
36
|
+
limit: {
|
|
37
|
+
type: 'number',
|
|
38
|
+
description:
|
|
39
|
+
'Maximum number of results to return (default: 5, max: 10)',
|
|
40
|
+
minimum: 1,
|
|
41
|
+
maximum: 10
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
required: ['query']
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'web_fetch',
|
|
49
|
+
description:
|
|
50
|
+
'Fetch the text/Markdown content of a specific web URL, stripped of header/footer boilerplate.',
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
url: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'The URL to fetch content from'
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
required: ['url']
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Tool Handlers
|
|
68
|
+
*/
|
|
69
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
70
|
+
const { name, arguments: args } = request.params;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
switch (name) {
|
|
74
|
+
case 'web_search': {
|
|
75
|
+
const { query, limit = 5 } = args as { query: string; limit?: number };
|
|
76
|
+
if (!query) throw new Error('Query is required.');
|
|
77
|
+
|
|
78
|
+
const results = await performWebSearch(query, limit);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: 'text',
|
|
84
|
+
text: JSON.stringify(results, null, 2)
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case 'web_fetch': {
|
|
91
|
+
const { url } = args as { url: string };
|
|
92
|
+
if (!url) throw new Error('URL is required.');
|
|
93
|
+
|
|
94
|
+
const markdown = await fetchWebPage(url);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: 'text',
|
|
100
|
+
text: markdown
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
108
|
+
}
|
|
109
|
+
} catch (error: any) {
|
|
110
|
+
return {
|
|
111
|
+
content: [{ type: 'text', text: `❌ Error: ${error.message}` }],
|
|
112
|
+
isError: true
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Start Server
|
|
118
|
+
async function main() {
|
|
119
|
+
const transport = new StdioServerTransport();
|
|
120
|
+
await server.connect(transport);
|
|
121
|
+
console.error('tars-search MCP server running on stdio');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
main().catch((error) => {
|
|
125
|
+
console.error('Server error:', error);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|