Merge branch 'main' into convert-everything-to-modern-api

This commit is contained in:
Cliff Hall
2025-11-22 14:07:50 -05:00
committed by GitHub
11 changed files with 823 additions and 1010 deletions

View File

@@ -32,7 +32,7 @@ jobs:
- name: Run Claude Code - name: Run Claude Code
id: claude id: claude
uses: anthropics/claude-code-action@beta uses: anthropics/claude-code-action@v1
with: with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -42,10 +42,7 @@ jobs:
# Trigger when assigned to an issue # Trigger when assigned to an issue
assignee_trigger: "claude" assignee_trigger: "claude"
# Allow Claude to run bash claude_args: |
# This should be safe given the repo is already public --allowedTools Bash
allowed_tools: "Bash" --system-prompt "If posting a comment to GitHub, give a concise summary of the comment at the top and put all the details in a <details> block."
custom_instructions: |
If posting a comment to GitHub, give a concise summary of the comment at the top and put all the details in a <details> block.

View File

@@ -41,21 +41,9 @@ jobs:
working-directory: src/${{ matrix.package }} working-directory: src/${{ matrix.package }}
run: npm ci run: npm ci
- name: Check if tests exist
id: check-tests
working-directory: src/${{ matrix.package }}
run: |
if npm run test --silent 2>/dev/null; then
echo "has-tests=true" >> $GITHUB_OUTPUT
else
echo "has-tests=false" >> $GITHUB_OUTPUT
fi
continue-on-error: true
- name: Run tests - name: Run tests
if: steps.check-tests.outputs.has-tests == 'true'
working-directory: src/${{ matrix.package }} working-directory: src/${{ matrix.package }}
run: npm test run: npm test --if-present
build: build:
needs: [detect-packages, test] needs: [detect-packages, test]

View File

