From b7b42e9d8d07db0d069930016577d435de149d16 Mon Sep 17 00:00:00 2001 From: jordansilly77-stack Date: Thu, 25 Jun 2026 20:56:45 +0800 Subject: [PATCH] refactor: reuse shared JSON object guard --- packages/agentctx/src/mcp/server.ts | 13 +++++-------- packages/agentctx/src/profile/detect.ts | 21 ++++++++++----------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/agentctx/src/mcp/server.ts b/packages/agentctx/src/mcp/server.ts index a89faac..6579524 100644 --- a/packages/agentctx/src/mcp/server.ts +++ b/packages/agentctx/src/mcp/server.ts @@ -13,6 +13,7 @@ * nothing throws raw into the channel. */ import { createInterface } from "node:readline"; +import { isJsonObject } from "../claude/json-file.js"; import { VERSION } from "../version.js"; import { type ToolContext, type ToolDefinition, callTool } from "./tools.js"; @@ -82,13 +83,13 @@ export function serveMcp(options: McpServerOptions): Promise { /** Returns the response to write, or null for notifications. */ function handleMessage(message: unknown, options: McpServerOptions): JsonRpcResponse | null { - if (!isObject(message) || message.jsonrpc !== "2.0" || typeof message.method !== "string") { + if (!isJsonObject(message) || message.jsonrpc !== "2.0" || typeof message.method !== "string") { return error(idOf(message), INVALID_REQUEST, "expected a JSON-RPC 2.0 request"); } const id = idOf(message); const isNotification = !("id" in message); - const params = isObject(message.params) ? message.params : {}; + const params = isJsonObject(message.params) ? message.params : {}; switch (message.method) { case "initialize": @@ -111,7 +112,7 @@ function handleMessage(message: unknown, options: McpServerOptions): JsonRpcResp if (typeof params.name !== "string") { return error(id, INVALID_PARAMS, "tools/call requires a string 'name'"); } - const args = isObject(params.arguments) ? params.arguments : {}; + const args = isJsonObject(params.arguments) ? params.arguments : {}; const { payload, isError } = callTool(options.tools, options.context, params.name, args); return result(id, { content: [{ type: "text", text: JSON.stringify(payload) }], @@ -143,12 +144,8 @@ function error(id: JsonRpcId, code: number, message: string): JsonRpcResponse { } function idOf(message: unknown): JsonRpcId { - if (isObject(message) && (typeof message.id === "string" || typeof message.id === "number")) { + if (isJsonObject(message) && (typeof message.id === "string" || typeof message.id === "number")) { return message.id; } return null; } - -function isObject(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} diff --git a/packages/agentctx/src/profile/detect.ts b/packages/agentctx/src/profile/detect.ts index 4c53ad4..587f321 100644 --- a/packages/agentctx/src/profile/detect.ts +++ b/packages/agentctx/src/profile/detect.ts @@ -10,6 +10,7 @@ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; import type { Database } from "better-sqlite3"; +import { isJsonObject } from "../claude/json-file.js"; import { insertRecord, listRecords } from "../storage/records.js"; import { BODY_MAX_CHARS } from "../storage/types.js"; @@ -152,7 +153,7 @@ function detectStack(dir: string, pkg: PackageJson | null): string[] { const lines: string[] = []; if (pkg !== null) { - const engines = isObject(pkg.engines) ? pkg.engines : {}; + const engines = isJsonObject(pkg.engines) ? pkg.engines : {}; const nodeRange = typeof engines.node === "string" ? ` (node ${engines.node})` : ""; lines.push(`Runtime: Node.js${nodeRange}`); @@ -192,7 +193,7 @@ function detectStack(dir: string, pkg: PackageJson | null): string[] { } function detectCommands(dir: string, pkg: PackageJson): string[] { - const scripts = isObject(pkg.scripts) ? pkg.scripts : {}; + const scripts = isJsonObject(pkg.scripts) ? pkg.scripts : {}; const names = Object.keys(scripts).filter((name) => typeof scripts[name] === "string"); if (names.length === 0) { return []; @@ -212,7 +213,7 @@ function detectEntryPoints(pkg: PackageJson): string[] { if (typeof pkg.bin === "string") { lines.push(`bin: ${pkg.bin}`); - } else if (isObject(pkg.bin)) { + } else if (isJsonObject(pkg.bin)) { for (const [name, target] of Object.entries(pkg.bin)) { lines.push(`bin ${name}: ${String(target)}`); } @@ -224,7 +225,9 @@ function detectEntryPoints(pkg: PackageJson): string[] { lines.push(`module: ${pkg.module}`); } if (pkg.exports !== undefined) { - const keys = isObject(pkg.exports) ? Object.keys(pkg.exports).join(", ") : String(pkg.exports); + const keys = isJsonObject(pkg.exports) + ? Object.keys(pkg.exports).join(", ") + : String(pkg.exports); lines.push(`exports: ${keys}`); } @@ -262,7 +265,7 @@ function scriptRunner(packageManager: string | null): string { function collectDependencies(pkg: PackageJson): Set { const deps = new Set(); for (const group of [pkg.dependencies, pkg.devDependencies]) { - if (isObject(group)) { + if (isJsonObject(group)) { for (const name of Object.keys(group)) { deps.add(name); } @@ -275,7 +278,7 @@ function workspacePackages(workspaces: unknown): string[] { if (Array.isArray(workspaces)) { return workspaces.filter((workspace): workspace is string => typeof workspace === "string"); } - if (isObject(workspaces) && Array.isArray(workspaces.packages)) { + if (isJsonObject(workspaces) && Array.isArray(workspaces.packages)) { return workspaces.packages.filter( (workspace): workspace is string => typeof workspace === "string", ); @@ -286,16 +289,12 @@ function workspacePackages(workspaces: unknown): string[] { function readPackageJson(dir: string): PackageJson | null { try { const parsed: unknown = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")); - return isObject(parsed) ? (parsed as PackageJson) : null; + return isJsonObject(parsed) ? (parsed as PackageJson) : null; } catch { return null; } } -function isObject(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - function clip(body: string): string { return body.length <= BODY_MAX_CHARS ? body : `${body.slice(0, BODY_MAX_CHARS - 1)}…`; }