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/README.md +251 -0
- package/dist/compilers.d.ts +12 -0
- package/dist/compilers.d.ts.map +1 -0
- package/dist/compilers.js +871 -0
- package/dist/compilers.js.map +1 -0
- package/dist/format.d.ts +84 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +177 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +251 -0
- package/dist/index.js.map +1 -0
- package/dist/planners.d.ts +12 -0
- package/dist/planners.d.ts.map +1 -0
- package/dist/planners.js +608 -0
- package/dist/planners.js.map +1 -0
- package/dist/schema.d.ts +1947 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +232 -0
- package/dist/schema.js.map +1 -0
- package/docs/MCP_GUIDANCE.md +409 -0
- package/docs/TOOL_CONTRACTS.md +431 -0
- package/examples/example_workflow.md +464 -0
- package/package.json +38 -0
- package/src/compilers.ts +1113 -0
- package/src/format.ts +219 -0
- package/src/index.ts +270 -0
- package/src/planners.ts +687 -0
- package/src/schema.ts +303 -0
- package/tsconfig.json +24 -0
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
|
+
});
|