@@ -854,6 +854,7 @@ A growing set of community-developed and maintained servers demonstrates various
- **[GraphQL](https://github.com/drestrepom/mcp_graphql)** - Comprehensive GraphQL API integration that automatically exposes each GraphQL query as a separate tool. - **[GraphQL](https://github.com/drestrepom/mcp_graphql)** - Comprehensive GraphQL API integration that automatically exposes each GraphQL query as a separate tool.
- **[GraphQL Schema](https://github.com/hannesj/mcp-graphql-schema)** - Allow LLMs to explore large GraphQL schemas without bloating the context. - **[GraphQL Schema](https://github.com/hannesj/mcp-graphql-schema)** - Allow LLMs to explore large GraphQL schemas without bloating the context.
- **[Graylog](https://github.com/Pranavj17/mcp-server-graylog)** - Search Graylog logs by absolute/relative timestamps, filter by streams, and debug production issues directly from Claude Desktop. - **[Graylog](https://github.com/Pranavj17/mcp-server-graylog)** - Search Graylog logs by absolute/relative timestamps, filter by streams, and debug production issues directly from Claude Desktop.
- **[Grok-MCP](https://github.com/merterbak/Grok-MCP)** - MCP server for xAIs API featuring the latest Grok models, image analysis & generation, and web search.
- **[gx-mcp-server](https://github.com/davidf9999/gx-mcp-server)** - Expose Great Expectations data validation and quality checks as MCP tools for AI agents. - **[gx-mcp-server](https://github.com/davidf9999/gx-mcp-server)** - Expose Great Expectations data validation and quality checks as MCP tools for AI agents.
- **[HackMD](https://github.com/yuna0x0/hackmd-mcp)** (by yuna0x0) - An MCP server for HackMD, a collaborative markdown editor. It allows users to create, read, and update documents in HackMD using the Model Context Protocol. - **[HackMD](https://github.com/yuna0x0/hackmd-mcp)** (by yuna0x0) - An MCP server for HackMD, a collaborative markdown editor. It allows users to create, read, and update documents in HackMD using the Model Context Protocol.
- **[HAProxy](https://github.com/tuannvm/haproxy-mcp-server)** - A Model Context Protocol (MCP) server for HAProxy implemented in Go, leveraging HAProxy Runtime API. - **[HAProxy](https://github.com/tuannvm/haproxy-mcp-server)** - A Model Context Protocol (MCP) server for HAProxy implemented in Go, leveraging HAProxy Runtime API.

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@
"exclude": [ "exclude": [
"**/__tests__/**", "**/__tests__/**",
"**/*.test.ts", "**/*.test.ts",
"**/*.spec.ts" "**/*.spec.ts",
"vitest.config.ts"
] ]
} }

View File

@@ -1,11 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { import { z } from "zod";
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import path from 'path'; import path from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
@@ -226,243 +223,235 @@ export class KnowledgeGraphManager {
let knowledgeGraphManager: KnowledgeGraphManager; let knowledgeGraphManager: KnowledgeGraphManager;
// Zod schemas for entities and relations
const EntitySchema = z.object({
name: z.string().describe("The name of the entity"),
entityType: z.string().describe("The type of the entity"),
observations: z.array(z.string()).describe("An array of observation contents associated with the entity")
});
const RelationSchema = z.object({
from: z.string().describe("The name of the entity where the relation starts"),
to: z.string().describe("The name of the entity where the relation ends"),
relationType: z.string().describe("The type of the relation")
});
// The server instance and tools exposed to Claude // The server instance and tools exposed to Claude
const server = new Server({ const server = new McpServer({
name: "memory-server", name: "memory-server",
version: "0.6.3", version: "0.6.3",
}, { });
capabilities: {
tools: {}, // Register create_entities tool
server.registerTool(
"create_entities",
{
title: "Create Entities",
description: "Create multiple new entities in the knowledge graph",
inputSchema: {
entities: z.array(EntitySchema)
}, },
},); outputSchema: {
entities: z.array(EntitySchema)
server.setRequestHandler(ListToolsRequestSchema, async () => { }
return { },
tools: [ async ({ entities }) => {
{ const result = await knowledgeGraphManager.createEntities(entities);
name: "create_entities", return {
description: "Create multiple new entities in the knowledge graph", content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
inputSchema: { structuredContent: { entities: result }
type: "object", };
properties: {
entities: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string", description: "The name of the entity" },
entityType: { type: "string", description: "The type of the entity" },
observations: {
type: "array",
items: { type: "string" },
description: "An array of observation contents associated with the entity"
},
},
required: ["name", "entityType", "observations"],
additionalProperties: false,
},
},
},
required: ["entities"],
additionalProperties: false,
},
},
{
name: "create_relations",
description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice",
inputSchema: {
type: "object",
properties: {
relations: {
type: "array",
items: {
type: "object",
properties: {
from: { type: "string", description: "The name of the entity where the relation starts" },
to: { type: "string", description: "The name of the entity where the relation ends" },
relationType: { type: "string", description: "The type of the relation" },
},
required: ["from", "to", "relationType"],
additionalProperties: false,
},
},
},
required: ["relations"],
additionalProperties: false,
},
},
{
name: "add_observations",
description: "Add new observations to existing entities in the knowledge graph",
inputSchema: {
type: "object",
properties: {
observations: {
type: "array",
items: {
type: "object",
properties: {
entityName: { type: "string", description: "The name of the entity to add the observations to" },
contents: {
type: "array",
items: { type: "string" },
description: "An array of observation contents to add"
},
},
required: ["entityName", "contents"],
additionalProperties: false,
},
},
},
required: ["observations"],
additionalProperties: false,
},
},
{
name: "delete_entities",
description: "Delete multiple entities and their associated relations from the knowledge graph",
inputSchema: {
type: "object",
properties: {
entityNames: {
type: "array",
items: { type: "string" },
description: "An array of entity names to delete"
},
},
required: ["entityNames"],
additionalProperties: false,
},
},
{
name: "delete_observations",
description: "Delete specific observations from entities in the knowledge graph",
inputSchema: {
type: "object",
properties: {
deletions: {
type: "array",
items: {
type: "object",
properties: {
entityName: { type: "string", description: "The name of the entity containing the observations" },
observations: {
type: "array",
items: { type: "string" },
description: "An array of observations to delete"
},
},
required: ["entityName", "observations"],
additionalProperties: false,
},
},
},
required: ["deletions"],
additionalProperties: false,
},
},
{
name: "delete_relations",
description: "Delete multiple relations from the knowledge graph",
inputSchema: {
type: "object",
properties: {
relations: {
type: "array",
items: {
type: "object",
properties: {
from: { type: "string", description: "The name of the entity where the relation starts" },
to: { type: "string", description: "The name of the entity where the relation ends" },
relationType: { type: "string", description: "The type of the relation" },
},
required: ["from", "to", "relationType"],
additionalProperties: false,
},
description: "An array of relations to delete"
},
},
required: ["relations"],
additionalProperties: false,
},
},
{
name: "read_graph",
description: "Read the entire knowledge graph",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false,
},
},
{
name: "search_nodes",
description: "Search for nodes in the knowledge graph based on a query",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "The search query to match against entity names, types, and observation content" },
},
required: ["query"],
additionalProperties: false,
},
},
{
name: "open_nodes",
description: "Open specific nodes in the knowledge graph by their names",
inputSchema: {
type: "object",
properties: {
names: {
type: "array",
items: { type: "string" },
description: "An array of entity names to retrieve",
},
},
required: ["names"],
additionalProperties: false,
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "read_graph") {
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.readGraph(), null, 2) }] };
} }
);
if (!args) { // Register create_relations tool
throw new Error(`No arguments provided for tool: ${name}`); server.registerTool(
"create_relations",
{
title: "Create Relations",
description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice",
inputSchema: {
relations: z.array(RelationSchema)
},
outputSchema: {
relations: z.array(RelationSchema)
}
},
async ({ relations }) => {
const result = await knowledgeGraphManager.createRelations(relations);
return {
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
structuredContent: { relations: result }
};
} }
);
switch (name) { // Register add_observations tool
case "create_entities": server.registerTool(
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createEntities(args.entities as Entity[]), null, 2) }] }; "add_observations",
case "create_relations": {
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createRelations(args.relations as Relation[]), null, 2) }] }; title: "Add Observations",
case "add_observations": description: "Add new observations to existing entities in the knowledge graph",
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.addObservations(args.observations as { entityName: string; contents: string[] }[]), null, 2) }] }; inputSchema: {
case "delete_entities": observations: z.array(z.object({
await knowledgeGraphManager.deleteEntities(args.entityNames as string[]); entityName: z.string().describe("The name of the entity to add the observations to"),
return { content: [{ type: "text", text: "Entities deleted successfully" }] }; contents: z.array(z.string()).describe("An array of observation contents to add")
case "delete_observations": }))
await knowledgeGraphManager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[]); },
return { content: [{ type: "text", text: "Observations deleted successfully" }] }; outputSchema: {
case "delete_relations": results: z.array(z.object({
await knowledgeGraphManager.deleteRelations(args.relations as Relation[]); entityName: z.string(),
return { content: [{ type: "text", text: "Relations deleted successfully" }] }; addedObservations: z.array(z.string())
case "search_nodes": }))
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.searchNodes(args.query as string), null, 2) }] }; }
case "open_nodes": },
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.openNodes(args.names as string[]), null, 2) }] }; async ({ observations }) => {
default: const result = await knowledgeGraphManager.addObservations(observations);
throw new Error(`Unknown tool: ${name}`); return {
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
structuredContent: { results: result }
};
} }
}); );
// Register delete_entities tool
server.registerTool(
"delete_entities",
{
title: "Delete Entities",
description: "Delete multiple entities and their associated relations from the knowledge graph",
inputSchema: {
entityNames: z.array(z.string()).describe("An array of entity names to delete")
},
outputSchema: {
success: z.boolean(),
message: z.string()
}
},
async ({ entityNames }) => {
await knowledgeGraphManager.deleteEntities(entityNames);
return {
content: [{ type: "text" as const, text: "Entities deleted successfully" }],
structuredContent: { success: true, message: "Entities deleted successfully" }
};
}
);
// Register delete_observations tool
server.registerTool(
"delete_observations",
{
title: "Delete Observations",
description: "Delete specific observations from entities in the knowledge graph",
inputSchema: {
deletions: z.array(z.object({
entityName: z.string().describe("The name of the entity containing the observations"),
observations: z.array(z.string()).describe("An array of observations to delete")
}))
},
outputSchema: {
success: z.boolean(),
message: z.string()
}
},
async ({ deletions }) => {
await knowledgeGraphManager.deleteObservations(deletions);
return {
content: [{ type: "text" as const, text: "Observations deleted successfully" }],
structuredContent: { success: true, message: "Observations deleted successfully" }
};
}
);
// Register delete_relations tool
server.registerTool(
"delete_relations",
{
title: "Delete Relations",
description: "Delete multiple relations from the knowledge graph",
inputSchema: {
relations: z.array(RelationSchema).describe("An array of relations to delete")
},
outputSchema: {
success: z.boolean(),
message: z.string()
}
},
async ({ relations }) => {
await knowledgeGraphManager.deleteRelations(relations);
return {
content: [{ type: "text" as const, text: "Relations deleted successfully" }],
structuredContent: { success: true, message: "Relations deleted successfully" }
};
}
);
// Register read_graph tool
server.registerTool(
"read_graph",
{
title: "Read Graph",
description: "Read the entire knowledge graph",
inputSchema: {},
outputSchema: {
entities: z.array(EntitySchema),
relations: z.array(RelationSchema)
}
},
async () => {
const graph = await knowledgeGraphManager.readGraph();
return {
content: [{ type: "text" as const, text: JSON.stringify(graph, null, 2) }],
structuredContent: { ...graph }
};
}
);
// Register search_nodes tool
server.registerTool(
"search_nodes",
{
title: "Search Nodes",
description: "Search for nodes in the knowledge graph based on a query",
inputSchema: {
query: z.string().describe("The search query to match against entity names, types, and observation content")
},
outputSchema: {
entities: z.array(EntitySchema),
relations: z.array(RelationSchema)
}
},
async ({ query }) => {
const graph = await knowledgeGraphManager.searchNodes(query);
return {
content: [{ type: "text" as const, text: JSON.stringify(graph, null, 2) }],
structuredContent: { ...graph }
};
}
);
// Register open_nodes tool
server.registerTool(
"open_nodes",
{
title: "Open Nodes",
description: "Open specific nodes in the knowledge graph by their names",
inputSchema: {
names: z.array(z.string()).describe("An array of entity names to retrieve")
},
outputSchema: {
entities: z.array(EntitySchema),
relations: z.array(RelationSchema)
}
},
async ({ names }) => {
const graph = await knowledgeGraphManager.openNodes(names);
return {
content: [{ type: "text" as const, text: JSON.stringify(graph, null, 2) }],
structuredContent: { ...graph }
};
}
);
async function main() { async function main() {
// Initialize memory file path with backward compatibility // Initialize memory file path with backward compatibility

View File

@@ -1,11 +1,14 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./dist", "outDir": "./dist",
"rootDir": "." "rootDir": "."
}, },
"include": [ "include": [
"./**/*.ts" "./**/*.ts"
] ],
} "exclude": [
"**/*.test.ts",
"vitest.config.ts"
]
}

