mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-27 06:15:15 +00:00
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:
parent
360fc3ef91
commit
722b1e8053
4 changed files with 51 additions and 28 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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}'.`),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue