mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
* fix(cli): stateful MCP HTTP transport for display_image MCP SDK 1.29+ rejects stateless StreamableHTTP reuse across separate POSTs (initialize, notifications/initialized, tools/call), so display_image 500'd on the second request. Generate per-session IDs instead. Add hapi-display-image.mjs to call the live session CLI's MCP via hostPid so generated-image bytes stay in the owning process. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): multi-session MCP transport + hapiMcpUrl metadata Route streamable HTTP by mcp-session-id so agent bridge and hapi-display-image can each initialize without "already initialized". Publish metadata.hapiMcpUrl at MCP start; helper uses that instead of guessing loopback ports (hook server collision). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(scripts): preserve namespaced CLI_API_TOKEN in display-image helper Do not append :default; namespace is already encoded in the stored token. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(scripts): read settings only when CLI_API_TOKEN unset Env-only auth must not require ~/.hapi/settings.json to exist. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
77 lines
2.6 KiB
JavaScript
77 lines
2.6 KiB
JavaScript
#!/usr/bin/env bun
|
|
/**
|
|
* Post a local image inline to a HAPI session via the session CLI's display_image MCP tool.
|
|
*
|
|
* Uses session.metadata.hapiMcpUrl (published at MCP server start) so we hit the MCP
|
|
* endpoint, not the session hook server on another loopback port in the same process.
|
|
*
|
|
* Usage:
|
|
* bun scripts/tooling/hapi-display-image.mjs <session-id-prefix> <image-path> [title]
|
|
*/
|
|
|
|
import { readFileSync, lstatSync } from 'node:fs'
|
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
|
|
const HAPI_HOST = process.env.HAPI_HOST ?? 'http://localhost:3006'
|
|
const SETTINGS = process.env.HAPI_SETTINGS ?? `${process.env.HOME}/.hapi/settings.json`
|
|
|
|
const sessionArg = process.argv[2]
|
|
const imagePath = process.argv[3]
|
|
const title = process.argv[4]
|
|
|
|
if (!sessionArg || !imagePath) {
|
|
console.error('usage: hapi-display-image.mjs <session-id-prefix> <image-path> [title]')
|
|
process.exit(2)
|
|
}
|
|
|
|
if (!lstatSync(imagePath).isFile()) {
|
|
console.error(`not a file: ${imagePath}`)
|
|
process.exit(2)
|
|
}
|
|
|
|
const token = process.env.CLI_API_TOKEN ?? JSON.parse(readFileSync(SETTINGS, 'utf8')).cliApiToken
|
|
if (!token) {
|
|
console.error('missing CLI_API_TOKEN env and no cliApiToken in settings')
|
|
process.exit(2)
|
|
}
|
|
const authRes = await fetch(`${HAPI_HOST}/api/auth`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ accessToken: token }),
|
|
})
|
|
if (!authRes.ok) {
|
|
console.error('auth failed', authRes.status)
|
|
process.exit(3)
|
|
}
|
|
const { token: jwt } = await authRes.json()
|
|
|
|
const sessionsRes = await fetch(`${HAPI_HOST}/api/sessions?limit=500`, {
|
|
headers: { Authorization: `Bearer ${jwt}` },
|
|
})
|
|
const sessionsBody = await sessionsRes.json()
|
|
const sessions = sessionsBody.sessions ?? sessionsBody
|
|
const session = sessions.find((s) => s.id.startsWith(sessionArg))
|
|
if (!session) {
|
|
console.error(`no session for prefix ${sessionArg}`)
|
|
process.exit(4)
|
|
}
|
|
|
|
const mcpUrl = session.metadata?.hapiMcpUrl
|
|
if (!mcpUrl) {
|
|
console.error('session has no hapiMcpUrl metadata (restart session CLI after MCP fix lands)')
|
|
process.exit(5)
|
|
}
|
|
|
|
console.error(`hapi-display-image: session=${session.id} mcp=${mcpUrl}`)
|
|
|
|
const client = new Client({ name: 'hapi-display-image', version: '1.0.0' }, { capabilities: {} })
|
|
const transport = new StreamableHTTPClientTransport(new URL(mcpUrl))
|
|
await client.connect(transport)
|
|
const result = await client.callTool({
|
|
name: 'display_image',
|
|
arguments: { path: imagePath, title: title ?? undefined },
|
|
})
|
|
await client.close()
|
|
console.log(JSON.stringify(result, null, 2))
|