Skip to content

Vercel AI SDK ​

Give an agent built on the Vercel AI SDK a memory that outlives the process: it recalls earlier decisions before answering, stores what it learned, and rates what helped so the next recall ranks it higher. Two ways in, both run live on 2026-10-02 with ai 7.0.127 on Node 22.23:

  • Three tools over REST. remember, recall and rate, written with tool() and zod, calling https://core.mnemoverse.com/api/v1 with your API key. No subprocess, so it works wherever fetch works, including serverless functions.
  • The MCP server over stdio. createMCPClient from @ai-sdk/mcp starts @mnemoverse/mcp-memory-server and turns every tool it exposes into an AI SDK tool. Node only.

You need an API key from console.mnemoverse.com (the free tier is enough; keys start with mk_live_), Node 22 or newer (ai 7 declares engines.node >=22), and the package of your model provider. The examples use @ai-sdk/anthropic; any provider the SDK supports goes in the same model slot.

bash
npm install ai zod @ai-sdk/anthropic

Three tools over REST ​

ts
// memory-tools.ts
import { tool } from 'ai';
import { z } from 'zod';

const BASE = 'https://core.mnemoverse.com/api/v1';
// One namespace per project or user. A read never crosses it.
const DOMAIN = process.env.MNEMOVERSE_DOMAIN ?? 'project:acme';

async function call(path: string, body: unknown) {
  const res = await fetch(`${BASE}${path}`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.MNEMOVERSE_API_KEY!,
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`${path} -> ${res.status} ${await res.text()}`);
  return res.json();
}

export const memoryTools = {
  remember: tool({
    description:
      'Store a durable fact, decision or lesson in long-term memory. Write it as a self-contained statement.',
    inputSchema: z.object({
      content: z.string().min(1).max(10000),
      concepts: z.array(z.string()).max(256).optional(),
    }),
    execute: async ({ content, concepts }) =>
      call('/memory/write', { content, concepts, domain: DOMAIN }),
  }),
  recall: tool({
    description:
      'Search long-term memory for earlier decisions, preferences and project context before answering.',
    inputSchema: z.object({
      query: z.string().min(1).max(5000),
      top_k: z.number().int().min(1).max(500).optional(),
    }),
    execute: async ({ query, top_k }) =>
      call('/memory/read', { query, top_k: top_k ?? 10, domain: DOMAIN }),
  }),
  rate: tool({
    description:
      'Report whether recalled memories helped (1) or misled (-1), so the next recall ranks them accordingly.',
    inputSchema: z.object({
      atom_ids: z.array(z.string()).min(1),
      outcome: z.number().min(-1).max(1),
    }),
    execute: async ({ atom_ids, outcome }) =>
      call('/memory/feedback', { atom_ids, outcome }),
  }),
};
ts
// agent.ts
import { generateText, isStepCount } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { memoryTools } from './memory-tools';

const { text, steps } = await generateText({
  model: anthropic('claude-sonnet-4-5'),
  tools: memoryTools,
  stopWhen: isStepCount(6),
  system:
    'You have long-term memory tools. Recall before answering questions about the project; remember durable facts the user states.',
  prompt:
    'Remember that our deploy target is Railway and nobody pushes to main on Fridays. Then recall what you know about deploys and summarise it in one sentence.',
});

for (const step of steps)
  for (const c of step.toolCalls ?? []) console.log('tool call:', c.toolName, JSON.stringify(c.input));
console.log(text);

stopWhen: isStepCount(6) lets the model call a tool, read the result and continue; without it, generateText returns after the first tool call (AI SDK: multi-step calls).

The run on 2026-10-02, with MNEMOVERSE_DOMAIN=smoke:ai-sdk-2026-10-02 and the model reached through @ai-sdk/openai-compatible; the provider line is the only difference from the code above:

