@gotza02/sequential-thinking 2026.2.32 → 2026.2.33

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/index.js CHANGED
@@ -16,6 +16,7 @@ import { registerNoteTools } from './tools/notes.js';
16
16
  import { registerCodingTools } from './tools/coding.js';
17
17
  import { registerCodeDbTools } from './tools/codestore.js';
18
18
  import { registerHumanTools } from './tools/human.js';
19
+ import { registerSportsTools } from './tools/sports.js';
19
20
  const __filename = fileURLToPath(import.meta.url);
20
21
  const __dirname = dirname(__filename);
21
22
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
@@ -36,6 +37,7 @@ registerNoteTools(server, notesManager);
36
37
  registerCodingTools(server, knowledgeGraph);
37
38
  registerCodeDbTools(server, codeDb);
38
39
  registerHumanTools(server);
40
+ registerSportsTools(server);
39
41
  async function runServer() {
40
42
  const transport = new StdioServerTransport();
41
43
  await server.connect(transport);
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSportsTools(server: McpServer): void;
@@ -0,0 +1,133 @@
1
+ import { z } from "zod";
2
+ import { fetchWithRetry } from "../utils.js";
3
+ import { scrapeWebpage } from "./web.js";
4
+ export function registerSportsTools(server) {
5
+ // 19. analyze_football_match
6
+ server.tool("analyze_football_match", "Gather comprehensive data for a football (soccer) match to perform a detailed analysis. Fetches Head-to-Head, Recent Form, Team News, and Predictions from the web. It also performs a 'Deep Dive' on the best data source found.", {
7
+ homeTeam: z.string().describe("Name of the home team"),
8
+ awayTeam: z.string().describe("Name of the away team"),
9
+ league: z.string().optional().describe("League name (optional, helps with accuracy)"),
10
+ context: z.string().optional().describe("Any specific angle to focus on (e.g., 'betting', 'tactical', 'injury news')")
11
+ }, async ({ homeTeam, awayTeam, league, context }) => {
12
+ const errors = [];
13
+ // Identify available search provider
14
+ let searchProvider = null;
15
+ if (process.env.BRAVE_API_KEY)
16
+ searchProvider = 'brave';
17
+ else if (process.env.EXA_API_KEY)
18
+ searchProvider = 'exa';
19
+ else if (process.env.GOOGLE_SEARCH_API_KEY && process.env.GOOGLE_SEARCH_CX)
20
+ searchProvider = 'google';
21
+ if (!searchProvider) {
22
+ return {
23
+ content: [{ type: "text", text: "Error: No search provider configured (BRAVE_API_KEY, EXA_API_KEY, or GOOGLE_SEARCH_API_KEY). Cannot fetch live match data." }],
24
+ isError: true
25
+ };
26
+ }
27
+ const leagueStr = league ? `${league} ` : '';
28
+ const baseQuery = `${leagueStr}${homeTeam} vs ${awayTeam}`;
29
+ // Critical: Add date context to ensure freshness (Accuracy Upgrade)
30
+ const today = new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); // e.g., "January 2025"
31
+ const dateQuery = ` ${today}`;
32
+ // Define targeted queries for "Detailed" analysis
33
+ const queries = [
34
+ { type: 'General & Prediction', query: `${baseQuery} match prediction stats${dateQuery}` },
35
+ { type: 'Head to Head', query: `${baseQuery} head to head history` },
36
+ { type: 'Recent Form', query: `${homeTeam} ${awayTeam} recent form last 5 matches results${dateQuery}` },
37
+ { type: 'Team News & Lineups', query: `${baseQuery} team news injuries suspensions predicted confirmed lineups${dateQuery}` },
38
+ { type: 'Advanced Metrics', query: `${baseQuery} xG expected goals stats possession shots per game${dateQuery}` },
39
+ // Combine Manager/Press with "Sack Race/Pressure" context
40
+ { type: 'Manager & Atmosphere', query: `${homeTeam} ${awayTeam} manager quotes fan sentiment sack race pressure${dateQuery}` },
41
+ // NEW: Market Intelligence
42
+ { type: 'Market & Odds', query: `${baseQuery} odds movement dropping odds betting trends${dateQuery}` },
43
+ // NEW: Environmental Factors
44
+ { type: 'Ref & Weather', query: `${baseQuery} referee stats cards per game weather forecast stadium${dateQuery}` },
45
+ // NEW: Fatigue & Schedule (Critical for late game performance)
46
+ { type: 'Fatigue & Schedule', query: `${homeTeam} ${awayTeam} days rest fixture congestion travel distance${dateQuery}` },
47
+ // NEW: Set Piece Analysis (Where 30% of goals come from)
48
+ { type: 'Set Pieces', query: `${homeTeam} ${awayTeam} set piece goals scored conceded corners stats aerial duels${dateQuery}` }
49
+ ];
50
+ if (context) {
51
+ queries.push({ type: 'Specific Context', query: `${baseQuery} ${context} ${today}` });
52
+ }
53
+ let combinedResults = `--- FOOTBALL MATCH DATA: ${homeTeam} vs ${awayTeam} ---\nMatch Context Date: ${today}\n\n`;
54
+ // Store potential URLs for deep dive
55
+ const candidateUrls = [];
56
+ // Execute searches in parallel
57
+ const searchPromises = queries.map(async (q) => {
58
+ try {
59
+ let resultsText = "";
60
+ if (searchProvider === 'brave') {
61
+ const response = await fetchWithRetry(`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(q.query)}&count=4`, {
62
+ headers: {
63
+ 'X-Subscription-Token': process.env.BRAVE_API_KEY,
64
+ 'Accept': 'application/json'
65
+ }
66
+ });
67
+ if (!response.ok)
68
+ throw new Error(`Brave Status ${response.status}`);
69
+ const data = await response.json();
70
+ const items = data.web?.results || [];
71
+ items.forEach((item) => candidateUrls.push(item.url));
72
+ resultsText = items.map((item) => `- [${item.title}](${item.url}): ${item.description}`).join('\n');
73
+ }
74
+ else if (searchProvider === 'exa') {
75
+ const response = await fetchWithRetry('https://api.exa.ai/search', {
76
+ method: 'POST',
77
+ headers: {
78
+ 'x-api-key': process.env.EXA_API_KEY,
79
+ 'Content-Type': 'application/json'
80
+ },
81
+ body: JSON.stringify({ query: q.query, numResults: 4 })
82
+ });
83
+ if (!response.ok)
84
+ throw new Error(`Exa Status ${response.status}`);
85
+ const data = await response.json();
86
+ const items = data.results || [];
87
+ items.forEach((item) => candidateUrls.push(item.url));
88
+ resultsText = items.map((item) => `- [${item.title}](${item.url}): ${item.text || item.title}`).join('\n');
89
+ }
90
+ else if (searchProvider === 'google') {
91
+ const response = await fetchWithRetry(`https://www.googleapis.com/customsearch/v1?key=${process.env.GOOGLE_SEARCH_API_KEY}&cx=${process.env.GOOGLE_SEARCH_CX}&q=${encodeURIComponent(q.query)}&num=4`);
92
+ if (!response.ok)
93
+ throw new Error(`Google Status ${response.status}`);
94
+ const data = await response.json();
95
+ const items = data.items || [];
96
+ items.forEach((item) => candidateUrls.push(item.link));
97
+ resultsText = items.map((item) => `- [${item.title}](${item.link}): ${item.snippet}`).join('\n');
98
+ }
99
+ return `### ${q.type}\n${resultsText}\n`;
100
+ }
101
+ catch (error) {
102
+ return `### ${q.type} (Failed)\nError: ${error instanceof Error ? error.message : String(error)}\n`;
103
+ }
104
+ });
105
+ const results = await Promise.all(searchPromises);
106
+ combinedResults += results.join('\n');
107
+ // --- Deep Dive: Scrape the best URL ---
108
+ if (candidateUrls.length > 0) {
109
+ // Priority domains for sports data
110
+ const priorityDomains = ['whoscored.com', 'flashscore', 'sofascore', 'bbc.co.uk/sport', 'skysports.com', 'sportsmole', 'goal.com', 'understat.com', 'fbref.com'];
111
+ // Find first match from priority list, else take the first valid URL
112
+ const bestUrl = candidateUrls.find(url => priorityDomains.some(domain => url.includes(domain))) || candidateUrls[0];
113
+ if (bestUrl) {
114
+ combinedResults += `\n--- DEEP DIVE ANALYSIS (${bestUrl}) ---\n`;
115
+ try {
116
+ // Stability: Wrap scrape in a 15s timeout to prevent hanging
117
+ const scrapePromise = scrapeWebpage(bestUrl);
118
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("Deep dive timeout (15s)")), 15000));
119
+ const scrapedContent = await Promise.race([scrapePromise, timeoutPromise]);
120
+ combinedResults += scrapedContent;
121
+ }
122
+ catch (scrapeError) {
123
+ combinedResults += `(Deep dive failed/skipped: ${scrapeError instanceof Error ? scrapeError.message : String(scrapeError)})`;
124
+ }
125
+ combinedResults += `\n--- END DEEP DIVE ---\n`;
126
+ }
127
+ }
128
+ combinedResults += `\n--- END DATA ---\n\nINSTRUCTIONS: Act as a World-Class Football Analysis Panel. Provide a deep, non-obvious analysis using this framework:\n\n1. 📊 THE DATA SCIENTIST (Quantitative):\n - Analyze xG trends & Possession stats.\n - Assess Home/Away variance.\n\n2. 🧠 THE TACTICAL SCOUT (Qualitative):\n - Stylistic Matchup (Press vs Block).\n - Key Battles.\n\n3. 🚑 THE PHYSIO (Physical Condition):\n - FATIGUE CHECK: Days rest? Travel distance? (Critical for last 20 mins).\n - Squad Depth: Who has the better bench?\n\n4. 🎯 SET PIECE ANALYST (Special Teams):\n - Corners/Free Kicks: Strong vs Weak? (Aerial Duel stats).\n - Who is most likely to score from a header?\n\n5. 💎 THE INSIDER (External Factors):\n - Market Movements (Odds dropping?).\n - Referee & Weather impact.\n - Managerial Pressure/Sack Race context.\n\n6. 🕵️ THE SKEPTIC & SCENARIOS:\n - Why might the favorite LOSE? (The "Trap").\n - Game Script: "If Team A scores first..."\n\n🏆 FINAL VERDICT:\n - Asian Handicap Leans\n - Goal Line (Over/Under)\n - The "Value Pick"`;
129
+ return {
130
+ content: [{ type: "text", text: combinedResults }]
131
+ };
132
+ });
133
+ }
@@ -1,2 +1,3 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function scrapeWebpage(url: string): Promise<string>;
2
3
  export declare function registerWebTools(server: McpServer): void;
