Skip to content
Draft
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
58 changes: 58 additions & 0 deletions .nanites/run.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"managerKey": "installation:122731586:repo:937499295",
"githubInstallationId": 122731586,
"githubRepositoryId": 937499295,
"repository": {
"id": 937499295,
"name": "demos",
"full_name": "hyparam/demos",
"owner": {
"login": "hyparam"
},
"default_branch": "master",
"private": false,
"permissions": {
"admin": true,
"push": true,
"pull": true
}
},
"runKey": "manual:27646492-52e1-432c-8d70-f8421affeac7",
"nanite": {
"id": "webmcp-maintainer",
"label": "WebMCP maintainer",
"purpose": "Implement a minimal WebMCP slice in the repo's main public-facing web app, prefer a landing or marketing surface when one exists, prefer the CDN script tag over package installs for browser-side WebMCP when possible, verify the real page behavior, and produce a support artifact suitable for a durable support PR.",
"variant": "workspace-browser",
"soul": "webmcp-soul",
"skills": [
"webmcp-maintainer"
],
"toolSurface": [
"workspace",
"git",
"browser",
"publishing"
],
"mcpServers": [
{
"name": "WebMCP Documentation",
"url": "https://docs.mcp-b.ai/mcp"
}
]
},
"naniteId": "webmcp-maintainer",
"variant": "workspace-browser",
"trigger": {
"kind": "manual",
"label": "Manual WebMCP maintainer"
},
"task": "Implement a minimal WebMCP slice in the repo's main public-facing web app, prefer a landing or marketing surface when one exists, prefer the CDN script tag over package installs for browser-side WebMCP when possible, verify the real page behavior, and produce a support artifact suitable for a durable support PR. Dogfood the existing codebase: inspect only enough to find the main public-facing web app, prefer a landing or marketing surface over dashboards when both exist, use the configured docs MCP server for the current WebMCP syntax, prefer the CDN script tag over package installs when adding browser-side WebMCP, ship one narrow tool instead of a catalog, verify the result, and finish with a clean artifact.",
"scope": {
"repositoryFullName": "hyparam/demos",
"branch": "master",
"changedFiles": [],
"detailsUrl": "https://app.sigvelo.com/repos/937499295?run=manual%3A27646492-52e1-432c-8d70-f8421affeac7"
},
"initialGitHubResult": null,
"startedAt": "2026-04-09T20:17:44.595Z"
}
4 changes: 4 additions & 0 deletions .sigvelo/nanites/bootstrap/webmcp-maintainer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Sigvelo support PR bootstrap

This file exists only so the support PR for `webmcp-maintainer` can be created immediately.
The runtime removes it on the first substantive publish when real repo changes are available.
3 changes: 2 additions & 1 deletion hyparquet/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
</main>
<input id="file-input" type="file">

<script type="module" src="/src/main.tsx"></script>
<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
5 changes: 5 additions & 0 deletions hyparquet/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { byteLengthFromUrl, parquetMetadataAsync } from 'hyparquet'
import { AsyncBufferFrom, asyncBufferFrom, parquetDataFrame } from 'hyperparam'
import { useCallback, useEffect, useState } from 'react'
import Dropzone from './Dropzone.js'
import { setCurrentFile, setLoadUrlFn } from './webmcp.js'
import Layout from './Layout.js'

