@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.
@@ -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,11 @@
1
+ {
2
+ "name": "tars-search",
3
+ "version": "1.0.0",
4
+ "description": "Web search and page fetching for Tars",
5
+ "mcpServers": {
6
+ "search": {
7
+ "command": "node",
8
+ "args": ["${extensionPath}/dist/server.js"]
9
+ }
10
+ }
11
+ }
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saccolabs/tars",
3
- "version": "1.37.0",
3
+ "version": "1.37.1",
4
4
  "description": "Tars — Your personal AI assistant",
5
5
  "publishConfig": {
6
6
  "access": "public"