@noelclaw/mcp 2.3.1 → 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)
@@ -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
- });
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
446
  case "vault_store_credential": {
504
447
  const parsed = StoreCredentialSchema.safeParse(args);
505
448
  if (!parsed.success)
@@ -572,42 +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
- (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
- };
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(", ")}` }] };
611
556
  }
612
557
  default:
613
558
  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": "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
- }