Merge branch 'main' into add-tasks-support

This commit is contained in:
Ola Hungerford
2026-01-17 14:05:35 -07:00
committed by GitHub
8 changed files with 754 additions and 192 deletions

9
package-lock.json generated
View File

@@ -1688,9 +1688,10 @@
}
},
"node_modules/diff": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz",
"integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
@@ -3804,7 +3805,7 @@
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.2",
"diff": "^5.1.0",
"diff": "^8.0.3",
"glob": "^10.5.0",
"minimatch": "^10.0.1",
"zod-to-json-schema": "^3.23.5"

View File

@@ -30,7 +30,7 @@ export const roots: Map<string | undefined, Root[]> = new Map<
*/
export const syncRoots = async (server: McpServer, sessionId?: string) => {
const clientCapabilities = server.server.getClientCapabilities() || {};
const clientSupportsRoots: boolean = clientCapabilities.roots !== undefined;
const clientSupportsRoots: boolean = clientCapabilities?.roots !== undefined;
// Fetch the roots list for this client
if (clientSupportsRoots) {
@@ -48,7 +48,7 @@ export const syncRoots = async (server: McpServer, sessionId?: string) => {
{
level: "info",
logger: "everything-server",
data: `Roots updated: ${response.roots.length} root(s) received from client`,
data: `Roots updated: ${response?.roots?.length} root(s) received from client`,
},
sessionId
);

View File

@@ -26,7 +26,7 @@
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.2",
"diff": "^5.1.0",
"diff": "^8.0.3",
"glob": "^10.5.0",
"minimatch": "^10.0.1",
"zod-to-json-schema": "^3.23.5"

View File

@@ -390,5 +390,94 @@ describe('KnowledgeGraphManager', () => {
expect(JSON.parse(lines[0])).toHaveProperty('type', 'entity');
expect(JSON.parse(lines[1])).toHaveProperty('type', 'relation');
});
it('should strip type field from entities when loading from file', async () => {
// Create entities and relations (these get saved with type field)
await manager.createEntities([
{ name: 'Alice', entityType: 'person', observations: ['test observation'] },
{ name: 'Bob', entityType: 'person', observations: [] },
]);
await manager.createRelations([
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
]);
// Verify file contains type field (order may vary)
const fileContent = await fs.readFile(testFilePath, 'utf-8');
const fileLines = fileContent.split('\n').filter(line => line.trim());
const fileItems = fileLines.map(line => JSON.parse(line));
const fileEntity = fileItems.find(item => item.type === 'entity');
const fileRelation = fileItems.find(item => item.type === 'relation');
expect(fileEntity).toBeDefined();
expect(fileEntity).toHaveProperty('type', 'entity');
expect(fileRelation).toBeDefined();
expect(fileRelation).toHaveProperty('type', 'relation');
// Create new manager instance to force reload from file
const manager2 = new KnowledgeGraphManager(testFilePath);
const graph = await manager2.readGraph();
// Verify loaded entities don't have type field
expect(graph.entities).toHaveLength(2);
graph.entities.forEach(entity => {
expect(entity).not.toHaveProperty('type');
expect(entity).toHaveProperty('name');
expect(entity).toHaveProperty('entityType');
expect(entity).toHaveProperty('observations');
});
// Verify loaded relations don't have type field
expect(graph.relations).toHaveLength(1);
graph.relations.forEach(relation => {
expect(relation).not.toHaveProperty('type');
expect(relation).toHaveProperty('from');
expect(relation).toHaveProperty('to');
expect(relation).toHaveProperty('relationType');
});
});
it('should strip type field from searchNodes results', async () => {
await manager.createEntities([
{ name: 'Alice', entityType: 'person', observations: ['works at Acme'] },
]);
await manager.createRelations([
{ from: 'Alice', to: 'Alice', relationType: 'self' },
]);
// Create new manager instance to force reload from file
const manager2 = new KnowledgeGraphManager(testFilePath);
const result = await manager2.searchNodes('Alice');
// Verify search results don't have type field
expect(result.entities).toHaveLength(1);
expect(result.entities[0]).not.toHaveProperty('type');
expect(result.entities[0].name).toBe('Alice');
expect(result.relations).toHaveLength(1);
expect(result.relations[0]).not.toHaveProperty('type');
expect(result.relations[0].from).toBe('Alice');
});
it('should strip type field from openNodes results', async () => {
await manager.createEntities([
{ name: 'Alice', entityType: 'person', observations: [] },
{ name: 'Bob', entityType: 'person', observations: [] },
]);
await manager.createRelations([
{ from: 'Alice', to: 'Bob', relationType: 'knows' },
]);
// Create new manager instance to force reload from file
const manager2 = new KnowledgeGraphManager(testFilePath);
const result = await manager2.openNodes(['Alice', 'Bob']);
// Verify open results don't have type field
expect(result.entities).toHaveLength(2);
result.entities.forEach(entity => {
expect(entity).not.toHaveProperty('type');
});
expect(result.relations).toHaveLength(1);
expect(result.relations[0]).not.toHaveProperty('type');
});
});
});

View File

@@ -74,8 +74,20 @@ export class KnowledgeGraphManager {
const lines = data.split("\n").filter(line => line.trim() !== "");
return lines.reduce((graph: KnowledgeGraph, line) => {
const item = JSON.parse(line);
if (item.type === "entity") graph.entities.push(item as Entity);
if (item.type === "relation") graph.relations.push(item as Relation);
if (item.type === "entity") {
graph.entities.push({
name: item.name,
entityType: item.entityType,
observations: item.observations
});
}
if (item.type === "relation") {
graph.relations.push({
from: item.from,
to: item.to,
relationType: item.relationType
});
}
return graph;
}, { entities: [], relations: [] });
} catch (error) {

View File

@@ -17,10 +17,10 @@ classifiers = [
"Programming Language :: Python :: 3.10",
]
dependencies = [
"mcp>=1.0.0",
"mcp>=1.23.0",
"pydantic>=2.0.0",
"tzdata>=2024.2",
"tzlocal>=5.3.1"
"tzlocal>=5.3.1",
]
[project.scripts]

View File

@@ -8,7 +8,7 @@ from tzlocal import get_localzone_name # ← returns "Europe/Paris", etc.
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource, ErrorData, INVALID_PARAMS
from mcp.shared.exceptions import McpError
from pydantic import BaseModel
@@ -54,7 +54,7 @@ def get_zoneinfo(timezone_name: str) -> ZoneInfo:
try:
return ZoneInfo(timezone_name)
except Exception as e:
raise McpError(f"Invalid timezone: {str(e)}")
raise McpError(ErrorData(code=INVALID_PARAMS, message=f"Invalid timezone: {str(e)}"))
class TimeServer:

818
src/time/uv.lock generated

File diff suppressed because it is too large Load Diff