text
tool call: remember {"content":"The project's deploy target is Railway, and the team policy is that nobody pushes to main on Fridays.","concepts":["deployment","Railway","main branch","release policy"]}
tool call: recall {"query":"What is known about this project's deploy target, deploy process, branch rules, and timing restrictions?","top_k":20}
tool call: rate {"atom_ids":["39f4889d-5ac4-4323-bd76-51d3da3e37c3"],"outcome":1}
tool result: remember {"stored":true,"atom_id":"39f4889d-5ac4-4323-bd76-51d3da3e37c3","importance":1,"reason":"","superseded":[]}
tool result: recall {"items":[{"atom_id":"39f4889d-5ac4-4323-bd76-51d3da3e37c3","content":"The project's deploy target is Railway, and the team policy is that nobody pushes to main on Fridays.","relevance":0.65, ...
tool result: rate {"updated_count":1,"avg_valence":1,"coactivation_edges":0,"feedback_time_ms":104.45}
answer: Deploys target Railway, and nobody pushes to `main` on Fridays.

The model called all three tools on its own, in the order the system prompt implies. The bounds in the schemas are the API's: content up to 10,000 characters, query up to 5,000 characters, concepts up to 256 items, top_k 1 to 500 with a default of 10 (API reference). recall returns items with atom_id, content, relevance and source; rate takes those ids back.

The MCP server over stdio ​

The same memory through the published server package, so the tools and their descriptions come from the server rather than from your code.

bash
npm install @ai-sdk/mcp @modelcontextprotocol/sdk
ts
// agent-mcp.ts
import { generateText, isStepCount } from 'ai';
import { createMCPClient } from '@ai-sdk/mcp';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { anthropic } from '@ai-sdk/anthropic';

const mcpClient = await createMCPClient({
  transport: new StdioClientTransport({
    command: 'npx',
    args: ['-y', '@mnemoverse/mcp-memory-server@latest'],
    // The transport starts the server with a small safe subset of your
    // environment unless you pass env; the key has to be passed explicitly.
    env: { ...process.env, MNEMOVERSE_API_KEY: process.env.MNEMOVERSE_API_KEY! },
  }),
});

try {
  const tools = await mcpClient.tools();
  const { text } = await generateText({
    model: anthropic('claude-sonnet-4-5'),
    tools,
    stopWhen: isStepCount(6),
    prompt:
      'What do we know about deploys? Use memory_read with domain "project:acme" and answer in one sentence.',
  });
  console.log(text);
} finally {
  await mcpClient.close();
}

With streamText, close the client in the onEnd callback instead of finally (AI SDK: MCP tools). The run on 2026-10-02, against the memory written in the REST example:

text
tools from server: memory_write, memory_read, memory_list_recent, memory_feedback, memory_stats, memory_create_room, memory_invite_to_room, memory_join_room, memory_list_rooms, vault_list, memory_graph
tool call: memory_read {"query":"deploys deployment process decisions status","top_k":10,"domain":"smoke:ai-sdk-2026-10-02","order_by":"relevance", ...}
tool call: memory_feedback {"memory_ids":["39f4889d-5ac4-4323-bd76-51d3da3e37c3"],"outcome":1,"domain":"smoke:ai-sdk-2026-10-02"}
answer: Deploys target Railway, and team policy prohibits pushes to `main` on Fridays.

The server's memory_read fills in top_k: 5 when the model omits it; the REST default is 10. What each tool accepts is on the MCP server page.

The hosted connector and the AI SDK ​

createMCPClient also speaks Streamable HTTP (transport: { type: 'http', url }), and the hosted connector lives at https://mcp.mnemoverse.com/mcp. It signs a user in through the browser (OAuth 2.1 with PKCE) and refuses an API key sent as a bearer token: a POST /mcp with Authorization: Bearer mk_live_... answered 401 on 2026-10-02, the same as a request with no credentials. The SDK's authProvider option is for a client that can complete that sign-in; a server-side app without a browser uses the two paths above. Details: Remote MCP server.

Habits that make the memory worth having ​

  • Recall before answering, remember after. Put both in the system prompt, as the example does; a registered tool is not a used tool. The agent memory page has the standing rules.
  • One domain per project or user. domain is a byte-for-byte namespace; a read with a domain never returns another domain's memories.
  • Rate what you acted on. rate with 1 or -1 changes the order of the next recall, not the stored text; nothing is erased.
  • Do not re-inject what the conversation already holds. When you build the prompt from a recall, replace the memory block, do not append a second one.

Common questions ​

How do I add long-term memory to a Vercel AI SDK agent? ​

Define three tools with tool() and zod that call the Mnemoverse REST API (write, read, feedback), pass them to generateText or streamText with stopWhen: isStepCount(n), and tell the model in the system prompt to recall before answering and to remember durable facts. The page shows the exact code and a recorded run.

Can the AI SDK use the Mnemoverse MCP server directly? ​

Yes. createMCPClient from @ai-sdk/mcp with a stdio transport starts @mnemoverse/mcp-memory-server and converts every tool it exposes into an AI SDK tool. Pass the API key in the transport's env, and close the client when the response is done.

Does the hosted connector at mcp.mnemoverse.com work with createMCPClient over HTTP? ​

Only for a client that can complete a browser sign-in: the hosted connector uses OAuth 2.1 with PKCE and refuses an API key sent as a bearer token. A server-side app without a browser uses the REST tools or the stdio server instead.

Which AI SDK version does this need? ​

The examples ran on ai 7.0.127 and @ai-sdk/mcp 2.0.66 with Node 22. In AI SDK 7 the multi-step condition is isStepCount and tool schemas use inputSchema; older releases named them stepCountIs and parameters.