@stackmemoryai/stackmemory
Version:
Lossless, project-scoped memory for AI coding tools. Durable context across sessions with 56 MCP tools, FTS5 search, conductor orchestrator, loop/watch monitoring, snapshot capture, pre-flight overlap checks, Claude/Codex/OpenCode wrappers, Linear sync, a
102 lines (101 loc) • 2.87 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { DEFAULT_GREPTILE_CONFIG } from "./config.js";
class GreptileClientError extends Error {
constructor(message, code) {
super(message);
this.code = code;
this.name = "GreptileClientError";
}
}
class GreptileClient {
config;
client = null;
transport = null;
connecting = null;
constructor(config = {}) {
this.config = { ...DEFAULT_GREPTILE_CONFIG, ...config };
if (!this.config.enabled || !this.config.apiKey) {
throw new GreptileClientError(
"Greptile integration disabled (GREPTILE_API_KEY not set)",
"DISABLED"
);
}
}
async ensureConnected() {
if (this.client) return this.client;
if (this.connecting) {
await this.connecting;
return this.client;
}
this.connecting = this.connect();
try {
await this.connecting;
return this.client;
} finally {
this.connecting = null;
}
}
async connect() {
const transport = new StreamableHTTPClientTransport(
new URL(this.config.mcpEndpoint),
{
requestInit: {
headers: {
Authorization: `Bearer ${this.config.apiKey}`
}
},
reconnectionOptions: {
maxRetries: this.config.maxRetries,
initialReconnectionDelay: 1e3,
reconnectionDelayGrowFactor: 1.5,
maxReconnectionDelay: 1e4
}
}
);
const client = new Client(
{ name: "stackmemory-greptile", version: "1.0.0" },
{ capabilities: {} }
);
transport.onclose = () => {
this.client = null;
this.transport = null;
};
await client.connect(transport);
this.client = client;
this.transport = transport;
}
async callTool(name, args = {}) {
const client = await this.ensureConnected();
const result = await client.callTool({ name, arguments: args });
if (result.content && Array.isArray(result.content)) {
const textParts = result.content.filter(
(c) => c.type === "text" && typeof c.text === "string"
).map((c) => c.text);
if (textParts.length === 0) return result;
const combined = textParts.join("\n");
try {
return JSON.parse(combined);
} catch {
return combined;
}
}
return result;
}
async disconnect() {
if (this.transport) {
await this.transport.close();
}
this.client = null;
this.transport = null;
this.connecting = null;
}
}
export {
GreptileClient,
GreptileClientError
};