mcp-sequential-research 1.0.0

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/src/format.ts ADDED
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Formatting utilities for sequential research output
3
+ */
4
+
5
+ import type { Citation, ReportSection } from "./schema.js";
6
+
7
+ /**
8
+ * Generate a unique ID with optional prefix
9
+ */
10
+ export function generateId(prefix: string = "id"): string {
11
+ const timestamp = Date.now().toString(36);
12
+ const random = Math.random().toString(36).substring(2, 8);
13
+ return `${prefix}-${timestamp}-${random}`;
14
+ }
15
+
16
+ /**
17
+ * Get current ISO timestamp
18
+ */
19
+ export function isoNow(): string {
20
+ return new Date().toISOString();
21
+ }
22
+
23
+ /**
24
+ * Format a citation for inline display
25
+ */
26
+ export function formatCitationInline(citation: Citation): string {
27
+ return `[${citation.id}]`;
28
+ }
29
+
30
+ /**
31
+ * Format a citation for the sources section
32
+ */
33
+ export function formatCitationFull(citation: Citation): string {
34
+ const parts: string[] = [];
35
+
36
+ parts.push(`**${citation.id}**:`);
37
+ parts.push(citation.title);
38
+
39
+ if (citation.url) {
40
+ parts.push(`<${citation.url}>`);
41
+ }
42
+
43
+ if (citation.source_type !== "web") {
44
+ parts.push(`(${citation.source_type})`);
45
+ }
46
+
47
+ if (citation.accessed_date) {
48
+ parts.push(`[Accessed: ${citation.accessed_date}]`);
49
+ }
50
+
51
+ return parts.join(" ");
52
+ }
53
+
54
+ /**
55
+ * Format a report section as markdown
56
+ */
57
+ export function formatSection(section: ReportSection): string {
58
+ const heading = "#".repeat(section.level) + " " + section.heading;
59
+ return `${heading}\n\n${section.content}`;
60
+ }
61
+
62
+ /**
63
+ * Format multiple sections as markdown document
64
+ */
65
+ export function formatSections(sections: ReportSection[]): string {
66
+ return sections.map(formatSection).join("\n\n");
67
+ }
68
+
69
+ /**
70
+ * Format sources list as markdown
71
+ */
72
+ export function formatSourcesList(
73
+ sources: Citation[],
74
+ style: "inline" | "footnote" | "endnote" = "inline"
75
+ ): string {
76
+ if (sources.length === 0) {
77
+ return "*No sources cited.*";
78
+ }
79
+
80
+ const lines: string[] = [];
81
+
82
+ if (style === "endnote") {
83
+ lines.push("## Sources\n");
84
+ } else {
85
+ lines.push("---\n");
86
+ lines.push("### References\n");
87
+ }
88
+
89
+ for (const source of sources) {
90
+ lines.push(`- ${formatCitationFull(source)}`);
91
+ }
92
+
93
+ return lines.join("\n");
94
+ }
95
+
96
+ /**
97
+ * Count words in text (simple whitespace split)
98
+ */
99
+ export function countWords(text: string): number {
100
+ return text.split(/\s+/).filter(Boolean).length;
101
+ }
102
+
103
+ /**
104
+ * Create a markdown table from data
105
+ */
106
+ export function formatTable(
107
+ headers: string[],
108
+ rows: string[][]
109
+ ): string {
110
+ const headerRow = `| ${headers.join(" | ")} |`;
111
+ const separator = `| ${headers.map(() => "---").join(" | ")} |`;
112
+ const dataRows = rows.map((row) => `| ${row.join(" | ")} |`);
113
+
114
+ return [headerRow, separator, ...dataRows].join("\n");
115
+ }
116
+
117
+ /**
118
+ * Wrap text in a markdown callout/admonition
119
+ */
120
+ export function formatCallout(
121
+ content: string,
122
+ type: "note" | "warning" | "info" | "tip" = "note"
123
+ ): string {
124
+ const icons: Record<string, string> = {
125
+ note: "📝",
126
+ warning: "⚠️",
127
+ info: "ℹ️",
128
+ tip: "💡",
129
+ };
130
+
131
+ return `> ${icons[type]} **${type.charAt(0).toUpperCase() + type.slice(1)}**\n> \n> ${content.split("\n").join("\n> ")}`;
132
+ }
133
+
134
+ /**
135
+ * Escape special markdown characters in text
136
+ */
137
+ export function escapeMarkdown(text: string): string {
138
+ return text.replace(/([*_`\[\]()#>+\-.!])/g, "\\$1");
139
+ }
140
+
141
+ /**
142
+ * Truncate text to a maximum length with ellipsis
143
+ */
144
+ export function truncate(text: string, maxLength: number): string {
145
+ if (text.length <= maxLength) return text;
146
+ return text.substring(0, maxLength - 3) + "...";
147
+ }
148
+
149
+ /**
150
+ * Format a list as markdown bullet points
151
+ */
152
+ export function formatBulletList(items: string[]): string {
153
+ return items.map((item) => `- ${item}`).join("\n");
154
+ }
155
+
156
+ /**
157
+ * Format a numbered list
158
+ */
159
+ export function formatNumberedList(items: string[]): string {
160
+ return items.map((item, i) => `${i + 1}. ${item}`).join("\n");
161
+ }
162
+
163
+ /**
164
+ * Create a blockquote
165
+ */
166
+ export function formatBlockquote(text: string): string {
167
+ return text
168
+ .split("\n")
169
+ .map((line) => `> ${line}`)
170
+ .join("\n");
171
+ }
172
+
173
+ /**
174
+ * Format code block with optional language
175
+ */
176
+ export function formatCodeBlock(code: string, language: string = ""): string {
177
+ return `\`\`\`${language}\n${code}\n\`\`\``;
178
+ }
179
+
180
+ /**
181
+ * Deduplicate citations by ID
182
+ */
183
+ export function deduplicateCitations(citations: Citation[]): Citation[] {
184
+ const seen = new Set<string>();
185
+ return citations.filter((citation) => {
186
+ if (seen.has(citation.id)) return false;
187
+ seen.add(citation.id);
188
+ return true;
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Renumber citations sequentially
194
+ */
195
+ export function renumberCitations(
196
+ citations: Citation[]
197
+ ): { citations: Citation[]; mapping: Map<string, string> } {
198
+ const mapping = new Map<string, string>();
199
+ const renumbered = citations.map((citation, index) => {
200
+ const newId = `[${index + 1}]`;
201
+ mapping.set(citation.id, newId);
202
+ return { ...citation, id: newId };
203
+ });
204
+ return { citations: renumbered, mapping };
205
+ }
206
+
207
+ /**
208
+ * Apply citation renumbering to text
209
+ */
210
+ export function applyCitationMapping(
211
+ text: string,
212
+ mapping: Map<string, string>
213
+ ): string {
214
+ let result = text;
215
+ for (const [oldId, newId] of mapping) {
216
+ result = result.replaceAll(oldId, newId);
217
+ }
218
+ return result;
219
+ }
package/src/index.ts ADDED
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Sequential Research Server
4
+ *
5
+ * Exposes two tools via stdio transport:
6
+ * - sequential_research_plan: Generate structured research queries
7
+ * - sequential_research_compile: Compile results into a report
8
+ */
9
+
10
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
11
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
+ import {
13
+ CallToolRequestSchema,
14
+ ListToolsRequestSchema,
15
+ } from "@modelcontextprotocol/sdk/types.js";
16
+
17
+ import { PlanInputSchema, CompileInputSchema } from "./schema.js";
18
+ import { generatePlan } from "./planners.js";
19
+ import { compileReport } from "./compilers.js";
20
+
21
+ // Create MCP server
22
+ const server = new Server(
23
+ {
24
+ name: "mcp-sequential-research",
25
+ version: "1.0.0",
26
+ },
27
+ {
28
+ capabilities: {
29
+ tools: {},
30
+ },
31
+ }
32
+ );
33
+
34
+ // Tool definitions with JSON Schema for MCP
35
+ const TOOLS = [
36
+ {
37
+ name: "sequential_research_plan",
38
+ description: `Generate a structured research plan with queries, extraction goals, and expected result schemas.
39
+
40
+ Use this tool when:
41
+ - Your prompt involves multiple concepts requiring systematic investigation
42
+ - You need a reproducible search strategy
43
+ - You want query coverage analysis and deduplication guidance
44
+
45
+ The plan includes:
46
+ - Ordered research queries with priorities and dependencies
47
+ - Extraction goals for each query
48
+ - Expected result schema for validation
49
+ - Execution order (parallel groups)
50
+
51
+ After receiving the plan, execute queries using appropriate MCP tools (search_patents, Google Custom Search, etc.),
52
+ then pass results to sequential_research_compile.`,
53
+ inputSchema: {
54
+ type: "object" as const,
55
+ properties: {
56
+ topic: {
57
+ type: "string",
58
+ description: "The research topic or question to investigate",
59
+ },
60
+ depth: {
61
+ type: "string",
62
+ enum: ["shallow", "standard", "deep"],
63
+ default: "standard",
64
+ description: "Research depth: shallow (3-5 queries), standard (5-10 queries), deep (10-20 queries)",
65
+ },
66
+ focus_areas: {
67
+ type: "array",
68
+ items: { type: "string" },
69
+ description: "Specific aspects to focus on (e.g., ['technical details', 'market analysis'])",
70
+ },
71
+ constraints: {
72
+ type: "array",
73
+ items: { type: "string" },
74
+ description: "Research constraints or exclusions (e.g., ['no paywalled sources', 'english only'])",
75
+ },
76
+ output_format: {
77
+ type: "string",
78
+ enum: ["markdown", "json", "structured"],
79
+ default: "markdown",
80
+ description: "Desired format for the final compiled output",
81
+ },
82
+ },
83
+ required: ["topic"],
84
+ },
85
+ },
86
+ {
87
+ name: "sequential_research_compile",
88
+ description: `Compile research results into a structured markdown report with citations and sources.
89
+
90
+ Use this tool when:
91
+ - You have executed searches via Google Patents MCP, Google Search MCP, or other sources
92
+ - You want a report with proper citations and risk analysis
93
+ - You need machine-parseable citation references for downstream processing
94
+
95
+ Input requirements:
96
+ - The original plan from sequential_research_plan
97
+ - Raw results array matching the expected schema (query_id, success, data, sources)
98
+
99
+ Output includes:
100
+ - Executive summary
101
+ - Organized sections by query type
102
+ - Inline citations [S1], [S2], etc.
103
+ - Consolidated sources table
104
+ - Statistics on query coverage
105
+
106
+ Citation format: Inline references like [S1] with a sources table at the end for claim-miner compatibility.`,
107
+ inputSchema: {
108
+ type: "object" as const,
109
+ properties: {
110
+ plan: {
111
+ type: "object",
112
+ description: "The research plan from sequential_research_plan",
113
+ },
114
+ raw_results: {
115
+ type: "array",
116
+ items: {
117
+ type: "object",
118
+ properties: {
119
+ query_id: { type: "string", description: "Query ID this result corresponds to" },
120
+ success: { type: "boolean", description: "Whether the query execution succeeded" },
121
+ data: { type: "object", description: "Extracted data matching the result schema" },
122
+ sources: {
123
+ type: "array",
124
+ items: {
125
+ type: "object",
126
+ properties: {
127
+ id: { type: "string", description: "Citation ID (e.g., S1, S2)" },
128
+ source_type: { type: "string", enum: ["web", "document", "api", "database", "manual"] },
129
+ title: { type: "string" },
130
+ url: { type: "string" },
131
+ accessed_date: { type: "string" },
132
+ excerpt: { type: "string" },
133
+ },
134
+ required: ["id", "source_type", "title"],
135
+ },
136
+ description: "Sources consulted for this query",
137
+ },
138
+ error: { type: "string", description: "Error message if success is false" },
139
+ },
140
+ required: ["query_id", "success"],
141
+ },
142
+ description: "Raw results from executing the plan queries",
143
+ },
144
+ include_sources: {
145
+ type: "boolean",
146
+ default: true,
147
+ description: "Include sources section in output",
148
+ },
149
+ include_methodology: {
150
+ type: "boolean",
151
+ default: false,
152
+ description: "Include methodology section in output",
153
+ },
154
+ citation_style: {
155
+ type: "string",
156
+ enum: ["inline", "footnote", "endnote"],
157
+ default: "inline",
158
+ description: "How to format citations in the output",
159
+ },
160
+ },
161
+ required: ["plan", "raw_results"],
162
+ },
163
+ },
164
+ ];
165
+
166
+ // Handle list tools request
167
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
168
+ return {
169
+ tools: TOOLS,
170
+ };
171
+ });
172
+
173
+ // Handle tool calls
174
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
175
+ const { name, arguments: args } = request.params;
176
+
177
+ try {
178
+ switch (name) {
179
+ case "sequential_research_plan": {
180
+ // Validate input
181
+ const parsed = PlanInputSchema.safeParse(args);
182
+ if (!parsed.success) {
183
+ return {
184
+ content: [
185
+ {
186
+ type: "text",
187
+ text: `Validation error: ${parsed.error.message}`,
188
+ },
189
+ ],
190
+ isError: true,
191
+ };
192
+ }
193
+
194
+ // Generate plan
195
+ const plan = generatePlan(parsed.data);
196
+
197
+ return {
198
+ content: [
199
+ {
200
+ type: "text",
201
+ text: JSON.stringify(plan, null, 2),
202
+ },
203
+ ],
204
+ };
205
+ }
206
+
207
+ case "sequential_research_compile": {
208
+ // Validate input
209
+ const parsed = CompileInputSchema.safeParse(args);
210
+ if (!parsed.success) {
211
+ return {
212
+ content: [
213
+ {
214
+ type: "text",
215
+ text: `Validation error: ${parsed.error.message}`,
216
+ },
217
+ ],
218
+ isError: true,
219
+ };
220
+ }
221
+
222
+ // Compile report
223
+ const report = compileReport(parsed.data);
224
+
225
+ return {
226
+ content: [
227
+ {
228
+ type: "text",
229
+ text: JSON.stringify(report, null, 2),
230
+ },
231
+ ],
232
+ };
233
+ }
234
+
235
+ default:
236
+ return {
237
+ content: [
238
+ {
239
+ type: "text",
240
+ text: `Unknown tool: ${name}`,
241
+ },
242
+ ],
243
+ isError: true,
244
+ };
245
+ }
246
+ } catch (error) {
247
+ const errorMessage = error instanceof Error ? error.message : String(error);
248
+ return {
249
+ content: [
250
+ {
251
+ type: "text",
252
+ text: `Error executing ${name}: ${errorMessage}`,
253
+ },
254
+ ],
255
+ isError: true,
256
+ };
257
+ }
258
+ });
259
+
260
+ // Run server
261
+ async function main() {
262
+ const transport = new StdioServerTransport();
263
+ await server.connect(transport);
264
+ console.error("MCP Sequential Research server running on stdio");
265
+ }
266
+
267
+ main().catch((error) => {
268
+ console.error("Fatal error:", error);
269
+ process.exit(1);
270
+ });