export default function App(): ReactNode {
Expand All @@ -16,6 +17,9 @@ export default function App(): ReactNode {
const [error, setError] = useState<Error>()
const [pageProps, setPageProps] = useState<PageProps>()

// Register the URL loader with WebMCP
setLoadUrlFn(onUrlDrop)

const setUnknownError = useCallback((e: unknown) => {
setError(e instanceof Error ? e : new Error(String(e)))
}, [])
Expand All @@ -25,6 +29,7 @@ export default function App(): ReactNode {
const metadata = await parquetMetadataAsync(asyncBuffer)
const df = sortableDataFrame(parquetDataFrame(from, metadata))
setPageProps({ metadata, df, name, byteLength: from.byteLength, setError: setUnknownError })
setCurrentFile(name, 'url' in from ? from.url : undefined)
}, [setUnknownError])

const onUrlDrop = useCallback(
Expand Down
4 changes: 4 additions & 0 deletions hyparquet/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import 'hyperparam/hyperparam.css'
import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.js'
import { initWebMCP } from './webmcp.js'
import './index.css'

// Initialize WebMCP for AI agent integration
initWebMCP()

const app = document.getElementById('app')
if (!app) throw new Error('missing app element')

Expand Down
135 changes: 135 additions & 0 deletions hyparquet/src/webmcp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// WebMCP Integration for hyparquet demo
// Exposes tools for AI agents to interact with the parquet viewer

declare global {
interface Window {
__hyparquetApp?: {
getCurrentFile: () => { name: string; url?: string } | null
loadUrl: (url: string) => void
getAvailableExamples: () => Array<{ name: string; url: string }>
}
}
}

const exampleFiles = [
{ name: 'wiki-en-00000-of-00041.parquet', url: 'https://hyperparam-public.s3.amazonaws.com/wiki-en-00000-of-00041.parquet' },
{ name: 'starcoderdata-js-00000-of-00065.parquet', url: 'https://hyperparam.blob.core.windows.net/hyperparam/starcoderdata-js-00000-of-00065.parquet' },
{ name: 'github-code-00000-of-01126.parquet', url: 'https://huggingface.co/datasets/codeparrot/github-code/resolve/main/data/train-00000-of-01126.parquet?download=true' },
{ name: 'rowgroups.parquet', url: 'https://raw.githubusercontent.com/hyparam/hyparquet/master/test/files/rowgroups.parquet' },
]

// Store for current app state
let appState: {
currentFile: { name: string; url?: string } | null
loadUrlFn: ((url: string) => void) | null
} = {
currentFile: null,
loadUrlFn: null,
}

export function setCurrentFile(name: string, url?: string) {
appState.currentFile = { name, url }
}

export function setLoadUrlFn(fn: (url: string) => void) {
appState.loadUrlFn = fn
}

export function initWebMCP() {
// Expose app API for tools
window.__hyparquetApp = {
getCurrentFile: () => appState.currentFile,
loadUrl: (url: string) => {
if (appState.loadUrlFn) {
appState.loadUrlFn(url)
} else {
console.warn('[WebMCP] loadUrl function not available')
}
},
getAvailableExamples: () => exampleFiles,
}

// Wait for WebMCP to be available (from CDN script)
const registerTools = () => {
if (!('modelContext' in navigator)) {
console.log('[WebMCP] navigator.modelContext not available, retrying...')
setTimeout(registerTools, 100)
return
}

const mc = navigator.modelContext

// Tool: Get current file info
mc.registerTool({
name: 'hyparquet_get_current_file',
description: 'Get information about the currently loaded parquet file',
inputSchema: {
type: 'object',
properties: {},
},
async execute() {
const file = appState.currentFile
if (!file) {
return {
content: [{ type: 'text', text: 'No file is currently loaded. Use hyparquet_load_url to load a parquet file.' }],
}
}
return {
content: [{ type: 'text', text: JSON.stringify(file, null, 2) }],
}
},
})

// Tool: Load a parquet URL
mc.registerTool({
name: 'hyparquet_load_url',
description: 'Load a parquet file from a URL into the viewer',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'URL of the parquet file to load',
},
},
required: ['url'],
},
async execute({ url }: { url: string }) {
if (!appState.loadUrlFn) {
return {
content: [{ type: 'text', text: 'Error: hyparquet viewer not ready' }],
isError: true,
}
}
appState.loadUrlFn(url)
return {
content: [{ type: 'text', text: `Loading parquet file: ${url}` }],
}
},
})

// Tool: List available example files
mc.registerTool({
name: 'hyparquet_list_examples',
description: 'List available example parquet files that can be loaded',
inputSchema: {
type: 'object',
properties: {},
},
async execute() {
return {
content: [{ type: 'text', text: JSON.stringify(exampleFiles, null, 2) }],
}
},
})

console.log('[WebMCP] Tools registered successfully')
}

// Start registration
registerTools()
}

export function getAppState() {
return appState
}
Loading