diff --git a/.stats.yml b/.stats.yml
index 8716811..d28156b 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 21
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-622b43986c45c1efbeb06dd933786980257f300b7a0edbb2d2a4f708afacce36.yml
-openapi_spec_hash: ade837ffc4873d3b50a0fab3f061b397
-config_hash: a3a8e3c71c17eabb21ab8173521181a4
+configured_endpoints: 22
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-fc4ab722e6762cc69d533f57bea0d70b00e44a30c4ad8144e14ff70a1170ec7c.yml
+openapi_spec_hash: 2533ea676c195d5f7d30a67c201fd32d
+config_hash: b32e7b67898ff493ca749cdd513ab870
diff --git a/MIGRATION.md b/MIGRATION.md
index 78e709a..3b0d763 100644
--- a/MIGRATION.md
+++ b/MIGRATION.md
@@ -54,6 +54,7 @@ client.parents.children.retrieve('c_456', { parent_id: 'p_123' });
This affects the following methods:
+- `client.memories.update()`
- `client.memories.delete()`
- `client.memories.get()`
diff --git a/api.md b/api.md
index c6adb2b..6a3115e 100644
--- a/api.md
+++ b/api.md
@@ -69,6 +69,7 @@ Types:
Methods:
+- client.memories.update(resourceID, { ...params }) -> MemoryStatus
- client.memories.list({ ...params }) -> MemoriesCursorPage
- client.memories.delete(resourceID, { ...params }) -> MemoryDeleteResponse
- client.memories.add({ ...params }) -> MemoryStatus
diff --git a/bin/migration-config.json b/bin/migration-config.json
index 14738c4..629c459 100644
--- a/bin/migration-config.json
+++ b/bin/migration-config.json
@@ -3,6 +3,43 @@
"githubRepo": "https://github.com/hyperspell/node-sdk",
"clientClass": "Hyperspell",
"methods": [
+ {
+ "base": "memories",
+ "name": "update",
+ "params": [
+ {
+ "type": "param",
+ "key": "resource_id",
+ "location": "path"
+ },
+ {
+ "type": "params",
+ "maybeOverload": false
+ },
+ {
+ "type": "options"
+ }
+ ],
+ "oldParams": [
+ {
+ "type": "param",
+ "key": "source",
+ "location": "path"
+ },
+ {
+ "type": "param",
+ "key": "resource_id",
+ "location": "path"
+ },
+ {
+ "type": "params",
+ "maybeOverload": false
+ },
+ {
+ "type": "options"
+ }
+ ]
+ },
{
"base": "memories",
"name": "delete",
diff --git a/packages/mcp-server/.dockerignore b/packages/mcp-server/.dockerignore
new file mode 100644
index 0000000..1850726
--- /dev/null
+++ b/packages/mcp-server/.dockerignore
@@ -0,0 +1,66 @@
+# Dependencies
+node_modules/
+**/node_modules/
+
+# Build outputs
+dist/
+**/dist/
+build/
+**/build/
+
+# Git
+.git/
+.gitignore
+
+# CI/CD
+.github/
+.gitlab-ci.yml
+.travis.yml
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Documentation
+*.md
+docs/
+LICENSE
+
+# Testing
+test/
+tests/
+__tests__/
+*.test.js
+*.spec.js
+coverage/
+.nyc_output/
+
+# Logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Environment
+.env
+.env.*
+
+# Temporary files
+*.tmp
+*.temp
+.cache/
+
+# Examples and scripts
+examples/
+bin/
+
+# Other packages (we only need mcp-server)
+packages/*/
+!packages/mcp-server/
diff --git a/packages/mcp-server/Dockerfile b/packages/mcp-server/Dockerfile
new file mode 100644
index 0000000..e9af9ae
--- /dev/null
+++ b/packages/mcp-server/Dockerfile
@@ -0,0 +1,77 @@
+# Dockerfile for Hyperspell MCP Server
+ #
+ # This Dockerfile builds a Docker image for the MCP Server.
+ #
+ # To build the image locally:
+ # docker build -f packages/mcp-server/Dockerfile -t hyperspell-mcp:local .
+ #
+ # To run the image:
+ # docker run -i hyperspell-mcp:local [OPTIONS]
+ #
+ # Common options:
+ # --tool= Include specific tools
+ # --resource= Include tools for specific resources
+ # --operation=read|write Filter by operation type
+ # --client= Set client compatibility (e.g., claude, cursor)
+ # --transport= Set transport type (stdio or http)
+ #
+ # For a full list of options:
+ # docker run -i hyperspell-mcp:local --help
+ #
+ # Note: The MCP server uses stdio transport by default. Docker's -i flag
+ # enables interactive mode, allowing the container to communicate over stdin/stdout.
+
+ # Build stage
+ FROM node:20-alpine AS builder
+
+ # Install bash for build script
+ RUN apk add --no-cache bash openssl
+
+ # Set working directory
+ WORKDIR /build
+
+ # Copy entire repository
+ COPY . .
+
+ # Install all dependencies and build everything
+ RUN yarn install --frozen-lockfile && \
+ yarn build
+
+ # Production stage
+
+ FROM denoland/deno:alpine
+ RUN apk add --no-cache npm
+
+ # Add non-root user
+ RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
+
+ # Set working directory
+ WORKDIR /app
+
+# Copy the built mcp-server dist directory
+COPY --from=builder /build/packages/mcp-server/dist ./
+
+# Copy node_modules from mcp-server (includes all production deps)
+COPY --from=builder /build/packages/mcp-server/node_modules ./node_modules
+
+ # Copy the built hyperspell into node_modules
+ COPY --from=builder /build/dist ./node_modules/hyperspell
+
+ # Change ownership to nodejs user
+ RUN chown -R nodejs:nodejs /app
+
+ # Switch to non-root user
+ USER nodejs
+
+ # The MCP server uses stdio transport by default
+ # No exposed ports needed for stdio communication
+
+ # This is needed for node to run on the deno:alpine image.
+ # See .
+ ENV LD_LIBRARY_PATH=/usr/lib:/usr/local/lib
+
+ # Set the entrypoint to the MCP server
+ ENTRYPOINT ["node", "index.js"]
+
+ # Allow passing arguments to the MCP server
+ CMD []
diff --git a/packages/mcp-server/README.md b/packages/mcp-server/README.md
index bbd404c..48db197 100644
--- a/packages/mcp-server/README.md
+++ b/packages/mcp-server/README.md
@@ -254,6 +254,7 @@ The following tools are available in this MCP server.
### Resource `memories`:
+- `update_memory` (`write`): This tool lets you update memory in Hyperspell.
- `add_memory` (`write`): This tool lets you add text, markdown, or JSON to the Hyperspell index so it can be searched later. It will return the `source` and `resource_id` that can be used to later retrieve the processed memory.
- `get_memory` (`read`): This tool lets you retrieve a memory that has been previously indexed.
- `search` (`write`): Search all memories indexed by Hyperspell. Set 'answer' to true to directly answer the query, or to 'false' to simply get all memories related to the query.
diff --git a/packages/mcp-server/manifest.json b/packages/mcp-server/manifest.json
index 967415c..6b52fd8 100644
--- a/packages/mcp-server/manifest.json
+++ b/packages/mcp-server/manifest.json
@@ -1,7 +1,7 @@
{
"dxt_version": "0.2",
"name": "hyperspell-mcp",
- "version": "0.22.1",
+ "version": "0.27.0",
"description": "The official MCP Server for the Hyperspell API",
"author": {
"name": "Hyperspell",
diff --git a/packages/mcp-server/src/tools/index.ts b/packages/mcp-server/src/tools/index.ts
index cabacce..e7cf91c 100644
--- a/packages/mcp-server/src/tools/index.ts
+++ b/packages/mcp-server/src/tools/index.ts
@@ -7,6 +7,7 @@ export { Metadata, Endpoint, HandlerFunction };
import list_connections from './connections/list-connections';
import list_integrations from './integrations/list-integrations';
import connect_integration from './integrations/connect-integration';
+import update_memory from './memories/update-memory';
import add_memory from './memories/add-memory';
import get_memory from './memories/get-memory';
import search from './memories/search';
@@ -22,6 +23,7 @@ function addEndpoint(endpoint: Endpoint) {
addEndpoint(list_connections);
addEndpoint(list_integrations);
addEndpoint(connect_integration);
+addEndpoint(update_memory);
addEndpoint(add_memory);
addEndpoint(get_memory);
addEndpoint(search);
diff --git a/packages/mcp-server/src/tools/memories/update-memory.ts b/packages/mcp-server/src/tools/memories/update-memory.ts
new file mode 100644
index 0000000..c692b75
--- /dev/null
+++ b/packages/mcp-server/src/tools/memories/update-memory.ts
@@ -0,0 +1,159 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+import { isJqError, maybeFilter } from 'hyperspell-mcp/filtering';
+import { Metadata, asErrorResult, asTextContentResult } from 'hyperspell-mcp/tools/types';
+
+import { Tool } from '@modelcontextprotocol/sdk/types.js';
+import Hyperspell from 'hyperspell';
+
+export const metadata: Metadata = {
+ resource: 'memories',
+ operation: 'write',
+ tags: [],
+ httpMethod: 'post',
+ httpPath: '/memories/update/{source}/{resource_id}',
+ operationId: 'update_memories_update__source___resource_id__post',
+};
+
+export const tool: Tool = {
+ name: 'update_memory',
+ description:
+ "When using this tool, always use the `jq_filter` parameter to reduce the response size and improve performance.\n\nOnly omit if you're sure you don't need the data.\n\nThis tool lets you update memory in Hyperspell.\n\n# Response Schema\n```json\n{\n $ref: '#/$defs/memory_status',\n $defs: {\n memory_status: {\n type: 'object',\n title: 'DocumentStatusResponse',\n properties: {\n resource_id: {\n type: 'string',\n title: 'Resource Id'\n },\n source: {\n type: 'string',\n title: 'DocumentProviders',\n enum: [ 'collections',\n 'vault',\n 'web_crawler',\n 'notion',\n 'slack',\n 'google_calendar',\n 'reddit',\n 'box',\n 'google_drive',\n 'airtable',\n 'algolia',\n 'amplitude',\n 'asana',\n 'ashby',\n 'bamboohr',\n 'basecamp',\n 'bubbles',\n 'calendly',\n 'confluence',\n 'clickup',\n 'datadog',\n 'deel',\n 'discord',\n 'dropbox',\n 'exa',\n 'facebook',\n 'front',\n 'github',\n 'gitlab',\n 'google_docs',\n 'google_mail',\n 'google_sheet',\n 'hubspot',\n 'jira',\n 'linear',\n 'microsoft_teams',\n 'mixpanel',\n 'monday',\n 'outlook',\n 'perplexity',\n 'rippling',\n 'salesforce',\n 'segment',\n 'todoist',\n 'twitter',\n 'zoom'\n ]\n },\n status: {\n type: 'string',\n title: 'DocumentStatus',\n enum: [ 'pending',\n 'processing',\n 'completed',\n 'failed'\n ]\n }\n },\n required: [ 'resource_id',\n 'source',\n 'status'\n ]\n }\n }\n}\n```",
+ inputSchema: {
+ type: 'object',
+ properties: {
+ source: {
+ type: 'string',
+ title: 'DocumentProviders',
+ enum: [
+ 'collections',
+ 'vault',
+ 'web_crawler',
+ 'notion',
+ 'slack',
+ 'google_calendar',
+ 'reddit',
+ 'box',
+ 'google_drive',
+ 'airtable',
+ 'algolia',
+ 'amplitude',
+ 'asana',
+ 'ashby',
+ 'bamboohr',
+ 'basecamp',
+ 'bubbles',
+ 'calendly',
+ 'confluence',
+ 'clickup',
+ 'datadog',
+ 'deel',
+ 'discord',
+ 'dropbox',
+ 'exa',
+ 'facebook',
+ 'front',
+ 'github',
+ 'gitlab',
+ 'google_docs',
+ 'google_mail',
+ 'google_sheet',
+ 'hubspot',
+ 'jira',
+ 'linear',
+ 'microsoft_teams',
+ 'mixpanel',
+ 'monday',
+ 'outlook',
+ 'perplexity',
+ 'rippling',
+ 'salesforce',
+ 'segment',
+ 'todoist',
+ 'twitter',
+ 'zoom',
+ ],
+ },
+ resource_id: {
+ type: 'string',
+ title: 'Resource Id',
+ },
+ collection: {
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ type: 'object',
+ additionalProperties: true,
+ },
+ ],
+ title: 'Collection',
+ description: 'The collection to move the document to. Set to null to remove the collection.',
+ },
+ metadata: {
+ anyOf: [
+ {
+ type: 'object',
+ additionalProperties: true,
+ },
+ {
+ type: 'object',
+ additionalProperties: true,
+ },
+ ],
+ title: 'Metadata',
+ description:
+ 'Custom metadata for filtering. Keys must be alphanumeric with underscores, max 64 chars. Values must be string, number, or boolean. Will be merged with existing metadata.',
+ },
+ text: {
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ type: 'object',
+ additionalProperties: true,
+ },
+ ],
+ title: 'Text',
+ description: 'Full text of the document. If provided, the document will be re-indexed.',
+ },
+ title: {
+ anyOf: [
+ {
+ type: 'string',
+ },
+ {
+ type: 'object',
+ additionalProperties: true,
+ },
+ ],
+ title: 'Title',
+ description: 'Title of the document.',
+ },
+ jq_filter: {
+ type: 'string',
+ title: 'jq Filter',
+ description:
+ 'A jq filter to apply to the response to include certain fields. Consult the output schema in the tool description to see the fields that are available.\n\nFor example: to include only the `name` field in every object of a results array, you can provide ".results[].name".\n\nFor more information, see the [jq documentation](https://jqlang.org/manual/).',
+ },
+ },
+ required: ['source', 'resource_id'],
+ },
+ annotations: {},
+};
+
+export const handler = async (client: Hyperspell, args: Record | undefined) => {
+ const { resource_id, jq_filter, ...body } = args as any;
+ try {
+ return asTextContentResult(await maybeFilter(jq_filter, await client.memories.update(resource_id, body)));
+ } catch (error) {
+ if (error instanceof Hyperspell.APIError || isJqError(error)) {
+ return asErrorResult(error.message);
+ }
+ throw error;
+ }
+};
+
+export default { metadata, tool, handler };
diff --git a/src/client.ts b/src/client.ts
index 812c4bd..6e4110c 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -40,6 +40,7 @@ import {
MemorySearchParams,
MemoryStatus,
MemoryStatusResponse,
+ MemoryUpdateParams,
MemoryUploadParams,
} from './resources/memories';
import { VaultListParams, VaultListResponse, VaultListResponsesCursorPage, Vaults } from './resources/vaults';
@@ -812,6 +813,7 @@ export declare namespace Hyperspell {
type MemoryDeleteResponse as MemoryDeleteResponse,
type MemoryStatusResponse as MemoryStatusResponse,
type MemoriesCursorPage as MemoriesCursorPage,
+ type MemoryUpdateParams as MemoryUpdateParams,
type MemoryListParams as MemoryListParams,
type MemoryDeleteParams as MemoryDeleteParams,
type MemoryAddParams as MemoryAddParams,
diff --git a/src/resources/index.ts b/src/resources/index.ts
index b220e10..da3c3d7 100644
--- a/src/resources/index.ts
+++ b/src/resources/index.ts
@@ -28,6 +28,7 @@ export {
type MemoryStatus,
type MemoryDeleteResponse,
type MemoryStatusResponse,
+ type MemoryUpdateParams,
type MemoryListParams,
type MemoryDeleteParams,
type MemoryAddParams,
diff --git a/src/resources/memories.ts b/src/resources/memories.ts
index 8802aed..15b979c 100644
--- a/src/resources/memories.ts
+++ b/src/resources/memories.ts
@@ -10,6 +10,26 @@ import { multipartFormRequestOptions } from '../internal/uploads';
import { path } from '../internal/utils/path';
export class Memories extends APIResource {
+ /**
+ * Updates an existing document in the index. You can update the text, collection,
+ * title, and metadata. The document must already exist or a 404 will be returned.
+ * This works for documents from any source (vault, slack, gmail, etc.).
+ *
+ * To remove a collection, set it to null explicitly.
+ *
+ * @example
+ * ```ts
+ * const memoryStatus = await client.memories.update(
+ * 'resource_id',
+ * { source: 'collections' },
+ * );
+ * ```
+ */
+ update(resourceID: string, params: MemoryUpdateParams, options?: RequestOptions): APIPromise {
+ const { source, ...body } = params;
+ return this._client.post(path`/memories/update/${source}/${resourceID}`, { body, ...options });
+ }
+
/**
* This endpoint allows you to paginate through all documents in the index. You can
* filter the documents by title, date, metadata, etc.
@@ -350,12 +370,95 @@ export interface MemoryStatusResponse {
total: { [key: string]: number };
}
+export interface MemoryUpdateParams {
+ /**
+ * Path param:
+ */
+ source:
+ | 'collections'
+ | 'vault'
+ | 'web_crawler'
+ | 'notion'
+ | 'slack'
+ | 'google_calendar'
+ | 'reddit'
+ | 'box'
+ | 'google_drive'
+ | 'airtable'
+ | 'algolia'
+ | 'amplitude'
+ | 'asana'
+ | 'ashby'
+ | 'bamboohr'
+ | 'basecamp'
+ | 'bubbles'
+ | 'calendly'
+ | 'confluence'
+ | 'clickup'
+ | 'datadog'
+ | 'deel'
+ | 'discord'
+ | 'dropbox'
+ | 'exa'
+ | 'facebook'
+ | 'front'
+ | 'github'
+ | 'gitlab'
+ | 'google_docs'
+ | 'google_mail'
+ | 'google_sheet'
+ | 'hubspot'
+ | 'jira'
+ | 'linear'
+ | 'microsoft_teams'
+ | 'mixpanel'
+ | 'monday'
+ | 'outlook'
+ | 'perplexity'
+ | 'rippling'
+ | 'salesforce'
+ | 'segment'
+ | 'todoist'
+ | 'twitter'
+ | 'zoom';
+
+ /**
+ * Body param: The collection to move the document to. Set to null to remove the
+ * collection.
+ */
+ collection?: string | unknown | null;
+
+ /**
+ * Body param: Custom metadata for filtering. Keys must be alphanumeric with
+ * underscores, max 64 chars. Values must be string, number, or boolean. Will be
+ * merged with existing metadata.
+ */
+ metadata?: { [key: string]: string | number | boolean } | unknown | null;
+
+ /**
+ * Body param: Full text of the document. If provided, the document will be
+ * re-indexed.
+ */
+ text?: string | unknown | null;
+
+ /**
+ * Body param: Title of the document.
+ */
+ title?: string | unknown | null;
+}
+
export interface MemoryListParams extends CursorPageParams {
/**
* Filter documents by collection.
*/
collection?: string | null;
+ /**
+ * Filter documents by metadata using MongoDB-style operators. Example:
+ * {"department": "engineering", "priority": {"$gt": 3}}
+ */
+ filter?: string | null;
+
/**
* Filter documents by source.
*/
@@ -1059,6 +1162,7 @@ export declare namespace Memories {
type MemoryDeleteResponse as MemoryDeleteResponse,
type MemoryStatusResponse as MemoryStatusResponse,
type MemoriesCursorPage as MemoriesCursorPage,
+ type MemoryUpdateParams as MemoryUpdateParams,
type MemoryListParams as MemoryListParams,
type MemoryDeleteParams as MemoryDeleteParams,
type MemoryAddParams as MemoryAddParams,
diff --git a/tests/api-resources/memories.test.ts b/tests/api-resources/memories.test.ts
index 2bfd8dd..a700ca3 100644
--- a/tests/api-resources/memories.test.ts
+++ b/tests/api-resources/memories.test.ts
@@ -9,6 +9,27 @@ const client = new Hyperspell({
});
describe('resource memories', () => {
+ test('update: only required params', async () => {
+ const responsePromise = client.memories.update('resource_id', { source: 'collections' });
+ const rawResponse = await responsePromise.asResponse();
+ expect(rawResponse).toBeInstanceOf(Response);
+ const response = await responsePromise;
+ expect(response).not.toBeInstanceOf(Response);
+ const dataAndResponse = await responsePromise.withResponse();
+ expect(dataAndResponse.data).toBe(response);
+ expect(dataAndResponse.response).toBe(rawResponse);
+ });
+
+ test('update: required and optional params', async () => {
+ const response = await client.memories.update('resource_id', {
+ source: 'collections',
+ collection: 'string',
+ metadata: { foo: 'string' },
+ text: 'string',
+ title: 'string',
+ });
+ });
+
test('list', async () => {
const responsePromise = client.memories.list();
const rawResponse = await responsePromise.asResponse();
@@ -24,7 +45,7 @@ describe('resource memories', () => {
// ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error
await expect(
client.memories.list(
- { collection: 'collection', cursor: 'cursor', size: 0, source: 'collections' },
+ { collection: 'collection', cursor: 'cursor', filter: 'filter', size: 0, source: 'collections' },
{ path: '/_stainless_unknown_path' },
),
).rejects.toThrow(Hyperspell.NotFoundError);