feat(integrations): add n8n community node for Mem0

Programmatic n8n node (n8n-nodes-mem0) with Add/Search/Get Many/Get/
Update/Delete memory operations against the Mem0 v3 API, plus API-key
credentials and usableAsTool support for the AI Agent node.

Add uses POST /v3/memories/add/ and polls GET /v1/event/{id}/ until the
async extraction resolves. No runtime dependencies (n8n verification
requirement); calls the REST API directly. MIT licensed per-package.
This commit is contained in:
Himanshu-Sangshetti
2026-07-03 01:10:54 +08:00
parent 9383e9a255
commit 4464dca825
13 changed files with 5538 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
module.exports = {
root: true,
env: { browser: true, es6: true, node: true },
parser: '@typescript-eslint/parser',
parserOptions: { sourceType: 'module', extraFileExtensions: ['.json'] },
ignorePatterns: ['.eslintrc.js', '**/*.js', '**/node_modules/**', '**/dist/**'],
overrides: [
{
files: ['package.json'],
plugins: ['eslint-plugin-n8n-nodes-base'],
extends: ['plugin:n8n-nodes-base/community'],
rules: { 'n8n-nodes-base/community-package-json-name-still-default': 'off' },
},
{
files: ['./credentials/**/*.ts'],
plugins: ['eslint-plugin-n8n-nodes-base'],
extends: ['plugin:n8n-nodes-base/credentials'],
},
{
files: ['./nodes/**/*.ts'],
plugins: ['eslint-plugin-n8n-nodes-base'],
extends: ['plugin:n8n-nodes-base/nodes'],
},
],
};
+7
View File
@@ -0,0 +1,7 @@
node_modules/
dist/
*.tsbuildinfo
.env
coverage/
*.log
.DS_Store
@@ -0,0 +1,9 @@
module.exports = {
semi: true,
trailingComma: 'all',
bracketSpacing: true,
useTabs: true,
tabWidth: 2,
printWidth: 100,
singleQuote: true,
};
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Mem0
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+54
View File
@@ -0,0 +1,54 @@
# n8n-nodes-mem0
This is an n8n community node that lets you use [Mem0](https://mem0.ai) — the memory layer for AI agents — in your n8n workflows.
Mem0 gives your agents long-term memory: add memories from conversations, then search and recall them across sessions.
[n8n](https://n8n.io) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
[Installation](#installation) · [Operations](#operations) · [Credentials](#credentials) · [Usage](#usage) · [Resources](#resources)
## Installation
Follow the [community nodes installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) and install `n8n-nodes-mem0`.
## Operations
The **Memory** resource supports:
| Operation | Description | Endpoint |
| --- | --- | --- |
| **Add** | Extract and store memories from messages | `POST /v3/memories/add/` |
| **Search** | Semantic search over stored memories | `POST /v3/memories/search/` |
| **Get Many** | List memories for a user (paginated) | `POST /v3/memories/` |
| **Get** | Retrieve a single memory by ID | `GET /v1/memories/{id}/` |
| **Update** | Update a memory's text or metadata | `PUT /v1/memories/{id}/` |
| **Delete** | Delete a single memory by ID | `DELETE /v1/memories/{id}/` |
### Add & asynchronous extraction
By default, **Add** runs LLM-based extraction asynchronously — the API returns an event ID and the node polls until extraction finishes, then returns the resulting memories. Disable **Wait for Completion** to return immediately with the event ID, or set **Infer = false** (under Additional Fields) to store messages verbatim and return synchronously.
## Credentials
You need a Mem0 API key. Create one at [app.mem0.ai](https://app.mem0.ai) → Settings → API Keys. The key is sent as `Authorization: Token <key>`.
## Usage
This node is also **usable as a tool** by n8n's AI Agent node — attach it so an agent can "remember" and "recall" autonomously.
A typical loop:
1. **Search** memory before answering, filtered by `User ID`.
2. **Add** durable facts after a meaningful exchange.
Memory writes are asynchronous by default; allow a moment after an Add before searching for the same content.
## Resources
- [Mem0 documentation](https://docs.mem0.ai)
- [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
## License
[MIT](./LICENSE)
@@ -0,0 +1,53 @@
import {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class Mem0Api implements ICredentialType {
name = 'mem0Api';
displayName = 'Mem0 API';
documentationUrl = 'https://docs.mem0.ai/platform/quickstart';
properties: INodeProperties[] = [
{
displayName: 'API Key',
name: 'apiKey',
type: 'string',
typeOptions: { password: true },
default: '',
required: true,
description: 'Your Mem0 API key (starts with "m0-"). Create one at app.mem0.ai → Settings → API Keys.',
},
{
displayName: 'Base URL',
name: 'baseUrl',
type: 'string',
default: 'https://api.mem0.ai',
description: 'Mem0 API base URL. Override only for self-hosted or non-default deployments.',
},
];
// Injects "Authorization: Token <apiKey>" on every request, matching the
// scheme used by Mem0's official SDKs (Authorization: Token m0-...).
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
Authorization: '=Token {{$credentials.apiKey}}',
},
},
};
// Cheap authenticated GET; validates the key when the user clicks "Test".
test: ICredentialTestRequest = {
request: {
baseURL: '={{$credentials.baseUrl}}',
url: '/v1/ping/',
method: 'GET',
},
};
}
+14
View File
@@ -0,0 +1,14 @@
const path = require('path');
const { task, src, dest } = require('gulp');
task('build:icons', copyIcons);
function copyIcons() {
const nodeSource = path.resolve('nodes', '**', '*.{png,svg}');
const nodeDestination = path.resolve('dist', 'nodes');
src(nodeSource).pipe(dest(nodeDestination));
const credSource = path.resolve('credentials', '**', '*.{png,svg}');
const credDestination = path.resolve('dist', 'credentials');
return src(credSource, { allowEmpty: true }).pipe(dest(credDestination));
}
+3
View File
@@ -0,0 +1,3 @@
// n8n loads nodes and credentials via the "n8n" key in package.json.
// This entry point is intentionally empty.
module.exports = {};
@@ -0,0 +1,404 @@
import {
IExecuteFunctions,
IDataObject,
IHttpRequestMethods,
IHttpRequestOptions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
JsonObject,
NodeApiError,
NodeConnectionTypes,
NodeOperationError,
sleep,
} from 'n8n-workflow';
// Poll settings for asynchronous (infer=true) memory addition.
const POLL_INTERVAL_MS = 1500;
const MAX_POLL_ATTEMPTS = 40; // ~60s ceiling
export class Mem0 implements INodeType {
description: INodeTypeDescription = {
displayName: 'Mem0',
name: 'mem0',
icon: 'file:mem0.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Add, search, and manage long-term memories with Mem0',
defaults: {
name: 'Mem0',
},
// Makes the node available to the AI Agent (Tools Agent) node.
usableAsTool: true,
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
name: 'mem0Api',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [{ name: 'Memory', value: 'memory' }],
default: 'memory',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: { show: { resource: ['memory'] } },
options: [
{
name: 'Add',
value: 'add',
action: 'Add a memory',
description: 'Extract and store memories from messages',
},
{
name: 'Search',
value: 'search',
action: 'Search memories',
description: 'Semantic search over stored memories',
},
{
name: 'Get Many',
value: 'getAll',
action: 'Get many memories',
description: 'List stored memories for an entity',
},
{
name: 'Get',
value: 'get',
action: 'Get a memory',
description: 'Retrieve a single memory by ID',
},
{
name: 'Update',
value: 'update',
action: 'Update a memory',
description: 'Update the text or metadata of a memory',
},
{
name: 'Delete',
value: 'delete',
action: 'Delete a memory',
description: 'Delete a single memory by ID',
},
],
default: 'add',
},
// ---- Add ---------------------------------------------------------
{
displayName: 'Messages',
name: 'messages',
placeholder: 'Add Message',
type: 'fixedCollection',
typeOptions: { multipleValues: true },
displayOptions: { show: { resource: ['memory'], operation: ['add'] } },
default: {},
description: 'The conversation messages to extract memories from',
options: [
{
name: 'message',
displayName: 'Message',
values: [
{
displayName: 'Role',
name: 'role',
type: 'options',
options: [
{ name: 'User', value: 'user' },
{ name: 'Assistant', value: 'assistant' },
{ name: 'System', value: 'system' },
],
default: 'user',
},
{
displayName: 'Content',
name: 'content',
type: 'string',
typeOptions: { rows: 2 },
default: '',
},
],
},
],
},
{
displayName: 'User ID',
name: 'userId',
type: 'string',
default: '',
displayOptions: { show: { resource: ['memory'], operation: ['add'] } },
description: 'Associate the memories with this user',
},
{
displayName: 'Wait for Completion',
name: 'waitForCompletion',
type: 'boolean',
default: true,
displayOptions: { show: { resource: ['memory'], operation: ['add'] } },
description:
'Whether to poll until memory extraction finishes and return the resulting memories. Turn off to return immediately with the event ID.',
},
{
displayName: 'Additional Fields',
name: 'addFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: { show: { resource: ['memory'], operation: ['add'] } },
options: [
{
displayName: 'Agent ID',
name: 'agent_id',
type: 'string',
default: '',
},
{
displayName: 'Run ID',
name: 'run_id',
type: 'string',
default: '',
},
{
displayName: 'Metadata (JSON)',
name: 'metadata',
type: 'json',
default: '',
},
{
displayName: 'Infer',
name: 'infer',
type: 'boolean',
default: true,
description:
'Whether to run LLM extraction (async). Turn off to store messages verbatim (synchronous).',
},
],
},
// ---- Search ------------------------------------------------------
{
displayName: 'Query',
name: 'query',
type: 'string',
default: '',
required: true,
displayOptions: { show: { resource: ['memory'], operation: ['search'] } },
description: 'What to recall from memory',
},
{
displayName: 'User ID',
name: 'userId',
type: 'string',
default: '',
displayOptions: { show: { resource: ['memory'], operation: ['search'] } },
description: 'Restrict the search to this user',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
typeOptions: { minValue: 1 },
default: 10,
displayOptions: { show: { resource: ['memory'], operation: ['search'] } },
description: 'Max number of results to return',
},
// ---- Get Many ----------------------------------------------------
{
displayName: 'User ID',
name: 'userId',
type: 'string',
default: '',
displayOptions: { show: { resource: ['memory'], operation: ['getAll'] } },
description: 'Restrict the listing to this user',
},
{
displayName: 'Page',
name: 'page',
type: 'number',
typeOptions: { minValue: 1 },
default: 1,
displayOptions: { show: { resource: ['memory'], operation: ['getAll'] } },
},
{
displayName: 'Page Size',
name: 'pageSize',
type: 'number',
typeOptions: { minValue: 1 },
default: 50,
displayOptions: { show: { resource: ['memory'], operation: ['getAll'] } },
},
// ---- Get / Update / Delete (by ID) -------------------------------
{
displayName: 'Memory ID',
name: 'memoryId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: { resource: ['memory'], operation: ['get', 'update', 'delete'] },
},
},
{
displayName: 'Text',
name: 'text',
type: 'string',
default: '',
displayOptions: { show: { resource: ['memory'], operation: ['update'] } },
description: 'The new memory text',
},
{
displayName: 'Metadata (JSON)',
name: 'metadata',
type: 'json',
default: '',
displayOptions: { show: { resource: ['memory'], operation: ['update'] } },
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const baseUrl = ((await this.getCredentials('mem0Api')).baseUrl as string) || 'https://api.mem0.ai';
const request = async (
method: IHttpRequestMethods,
url: string,
body?: IDataObject,
qs?: IDataObject,
): Promise<IDataObject> => {
const options: IHttpRequestOptions = {
method,
url: `${baseUrl}${url}`,
json: true,
...(body ? { body } : {}),
...(qs ? { qs } : {}),
};
return (await this.helpers.httpRequestWithAuthentication.call(
this,
'mem0Api',
options,
)) as IDataObject;
};
for (let i = 0; i < items.length; i++) {
try {
const operation = this.getNodeParameter('operation', i) as string;
let responseData: IDataObject | IDataObject[] = {};
if (operation === 'add') {
const messagesUi = this.getNodeParameter('messages.message', i, []) as IDataObject[];
if (!messagesUi.length) {
throw new NodeOperationError(this.getNode(), 'At least one message is required', {
itemIndex: i,
});
}
const addFields = this.getNodeParameter('addFields', i, {}) as IDataObject;
const body: IDataObject = {
messages: messagesUi.map((m) => ({ role: m.role, content: m.content })),
infer: addFields.infer !== undefined ? addFields.infer : true,
};
const userId = this.getNodeParameter('userId', i, '') as string;
if (userId) body.user_id = userId;
if (addFields.agent_id) body.agent_id = addFields.agent_id;
if (addFields.run_id) body.run_id = addFields.run_id;
if (addFields.metadata) {
body.metadata =
typeof addFields.metadata === 'string'
? JSON.parse(addFields.metadata as string)
: addFields.metadata;
}
const addResp = await request('POST', '/v3/memories/add/', body);
const waitForCompletion = this.getNodeParameter('waitForCompletion', i, true) as boolean;
// infer=true returns {event_id, status:PENDING}; poll until done.
if (waitForCompletion && addResp.status === 'PENDING' && addResp.event_id) {
responseData = await pollEvent(request, addResp.event_id as string, this);
} else {
responseData = addResp;
}
} else if (operation === 'search') {
const body: IDataObject = {
query: this.getNodeParameter('query', i) as string,
output_format: 'v1.1',
limit: this.getNodeParameter('limit', i, 10) as number,
};
const userId = this.getNodeParameter('userId', i, '') as string;
if (userId) body.filters = { user_id: userId };
const resp = await request('POST', '/v3/memories/search/', body);
responseData = (resp.results as IDataObject[]) ?? resp;
} else if (operation === 'getAll') {
const userId = this.getNodeParameter('userId', i, '') as string;
const page = this.getNodeParameter('page', i, 1) as number;
const pageSize = this.getNodeParameter('pageSize', i, 50) as number;
const body: IDataObject = {};
if (userId) body.filters = { user_id: userId };
const resp = await request('POST', '/v3/memories/', body, { page, page_size: pageSize });
responseData = (resp.results as IDataObject[]) ?? resp;
} else if (operation === 'get') {
const memoryId = this.getNodeParameter('memoryId', i) as string;
responseData = await request('GET', `/v1/memories/${memoryId}/`);
} else if (operation === 'update') {
const memoryId = this.getNodeParameter('memoryId', i) as string;
const body: IDataObject = {};
const text = this.getNodeParameter('text', i, '') as string;
const metadata = this.getNodeParameter('metadata', i, '') as string;
if (text) body.text = text;
if (metadata) body.metadata = typeof metadata === 'string' ? JSON.parse(metadata) : metadata;
responseData = await request('PUT', `/v1/memories/${memoryId}/`, body);
} else if (operation === 'delete') {
const memoryId = this.getNodeParameter('memoryId', i) as string;
responseData = await request('DELETE', `/v1/memories/${memoryId}/`);
}
const arr = Array.isArray(responseData) ? responseData : [responseData];
for (const entry of arr) {
returnData.push({ json: entry, pairedItem: { item: i } });
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ json: { error: (error as Error).message }, pairedItem: { item: i } });
continue;
}
if (error instanceof NodeApiError || error instanceof NodeOperationError) throw error;
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
return [returnData];
}
}
// Polls GET /v1/event/{id}/ until the memory-addition event resolves.
async function pollEvent(
request: (m: IHttpRequestMethods, u: string) => Promise<IDataObject>,
eventId: string,
ctx: IExecuteFunctions,
): Promise<IDataObject> {
for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
const event = await request('GET', `/v1/event/${eventId}/`);
const status = event.status as string;
if (status === 'SUCCEEDED' || status === 'FAILED') {
return event;
}
await sleep(POLL_INTERVAL_MS);
}
throw new NodeOperationError(
ctx.getNode(),
`Timed out waiting for memory event ${eventId} to complete`,
);
}
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" fill="none">
<rect width="64" height="64" rx="14" fill="#1B1B1F"/>
<text x="32" y="41" font-family="Inter, Arial, sans-serif" font-size="24" font-weight="700" fill="#FFFFFF" text-anchor="middle">m0</text>
</svg>

After

Width:  |  Height:  |  Size: 299 B

File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
{
"name": "n8n-nodes-mem0",
"version": "0.1.0",
"description": "n8n community node for Mem0 — the memory layer for AI agents. Add, search, get, update, and delete long-term memories.",
"keywords": [
"n8n-community-node-package",
"mem0",
"memory",
"ai",
"agents",
"llm"
],
"license": "MIT",
"homepage": "https://mem0.ai",
"author": {
"name": "Mem0",
"email": "founders@mem0.ai"
},
"repository": {
"type": "git",
"url": "https://github.com/mem0ai/mem0",
"directory": "integrations/n8n-nodes-mem0"
},
"engines": {
"node": ">=20.15"
},
"main": "index.js",
"scripts": {
"build": "npx rimraf dist && tsc && gulp build:icons",
"dev": "tsc --watch",
"format": "prettier nodes credentials --write",
"lint": "eslint nodes credentials package.json",
"lintfix": "eslint nodes credentials package.json --fix",
"prepublishOnly": "npm run build && npm run lint"
},
"files": [
"dist"
],
"n8n": {
"n8nNodesApiVersion": 1,
"credentials": [
"dist/credentials/Mem0Api.credentials.js"
],
"nodes": [
"dist/nodes/Mem0/Mem0.node.js"
]
},
"devDependencies": {
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^8.57.0",
"eslint-plugin-n8n-nodes-base": "^1.16.3",
"gulp": "^5.0.0",
"n8n-workflow": "*",
"prettier": "^3.3.0",
"rimraf": "^5.0.0",
"typescript": "^5.5.0"
},
"peerDependencies": {
"n8n-workflow": "*"
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"strict": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es2019",
"lib": ["es2019", "es2020", "es2022.error"],
"removeComments": true,
"useUnknownInCatchVariables": false,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"preserveConstEnums": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"incremental": true,
"declaration": true,
"sourceMap": true,
"skipLibCheck": true,
"outDir": "./dist/"
},
"include": ["credentials/**/*", "nodes/**/*"],
"exclude": ["node_modules", "dist"]
}