@noelclaw/mcp 2.3.0 → 2.4.0

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.
@@ -13,7 +13,8 @@ exports.VAULT_TOOLS = [
13
13
  description: "Save or update an artifact in Noel-Vault — the persistent memory layer for agents. " +
14
14
  "Use this to store research outputs, execution logs, workflows, versioned prompts, " +
15
15
  "generated files, or long-term memory. Each save creates a new version automatically. " +
16
- "Same key = update existing (git-style). Types: research | execution | workflow | prompt | file | memory.",
16
+ "Same key = update existing (git-style). Types: research | execution | workflow | prompt | file | memory. " +
17
+ "For a quick note or preference, use type='memory' with just content — title is optional.",
17
18
  inputSchema: {
18
19
  type: "object",
19
20
  properties: {
@@ -100,7 +101,7 @@ exports.VAULT_TOOLS = [
100
101
  {
101
102
  name: "vault_export",
102
103
  description: "Export your entire Noel-Vault or a specific type as a structured bundle. " +
103
- "Useful for archiving, syncing to Gitlawb, or passing context to another agent.",
104
+ "Useful for archiving, syncing to GitHub, or passing context to another agent.",
104
105
  inputSchema: {
105
106
  type: "object",
106
107
  properties: {
@@ -109,38 +110,6 @@ exports.VAULT_TOOLS = [
109
110
  required: [],
110
111
  },
111
112
  },
112
- {
113
- name: "vault_remember",
114
- description: "One-liner: save anything to Noel-Vault without specifying type or title. " +
115
- "Just pass content — Noel infers the title and saves as a memory entry. " +
116
- "Use this for quick notes, preferences, decisions, and things to remember across sessions. " +
117
- "Example: vault_remember content: 'User prefers low-risk DeFi, confirmed multiple times'",
118
- inputSchema: {
119
- type: "object",
120
- properties: {
121
- content: { type: "string", description: "What to remember — plain text, markdown, or a short note" },
122
- title: { type: "string", description: "Optional title. Auto-inferred from content if omitted." },
123
- tags: { type: "array", items: { type: "string" }, description: "Optional tags for later retrieval" },
124
- },
125
- required: ["content"],
126
- },
127
- },
128
- {
129
- name: "vault_context",
130
- description: "Load semantically relevant vault entries for a topic — formatted context ready to inject into a prompt. " +
131
- "Uses Supermemory vector search when available (understands meaning, not just keywords), " +
132
- "with automatic fallback to full-text search. " +
133
- "Call this at the start of any research or analysis task to prime your AI with all past knowledge on the topic. " +
134
- "Returns full content of matching entries. Credentials are never included.",
135
- inputSchema: {
136
- type: "object",
137
- properties: {
138
- topic: { type: "string", description: "Topic or query to load context for, e.g. 'ETH DeFi strategy' or 'BTC market thesis'" },
139
- limit: { type: "number", description: "Max entries to return (default 8, max 20)" },
140
- },
141
- required: ["topic"],
142
- },
143
- },
144
113
  {
145
114
  name: "vault_store_credential",
146
115
  description: "Securely store an API key, token, or secret in your vault. " +
@@ -199,20 +168,56 @@ exports.VAULT_TOOLS = [
199
168
  },
200
169
  },
201
170
  {
202
- name: "vault_connect",
203
- description: "Connect an external URL or data source to your vault fetches, indexes, and stores the content " +
204
- "as a vault entry AND mirrors it to semantic memory for future retrieval. " +
205
- "Perfect for indexing GitHub repos, research papers, Notion pages, or any web page. " +
206
- "For full Google Drive / Gmail / Notion workspace sync, connect via the Noelclaw dashboard.",
171
+ name: "vault_pin",
172
+ description: "Pin or unpin a Noel-Vault entry. Pinned entries always appear first in vault_list and are " +
173
+ "prioritized in memory_context and search results. Use for your most important research, key prompts, or canonical references.",
174
+ inputSchema: {
175
+ type: "object",
176
+ properties: {
177
+ key: { type: "string", description: "Entry key to pin or unpin" },
178
+ pinned: { type: "boolean", description: "true to pin, false to unpin (default: true)" },
179
+ },
180
+ required: ["key"],
181
+ },
182
+ },
183
+ {
184
+ name: "vault_delete",
185
+ description: "Permanently delete a Noel-Vault entry including all its version history. This cannot be undone. " +
186
+ "Use vault_list to browse entries before deleting.",
187
+ inputSchema: {
188
+ type: "object",
189
+ properties: {
190
+ key: { type: "string", description: "Entry key to delete permanently" },
191
+ },
192
+ required: ["key"],
193
+ },
194
+ },
195
+ {
196
+ name: "vault_link",
197
+ description: "Create a semantic link between two Noel-Vault entries. Links appear in vault_read output and " +
198
+ "help agents navigate related knowledge. Relations: references | derived_from | supersedes | related | continues.",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ fromKey: { type: "string", description: "Source entry key" },
203
+ toKey: { type: "string", description: "Target entry key" },
204
+ relation: { type: "string", enum: [...LINK_RELATIONS], description: "Relationship type (default: related)" },
205
+ },
206
+ required: ["fromKey", "toKey"],
207
+ },
208
+ },
209
+ {
210
+ name: "vault_tag",
211
+ description: "Add or replace tags on an existing Noel-Vault entry without modifying its content. " +
212
+ "Useful for organizing entries retroactively. Set replace=true to overwrite all existing tags.",
207
213
  inputSchema: {
208
214
  type: "object",
209
215
  properties: {
210
- url: { type: "string", description: "URL to fetch and index (GitHub, Notion, web page, etc.)" },
211
- title: { type: "string", description: "Title for this vault entry" },
212
- type: { type: "string", enum: ["research", "file", "memory", "workflow"], description: "Vault entry type (default: research)" },
213
- tags: { type: "array", items: { type: "string" }, description: "Tags for filtering" },
216
+ key: { type: "string", description: "Entry key to update tags on" },
217
+ tags: { type: "array", items: { type: "string" }, description: "Tags to add (or replace if replace=true)" },
218
+ replace: { type: "boolean", description: "If true, replaces all existing tags. If false (default), merges with existing." },
214
219
  },
215
- required: ["url"],
220
+ required: ["key", "tags"],
216
221
  },
217
222
  },
218
223
  ];
@@ -243,13 +248,14 @@ const SearchSchema = zod_1.z.object({
243
248
  const HistorySchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
244
249
  const DiffSchema = zod_1.z.object({ key: zod_1.z.string().min(1), fromVersion: zod_1.z.number(), toVersion: zod_1.z.number() });
245
250
  const ExportSchema = zod_1.z.object({ type: zod_1.z.enum(VAULT_TYPES).optional() });
246
- const RememberSchema = zod_1.z.object({ content: zod_1.z.string().min(1), title: zod_1.z.string().optional(), tags: zod_1.z.array(zod_1.z.string()).optional() });
247
- const ContextSchema = zod_1.z.object({ topic: zod_1.z.string().min(1), limit: zod_1.z.number().optional() });
248
251
  const StoreCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1), value: zod_1.z.string().min(1), description: zod_1.z.string().optional() });
249
252
  const GetCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1) });
250
253
  const PublishSchema = zod_1.z.object({ key: zod_1.z.string().min(1), isPublic: zod_1.z.boolean().optional(), authorName: zod_1.z.string().optional() });
251
254
  const ExploreSchema = zod_1.z.object({ type: zod_1.z.enum(["research", "execution", "workflow", "prompt", "file", "memory"]).optional(), search: zod_1.z.string().optional(), limit: zod_1.z.number().optional() });
252
- const VaultConnectSchema = zod_1.z.object({ url: zod_1.z.string().url(), title: zod_1.z.string().optional(), type: zod_1.z.enum(["research", "file", "memory", "workflow"]).optional(), tags: zod_1.z.array(zod_1.z.string()).optional() });
255
+ const PinSchema = zod_1.z.object({ key: zod_1.z.string().min(1), pinned: zod_1.z.boolean().optional() });
256
+ const DeleteSchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
257
+ const LinkSchema = zod_1.z.object({ fromKey: zod_1.z.string().min(1), toKey: zod_1.z.string().min(1), relation: zod_1.z.enum(LINK_RELATIONS).optional() });
258
+ const TagSchema = zod_1.z.object({ key: zod_1.z.string().min(1), tags: zod_1.z.array(zod_1.z.string()).min(1), replace: zod_1.z.boolean().optional() });
253
259
  // ─── Helpers ─────────────────────────────────────────────────────────────────
254
260
  function formatBytes(n) {
255
261
  if (!n)
@@ -274,19 +280,19 @@ async function handleVaultTool(name, args) {
274
280
  if (data.error)
275
281
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
276
282
  const { key, version, changed } = data;
277
- // Mirror to semantic memory (fire-and-forget, don't fail the save if SM is unavailable)
283
+ // Mirror to semantic memory (fire-and-forget)
278
284
  if (parsed.data.type !== "credential") {
279
285
  (0, memory_js_1.syncToSupermemory)(parsed.data.content, {
280
286
  vaultKey: key, title: parsed.data.title, type: parsed.data.type,
281
287
  tags: parsed.data.tags, version, source: "vault_save",
282
- }).catch(() => { });
288
+ });
283
289
  }
