@luisarg/memory-mcp 0.1.0 → 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.
package/cordis.patch.yml CHANGED
@@ -3,6 +3,8 @@
3
3
  # defaults resuelven bajo el home del harness (cwd-independent):
4
4
  # $DSH_HOME/memory-vault-server y $DSH_HOME/memory-vault (~/.dsh por defecto)
5
5
  - insert:
6
+ - id: memory-mcp-bootstrap
7
+ name: '@luisarg/memory-mcp'
6
8
  - id: memory-mcp
7
9
  name: '@deepseek-ai/dsh-mcp-client'
8
10
  config:
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ import { Context } from "@deepseek-ai/cordis";
5
5
  declare const name = "memory-mcp";
6
6
  interface Config {
7
7
  memoryPath: string;
8
+ serverDir: string;
8
9
  }
9
10
  declare const Config: Schema<Config>;
10
11
  declare function apply(ctx: Context, config: Config): void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;cAMa,IAAA;UAEI,MAAA;EAFJ,UAAI,EAAA,MAAA;AAEjB;AAIa,cAAA,MAAe,EAAP,MAAA,CAAO,MAAD,CAAA;AAcX,iBAAA,KAAA,CAAW,GAAiB,EAAjB,OAAuB,EAAA,MAAA,EAAN,MAAM,CAAA,EAAA,IAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;cAOa,IAAA;UAEI,MAAA;EAFJ,UAAI,EAAA,MAAA;EAEA,SAAM,EAAA,MAAA;AAKvB;AA6BgB,cA7BH,MA6Bc,EA7BN,MA6BuB,CA7BhB,MA6BsB,CAAA;iBAAlC,KAAA,MAAW,iBAAiB"}
package/dist/index.js CHANGED
@@ -1,24 +1,41 @@
1
- import { existsSync } from "node:fs";
1
+ import { cpSync, existsSync, mkdirSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
- import { isAbsolute, join } from "node:path";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  import Schema from "@deepseek-ai/schemastery";
5
6
 
6
7
  //#region src/index.ts
7
8
  const name = "memory-mcp";
8
- const Config = Schema.object({ memoryPath: Schema.string().default(process.env.DSH_MEMORY_PATH ?? "") });
9
- /**
10
- * Resolve the harness home the same way the harness does (`$DSH_HOME`, or
11
- * `~/.dsh`). Paths must never depend on the launch cwd: DSH does not chdir.
12
- */
13
- function resolveMemoryPath(value) {
9
+ const Config = Schema.object({
10
+ memoryPath: Schema.string().default(process.env.DSH_MEMORY_PATH ?? ""),
11
+ serverDir: Schema.string().default(process.env.DSH_MEMORY_SERVER_DIR ?? "")
12
+ });
13
+ const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
14
+ /** Harness home, resolved like the harness itself ($DSH_HOME, or ~/.dsh). */
15
+ function dshHome() {
16
+ const env = process.env.DSH_HOME?.trim();
17
+ return env && env.length > 0 ? env : join(homedir(), ".dsh");
18
+ }
19
+ /** Absolute paths stay; empty/relative values resolve under the harness home. */
20
+ function resolveUnderHome(value, segment) {
14
21
  const v = value.trim();
15
- if (v.length === 0) return join(process.env.DSH_HOME?.trim() || join(homedir(), ".dsh"), "memory-vault");
16
- return isAbsolute(v) ? v : join(process.env.DSH_HOME?.trim() || join(homedir(), ".dsh"), v);
22
+ if (v.length === 0) return join(dshHome(), segment);
23
+ return isAbsolute(v) ? v : join(dshHome(), v);
24
+ }
25
+ /** Copy the bundled dir into `target` when `key` is missing there. */
26
+ function ensure(target, bundled, key) {
27
+ if (existsSync(join(target, key))) return false;
28
+ if (!existsSync(bundled)) return false;
29
+ mkdirSync(target, { recursive: true });
30
+ cpSync(bundled, target, { recursive: true });
31
+ return true;
17
32
  }
18
33
  function apply(ctx, config) {
19
- const memoryPath = resolveMemoryPath(config.memoryPath);
20
- if (!existsSync(memoryPath)) console.warn(`[memory-mcp] vault not found at ${memoryPath} — it will be created on first use.`);
21
- console.log(`[memory-mcp] MEMORY_PATH=${memoryPath}`);
34
+ const serverDir = resolveUnderHome(config.serverDir, "memory-vault-server");
35
+ const memoryPath = resolveUnderHome(config.memoryPath, "memory-vault");
36
+ if (ensure(serverDir, join(packageRoot, "server"), "server.py")) console.log(`[memory-mcp] installed memory-vault-server -> ${serverDir}`);
37
+ if (ensure(memoryPath, join(packageRoot, "vault"), "type-registry.yaml")) console.log(`[memory-mcp] installed vault starter -> ${memoryPath}`);
38
+ if (!existsSync(join(serverDir, "server.py"))) console.warn(`[memory-mcp] memory-vault-server not found at ${serverDir} and not bundled — set DSH_MEMORY_SERVER_DIR (or run \`node scripts/bundle-assets.mjs\` in a checkout)`);
22
39
  }
23
40
 
24
41
  //#endregion
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Config: Schema<Config>"],"sources":["../src/index.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { isAbsolute, join } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport Schema from '@deepseek-ai/schemastery'\n\nexport const name = 'memory-mcp'\n\nexport interface Config {\n memoryPath: string\n}\n\nexport const Config: Schema<Config> = Schema.object({\n memoryPath: Schema.string().default(process.env.DSH_MEMORY_PATH ?? ''),\n})\n\n/**\n * Resolve the harness home the same way the harness does (`$DSH_HOME`, or\n * `~/.dsh`). Paths must never depend on the launch cwd: DSH does not chdir.\n */\nfunction resolveMemoryPath(value: string): string {\n const v = value.trim()\n if (v.length === 0) return join((process.env.DSH_HOME?.trim() || join(homedir(), '.dsh')), 'memory-vault')\n return isAbsolute(v) ? v : join(process.env.DSH_HOME?.trim() || join(homedir(), '.dsh'), v)\n}\n\nexport function apply(ctx: Context, config: Config) {\n // This plugin is a placeholder; actual MCP wiring is via cordis.patch.yml\n // dsh-mcp-client row. This module allows Config validation and logs.\n const memoryPath = resolveMemoryPath(config.memoryPath)\n if (!existsSync(memoryPath)) {\n console.warn(`[memory-mcp] vault not found at ${memoryPath} it will be created on first use.`)\n }\n console.log(`[memory-mcp] MEMORY_PATH=${memoryPath}`)\n}\n"],"mappings":";;;;;;AAMA,MAAa,OAAO;AAMpB,MAAaA,SAAyB,OAAO,OAAO,EAClD,YAAY,OAAO,QAAQ,CAAC,QAAQ,QAAQ,IAAI,mBAAmB,GAAG,EACvE,CAAC;;;;;AAMF,SAAS,kBAAkB,OAAuB;CAChD,MAAM,IAAI,MAAM,MAAM;AACtB,KAAI,EAAE,WAAW,EAAG,QAAO,KAAM,QAAQ,IAAI,UAAU,MAAM,IAAI,KAAK,SAAS,EAAE,OAAO,EAAG,eAAe;AAC1G,QAAO,WAAW,EAAE,GAAG,IAAI,KAAK,QAAQ,IAAI,UAAU,MAAM,IAAI,KAAK,SAAS,EAAE,OAAO,EAAE,EAAE;;AAG7F,SAAgB,MAAM,KAAc,QAAgB;CAGlD,MAAM,aAAa,kBAAkB,OAAO,WAAW;AACvD,KAAI,CAAC,WAAW,WAAW,CACzB,SAAQ,KAAK,mCAAmC,WAAW,qCAAqC;AAElG,SAAQ,IAAI,4BAA4B,aAAa"}
1
+ {"version":3,"file":"index.js","names":["Config: Schema<Config>"],"sources":["../src/index.ts"],"sourcesContent":["import { cpSync, existsSync, mkdirSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, isAbsolute, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { Context } from '@deepseek-ai/cordis'\nimport Schema from '@deepseek-ai/schemastery'\n\nexport const name = 'memory-mcp'\n\nexport interface Config {\n memoryPath: string\n serverDir: string\n}\n\nexport const Config: Schema<Config> = Schema.object({\n memoryPath: Schema.string().default(process.env.DSH_MEMORY_PATH ?? ''),\n serverDir: Schema.string().default(process.env.DSH_MEMORY_SERVER_DIR ?? ''),\n})\n\nconst packageRoot = dirname(dirname(fileURLToPath(import.meta.url)))\n\n/** Harness home, resolved like the harness itself ($DSH_HOME, or ~/.dsh). */\nfunction dshHome(): string {\n const env = process.env.DSH_HOME?.trim()\n return env && env.length > 0 ? env : join(homedir(), '.dsh')\n}\n\n/** Absolute paths stay; empty/relative values resolve under the harness home. */\nfunction resolveUnderHome(value: string, segment: string): string {\n const v = value.trim()\n if (v.length === 0) return join(dshHome(), segment)\n return isAbsolute(v) ? v : join(dshHome(), v)\n}\n\n/** Copy the bundled dir into `target` when `key` is missing there. */\nfunction ensure(target: string, bundled: string, key: string): boolean {\n if (existsSync(join(target, key))) return false\n if (!existsSync(bundled)) return false\n mkdirSync(target, { recursive: true })\n cpSync(bundled, target, { recursive: true })\n return true\n}\n\nexport function apply(ctx: Context, config: Config) {\n const serverDir = resolveUnderHome(config.serverDir, 'memory-vault-server')\n const memoryPath = resolveUnderHome(config.memoryPath, 'memory-vault')\n\n // Self-contained install: first boot copies the bundled server and vault\n // starter under the harness home when they are missing. Env-overridden\n // paths are respected (never overwritten, never copied over).\n if (ensure(serverDir, join(packageRoot, 'server'), 'server.py')) {\n console.log(`[memory-mcp] installed memory-vault-server -> ${serverDir}`)\n }\n if (ensure(memoryPath, join(packageRoot, 'vault'), 'type-registry.yaml')) {\n console.log(`[memory-mcp] installed vault starter -> ${memoryPath}`)\n }\n if (!existsSync(join(serverDir, 'server.py'))) {\n console.warn(\n `[memory-mcp] memory-vault-server not found at ${serverDir} and not bundled — ` +\n 'set DSH_MEMORY_SERVER_DIR (or run `node scripts/bundle-assets.mjs` in a checkout)',\n )\n }\n}\n"],"mappings":";;;;;;;AAOA,MAAa,OAAO;AAOpB,MAAaA,SAAyB,OAAO,OAAO;CAClD,YAAY,OAAO,QAAQ,CAAC,QAAQ,QAAQ,IAAI,mBAAmB,GAAG;CACtE,WAAW,OAAO,QAAQ,CAAC,QAAQ,QAAQ,IAAI,yBAAyB,GAAG;CAC5E,CAAC;AAEF,MAAM,cAAc,QAAQ,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,CAAC;;AAGpE,SAAS,UAAkB;CACzB,MAAM,MAAM,QAAQ,IAAI,UAAU,MAAM;AACxC,QAAO,OAAO,IAAI,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,OAAO;;;AAI9D,SAAS,iBAAiB,OAAe,SAAyB;CAChE,MAAM,IAAI,MAAM,MAAM;AACtB,KAAI,EAAE,WAAW,EAAG,QAAO,KAAK,SAAS,EAAE,QAAQ;AACnD,QAAO,WAAW,EAAE,GAAG,IAAI,KAAK,SAAS,EAAE,EAAE;;;AAI/C,SAAS,OAAO,QAAgB,SAAiB,KAAsB;AACrE,KAAI,WAAW,KAAK,QAAQ,IAAI,CAAC,CAAE,QAAO;AAC1C,KAAI,CAAC,WAAW,QAAQ,CAAE,QAAO;AACjC,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;AACtC,QAAO,SAAS,QAAQ,EAAE,WAAW,MAAM,CAAC;AAC5C,QAAO;;AAGT,SAAgB,MAAM,KAAc,QAAgB;CAClD,MAAM,YAAY,iBAAiB,OAAO,WAAW,sBAAsB;CAC3E,MAAM,aAAa,iBAAiB,OAAO,YAAY,eAAe;AAKtE,KAAI,OAAO,WAAW,KAAK,aAAa,SAAS,EAAE,YAAY,CAC7D,SAAQ,IAAI,iDAAiD,YAAY;AAE3E,KAAI,OAAO,YAAY,KAAK,aAAa,QAAQ,EAAE,qBAAqB,CACtE,SAAQ,IAAI,2CAA2C,aAAa;AAEtE,KAAI,CAAC,WAAW,KAAK,WAAW,YAAY,CAAC,CAC3C,SAAQ,KACN,iDAAiD,UAAU,wGAE5D"}
package/package.json CHANGED
@@ -1,18 +1,43 @@
1
1
  {
2
2
  "name": "@luisarg/memory-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "DSH MCP client for the memory vault",
6
- "repository": { "type": "git", "url": "git+https://github.com/Luisarg03/dsh-memory-vault.git" },
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Luisarg03/dsh-memory-vault.git"
9
+ },
7
10
  "main": "dist/index.js",
8
11
  "types": "dist/index.d.ts",
9
- "exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
10
- "files": ["dist/", "cordis.patch.yml"],
11
- "dsh": { "bundle": { "patch": "./cordis.patch.yml" } },
12
- "scripts": { "build": "tsdown", "dev": "tsdown --watch", "test": "vitest run --passWithNoTests", "prepare": "tsdown" },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist/",
20
+ "cordis.patch.yml",
21
+ "server/",
22
+ "vault/"
23
+ ],
24
+ "dsh": {
25
+ "bundle": {
26
+ "patch": "./cordis.patch.yml"
27
+ }
28
+ },
29
+ "scripts": {
30
+ "build": "tsdown",
31
+ "dev": "tsdown --watch",
32
+ "test": "vitest run --passWithNoTests",
33
+ "prepare": "tsdown && node ../../scripts/bundle-assets.mjs"
34
+ },
13
35
  "dependencies": {
14
36
  "@deepseek-ai/cordis": "4.0.1",
15
37
  "@deepseek-ai/schemastery": "3.18.1"
16
38
  },
17
- "devDependencies": { "typescript": "^5.9.2", "tsdown": "^0.15.6" }
39
+ "devDependencies": {
40
+ "typescript": "^5.9.2",
41
+ "tsdown": "^0.15.6"
42
+ }
18
43
  }
@@ -0,0 +1 @@
1
+ """Memory store: SQLite + OKF Markdown for the DSH memory vault."""
package/server/cli.py ADDED
@@ -0,0 +1,27 @@
1
+ """Memory server CLI helpers (memory path resolution + storage validation)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ from pathlib import Path
8
+
9
+
10
+ def get_memory_path() -> Path:
11
+ """Return MEMORY_PATH from env, or default to `<project>/memory-vault/`."""
12
+ env = os.environ.get("MEMORY_PATH")
13
+ if env:
14
+ return Path(env)
15
+ return Path(__file__).resolve().parent.parent / "memory-vault"
16
+
17
+
18
+ def validate_storage_path(path: str) -> None:
19
+ """Exit 1 with a clear message if `path` is missing or not a directory."""
20
+ p = Path(path)
21
+ if not p.exists():
22
+ print(f"ERROR: Memory storage path does not exist: {path}", file=sys.stderr)
23
+ print(f" Run: mkdir -p {path}", file=sys.stderr)
24
+ sys.exit(1)
25
+ if not p.is_dir():
26
+ print(f"ERROR: Memory storage path is not a directory: {path}", file=sys.stderr)
27
+ sys.exit(1)
@@ -0,0 +1,14 @@
1
+ [project]
2
+ name = "dsh-memory-vault-server"
3
+ version = "0.1.0"
4
+ description = "MCP server (SQLite FTS5 + markdown OKF) for dsh-memory-vault"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "mcp>=1.2.0",
8
+ "PyYAML>=6.0",
9
+ ]
10
+
11
+ # Virtual project: uv installs dependencies and runs scripts without
12
+ # packaging this directory itself (no build backend needed).
13
+ [tool.uv]
14
+ package = false
@@ -0,0 +1,141 @@
1
+ """Type registry loader — reads memory/type-registry.yaml and provides
2
+ derived maps for store, server, and digest_session modules.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from functools import lru_cache
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+
12
+ class RegistryValidationError(Exception):
13
+ """Raised when the type registry fails validation."""
14
+
15
+
16
+ _REQUIRED_KEYS = ("name", "directory", "singular", "tool", "template", "extraction_hint")
17
+ _RESERVED_DIRECTORIES = {"index.md", "log.md", ".obsidian"}
18
+
19
+
20
+ def _validate_registry(types: list[dict[str, Any]]) -> None:
21
+ """Validate a parsed registry. Raises RegistryValidationError on failure."""
22
+ # Required keys
23
+ for t in types:
24
+ for key in _REQUIRED_KEYS:
25
+ if key not in t:
26
+ raise RegistryValidationError(
27
+ f"missing required key {key!r} in type {t.get('name', '<unnamed>')}"
28
+ )
29
+
30
+ # Duplicate name
31
+ seen_names: set[str] = set()
32
+ for t in types:
33
+ n: str = t["name"]
34
+ if n in seen_names:
35
+ raise RegistryValidationError(f"duplicate name: {n!r}")
36
+ seen_names.add(n)
37
+
38
+ # Duplicate directory across project-attached types
39
+ seen_project_dirs: set[str] = set()
40
+ for t in types:
41
+ if t.get("project_attached", True) is True:
42
+ d: str = t["directory"]
43
+ if d in seen_project_dirs:
44
+ raise RegistryValidationError(
45
+ f"duplicate directory for project-attached types: {d!r}"
46
+ )
47
+ seen_project_dirs.add(d)
48
+
49
+ # Reserved directory collision
50
+ for t in types:
51
+ d = t.get("directory", "")
52
+ if d in _RESERVED_DIRECTORIES:
53
+ raise RegistryValidationError(
54
+ f"directory {d!r} collides with reserved path"
55
+ )
56
+
57
+ # Empty extraction_hint on project-attached types
58
+ for t in types:
59
+ if t.get("project_attached", True) is True:
60
+ hint = t.get("extraction_hint")
61
+ if hint is None or (isinstance(hint, str) and hint == ""):
62
+ raise RegistryValidationError(
63
+ f"empty extraction_hint on project-attached type {t.get('name')!r}"
64
+ )
65
+
66
+
67
+ @lru_cache(maxsize=1)
68
+ def load_type_registry(bundle_root: Path) -> list[dict[str, Any]]:
69
+ """Load and validate the type registry from bundle_root/type-registry.yaml.
70
+
71
+ Returns the list of type dicts. Raises RegistryValidationError on
72
+ validation failure. Uses @lru_cache so the file is read only once.
73
+ """
74
+ import yaml # lazy import: only needed when loading the registry
75
+
76
+ registry_path = bundle_root / "type-registry.yaml"
77
+ if not registry_path.is_file():
78
+ raise RegistryValidationError(
79
+ f"registry file not found: {registry_path}"
80
+ )
81
+
82
+ raw = registry_path.read_text(encoding="utf-8")
83
+ try:
84
+ data = yaml.safe_load(raw)
85
+ except yaml.YAMLError as exc:
86
+ raise RegistryValidationError(f"YAML parse error: {exc}") from exc
87
+
88
+ if not isinstance(data, dict) or "types" not in data:
89
+ raise RegistryValidationError(
90
+ "registry must contain a top-level 'types' key"
91
+ )
92
+
93
+ types = data["types"]
94
+ if not isinstance(types, list) or len(types) == 0:
95
+ raise RegistryValidationError("'types' must be a non-empty list")
96
+
97
+ _validate_registry(types)
98
+ return types
99
+
100
+
101
+ def build_type_maps(
102
+ types: list[dict[str, Any]],
103
+ ) -> dict[str, Any]:
104
+ """Derive the standard maps from a registry type list.
105
+
106
+ Returns a dict with keys:
107
+ valid_types: set of singular names
108
+ type_dir_map: singular -> directory
109
+ type_label_map: directory -> name (label)
110
+ dir_to_singular: directory -> singular
111
+ type_order: list of directory names in registry order (project-attached only)
112
+ extraction_types: list of dicts for types with non-null extraction_hint
113
+ """
114
+ valid_types: set[str] = set()
115
+ type_dir_map: dict[str, str] = {}
116
+ type_label_map: dict[str, str] = {}
117
+ dir_to_singular: dict[str, str] = {}
118
+ type_order: list[str] = []
119
+ extraction_types: list[dict[str, Any]] = []
120
+
121
+ for t in types:
122
+ singular = t["singular"]
123
+ directory = t["directory"]
124
+ name = t["name"]
125
+ valid_types.add(singular)
126
+ type_dir_map[singular] = directory
127
+ type_label_map[directory] = name
128
+ dir_to_singular[directory] = singular
129
+ if t.get("project_attached", True):
130
+ type_order.append(directory)
131
+ if t.get("extraction_hint") is not None:
132
+ extraction_types.append(t)
133
+
134
+ return {
135
+ "valid_types": valid_types,
136
+ "type_dir_map": type_dir_map,
137
+ "type_label_map": type_label_map,
138
+ "dir_to_singular": dir_to_singular,
139
+ "type_order": type_order,
140
+ "extraction_types": extraction_types,
141
+ }