import type { RequestCredential } from "../token-scope"; import type { Viewer } from "~/lib/types"; /** Stateless revision: no sessions, `server/discover`, `subscriptions/listen`, multi round-trip requests. */ export const MODERN_VERSION = "2026-07-28"; /** Session-era revisions: `initialize`, `Mcp-Session-Id`, GET stream with `Last-Event-ID` resumption. */ export const LEGACY_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26"] as const; export const SUPPORTED_VERSIONS: string[] = [MODERN_VERSION, ...LEGACY_VERSIONS]; export const SERVER_INFO = { name: "gitfor", title: "gitfor forge", version: "1.0.0" }; export const META = { protocolVersion: "io.modelcontextprotocol/protocolVersion", clientInfo: "io.modelcontextprotocol/clientInfo", clientCapabilities: "io.modelcontextprotocol/clientCapabilities", serverInfo: "io.modelcontextprotocol/serverInfo", subscriptionId: "io.modelcontextprotocol/subscriptionId", } as const; export const RPC = { parse: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602, internal: -32603, headerMismatch: -32020, missingCapability: -32021, unsupportedVersion: -32022, } as const; export const SERVER_INSTRUCTIONS = [ "gitfor is a git forge. Every tool call is limited by the repositories and scopes you were granted.", "Before editing, claim the files you will touch with claim_files so concurrent agents see your intent;", "release them when done. Work on a branch from create_workspace, commit with commit_files, then open_pull.", "Subscribe to repository events with subscribe_events and poll_events (or resources/subscribe on", "gitfor-events://{owner}/{repo}) to react to pushes, reviews and CI.", ].join(" "); export type JsonRpcId = string | number; export type JsonObject = Record; /** JSON crosses Durable Object RPC as text: recursive JSON types are too deep for RPC stub typing. */ export function toJsonText(value: unknown): string { return JSON.stringify(value ?? {}); } export type JsonRpcMessage = { jsonrpc: "2.0"; id?: JsonRpcId | null; method?: string; params?: JsonObject; result?: unknown; error?: { code: number; message: string; data?: unknown }; }; export class RpcError extends Error { constructor( readonly code: number, message: string, readonly data?: unknown, readonly httpStatus = 200, ) { super(message); } } export type Era = "modern" | "legacy"; /** * `name` is trustworthy: it comes from a registered agent, an OAuth client the user consented to, or * the token that delegated it. Plain PATs are `unregistered`; whatever they call themselves is only * `claimedName`, which is never used for attribution without an "unverified" label. */ export type AgentIdentity = { name: string; kind: "registered" | "oauth" | "delegated" | "unregistered"; claimedName: string | null; agentId: string | null; clientId: string | null; authorizedBy: string; }; /** Everything a tool needs about who is calling; permissions always come from `credential`. */ export type McpCaller = { credential: RequestCredential; viewer: Viewer; tokenId: string | null; agent: AgentIdentity; era: Era; sessionId: string | null; clientCapabilities: JsonObject; origin: string; }; export function isObject(value: unknown): value is JsonObject { return typeof value === "object" && value !== null && !Array.isArray(value); } export function rpcResult(id: JsonRpcId, result: unknown): JsonRpcMessage { return { jsonrpc: "2.0", id, result }; } export function rpcError(id: JsonRpcId | null, error: RpcError): JsonRpcMessage { return { jsonrpc: "2.0", id, error: { code: error.code, message: error.message, ...(error.data === undefined ? {} : { data: error.data }) }, }; } export function notification(method: string, params: JsonObject): JsonRpcMessage { return { jsonrpc: "2.0", method, params }; }