package/dist/tools/web.js CHANGED
@@ -3,6 +3,56 @@ import { fetchWithRetry, validatePublicUrl } from "../utils.js";
3
3
  import { JSDOM } from 'jsdom';
4
4
  import { Readability } from '@mozilla/readability';
5
5
  import TurndownService from 'turndown';
6
+ export async function scrapeWebpage(url) {
7
+ await validatePublicUrl(url);
8
+ const response = await fetchWithRetry(url);
9
+ const html = await response.text();
10
+ const doc = new JSDOM(html, { url });
11
+ const reader = new Readability(doc.window.document);
12
+ const article = reader.parse();
13
+ if (!article)
14
+ throw new Error("Could not parse article content");
15
+ const turndownService = new TurndownService({
16
+ headingStyle: 'atx',
17
+ codeBlockStyle: 'fenced'
18
+ });
19
+ // Custom Rule for GitHub Flavored Markdown Tables
20
+ turndownService.addRule('tables', {
21
+ filter: ['table'],
22
+ replacement: function (content, node) {
23
+ const rows = [];
24
+ const table = node;
25
+ const trs = Array.from(table.querySelectorAll('tr'));
26
+ trs.forEach((tr, index) => {
27
+ const cols = [];
28
+ const tds = Array.from(tr.querySelectorAll('th, td'));
29
+ tds.forEach(td => {
30
+ // Clean content: remove newlines and pipe characters
31
+ cols.push(td.textContent?.replace(/[\n\r]/g, ' ').replace(/\|/g, '\\|').trim() || "");
32
+ });
33
+ if (cols.length > 0) {
34
+ rows.push(`| ${cols.join(' | ')} |`);
35
+ // Add separator after header
36
+ if (index === 0 || tr.querySelector('th')) {
37
+ rows.push(`| ${cols.map(() => '---').join(' | ')} |`);
38
+ }
39
+ }
40
+ });
41
+ // Filter out duplicate separator lines if any
42
+ const uniqueRows = rows.filter((row, i) => {
43
+ if (row.includes('---') && rows[i - 1]?.includes('---'))
44
+ return false;
45
+ return true;
46
+ });
47
+ return '\n\n' + uniqueRows.join('\n') + '\n\n';
48
+ }
49
+ });
50
+ let markdown = turndownService.turndown(article.content || "");
51
+ if (markdown.length > 20000) {
52
+ markdown = markdown.substring(0, 20000) + "\n...(truncated)";
53
+ }
54
+ return `Title: ${article.title}\n\n${markdown}`;
55
+ }
6
56
  export function registerWebTools(server) {
7
57
  // 1. web_search
8
58
  server.tool("web_search", "Search the web using Brave or Exa APIs (requires API keys in environment variables: BRAVE_API_KEY or EXA_API_KEY).", {
@@ -128,57 +178,11 @@ export function registerWebTools(server) {
128
178
  url: z.string().url().describe("The URL to read")
129
179
  }, async ({ url }) => {
130
180
  try {
131
- await validatePublicUrl(url);
132
- const response = await fetchWithRetry(url);
133
- const html = await response.text();
134
- const doc = new JSDOM(html, { url });
135
- const reader = new Readability(doc.window.document);
136
- const article = reader.parse();
137
- if (!article)
138
- throw new Error("Could not parse article content");
139
- const turndownService = new TurndownService({
140
- headingStyle: 'atx',
141
- codeBlockStyle: 'fenced'
142
- });
143
- // Custom Rule for GitHub Flavored Markdown Tables
144
- turndownService.addRule('tables', {
145
- filter: ['table'],
146
- replacement: function (content, node) {
147
- const rows = [];
148
- const table = node;
149
- const trs = Array.from(table.querySelectorAll('tr'));
150
- trs.forEach((tr, index) => {
151
- const cols = [];
152
- const tds = Array.from(tr.querySelectorAll('th, td'));
153
- tds.forEach(td => {
154
- // Clean content: remove newlines and pipe characters
155
- cols.push(td.textContent?.replace(/[\n\r]/g, ' ').replace(/\|/g, '\\|').trim() || "");
156
- });
157
- if (cols.length > 0) {
158
- rows.push(`| ${cols.join(' | ')} |`);
159
- // Add separator after header
160
- if (index === 0 || tr.querySelector('th')) {
161
- rows.push(`| ${cols.map(() => '---').join(' | ')} |`);
162
- }
163
- }
164
- });
165
- // Filter out duplicate separator lines if any
166
- const uniqueRows = rows.filter((row, i) => {
167
- if (row.includes('---') && rows[i - 1]?.includes('---'))
168
- return false;
169
- return true;
170
- });
171
- return '\n\n' + uniqueRows.join('\n') + '\n\n';
172
- }
173
- });
174
- let markdown = turndownService.turndown(article.content || "");
175
- if (markdown.length > 20000) {
176
- markdown = markdown.substring(0, 20000) + "\n...(truncated)";
177
- }
181
+ const result = await scrapeWebpage(url);
178
182
  return {
179
183
  content: [{
180
184
  type: "text",
181
- text: `Title: ${article.title}\n\n${markdown}`
185
+ text: result
182
186
  }]
183
187
  };
184
188
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,129 @@
1
+ import { SequentialThinkingServer } from './lib.js';
2
+ import { ProjectKnowledgeGraph } from './graph.js';
3
+ import { NotesManager } from './notes.js';
4
+ import { CodeDatabase } from './codestore.js';
5
+ import { fetchWithRetry } from './utils.js';
6
+ import * as fs from 'fs/promises';
7
+ // ANSI Colors
8
+ const colors = {
9
+ reset: "\x1b[0m",
10
+ green: "\x1b[32m",
11
+ blue: "\x1b[34m",
12
+ yellow: "\x1b[33m",
13
+ red: "\x1b[31m",
14
+ bold: "\x1b[1m"
15
+ };
16
+ function log(step, msg) {
17
+ console.log(`${colors.blue}[${step}]${colors.reset} ${msg}`);
18
+ }
19
+ function success(msg) {
20
+ console.log(`${colors.green} ✓ ${msg}${colors.reset}`);
21
+ }
22
+ async function main() {
23
+ console.log(`${colors.bold}=== STARTING AUTOMATED TOOL VERIFICATION ===${colors.reset}\n`);
24
+ // 1. Initialize Managers
25
+ log("INIT", "Initializing Core Managers...");
26
+ const thinking = new SequentialThinkingServer('test_thoughts.json', 0);
27
+ const graph = new ProjectKnowledgeGraph();
28
+ const notes = new NotesManager('test_notes.json');
29
+ const codeDb = new CodeDatabase('test_codedb.json');
30
+ // Clean up previous tests
31
+ try {
32
+ await Promise.all([
33
+ fs.unlink('test_thoughts.json').catch(() => { }),
34
+ fs.unlink('test_notes.json').catch(() => { }),
35
+ fs.unlink('test_codedb.json').catch(() => { })
36
+ ]);
37
+ }
38
+ catch { } // This catch block is intentionally empty
39
+ // 2. Test Thinking Engine
40
+ log("THINKING", "Testing Sequential Thinking Engine...");
41
+ await thinking.processThought({
42
+ thought: "Analysis step for testing",
43
+ thoughtNumber: 1,
44
+ totalThoughts: 3,
45
+ nextThoughtNeeded: true,
46
+ thoughtType: "analysis",
47
+ blockId: "test-block"
48
+ });
49
+ await thinking.processThought({
50
+ thought: "Planning step",
51
+ thoughtNumber: 2,
52
+ totalThoughts: 3,
53
+ nextThoughtNeeded: true,
54
+ thoughtType: "planning",
55
+ blockId: "test-block"
56
+ });
57
+ const history = await thinking.searchHistory("Analysis");
58
+ if (history.length > 0)
59
+ success(`History recorded and retrieved (${history.length} items)`);
60
+ else
61
+ throw new Error("Thinking history failed");
62
+ // 3. Test Notes Manager
63
+ log("NOTES", "Testing Notes Manager...");
64
+ const note = await notes.addNote("Test Note", "This is a test content", ["test"], "high");
65
+ success(`Note created: ${note.id}`);
66
+ const foundNotes = await notes.searchNotes("test content");
67
+ if (foundNotes.length > 0)
68
+ success(`Search successful (found ${foundNotes.length})`);
69
+ else
70
+ throw new Error("Note search failed");
71
+ await notes.deleteNote(note.id);
72
+ success("Note deleted");
73
+ // 4. Test Code Database
74
+ log("CODEDB", "Testing Code Database...");
75
+ const snippet = await codeDb.addSnippet({
76
+ title: "Hello World",
77
+ code: "console.log('Hello');",
78
+ language: "typescript",
79
+ description: "Simple log",
80
+ tags: ["example"],
81
+ context: "test.ts"
82
+ });
83
+ success(`Snippet added: ${snippet.id}`);
84
+ await codeDb.learnPattern("Singleton", "Use a static instance...");
85
+ success("Pattern learned");
86
+ const searchRes = await codeDb.searchSnippets("Hello");
87
+ if (searchRes.length > 0)
88
+ success(`Snippet search working`);
89
+ // 5. Test Knowledge Graph
90
+ log("GRAPH", "Testing Project Knowledge Graph...");
91
+ const buildRes = await graph.build(process.cwd());
92
+ success(`Graph built: ${buildRes.nodeCount} nodes, ${buildRes.totalFiles} files`);
93
+ const summary = graph.getSummary();
94
+ success(`Summary retrieved: ${summary.fileCount} files in graph`);
95
+ const rel = graph.getRelationships('src/index.ts');
96
+ if (rel) {
97
+ success(`Relationships for src/index.ts found (${rel.imports.length} imports)`);
98
+ }
99
+ else {
100
+ console.warn(`${colors.yellow} ! Warning: src/index.ts not found in graph (might be excluded or path issue)${colors.reset}`);
101
+ }
102
+ // 6. Test FileSystem Logic (Reading self)
103
+ log("FS", "Testing File Operations...");
104
+ const content = await fs.readFile('src/index.ts', 'utf-8');
105
+ if (content.length > 0)
106
+ success("read_file working");
107
+ // 7. Test Web Tools (Utils)
108
+ log("WEB", "Testing Web Utilities...");
109
+ try {
110
+ const res = await fetchWithRetry('https://example.com');
111
+ if (res.ok)
112
+ success("Web fetch working");
113
+ }
114
+ catch (e) {
115
+ console.warn(`${colors.yellow} ! Web fetch failed (network might be offline), skipping.${colors.reset}`);
116
+ }
117
+ // Cleanup
118
+ log("CLEANUP", "Removing test artifacts...");
119
+ await Promise.all([
120
+ fs.unlink('test_thoughts.json').catch(() => { }),
121
+ fs.unlink('test_notes.json').catch(() => { }),
122
+ fs.unlink('test_codedb.json').catch(() => { })
123
+ ]);
124
+ console.log(`\n${colors.green}${colors.bold}=== ALL TESTS PASSED SUCCESSFULLY ===${colors.reset}`);
125
+ }
126
+ main().catch(err => {
127
+ console.error(`\n${colors.red}${colors.bold}FATAL ERROR:${colors.reset}`, err);
128
+ process.exit(1);
129
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotza02/sequential-thinking",
3
- "version": "2026.2.32",
3
+ "version": "2026.2.33",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },