@noelclaw/mcp 2.3.1 → 3.0.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.
@@ -6,14 +6,14 @@ const zod_1 = require("zod");
6
6
  const convex_js_1 = require("../convex.js");
7
7
  const memory_js_1 = require("./memory.js");
8
8
  const VAULT_TYPES = ["research", "execution", "workflow", "prompt", "file", "memory", "credential"];
9
- const LINK_RELATIONS = ["references", "derived_from", "supersedes", "related", "continues"];
10
9
  exports.VAULT_TOOLS = [
11
10
  {
12
11
  name: "vault_save",
13
12
  description: "Save or update an artifact in Noel-Vault — the persistent memory layer for agents. " +
14
13
  "Use this to store research outputs, execution logs, workflows, versioned prompts, " +
15
14
  "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.",
15
+ "Same key = update existing (git-style). Types: research | execution | workflow | prompt | file | memory. " +
16
+ "For a quick note or preference, use type='memory' with just content — title is optional.",
17
17
  inputSchema: {
18
18
  type: "object",
19
19
  properties: {
@@ -100,7 +100,7 @@ exports.VAULT_TOOLS = [
100
100
  {
101
101
  name: "vault_export",
102
102
  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.",
103
+ "Useful for archiving, syncing to GitHub, or passing context to another agent.",
104
104
  inputSchema: {
105
105
  type: "object",
106
106
  properties: {
@@ -109,38 +109,6 @@ exports.VAULT_TOOLS = [
109
109
  required: [],
110
110
  },
111
111
  },
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
112
  {
145
113
  name: "vault_store_credential",
146
114
  description: "Securely store an API key, token, or secret in your vault. " +
@@ -169,50 +137,42 @@ exports.VAULT_TOOLS = [
169
137
  },
170
138
  },
171
139
  {
172
- name: "vault_publish",
173
- description: "Publish a vault entry to the community vault so other Noelclaw users can discover and fork it. " +
174
- "Credentials are never published only research, prompts, workflows, memory, and execution entries. " +
175
- "You can also unpublish by setting isPublic to false.",
140
+ name: "vault_pin",
141
+ description: "Pin or unpin a Noel-Vault entry. Pinned entries always appear first in vault_list and are " +
142
+ "prioritized in memory_context and search results. Use for your most important research, key prompts, or canonical references.",
176
143
  inputSchema: {
177
144
  type: "object",
178
145
  properties: {
179
- key: { type: "string", description: "Entry key to publish or unpublish" },
180
- isPublic: { type: "boolean", description: "true to publish, false to unpublish (default true)" },
181
- authorName: { type: "string", description: "Display name shown to the community (default: Anonymous)" },
146
+ key: { type: "string", description: "Entry key to pin or unpin" },
147
+ pinned: { type: "boolean", description: "true to pin, false to unpin (default: true)" },
182
148
  },
183
149
  required: ["key"],
184
150
  },
185
151
  },
186
152
  {
187
- name: "vault_explore",
188
- description: "Browse the community vault public entries shared by all Noelclaw users. " +
189
- "Discover research, prompts, and workflows published by the community. " +
190
- "Use vault_save to fork any entry into your own vault.",
153
+ name: "vault_delete",
154
+ description: "Permanently delete a Noel-Vault entry including all its version history. This cannot be undone. " +
155
+ "Use vault_list to browse entries before deleting.",
191
156
  inputSchema: {
192
157
  type: "object",
193
158
  properties: {
194
- type: { type: "string", enum: ["research", "execution", "workflow", "prompt", "file", "memory"], description: "Filter by entry type" },
195
- search: { type: "string", description: "Search query to filter community entries" },
196
- limit: { type: "number", description: "Max entries to return (default 20)" },
159
+ key: { type: "string", description: "Entry key to delete permanently" },
197
160
  },
198
- required: [],
161
+ required: ["key"],
199
162
  },
200
163
  },
201
164
  {
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.",
165
+ name: "vault_tag",
166
+ description: "Add or replace tags on an existing Noel-Vault entry without modifying its content. " +
167
+ "Useful for organizing entries retroactively. Set replace=true to overwrite all existing tags.",
207
168
  inputSchema: {
208
169
  type: "object",
209
170
  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" },
171
+ key: { type: "string", description: "Entry key to update tags on" },
172
+ tags: { type: "array", items: { type: "string" }, description: "Tags to add (or replace if replace=true)" },
173
+ replace: { type: "boolean", description: "If true, replaces all existing tags. If false (default), merges with existing." },
214
174
  },
215
- required: ["url"],
175
+ required: ["key", "tags"],
216
176
  },
217
177
  },
218
178
  ];
@@ -243,13 +203,11 @@ const SearchSchema = zod_1.z.object({
243
203
  const HistorySchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
244
204
  const DiffSchema = zod_1.z.object({ key: zod_1.z.string().min(1), fromVersion: zod_1.z.number(), toVersion: zod_1.z.number() });
245
205
  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
206
  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
207
  const GetCredentialSchema = zod_1.z.object({ name: zod_1.z.string().min(1) });
250
- 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
- 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() });
208
+ const PinSchema = zod_1.z.object({ key: zod_1.z.string().min(1), pinned: zod_1.z.boolean().optional() });
209
+ const DeleteSchema = zod_1.z.object({ key: zod_1.z.string().min(1) });
210
+ 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
211
  // ─── Helpers ─────────────────────────────────────────────────────────────────
254
212
  function formatBytes(n) {
255
213
  if (!n)
@@ -437,69 +395,6 @@ async function handleVaultTool(name, args) {
437
395
  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
396
  return { content: [{ type: "text", text: [...header, ...rows.join("\n\n---\n\n").split("\n")].join("\n") }] };
439
397
  }
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
- });
460
- const smNote = " 🧠 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 (proxied through Convex)
469
- {
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
398
  case "vault_store_credential": {
504
399
  const parsed = StoreCredentialSchema.safeParse(args);
505
400
  if (!parsed.success)
@@ -524,90 +419,34 @@ async function handleVaultTool(name, args) {
524
419
  lines.push(`Stored: ${data.storedAt}`);
525
420
  return { content: [{ type: "text", text: lines.join("\n") }] };
526
421
  }
527
- case "vault_publish": {
528
- const parsed = PublishSchema.safeParse(args);
422
+ case "vault_pin": {
423
+ const parsed = PinSchema.safeParse(args);
529
424
  if (!parsed.success)
530
425
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
531
- const { key, isPublic = true, authorName } = parsed.data;
532
- const endpoint = isPublic ? "/vault/publish" : "/vault/unpublish";
533
- const data = await (0, convex_js_1.callConvex)(endpoint, "POST", { key, authorName }, "vault_publish");
426
+ const { key, pinned = true } = parsed.data;
427
+ const data = await (0, convex_js_1.callConvex)("/vault/pin", "POST", { key, pinned }, "vault_pin");
534
428
  if (data.error)
535
429
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
536
- return {
537
- content: [{
538
- type: "text",
539
- text: isPublic
540
- ? `🌐 **Published to community vault**\nKey: \`${key}\`\nOther Noelclaw users can now discover this entry.\nUse \`vault_publish key: "${key}" isPublic: false\` to unpublish.`
541
- : `🔒 **Unpublished**\nKey: \`${key}\` is now private.`,
542
- }],
543
- };
430
+ return { content: [{ type: "text", text: pinned ? `📌 Pinned: \`${key}\`` : `📌 Unpinned: \`${key}\`` }] };
544
431
  }
545
- case "vault_explore": {
546
- const parsed = ExploreSchema.safeParse(args ?? {});
432
+ case "vault_delete": {
433
+ const parsed = DeleteSchema.safeParse(args);
547
434
  if (!parsed.success)
548
435
  return { content: [{ type: "text", text: `Invalid input: ${parsed.error.issues[0].message}` }], isError: true };
549
- const params = new URLSearchParams();
550
- if (parsed.data.type)
551
- params.set("type", parsed.data.type);
552
- if (parsed.data.search)
553
- params.set("search", parsed.data.search);
554
- if (parsed.data.limit)
555
- params.set("limit", String(parsed.data.limit));
556
- const data = await (0, convex_js_1.callConvex)(`/vault/community?${params}`, "GET", undefined, "vault_explore");
436
+ const data = await (0, convex_js_1.callConvex)("/vault/delete", "POST", { key: parsed.data.key }, "vault_delete");
557
437
  if (data.error)
558
438
  return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
559
- const entries = data.entries ?? [];
560
- if (!entries.length)
561
- return { content: [{ type: "text", text: `No community vault entries found${parsed.data.type ? ` of type '${parsed.data.type}'` : ""}.${parsed.data.search ? ` Try a different search term.` : "\nBe the first to publish with vault_publish!"}` }] };
562
- const header = `🌐 **Community Vault** — ${entries.length} public entries`;
563
- const rows = entries.map((e) => [
564
- `**[${e.type}]** ${e.title} · by ${e.authorName ?? "Anonymous"}`,
565
- ` \`${e.key}\` · v${e.version} · ${formatDate(e.updatedAt)}`,
566
- e.content ? ` ${e.content.slice(0, 100)}${e.content.length > 100 ? "…" : ""}` : "",
567
- ].filter(Boolean).join("\n"));
568
- return {
569
- content: [{
570
- type: "text",
571
- text: [header, "", ...rows, "", "To fork an entry: `vault_save type: <type> title: <title> content: <content>`"].join("\n"),
572
- }],
573
- };
439
+ return { content: [{ type: "text", text: `🗑️ Deleted: \`${parsed.data.key}\` (${data.versionsRemoved ?? 0} versions removed)` }] };
574
440
  }
575
- case "vault_connect": {
576
- const parsed = VaultConnectSchema.safeParse(args);
441
+ case "vault_tag": {
442
+ const parsed = TagSchema.safeParse(args);
577
443
  if (!parsed.success)
578
444
  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
- (0, memory_js_1.syncToSupermemory)("", {
593
- vaultKey: vaultData.key, title: inferredTitle, type, tags, source: "vault_connect",
594
- }, url);
595
- if (vaultData.error)
596
- return { content: [{ type: "text", text: `Error: ${vaultData.error}` }], isError: true };
597
- return {
598
- content: [{
599
- type: "text",
600
- text: [
601
- `🔗 **Vault Connected**`,
602
- `URL: ${url}`,
603
- `Vault key: \`${vaultData.key}\``,
604
- `🧠 Indexing to semantic memory… searchable in ~30s`,
605
- ``,
606
- `Search it with: \`vault_search query: "${inferredTitle}"\``,
607
- `Or load as context: \`vault_context topic: "${inferredTitle}"\``,
608
- ].filter(Boolean).join("\n"),
609
- }],
610
- };
445
+ const { key, tags, replace = false } = parsed.data;
446
+ const data = await (0, convex_js_1.callConvex)("/vault/tag", "POST", { key, tags, replace }, "vault_tag");
447
+ if (data.error)
448
+ return { content: [{ type: "text", text: `Error: ${data.error}` }], isError: true };
449
+ return { content: [{ type: "text", text: `🏷️ Tags ${replace ? "set" : "updated"} on \`${key}\`: ${(data.tags ?? tags).join(", ")}` }] };
611
450
  }
612
451
  default:
613
452
  return null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@noelclaw/mcp",
3
- "version": "2.3.1",
4
- "description": "Noelclaw as an MCP skill — persistent memory, multi-agent coordination, scenario simulation, DeFi execution, and Sentinel-gated playbooks.",
3
+ "version": "3.0.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
- }