Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions AGENTS.md

This file was deleted.

2 changes: 0 additions & 2 deletions CLAUDE.md

This file was deleted.

4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ We're more selective about:
We don't accept:
- **New server implementations** — We encourage you to publish them yourself, and link to them from the README.

## Testing

When adding or configuring tests for servers implemented in TypeScript, use **vitest** as the test framework. Vitest provides better ESM support, faster test execution, and a more modern testing experience.

## Documentation

Improvements to existing documentation is welcome - although generally we'd prefer ergonomic improvements than documenting pain points if possible!
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ Official integrations are maintained by companies building production ready MCP
- <img height="12" width="12" src="https://polymarket.com/favicon.ico" alt="Polymarket Logo" /> **[Polymarket](https://github.com/ozgureyilmaz/polymarket-mcp)** - Real-time prediction market data from Polymarket - search markets, analyze prices, identify trading opportunities.
- <img height="12" width="12" src="https://github.com/port-labs/port-mcp-server/blob/main/assets/port_symbol_white.svg" alt="Port Logo" /> **[Port IO](https://github.com/port-labs/port-mcp-server)** - Access and manage your software catalog to improve service quality and compliance.
- **[PostHog](https://github.com/posthog/mcp)** - Interact with PostHog analytics, feature flags, error tracking and more with the official PostHog MCP server.
- <img height="12" width="12" src="https://postidentity.com/favicon.ico" alt="PostIdentity Logo" /> **[PostIdentity](https://github.com/PostIdentity/mcp-server)** - Generate AI-powered social media posts from any AI assistant. Manage identities, create posts, track referrals, and browse marketplace templates, powered by [PostIdentity](https://postidentity.com).
- **[Postman API](https://github.com/postmanlabs/postman-api-mcp)** - Manage your Postman resources using the [Postman API](https://www.postman.com/postman/postman-public-workspace/collection/i2uqzpp/postman-api).
- <img height="12" width="12" src="https://powerdrill.ai/_next/static/media/powerdrill.0fa27d00.webp" alt="Powerdrill Logo" /> **[Powerdrill](https://github.com/powerdrillai/powerdrill-mcp)** - An MCP server that provides tools to interact with Powerdrill datasets, enabling smart AI data analysis and insights.
- <img height="12" width="12" src="https://www.prisma.io/images/favicon-32x32.png" alt="Prisma Logo" /> **[Prisma](https://www.prisma.io/docs/postgres/mcp-server)** - Create and manage Prisma Postgres databases
Expand Down Expand Up @@ -525,6 +526,7 @@ Official integrations are maintained by companies building production ready MCP
- **[ZettelkastenSpace](https://github.com/joshylchen/zettelkasten_space)** - Built on the proven [Zettelkasten](https://www.zettelkasten.space/) method, enhanced with Claude Desktop integration via Model Context Protocol
- <img height="12" width="12" src="https://www.zine.ai/images/zine-logo.png" alt="Zine Logo" /> **[Zine](https://www.zine.ai)** - Your memory, everywhere AI goes. Think iPhoto for your knowledge - upload and curate. Like ChatGPT but portable - context that travels with you.
- <img height="12" width="12" src="https://zizai.work/images/logo.jpg" alt="ZIZAI Logo" /> **[ZIZAI Recruitment](https://github.com/zaiwork/mcp)** - Interact with the next-generation intelligent recruitment platform for employees and employers, powered by [ZIZAI Recruitment](https://zizai.work).

### 🌎 Community Servers

A growing set of community-developed and maintained servers demonstrates various applications of MCP across different domains.
Expand Down Expand Up @@ -1472,6 +1474,7 @@ Additional resources on MCP.
- <img height="12" width="12" src="https://mkinf.io/favicon-lilac.png" alt="mkinf Logo" /> **[mkinf](https://mkinf.io)** - An Open Source registry of hosted MCP Servers to accelerate AI agent workflows.
- **[Open-Sourced MCP Servers Directory](https://github.com/chatmcp/mcp-directory)** - A curated list of MCP servers by **[mcpso](https://mcp.so)**
- <img height="12" width="12" src="https://opentools.com/favicon.ico" alt="OpenTools Logo" /> **[OpenTools](https://opentools.com)** - An open registry for finding, installing, and building with MCP servers by **[opentoolsteam](https://github.com/opentoolsteam)**
- **[Programmatic MCP Prototype](https://github.com/domdomegg/programmatic-mcp-prototype)** - Experimental agent prototype demonstrating programmatic MCP tool composition, progressive tool discovery, state persistence, and skill building through TypeScript code execution by **[Adam Jones](https://github.com/domdomegg)**
- **[PulseMCP](https://www.pulsemcp.com)** ([API](https://www.pulsemcp.com/api)) - Community hub & weekly newsletter for discovering MCP servers, clients, articles, and news by **[Tadas Antanavicius](https://github.com/tadasant)**, **[Mike Coughlin](https://github.com/macoughl)**, and **[Ravina Patel](https://github.com/ravinahp)**
- **[r/mcp](https://www.reddit.com/r/mcp)** – A Reddit community dedicated to MCP by **[Frank Fiegel](https://github.com/punkpeye)**
- **[r/modelcontextprotocol](https://www.reddit.com/r/modelcontextprotocol)** – A Model Context Protocol community Reddit page - discuss ideas, get answers to your questions, network with like-minded people, and showcase your projects! by **[Alex Andru](https://github.com/QuantGeekDev)**
Expand Down
4 changes: 3 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 16 additions & 5 deletions src/filesystem/__tests__/path-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect } from 'vitest';
import { describe, it, expect, afterEach } from 'vitest';
import { normalizePath, expandHome, convertToWindowsPath } from '../path-utils.js';

describe('Path Utilities', () => {
Expand Down Expand Up @@ -196,11 +196,8 @@ describe('Path Utilities', () => {
});

it('returns normalized non-Windows/WSL/Unix-style Windows paths as is after basic normalization', () => {
// Relative path
const relativePath = 'some/relative/path';
expect(normalizePath(relativePath)).toBe(relativePath.replace(/\//g, '\\'));

// A path that looks somewhat absolute but isn't a drive or recognized Unix root for Windows conversion
// These paths should be preserved as-is (not converted to Windows C:\ format or WSL format)
const otherAbsolutePath = '\\someserver\\share\\file';
expect(normalizePath(otherAbsolutePath)).toBe(otherAbsolutePath);
});
Expand Down Expand Up @@ -350,5 +347,19 @@ describe('Path Utilities', () => {
expect(result).not.toContain('C:');
expect(result).not.toContain('\\');
});

it('should handle relative path slash conversion based on platform', () => {
// This test verifies platform-specific behavior naturally without mocking
// On Windows: forward slashes converted to backslashes
// On Linux/Unix: forward slashes preserved
const relativePath = 'some/relative/path';
const result = normalizePath(relativePath);

if (originalPlatform === 'win32') {
expect(result).toBe('some\\relative\\path');
} else {
expect(result).toBe('some/relative/path');
}
});
});
});
156 changes: 156 additions & 0 deletions src/memory/__tests__/file-path.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { ensureMemoryFilePath, defaultMemoryPath } from '../index.js';

describe('ensureMemoryFilePath', () => {
const testDir = path.dirname(fileURLToPath(import.meta.url));
const oldMemoryPath = path.join(testDir, '..', 'memory.json');
const newMemoryPath = path.join(testDir, '..', 'memory.jsonl');

let originalEnv: string | undefined;

beforeEach(() => {
// Save original environment variable
originalEnv = process.env.MEMORY_FILE_PATH;
// Delete environment variable
delete process.env.MEMORY_FILE_PATH;
});

afterEach(async () => {
// Restore original environment variable
if (originalEnv !== undefined) {
process.env.MEMORY_FILE_PATH = originalEnv;
} else {
delete process.env.MEMORY_FILE_PATH;
}

// Clean up test files
try {
await fs.unlink(oldMemoryPath);
} catch {
// Ignore if file doesn't exist
}
try {
await fs.unlink(newMemoryPath);
} catch {
// Ignore if file doesn't exist
}
});

describe('with MEMORY_FILE_PATH environment variable', () => {
it('should return absolute path when MEMORY_FILE_PATH is absolute', async () => {
const absolutePath = '/tmp/custom-memory.jsonl';
process.env.MEMORY_FILE_PATH = absolutePath;

const result = await ensureMemoryFilePath();

expect(result).toBe(absolutePath);
});

it('should convert relative path to absolute when MEMORY_FILE_PATH is relative', async () => {
const relativePath = 'custom-memory.jsonl';
process.env.MEMORY_FILE_PATH = relativePath;

const result = await ensureMemoryFilePath();

expect(path.isAbsolute(result)).toBe(true);
expect(result).toContain('custom-memory.jsonl');
});

it('should handle Windows absolute paths', async () => {
const windowsPath = 'C:\\temp\\memory.jsonl';
process.env.MEMORY_FILE_PATH = windowsPath;

const result = await ensureMemoryFilePath();

// On Windows, should return as-is; on Unix, will be treated as relative
if (process.platform === 'win32') {
expect(result).toBe(windowsPath);
} else {
expect(path.isAbsolute(result)).toBe(true);
}
});
});

describe('without MEMORY_FILE_PATH environment variable', () => {
it('should return default path when no files exist', async () => {
const result = await ensureMemoryFilePath();

expect(result).toBe(defaultMemoryPath);
});

it('should migrate from memory.json to memory.jsonl when only old file exists', async () => {
// Create old memory.json file
await fs.writeFile(oldMemoryPath, '{"test":"data"}');

const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

const result = await ensureMemoryFilePath();

expect(result).toBe(defaultMemoryPath);

// Verify migration happened
const newFileExists = await fs.access(newMemoryPath).then(() => true).catch(() => false);
const oldFileExists = await fs.access(oldMemoryPath).then(() => true).catch(() => false);

expect(newFileExists).toBe(true);
expect(oldFileExists).toBe(false);

// Verify console messages
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining('DETECTED: Found legacy memory.json file')
);
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining('COMPLETED: Successfully migrated')
);

consoleErrorSpy.mockRestore();
});

it('should use new file when both old and new files exist', async () => {
// Create both files
await fs.writeFile(oldMemoryPath, '{"old":"data"}');
await fs.writeFile(newMemoryPath, '{"new":"data"}');

const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

const result = await ensureMemoryFilePath();

expect(result).toBe(defaultMemoryPath);

// Verify no migration happened (both files should still exist)
const newFileExists = await fs.access(newMemoryPath).then(() => true).catch(() => false);
const oldFileExists = await fs.access(oldMemoryPath).then(() => true).catch(() => false);

expect(newFileExists).toBe(true);
expect(oldFileExists).toBe(true);

// Verify no console messages about migration
expect(consoleErrorSpy).not.toHaveBeenCalled();

consoleErrorSpy.mockRestore();
});

it('should preserve file content during migration', async () => {
const testContent = '{"entities": [{"name": "test", "type": "person"}]}';
await fs.writeFile(oldMemoryPath, testContent);

await ensureMemoryFilePath();

const migratedContent = await fs.readFile(newMemoryPath, 'utf-8');
expect(migratedContent).toBe(testContent);
});
});

describe('defaultMemoryPath', () => {
it('should end with memory.jsonl', () => {
expect(defaultMemoryPath).toMatch(/memory\.jsonl$/);
});

it('should be an absolute path', () => {
expect(path.isAbsolute(defaultMemoryPath)).toBe(true);
});
});
});
Loading
Loading