mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
Every first-party Python caller and test now names the mount the API actually answers on. Mechanical: URL literals only, and only where the /api/ was ours -- a preceding word character means it was a module path like insights/api/ or a third-party host like slack.com/api/, and those are untouched. Left saying /api/, each for a reason rather than for safety: - products/warehouse_sources/.../sources/** is a catalogue of vendor endpoints - services/llm-gateway deploys separately and is under a code freeze, so moving its two calls would break it during the skew - the mcp_store and Slack/Teams redirect_uris are registered with third parties and must byte-match what was registered - the Wizard and Raycast client-metadata paths are OAuth client_ids - /api/surveys and /api/feature_flag/local_evaluation are SDK aliases the ingress routes to the Rust fleets, held by released SDK versions Test URLconfs moved with the requests that call them, and the Datastore user-selection fixtures now carry the leading slash that request.path has.
92 lines
4.1 KiB
TypeScript
92 lines
4.1 KiB
TypeScript
import { readFileSync } from 'node:fs'
|
|
import { dirname, resolve } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import { strToU8, zipSync } from 'fflate'
|
|
import { http, HttpResponse, type JsonBodyType, type RequestHandler, type StrictRequest } from 'msw'
|
|
|
|
const FIXTURES_DIR = dirname(fileURLToPath(import.meta.url))
|
|
|
|
const fixture = (name: string): JsonBodyType =>
|
|
JSON.parse(readFileSync(resolve(FIXTURES_DIR, name), 'utf-8')) as JsonBodyType
|
|
|
|
// Real Insights API responses captured from a freshly-seeded local dev server
|
|
// (`./bin/start`) and sanitized — emails, distinct ids, project tokens, and
|
|
// org/team uuids replaced with stable test values.
|
|
const usersMe = fixture('api_users__me.json')
|
|
const personalApiKeyCurrent = fixture('api_personal_api_keys__current.json')
|
|
const project = fixture('api_projects_id.json')
|
|
const organization = fixture('api_organizations_id.json')
|
|
const groupTypes = fixture('api_projects_id_groups_types.json')
|
|
|
|
// `MCP.detectRegion` calls users/@me on both us.hanzo.ai and eu.hanzo.ai
|
|
// in parallel. Match any host with a leading `*` wildcard.
|
|
export const handlers: RequestHandler[] = [
|
|
// OAuth introspection. MCP only falls back here when /personal_api_keys/@current
|
|
// returns 401/403 — local dev rejects introspect for personal keys, so we
|
|
// synthesize a healthy session-token response instead of capturing one.
|
|
http.post('*/oauth/introspect', () =>
|
|
HttpResponse.json({
|
|
active: true,
|
|
scope: '',
|
|
scoped_teams: [],
|
|
scoped_organizations: [],
|
|
})
|
|
),
|
|
http.get('*/v1/users/@me', () => HttpResponse.json(usersMe)),
|
|
http.get('*/v1/users/@me/', () => HttpResponse.json(usersMe)),
|
|
http.get('*/v1/personal_api_keys/@current', () => HttpResponse.json(personalApiKeyCurrent)),
|
|
http.get('*/v1/personal_api_keys/@current/', () => HttpResponse.json(personalApiKeyCurrent)),
|
|
http.get('*/v1/organizations/:orgId', () => HttpResponse.json(organization)),
|
|
http.get('*/v1/organizations/:orgId/', () => HttpResponse.json(organization)),
|
|
http.get('*/v1/projects/:projectId/groups_types', () => HttpResponse.json(groupTypes)),
|
|
http.get('*/v1/projects/:projectId/groups_types/', () => HttpResponse.json(groupTypes)),
|
|
http.get('*/v1/projects/:projectId', () => HttpResponse.json(project)),
|
|
http.get('*/v1/projects/:projectId/', () => HttpResponse.json(project)),
|
|
]
|
|
|
|
const contextMillManifest = {
|
|
version: '1.0.0',
|
|
resources: [
|
|
{
|
|
id: 'test-guide',
|
|
name: 'Insights Getting Started',
|
|
uri: 'insights://guide/getting-started',
|
|
resource: {
|
|
mimeType: 'text/plain',
|
|
description: 'A guide to getting started with Insights',
|
|
text: 'Welcome to Insights. This is a test resource.',
|
|
},
|
|
},
|
|
],
|
|
}
|
|
|
|
const contextMillZip = zipSync({
|
|
'manifest.json': strToU8(JSON.stringify(contextMillManifest)),
|
|
})
|
|
|
|
export const contextMillHandler = http.get(
|
|
'https://github.com/Insights/context-mill/releases/latest/download/skills-mcp-resources.zip',
|
|
() => new HttpResponse(contextMillZip, { headers: { 'Content-Type': 'application/zip' } })
|
|
)
|
|
|
|
/**
|
|
* Dispatch a workerd outbound request to the matching MSW handler. Mirrors
|
|
* MSW's internal handler-resolution loop without relying on global fetch
|
|
* interception (which doesn't work inside the workerd runtime). The first
|
|
* handler whose `run()` returns a response wins; everything else 404s.
|
|
*/
|
|
export async function dispatchHandlers(request: Request): Promise<Response> {
|
|
// workerd's Request is structurally compatible with MSW's StrictRequest;
|
|
// the cast just appeases the cf-properties shape mismatch.
|
|
const msReq = request.clone() as unknown as StrictRequest<JsonBodyType>
|
|
for (const handler of handlers) {
|
|
// `run` is the same entrypoint MSW's setupServer uses internally.
|
|
const result = await handler.run({ request: msReq, requestId: crypto.randomUUID() })
|
|
if (result?.response) {
|
|
return result.response
|
|
}
|
|
}
|
|
return new Response(null, { status: 404 })
|
|
}
|