284
290
  const lines = [
285
291
  `📦 **Vault ${changed ? (version === 1 ? "Created" : "Updated") : "Unchanged"}**`,
286
292
  `Key: \`${key}\``,
287
293
  `Version: v${version}`,
288
294
  changed && version > 1 ? `Previous version auto-snapshotted.` : "",
289
- process.env.SUPERMEMORY_API_KEY ? `🧠 Synced to semantic memory` : "",
295
+ `🧠 Synced to semantic memory`,
290
296
  ``,
291
297
  `Use \`vault_read\` to retrieve, \`vault_history\` to see all versions.`,
292
298
  ].filter(Boolean);
@@ -341,8 +347,8 @@ async function handleVaultTool(name, args) {
341
347
  const parsed = SearchSchema.safeParse(args);
342
348
  if (!parsed.success)
343
349
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
344
- // Try Supermemory semantic search first
345
- if (process.env.SUPERMEMORY_API_KEY) {
350
+ // Try semantic search first (proxied through Convex)
351
+ {
346
352
  const smResults = await (0, memory_js_1.searchSupermemory)(parsed.data.query, parsed.data.limit ?? 20);
347
353
  if (smResults.length > 0) {
348
354
  // Filter by type if requested
@@ -437,69 +443,6 @@ async function handleVaultTool(name, args) {
437
443
  const rows = entries.map((e) => `**[\`${e.key}\`]** ${e.title} (${e.type} · v${e.version})\n${e.content.slice(0, 500)}${e.content.length > 500 ? "\n…" : ""}`);
438
444
  return { content: [{ type: "text", text: [...header, ...rows.join("\n\n---\n\n").split("\n")].join("\n") }] };
439
445
  }
440
- case "vault_remember": {
441
- const parsed = RememberSchema.safeParse(args);
442
- if (!parsed.success)
443
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
444
- const { content, title, tags } = parsed.data;
445
- const inferredTitle = title ?? content.slice(0, 60).replace(/\n/g, " ").trim() + (content.length > 60 ? "…" : "");
446
- const data = await (0, convex_js_1.callConvex)("/vault/save", "POST", {
447
- type: "memory",
448
- title: inferredTitle,
449
- content,
450
- tags,
451
- commitMsg: "vault_remember",
452
- }, "vault_remember");
453
- if (data.error)
454
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
455
- // Mirror to semantic memory
456
- (0, memory_js_1.syncToSupermemory)(content, {
457
- vaultKey: data.key, title: inferredTitle, type: "memory",
458
- tags, version: data.version, source: "vault_remember",
459
- }).catch(() => { });
460
- const smNote = process.env.SUPERMEMORY_API_KEY ? " 🧠 Synced to semantic memory" : "";
461
- return { content: [{ type: "text", text: `✅ Remembered — key: \`${data.key}\` (v${data.version})${smNote}\nRetrieve later with: \`vault_context topic: "${inferredTitle.slice(0, 40)}"\`` }] };
462
- }
463
- case "vault_context": {
464
- const parsed = ContextSchema.safeParse(args);
465
- if (!parsed.success)
466
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
467
- const limit = parsed.data.limit ?? 8;
468
- // Semantic context via Supermemory
469
- if (process.env.SUPERMEMORY_API_KEY) {
470
- const smResults = await (0, memory_js_1.searchSupermemory)(parsed.data.topic, limit);
471
- if (smResults.length > 0) {
472
- const contextParts = smResults.map((r, i) => {
473
- const title = r.metadata?.title ?? `Entry ${i + 1}`;
474
- return `### ${title}\n${r.content}`;
475
- });
476
- const summary = smResults.map(r => r.metadata?.title ?? r.content.slice(0, 50)).join(", ");
477
- return {
478
- content: [{
479
- type: "text",
480
- text: [
481
- `📚 **Vault Context** [Semantic] for: "${parsed.data.topic}"`,
482
- `Entries: ${summary}`,
483
- ``,
484
- `---`,
485
- ``,
486
- contextParts.join("\n\n---\n\n"),
487
- ].join("\n"),
488
- }],
489
- };
490
- }
491
- }
492
- // Fallback: Convex full-text context
493
- const params = new URLSearchParams({ q: parsed.data.topic });
494
- params.set("limit", String(limit));
495
- const data = await (0, convex_js_1.callConvex)(`/vault/context?${params}`, "GET", undefined, "vault_context");
496
- if (data.error)
497
- return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
498
- if (!data.context)
499
- return { content: [{ type: "text", text: `No vault context found for: "${parsed.data.topic}"\nStart building your knowledge base with vault_remember or vault_save.` }] };
500
- const summary = data.entries.map((e) => `[${e.type}] ${e.title}`).join(", ");
501
- return { content: [{ type: "text", text: `📚 **Vault Context** loaded for: "${parsed.data.topic}"\nEntries: ${summary}\n\n---\n\n${data.context}` }] };
502
- }
503
446
  case "vault_store_credential": {
504
447
  const parsed = StoreCredentialSchema.safeParse(args);
505
448
  if (!parsed.success)
@@ -572,46 +515,44 @@ async function handleVaultTool(name, args) {
572
515
  }],
573
516
  };
574
517
  }
575
- case "vault_connect": {
576
- const parsed = VaultConnectSchema.safeParse(args);
518
+ case "vault_pin": {
519
+ const parsed = PinSchema.safeParse(args);
577
520
  if (!parsed.success)
578
521
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
579
- const { url, title, type = "research", tags } = parsed.data;
580
- const inferredTitle = title ?? url.split("/").filter(Boolean).pop() ?? url;
581
- // Save to Convex vault
582
- const vaultData = await (0, convex_js_1.callConvex)("/vault/save", "POST", {
583
- type,
584
- title: inferredTitle,
585
- content: `Connected from: ${url}\n\n(Content indexed into semantic memory via Supermemory)`,
586
- key: `connected/${inferredTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50)}`,
587
- tags: [...(tags ?? []), "connected", new URL(url).hostname],
588
- commitMsg: "vault_connect",
589
- metadata: JSON.stringify({ sourceUrl: url }),
590
- }, "vault_connect");
591
- // Mirror full content to Supermemory with source URL for ingestion
592
- if (process.env.SUPERMEMORY_API_KEY) {
593
- (0, memory_js_1.syncToSupermemory)("", {
594
- vaultKey: vaultData.key, title: inferredTitle, type, tags, source: "vault_connect",
595
- }, url).catch(() => { });
596
- }
597
- if (vaultData.error)
598
- return { content: [{ type: "text", text: `Error: ${vaultData.error}` }], isError: true };
599
- return {
600
- content: [{
601
- type: "text",
602
- text: [
603
- `🔗 **Vault Connected**`,
604
- `URL: ${url}`,
605
- `Vault key: \`${vaultData.key}\``,
606
- process.env.SUPERMEMORY_API_KEY
607
- ? `🧠 Indexing to semantic memory… searchable in ~30s`
608
- : `⚠️ Set SUPERMEMORY_API_KEY for full semantic indexing`,
609
- ``,
610
- `Search it with: \`vault_search query: "${inferredTitle}"\``,
611
- `Or load as context: \`vault_context topic: "${inferredTitle}"\``,
612
- ].filter(Boolean).join("\n"),
613
- }],
614
- };
522
+ const { key, pinned = true } = parsed.data;
523
+ const data = await (0, convex_js_1.callConvex)("/vault/pin", "POST", { key, pinned }, "vault_pin");
524
+ if (data.error)
525
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
526
+ return { content: [{ type: "text", text: pinned ? `📌 Pinned: \`${key}\`` : `📌 Unpinned: \`${key}\`` }] };
527
+ }
528
+ case "vault_delete": {
529
+ const parsed = DeleteSchema.safeParse(args);
530
+ if (!parsed.success)
531
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
532
+ const data = await (0, convex_js_1.callConvex)("/vault/delete", "POST", { key: parsed.data.key }, "vault_delete");
533
+ if (data.error)
534
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
535
+ return { content: [{ type: "text", text: `🗑️ Deleted: \`${parsed.data.key}\` (${data.versionsRemoved ?? 0} versions removed)` }] };
536
+ }
537
+ case "vault_link": {
538
+ const parsed = LinkSchema.safeParse(args);
539
+ if (!parsed.success)
540
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
541
+ const { fromKey, toKey, relation = "related" } = parsed.data;
542
+ const data = await (0, convex_js_1.callConvex)("/vault/link", "POST", { fromKey, toKey, relation }, "vault_link");
543
+ if (data.error)
544
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
545
+ return { content: [{ type: "text", text: `🔗 Linked: \`${fromKey}\` → [${relation}] → \`${toKey}\`` }] };
546
+ }
547
+ case "vault_tag": {
548
+ const parsed = TagSchema.safeParse(args);
549
+ if (!parsed.success)
550
+ return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
551
+ const { key, tags, replace = false } = parsed.data;
552
+ const data = await (0, convex_js_1.callConvex)("/vault/tag", "POST", { key, tags, replace }, "vault_tag");
553
+ if (data.error)
554
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
555
+ return { content: [{ type: "text", text: `🏷️ Tags ${replace ? "set" : "updated"} on \`${key}\`: ${(data.tags ?? tags).join(", ")}` }] };
615
556
  }
616
557
  default:
617
558
  return null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@noelclaw/mcp",
3
- "version": "2.3.0",
4
- "description": "Noelclaw as an MCP skill — persistent memory, multi-agent coordination, scenario simulation, DeFi execution, and Sentinel-gated playbooks.",
3
+ "version": "2.4.0",
4
+ "description": "Noelclaw AI Operating System — persistent memory, multi-agent swarm, DeFi execution, market intelligence, and Sentinel-gated playbooks.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "noelclaw-mcp": "dist/index.js"
@@ -52,5 +52,8 @@
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=18"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
55
58
  }
56
59
  }
@@ -1,6 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NEWS_TOOLS = void 0;
4
- exports.handleNewsTool = handleNewsTool;
5
- exports.NEWS_TOOLS = [];
6
- async function handleNewsTool(_name, _args) { return null; }
@@ -1,8 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RESEARCH_TOOLS = void 0;
4
- exports.handleResearchTool = handleResearchTool;
5
- exports.RESEARCH_TOOLS = [];
6
- async function handleResearchTool(_name, _args) {
7
- return null;
8
- }
@@ -1,67 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TWITTER_TOOLS = void 0;
4
- exports.handleTwitterTool = handleTwitterTool;
5
- const zod_1 = require("zod");
6
- exports.TWITTER_TOOLS = [
7
- {
8
- name: "post_tweet",
9
- description: "Post a tweet on X (Twitter) via Ayrshare. Requires AYRSHARE_API_KEY env var.",
10
- inputSchema: {
11
- type: "object",
12
- properties: {
13
- text: { type: "string", description: "Tweet content (max 280 characters)" },
14
- reply_to: { type: "string", description: "Optional: tweet ID to reply to" },
15
- },
16
- required: ["text"],
17
- },
18
- },
19
- ];
20
- const PostTweetSchema = zod_1.z.object({
21
- text: zod_1.z.string().min(1).max(280),
22
- reply_to: zod_1.z.string().optional(),
23
- });
24
- async function handleTwitterTool(name, args) {
25
- if (name !== "post_tweet")
26
- return null;
27
- const parsed = PostTweetSchema.safeParse(args);
28
- if (!parsed.success)
29
- return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
30
- const apiKey = process.env.AYRSHARE_API_KEY;
31
- if (!apiKey) {
32
- return {
33
- content: [{ type: "text", text: "AYRSHARE_API_KEY not set.\n\nGet your key at ayrshare.com → Profile → API Key" }],
34
- isError: true,
35
- };
36
- }
37
- const body = {
38
- post: parsed.data.text,
39
- platforms: ["twitter"],
40
- };
41
- if (parsed.data.reply_to)
42
- body.twitterOptions = { inReplyToStatusId: parsed.data.reply_to };
43
- try {
44
- const res = await fetch("https://app.ayrshare.com/api/post", {
45
- method: "POST",
46
- headers: {
47
- "Content-Type": "application/json",
48
- Authorization: `Bearer ${apiKey}`,
49
- },
50
- body: JSON.stringify(body),
51
- signal: AbortSignal.timeout(15000),
52
- });
53
- const data = await res.json();
54
- if (!res.ok || data.status === "error") {
55
- const msg = data?.message ?? data?.errors?.[0] ?? JSON.stringify(data);
56
- return { content: [{ type: "text", text: `Ayrshare error: ${msg}` }], isError: true };
57
- }
58
- const tweetId = data?.postIds?.find((p) => p.platform === "twitter")?.id;
59
- const tweetUrl = tweetId ? `https://x.com/i/web/status/${tweetId}` : "";
60
- return {
61
- content: [{ type: "text", text: `✅ Tweet posted!\n\n"${parsed.data.text}"${tweetUrl ? `\n\n${tweetUrl}` : ""}` }],
62
- };
63
- }
64
- catch (err) {
65
- return { content: [{ type: "text", text: `Failed to post tweet: ${err.message}` }], isError: true };
66
- }
67
- }