fix(mcp): one namespace call where one is needed; upload prep falls back to the active space

listNamespaces fetches a single page instead of up to 20; set_active_tag checks access with namespaces.get (404 means no access) so namespaces past the first page work. prepare-file-upload resolves the namespace like every other tool, so an old cached widget that sends no namespace still uploads.
This commit is contained in:
Mahesh Sanikommu 2026-10-08 10:32:49 -07:00
parent 360fc3ef91
commit 722b1e8053
4 changed files with 51 additions and 28 deletions

View file

@ -22,7 +22,6 @@ export const DEFAULT_NAMESPACE = "sm_project_default"
const FETCH_TIMEOUT_MS = 30_000
const MCP_SOURCE = "supermemory-mcp"
const NAMESPACE_PAGE_LIMIT = 100
const MAX_NAMESPACE_PAGES = 20
// v4 defaults; v5 lowered the threshold to 0.3.
const SEARCH_THRESHOLD = 0.6
// Analytics matches on this text to tag quota errors, keep them in sync.
@ -154,6 +153,18 @@ function extractApiErrorMessage(raw: unknown): string | undefined {
}
}
export interface NamespaceSettings {
namespace: string
supportingContext: string | null
createdAt: string
updatedAt: string
}
const isNotFound = (error: unknown): boolean => {
const e = error as { statusCode?: number; status?: number }
return e?.statusCode === 404 || e?.status === 404
}
export class SupermemoryClient {
private client: Supermemory
private namespace: string
@ -273,30 +284,35 @@ export class SupermemoryClient {
}
}
async listNamespaces(): Promise<NamespaceInfo[]> {
// v5 get returns settings only (no counts); null means missing or no access
async getNamespace(namespace: string): Promise<NamespaceSettings | null> {
try {
const namespaces: NamespaceInfo[] = []
for (let page = 1; page <= MAX_NAMESPACE_PAGES; page++) {
const result = await this.client.namespaces.list({
page,
limit: NAMESPACE_PAGE_LIMIT,
})
for (const entry of result.namespaces) {
namespaces.push({
id: entry.id,
namespace: entry.namespace,
description: entry.description,
documentCount: entry.documentCount,
memoryCount: entry.memoryCount,
createdAt: entry.system.createdAt,
updatedAt: entry.system.updatedAt,
})
}
if (result.pagination.currentPage >= result.pagination.totalPages) {
break
}
const entry = await this.client.namespaces.get(namespace)
return {
namespace: entry.namespace,
supportingContext: entry.supportingContext,
createdAt: entry.system.createdAt,
updatedAt: entry.system.updatedAt,
}
return namespaces
} catch (error) {
if (isNotFound(error)) return null
this.handleError(error)
}
}
// One page is enough for pickers; single-namespace reads use getNamespace
async listNamespaces(limit = NAMESPACE_PAGE_LIMIT): Promise<NamespaceInfo[]> {
try {
const result = await this.client.namespaces.list({ page: 1, limit })
return result.namespaces.map((entry) => ({
id: entry.id,
namespace: entry.namespace,
description: entry.description,
documentCount: entry.documentCount,
memoryCount: entry.memoryCount,
createdAt: entry.system.createdAt,
updatedAt: entry.system.updatedAt,
}))
} catch (error) {
this.handleError(error)
}

View file

@ -26,6 +26,16 @@ function fakeDeps() {
status: "queued",
namespace: "legacy_ns",
}),
getNamespace: vi.fn().mockImplementation(async (ns: string) =>
ns === "legacy_ns"
? {
namespace: ns,
supportingContext: null,
createdAt: "2026-01-01",
updatedAt: "2026-01-02",
}
: null,
),
listNamespaces: vi.fn().mockResolvedValue([
{
id: "1",

View file

@ -21,9 +21,7 @@ export function register(deps: ToolDeps) {
},
async (args) => {
try {
const namespace = namespaceArg(args)
if (!namespace)
return deps.errorResult(new Error("namespace is required"))
const namespace = await deps.resolveNamespace(namespaceArg(args))
const preparation = await deps.createUploadSession(namespace)
return {

View file

@ -26,8 +26,7 @@ export function register(deps: ToolDeps) {
return deps.errorResult(new Error("namespace is required"))
try {
const viewId = args.viewId ?? crypto.randomUUID()
const namespaces = await deps.getClient().listNamespaces()
if (!namespaces.some((entry) => entry.namespace === namespace)) {
if (!(await deps.getClient().getNamespace(namespace))) {
return deps.errorResult(
new Error(`No access to namespace '${namespace}'.`),
)