Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions apps/mcp/mcp-app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>Memory Graph</title>
</head>
<body>
<div id="graph"></div>

<div id="loading">
<div class="spinner"></div>
<span>Loading memory graph...</span>
</div>

<div id="stats"></div>

<div id="popup">
<span id="popup-type"></span>
<div id="popup-title"></div>
<div id="popup-content"></div>
<div id="popup-meta"></div>
</div>

<div id="controls">
<button id="fit-btn" title="Fit to view">&#x229E;</button>
<button id="zoom-in" title="Zoom in">+</button>
<button id="zoom-out" title="Zoom out">&minus;</button>
</div>

<div id="legend">
<div class="legend-row">
<svg width="12" height="12" viewBox="0 0 12 12"><polygon points="6,1.5 10.4,3.75 10.4,8.25 6,10.5 1.6,8.25 1.6,3.75" fill="#0D2034" stroke="#3B73B8" stroke-width="1.2"/></svg>
<span>Memory</span>
</div>
<div class="legend-row">
<svg width="12" height="12" viewBox="0 0 12 12"><polygon points="6,1.5 10.4,3.75 10.4,8.25 6,10.5 1.6,8.25 1.6,3.75" fill="#0D2034" stroke="#10B981" stroke-width="1.2"/></svg>
<span>Recent (&lt;1d)</span>
</div>
<div class="legend-row">
<svg width="12" height="12" viewBox="0 0 12 12"><polygon points="6,1.5 10.4,3.75 10.4,8.25 6,10.5 1.6,8.25 1.6,3.75" fill="#0D2034" stroke="#F59E0B" stroke-width="1.2"/></svg>
<span>Expiring</span>
</div>
<div class="legend-row">
<svg width="12" height="12" viewBox="0 0 12 12"><polygon points="6,1.5 10.4,3.75 10.4,8.25 6,10.5 1.6,8.25 1.6,3.75" fill="#0D2034" stroke="#EF4444" stroke-width="1.2"/></svg>
<span>Forgotten</span>
</div>
<div class="legend-row">
<div class="legend-doc"></div>
<span>Document</span>
</div>
<div class="legend-row">
<div class="legend-line" style="border-color: #4A5568;"></div>
<span>Doc &rarr; Memory</span>
</div>
<div class="legend-row">
<div class="legend-line" style="border-color: #8B5CF6; border-width: 2px;"></div>
<span>Version</span>
</div>
<div class="legend-row">
<div class="legend-line dashed" style="border-color: #00D4B8;"></div>
<span>Similarity</span>
</div>
</div>

<script type="module" src="/src/ui/mcp-app.ts"></script>
</body>
</html>
10 changes: 8 additions & 2 deletions apps/mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
"version": "4.0.0",
"type": "module",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy --minify",
"build:ui": "vite build",
"dev": "vite build && wrangler dev",
"deploy": "vite build && wrangler deploy --minify",
"cf-typegen": "wrangler types --env-interface CloudflareBindings"
},
"dependencies": {
"@cloudflare/workers-oauth-provider": "^0.2.2",
"@modelcontextprotocol/ext-apps": "^1.0.0",
"@modelcontextprotocol/sdk": "^1.25.2",
"agents": "^0.3.5",
"hono": "^4.11.1",
Expand All @@ -18,7 +20,11 @@
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250620.0",
"d3-force-3d": "^3.0.5",
"force-graph": "^1.49.0",
"typescript": "^5.8.3",
"vite": "^6.0.0",
"vite-plugin-singlefile": "^2.3.0",
"wrangler": "^4.4.0"
}
}
100 changes: 100 additions & 0 deletions apps/mcp/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,54 @@ export interface Project {
documentCount?: number
}

// Graph API types
export interface GraphApiMemory {
id: string
memory: string
isStatic: boolean
isLatest: boolean
isForgotten: boolean
forgetAfter: string | null
version: number
parentMemoryId: string | null
createdAt: string
updatedAt: string
}

export interface GraphApiDocument {
id: string
title: string | null
summary: string | null
documentType: string
createdAt: string
updatedAt: string
x: number
y: number
memories: GraphApiMemory[]
}

export interface GraphApiEdge {
source: string
target: string
similarity: number
}

export interface GraphViewportResponse {
documents: GraphApiDocument[]
edges: GraphApiEdge[]
viewport: { minX: number; maxX: number; minY: number; maxY: number }
totalCount: number
}

export interface GraphBoundsResponse {
bounds: {
minX: number
maxX: number
minY: number
maxY: number
} | null
}

function limitByChars(text: string, maxChars = MAX_CHARS): string {
return text.length > maxChars ? `${text.slice(0, maxChars)}...` : text
}
Expand Down Expand Up @@ -215,6 +263,58 @@ export class SupermemoryClient {
}
}

// Fetch graph bounds for coordinate range
async getGraphBounds(containerTags?: string[]): Promise<GraphBoundsResponse> {
try {
const params = new URLSearchParams()
if (containerTags?.length) {
params.set("containerTags", JSON.stringify(containerTags))
}
const url = `${this.apiUrl}/v3/graph/bounds${params.toString() ? `?${params}` : ""}`
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${this.bearerToken}`,
"Content-Type": "application/json",
},
})
if (!response.ok) {
throw Object.assign(new Error("Failed to fetch graph bounds"), {
status: response.status,
})
}
return (await response.json()) as GraphBoundsResponse
} catch (error) {
this.handleError(error)
}
}

// Fetch graph data for a viewport region
async getGraphViewport(
viewport: { minX: number; maxX: number; minY: number; maxY: number },
containerTags?: string[],
limit = 200,
): Promise<GraphViewportResponse> {
try {
const response = await fetch(`${this.apiUrl}/v3/graph/viewport`, {
method: "POST",
headers: {
Authorization: `Bearer ${this.bearerToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ viewport, containerTags, limit }),
})
if (!response.ok) {
throw Object.assign(new Error("Failed to fetch graph viewport"), {
status: response.status,
})
}
return (await response.json()) as GraphViewportResponse
} catch (error) {
this.handleError(error)
}
}

private handleError(error: unknown): never {
// Handle network/fetch errors
if (error instanceof TypeError) {
Expand Down
4 changes: 4 additions & 0 deletions apps/mcp/src/html.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module "*.html" {
const content: string
export default content
}
Loading
Loading