velixar-mcp-server 0.1.1

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.
@@ -0,0 +1,18 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-node@v4
15
+ with:
16
+ node-version: 20
17
+ - run: npm ci
18
+ - run: npm test
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # Velixar MCP Server
2
+
3
+ MCP server that gives AI assistants persistent memory via Velixar.
4
+
5
+ ## Setup
6
+
7
+ 1. Get an API key from https://velixarai.com/settings/api-keys
8
+
9
+ 2. Install:
10
+ ```bash
11
+ cd /Users/velixarai/velixar-mcp-server
12
+ npm install
13
+ ```
14
+
15
+ 3. Configure Kiro CLI (`~/.kiro/settings/mcp.json`):
16
+ ```json
17
+ {
18
+ "mcpServers": {
19
+ "velixar": {
20
+ "command": "node",
21
+ "args": ["/Users/velixarai/velixar-mcp-server/src/index.js"],
22
+ "env": {
23
+ "VELIXAR_API_KEY": "vlx_your_key_here",
24
+ "VELIXAR_USER_ID": "kiro_session"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ 4. Restart Kiro CLI
32
+
33
+ ## Tools
34
+
35
+ | Tool | Description |
36
+ |------|-------------|
37
+ | `velixar_store` | Store a memory |
38
+ | `velixar_search` | Search memories |
39
+ | `velixar_delete` | Delete a memory |
40
+
41
+ ## Usage
42
+
43
+ Once configured, the AI can:
44
+ - Remember facts across sessions
45
+ - Store user preferences
46
+ - Build context over time
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "velixar-mcp-server",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for Velixar persistent memory — give your AI agents long-term recall",
5
+ "type": "module",
6
+ "bin": {
7
+ "velixar-mcp-server": "./src/index.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node src/index.js",
11
+ "test": "node --test tests/*.test.js"
12
+ },
13
+ "keywords": [
14
+ "mcp",
15
+ "model-context-protocol",
16
+ "ai",
17
+ "memory",
18
+ "llm",
19
+ "persistent-memory",
20
+ "vector-search"
21
+ ],
22
+ "author": "Velixar <sdk@velixarai.com>",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/VelixarAi/velixar-mcp-server"
27
+ },
28
+ "homepage": "https://velixarai.com",
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.0.0"
31
+ }
32
+ }
package/src/index.js ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ } from "@modelcontextprotocol/sdk/types.js";
8
+
9
+ const API_KEY = process.env.VELIXAR_API_KEY;
10
+ const API_BASE = process.env.VELIXAR_API_URL || "https://t4xrnwgo7f.execute-api.us-east-1.amazonaws.com/v1";
11
+ const USER_ID = process.env.VELIXAR_USER_ID || "kiro-cli";
12
+
13
+ if (!API_KEY) {
14
+ console.error("VELIXAR_API_KEY environment variable required");
15
+ process.exit(1);
16
+ }
17
+
18
+ async function apiRequest(path, options = {}) {
19
+ const res = await fetch(`${API_BASE}${path}`, {
20
+ ...options,
21
+ headers: {
22
+ "authorization": `Bearer ${API_KEY}`,
23
+ "Content-Type": "application/json",
24
+ ...options.headers,
25
+ },
26
+ });
27
+ return res.json();
28
+ }
29
+
30
+ const server = new Server(
31
+ { name: "velixar-mcp-server", version: "0.1.1" },
32
+ { capabilities: { tools: {} } }
33
+ );
34
+
35
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
36
+ tools: [
37
+ {
38
+ name: "velixar_store",
39
+ description: "Store a memory for later retrieval. Use for important facts, user preferences, project context, or anything worth remembering.",
40
+ inputSchema: {
41
+ type: "object",
42
+ properties: {
43
+ content: { type: "string", description: "The memory content to store" },
44
+ tags: { type: "array", items: { type: "string" }, description: "Optional tags for categorization" },
45
+ tier: { type: "number", description: "Memory tier: 0=pinned, 1=session, 2=semantic, 3=org" },
46
+ },
47
+ required: ["content"],
48
+ },
49
+ },
50
+ {
51
+ name: "velixar_search",
52
+ description: "Search stored memories by semantic similarity. Use to recall past context, preferences, or facts.",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ query: { type: "string", description: "Search query" },
57
+ limit: { type: "number", description: "Max results (default 5)" },
58
+ },
59
+ required: ["query"],
60
+ },
61
+ },
62
+ {
63
+ name: "velixar_delete",
64
+ description: "Delete a memory by ID.",
65
+ inputSchema: {
66
+ type: "object",
67
+ properties: {
68
+ id: { type: "string", description: "Memory ID to delete" },
69
+ },
70
+ required: ["id"],
71
+ },
72
+ },
73
+ {
74
+ name: "velixar_list",
75
+ description: "List memories with pagination support.",
76
+ inputSchema: {
77
+ type: "object",
78
+ properties: {
79
+ limit: { type: "number", description: "Max results (default 10)" },
80
+ cursor: { type: "string", description: "Pagination cursor" },
81
+ },
82
+ required: [],
83
+ },
84
+ },
85
+ {
86
+ name: "velixar_update",
87
+ description: "Update an existing memory.",
88
+ inputSchema: {
89
+ type: "object",
90
+ properties: {
91
+ id: { type: "string", description: "Memory ID to update" },
92
+ content: { type: "string", description: "New content" },
93
+ tags: { type: "array", items: { type: "string" }, description: "New tags" },
94
+ },
95
+ required: ["id"],
96
+ },
97
+ },
98
+ ],
99
+ }));
100
+
101
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
102
+ const { name, arguments: args } = request.params;
103
+
104
+ try {
105
+ if (name === "velixar_store") {
106
+ const result = await apiRequest("/memory", {
107
+ method: "POST",
108
+ body: JSON.stringify({
109
+ content: args.content,
110
+ user_id: USER_ID,
111
+ tier: args.tier || 2,
112
+ tags: args.tags || [],
113
+ }),
114
+ });
115
+ if (result.error) throw new Error(result.error);
116
+ return { content: [{ type: "text", text: `✓ Stored memory` }] };
117
+ }
118
+
119
+ if (name === "velixar_search") {
120
+ const params = new URLSearchParams({ q: args.query, user_id: USER_ID });
121
+ if (args.limit) params.set("limit", String(args.limit));
122
+ const result = await apiRequest(`/memory/search?${params}`);
123
+ if (result.error) throw new Error(result.error);
124
+
125
+ if (!result.memories?.length) {
126
+ return { content: [{ type: "text", text: "No memories found." }] };
127
+ }
128
+ const memories = result.memories.map((m) => `• ${m.content}`).join("\n");
129
+ return { content: [{ type: "text", text: `Found ${result.count} memories:\n${memories}` }] };
130
+ }
131
+
132
+ if (name === "velixar_delete") {
133
+ const result = await apiRequest(`/memory/${args.id}`, { method: "DELETE" });
134
+ if (result.error) throw new Error(result.error);
135
+ return { content: [{ type: "text", text: `✓ Deleted memory: ${args.id}` }] };
136
+ }
137
+
138
+ if (name === "velixar_list") {
139
+ const params = new URLSearchParams({ user_id: USER_ID });
140
+ if (args.limit) params.set("limit", String(args.limit));
141
+ if (args.cursor) params.set("cursor", args.cursor);
142
+ const result = await apiRequest(`/memory/list?${params}`);
143
+ if (result.error) throw new Error(result.error);
144
+
145
+ if (!result.memories?.length) {
146
+ return { content: [{ type: "text", text: "No memories found." }] };
147
+ }
148
+ const memories = result.memories.map((m) => `• ${m.id}: ${m.content.substring(0, 100)}...`).join("\n");
149
+ const cursor = result.cursor ? `\nNext cursor: ${result.cursor}` : "";
150
+ return { content: [{ type: "text", text: `Found ${result.count} memories:${cursor}\n${memories}` }] };
151
+ }
152
+
153
+ if (name === "velixar_update") {
154
+ const body = { user_id: USER_ID };
155
+ if (args.content) body.content = args.content;
156
+ if (args.tags) body.tags = args.tags;
157
+ const result = await apiRequest(`/memory/${args.id}`, {
158
+ method: "PATCH",
159
+ body: JSON.stringify(body),
160
+ });
161
+ if (result.error) throw new Error(result.error);
162
+ return { content: [{ type: "text", text: `✓ Updated memory: ${args.id}` }] };
163
+ }
164
+
165
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
166
+ } catch (error) {
167
+ return { content: [{ type: "text", text: `Error: ${error.message}` }] };
168
+ }
169
+ });
170
+
171
+ const transport = new StdioServerTransport();
172
+ await server.connect(transport);
@@ -0,0 +1,96 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert";
3
+
4
+ // Mock fetch globally
5
+ global.fetch = async (url, options) => {
6
+ const mockResponses = {
7
+ "/memory": { success: true },
8
+ "/memory/search": { memories: [{ content: "test memory" }], count: 1 },
9
+ "/memory/test-id": { success: true }
10
+ };
11
+
12
+ const path = url.replace(/^.*\/v1/, "").split("?")[0];
13
+ return { json: async () => mockResponses[path] || {} };
14
+ };
15
+
16
+ // Set required env vars
17
+ process.env.VELIXAR_API_KEY = "test-key";
18
+
19
+ test("tool definitions have correct names and schemas", () => {
20
+ const tools = [
21
+ {
22
+ name: "velixar_store",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ content: { type: "string" },
27
+ tags: { type: "array", items: { type: "string" } },
28
+ tier: { type: "number" },
29
+ },
30
+ required: ["content"],
31
+ },
32
+ },
33
+ {
34
+ name: "velixar_search",
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: {
38
+ query: { type: "string" },
39
+ limit: { type: "number" },
40
+ },
41
+ required: ["query"],
42
+ },
43
+ },
44
+ {
45
+ name: "velixar_delete",
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ id: { type: "string" },
50
+ },
51
+ required: ["id"],
52
+ },
53
+ },
54
+ ];
55
+
56
+ assert.strictEqual(tools.length, 3);
57
+ assert.strictEqual(tools[0].name, "velixar_store");
58
+ assert.deepStrictEqual(tools[0].inputSchema.required, ["content"]);
59
+ assert.strictEqual(tools[1].name, "velixar_search");
60
+ assert.deepStrictEqual(tools[1].inputSchema.required, ["query"]);
61
+ assert.strictEqual(tools[2].name, "velixar_delete");
62
+ assert.deepStrictEqual(tools[2].inputSchema.required, ["id"]);
63
+ });
64
+
65
+ test("store handler formats correct response", async () => {
66
+ const mockHandler = async (name, args) => {
67
+ if (name === "velixar_store") {
68
+ return { content: [{ type: "text", text: "✓ Stored memory" }] };
69
+ }
70
+ };
71
+
72
+ const result = await mockHandler("velixar_store", { content: "test" });
73
+ assert.strictEqual(result.content[0].text, "✓ Stored memory");
74
+ });
75
+
76
+ test("search handler formats correct response", async () => {
77
+ const mockHandler = async (name, args) => {
78
+ if (name === "velixar_search") {
79
+ return { content: [{ type: "text", text: "Found 1 memories:\n• test memory" }] };
80
+ }
81
+ };
82
+
83
+ const result = await mockHandler("velixar_search", { query: "test" });
84
+ assert.ok(result.content[0].text.includes("Found 1 memories"));
85
+ });
86
+
87
+ test("delete handler formats correct response", async () => {
88
+ const mockHandler = async (name, args) => {
89
+ if (name === "velixar_delete") {
90
+ return { content: [{ type: "text", text: `✓ Deleted memory: ${args.id}` }] };
91
+ }
92
+ };
93
+
94
+ const result = await mockHandler("velixar_delete", { id: "test-id" });
95
+ assert.strictEqual(result.content[0].text, "✓ Deleted memory: test-id");
96
+ });