View File

@@ -22,107 +22,8 @@ describe('SequentialThinkingServer', () => {
server = new SequentialThinkingServer(); server = new SequentialThinkingServer();
}); });
describe('processThought - validation', () => { // Note: Input validation tests removed - validation now happens at the tool
it('should reject input with missing thought', () => { // registration layer via Zod schemas before processThought is called
const input = {
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thought');
});
it('should reject input with non-string thought', () => {
const input = {
thought: 123,
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thought');
});
it('should reject input with missing thoughtNumber', () => {
const input = {
thought: 'Test thought',
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thoughtNumber');
});
it('should reject input with non-number thoughtNumber', () => {
const input = {
thought: 'Test thought',
thoughtNumber: '1',
totalThoughts: 3,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thoughtNumber');
});
it('should reject input with missing totalThoughts', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid totalThoughts');
});
it('should reject input with non-number totalThoughts', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: '3',
nextThoughtNeeded: true
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid totalThoughts');
});
it('should reject input with missing nextThoughtNeeded', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: 3
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid nextThoughtNeeded');
});
it('should reject input with non-boolean nextThoughtNeeded', () => {
const input = {
thought: 'Test thought',
thoughtNumber: 1,
totalThoughts: 3,
nextThoughtNeeded: 'true'
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid nextThoughtNeeded');
});
});
describe('processThought - valid inputs', () => { describe('processThought - valid inputs', () => {
it('should accept valid basic thought', () => { it('should accept valid basic thought', () => {
@@ -275,19 +176,6 @@ describe('SequentialThinkingServer', () => {
}); });
describe('processThought - edge cases', () => { describe('processThought - edge cases', () => {
it('should reject empty thought string', () => {
const input = {
thought: '',
thoughtNumber: 1,
totalThoughts: 1,
nextThoughtNeeded: false
};
const result = server.processThought(input);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Invalid thought');
});
it('should handle very long thought strings', () => { it('should handle very long thought strings', () => {
const input = { const input = {
thought: 'a'.repeat(10000), thought: 'a'.repeat(10000),
@@ -349,25 +237,6 @@ describe('SequentialThinkingServer', () => {
expect(result.content[0]).toHaveProperty('text'); expect(result.content[0]).toHaveProperty('text');
}); });
it('should return correct error structure on failure', () => {
const input = {
thought: 'Test',
thoughtNumber: 1,
totalThoughts: 1
// missing nextThoughtNeeded
};
const result = server.processThought(input);
expect(result).toHaveProperty('isError', true);
expect(result).toHaveProperty('content');
expect(Array.isArray(result.content)).toBe(true);
const errorData = JSON.parse(result.content[0].text);
expect(errorData).toHaveProperty('error');
expect(errorData).toHaveProperty('status', 'failed');
});
it('should return valid JSON in response', () => { it('should return valid JSON in response', () => {
const input = { const input = {
thought: 'Test thought', thought: 'Test thought',

View File

@@ -1,17 +1,22 @@
#!/usr/bin/env node #!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { import { z } from "zod";
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { SequentialThinkingServer } from './lib.js'; import { SequentialThinkingServer } from './lib.js';
const SEQUENTIAL_THINKING_TOOL: Tool = { const server = new McpServer({
name: "sequentialthinking", name: "sequential-thinking-server",
description: `A detailed tool for dynamic and reflective problem-solving through thoughts. version: "0.2.0",
});
const thinkingServer = new SequentialThinkingServer();
server.registerTool(
"sequentialthinking",
{
title: "Sequential Thinking",
description: `A detailed tool for dynamic and reflective problem-solving through thoughts.
This tool helps analyze problems through a flexible thinking process that can adapt and evolve. This tool helps analyze problems through a flexible thinking process that can adapt and evolve.
Each thought can build on, question, or revise previous insights as understanding deepens. Each thought can build on, question, or revise previous insights as understanding deepens.
@@ -37,13 +42,13 @@ Key features:
Parameters explained: Parameters explained:
- thought: Your current thinking step, which can include: - thought: Your current thinking step, which can include:
* Regular analytical steps * Regular analytical steps
* Revisions of previous thoughts * Revisions of previous thoughts
* Questions about previous decisions * Questions about previous decisions
* Realizations about needing more analysis * Realizations about needing more analysis
* Changes in approach * Changes in approach
* Hypothesis generation * Hypothesis generation
* Hypothesis verification * Hypothesis verification
- nextThoughtNeeded: True if you need more thinking, even if at what seemed like the end - nextThoughtNeeded: True if you need more thinking, even if at what seemed like the end
- thoughtNumber: Current number in sequence (can go beyond initial total if needed) - thoughtNumber: Current number in sequence (can go beyond initial total if needed)
- totalThoughts: Current estimate of thoughts needed (can be adjusted up/down) - totalThoughts: Current estimate of thoughts needed (can be adjusted up/down)
@@ -65,86 +70,42 @@ You should:
9. Repeat the process until satisfied with the solution 9. Repeat the process until satisfied with the solution
10. Provide a single, ideally correct answer as the final output 10. Provide a single, ideally correct answer as the final output
11. Only set next_thought_needed to false when truly done and a satisfactory answer is reached`, 11. Only set next_thought_needed to false when truly done and a satisfactory answer is reached`,
inputSchema: { inputSchema: {
type: "object", thought: z.string().describe("Your current thinking step"),
properties: { nextThoughtNeeded: z.boolean().describe("Whether another thought step is needed"),
thought: { thoughtNumber: z.number().int().min(1).describe("Current thought number (numeric value, e.g., 1, 2, 3)"),
type: "string", totalThoughts: z.number().int().min(1).describe("Estimated total thoughts needed (numeric value, e.g., 5, 10)"),
description: "Your current thinking step" isRevision: z.boolean().optional().describe("Whether this revises previous thinking"),
}, revisesThought: z.number().int().min(1).optional().describe("Which thought is being reconsidered"),
nextThoughtNeeded: { branchFromThought: z.number().int().min(1).optional().describe("Branching point thought number"),
type: "boolean", branchId: z.string().optional().describe("Branch identifier"),
description: "Whether another thought step is needed" needsMoreThoughts: z.boolean().optional().describe("If more thoughts are needed")
}, },
thoughtNumber: { outputSchema: {
type: "integer", thoughtNumber: z.number(),
description: "Current thought number (numeric value, e.g., 1, 2, 3)", totalThoughts: z.number(),
minimum: 1 nextThoughtNeeded: z.boolean(),
}, branches: z.array(z.string()),
totalThoughts: { thoughtHistoryLength: z.number()
type: "integer",
description: "Estimated total thoughts needed (numeric value, e.g., 5, 10)",
minimum: 1
},
isRevision: {
type: "boolean",
description: "Whether this revises previous thinking"
},
revisesThought: {
type: "integer",
description: "Which thought is being reconsidered",
minimum: 1
},
branchFromThought: {
type: "integer",
description: "Branching point thought number",
minimum: 1
},
branchId: {
type: "string",
description: "Branch identifier"
},
needsMoreThoughts: {
type: "boolean",
description: "If more thoughts are needed"
}
}, },
required: ["thought", "nextThoughtNeeded", "thoughtNumber", "totalThoughts"]
}
};
const server = new Server(
{
name: "sequential-thinking-server",
version: "0.2.0",
}, },
{ async (args) => {
capabilities: { const result = thinkingServer.processThought(args);
tools: {},
}, if (result.isError) {
return result;
}
// Parse the JSON response to get structured content
const parsedContent = JSON.parse(result.content[0].text);
return {
content: result.content,
structuredContent: parsedContent
};
} }
); );
const thinkingServer = new SequentialThinkingServer();
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [SEQUENTIAL_THINKING_TOOL],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "sequentialthinking") {
return thinkingServer.processThought(request.params.arguments);
}
return {
content: [{
type: "text",
text: `Unknown tool: ${request.params.name}`
}],
isError: true
};
});
async function runServer() { async function runServer() {
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();
await server.connect(transport); await server.connect(transport);

View File

@@ -21,35 +21,6 @@ export class SequentialThinkingServer {
this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true"; this.disableThoughtLogging = (process.env.DISABLE_THOUGHT_LOGGING || "").toLowerCase() === "true";
} }
private validateThoughtData(input: unknown): ThoughtData {
const data = input as Record<string, unknown>;
if (!data.thought || typeof data.thought !== 'string') {
throw new Error('Invalid thought: must be a string');
}
if (!data.thoughtNumber || typeof data.thoughtNumber !== 'number') {
throw new Error('Invalid thoughtNumber: must be a number');
}
if (!data.totalThoughts || typeof data.totalThoughts !== 'number') {
throw new Error('Invalid totalThoughts: must be a number');
}
if (typeof data.nextThoughtNeeded !== 'boolean') {
throw new Error('Invalid nextThoughtNeeded: must be a boolean');
}
return {
thought: data.thought,
thoughtNumber: data.thoughtNumber,
totalThoughts: data.totalThoughts,
nextThoughtNeeded: data.nextThoughtNeeded,
isRevision: data.isRevision as boolean | undefined,
revisesThought: data.revisesThought as number | undefined,
branchFromThought: data.branchFromThought as number | undefined,
branchId: data.branchId as string | undefined,
needsMoreThoughts: data.needsMoreThoughts as boolean | undefined,
};
}
private formatThought(thoughtData: ThoughtData): string { private formatThought(thoughtData: ThoughtData): string {
const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId } = thoughtData; const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId } = thoughtData;
@@ -78,35 +49,35 @@ export class SequentialThinkingServer {
${border}`; ${border}`;
} }
public processThought(input: unknown): { content: Array<{ type: string; text: string }>; isError?: boolean } { public processThought(input: ThoughtData): { content: Array<{ type: "text"; text: string }>; isError?: boolean } {
try { try {
const validatedInput = this.validateThoughtData(input); // Validation happens at the tool registration layer via Zod
// Adjust totalThoughts if thoughtNumber exceeds it
if (validatedInput.thoughtNumber > validatedInput.totalThoughts) { if (input.thoughtNumber > input.totalThoughts) {
validatedInput.totalThoughts = validatedInput.thoughtNumber; input.totalThoughts = input.thoughtNumber;
} }
this.thoughtHistory.push(validatedInput); this.thoughtHistory.push(input);
if (validatedInput.branchFromThought && validatedInput.branchId) { if (input.branchFromThought && input.branchId) {
if (!this.branches[validatedInput.branchId]) { if (!this.branches[input.branchId]) {
this.branches[validatedInput.branchId] = []; this.branches[input.branchId] = [];
} }
this.branches[validatedInput.branchId].push(validatedInput); this.branches[input.branchId].push(input);
} }
if (!this.disableThoughtLogging) { if (!this.disableThoughtLogging) {
const formattedThought = this.formatThought(validatedInput); const formattedThought = this.formatThought(input);
console.error(formattedThought); console.error(formattedThought);
} }
return { return {
content: [{ content: [{
type: "text", type: "text" as const,
text: JSON.stringify({ text: JSON.stringify({
thoughtNumber: validatedInput.thoughtNumber, thoughtNumber: input.thoughtNumber,
totalThoughts: validatedInput.totalThoughts, totalThoughts: input.totalThoughts,
nextThoughtNeeded: validatedInput.nextThoughtNeeded, nextThoughtNeeded: input.nextThoughtNeeded,
branches: Object.keys(this.branches), branches: Object.keys(this.branches),
thoughtHistoryLength: this.thoughtHistory.length thoughtHistoryLength: this.thoughtHistory.length
}, null, 2) }, null, 2)
@@ -115,7 +86,7 @@ export class SequentialThinkingServer {
} catch (error) { } catch (error) {
return { return {
content: [{ content: [{
type: "text", type: "text" as const,
text: JSON.stringify({ text: JSON.stringify({
error: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error.message : String(error),
status: 'failed' status: 'failed'

View File

@@ -2,9 +2,13 @@
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./dist", "outDir": "./dist",
"rootDir": ".", "rootDir": "."
"moduleResolution": "NodeNext",
"module": "NodeNext"
}, },
"include": ["./**/*.ts"] "include": [
"./**/*.ts"
],
"exclude": [
"**/*.test.ts",
"vitest.config.ts"
]
} }