Harden MCP schema and wire Claude OAuth/token handling

This commit is contained in:
2026-02-23 14:48:01 -05:00
parent ef2a25b5fb
commit 62e2491cde
14 changed files with 770 additions and 56 deletions

View File

@@ -1,7 +1,12 @@
import "dotenv/config";
import { query, type Options } from "@anthropic-ai/claude-agent-sdk";
import { query, type Options, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
import { pathToFileURL } from "node:url";
import { getConfig } from "../config.js";
import {
buildClaudeAuthEnv,
getConfig,
resolveAnthropicToken,
type AppConfig,
} from "../config.js";
import { createSessionContext } from "./session-context.js";
function requiredPrompt(argv: string[]): string {
@@ -22,52 +27,96 @@ function buildOptions(config = getConfig()): Options {
};
}
export async function runClaudePrompt(prompt: string): Promise<void> {
const config = getConfig();
const sessionContext = await createSessionContext("claude", {
type ClaudeAuthOptionOverrides = {
apiKey?: string;
authToken?: string;
};
function buildClaudeAuthOptionOverrides(
config: Readonly<AppConfig>,
): ClaudeAuthOptionOverrides {
if (config.provider.anthropicOauthToken) {
return { authToken: config.provider.anthropicOauthToken };
}
const token = resolveAnthropicToken(config.provider);
return token ? { apiKey: token } : {};
}
export async function readClaudeResult(
session: AsyncIterable<SDKMessage>,
): Promise<string> {
let result = "";
for await (const message of session) {
if (message.type === "result" && message.subtype === "success") {
result = message.result.trim();
}
if (message.type === "result" && message.subtype !== "success") {
const detail = message.errors.join("; ");
throw new Error(
`Claude query failed (${message.subtype})${detail ? `: ${detail}` : ""}`,
);
}
}
if (!result) {
throw new Error("Claude run completed without a final result.");
}
return result;
}
type RunClaudePromptDependencies = {
config?: Readonly<AppConfig>;
createSessionContextFn?: typeof createSessionContext;
queryFn?: typeof query;
writeOutput?: (output: string) => void;
};
export async function runClaudePrompt(
prompt: string,
dependencies: RunClaudePromptDependencies = {},
): Promise<void> {
const config = dependencies.config ?? getConfig();
const createSessionContextFn = dependencies.createSessionContextFn ?? createSessionContext;
const queryFn = dependencies.queryFn ?? query;
const writeOutput = dependencies.writeOutput ?? ((output: string) => console.log(output));
const sessionContext = await createSessionContextFn("claude", {
prompt,
config,
});
try {
const runtimeEnv = {
...sessionContext.runtimeInjection.env,
...buildClaudeAuthEnv(config.provider),
};
const authOptionOverrides = buildClaudeAuthOptionOverrides(config);
const finalResponse = await sessionContext.runInSession(async () => {
const session = query({
const session = queryFn({
prompt: sessionContext.promptWithContext,
options: {
...buildOptions(config),
...(sessionContext.mcp.claudeMcpServers ? { mcpServers: sessionContext.mcp.claudeMcpServers } : {}),
...authOptionOverrides,
...(sessionContext.mcp.claudeMcpServers
? { mcpServers: sessionContext.mcp.claudeMcpServers as Options["mcpServers"] }
: {}),
cwd: sessionContext.runtimeInjection.workingDirectory,
env: sessionContext.runtimeInjection.env,
env: runtimeEnv,
},
});
let result = "";
try {
for await (const message of session) {
if (message.type === "result" && message.subtype === "success") {
result = message.result.trim();
}
if (message.type === "result" && message.subtype !== "success") {
const detail = message.errors.join("; ");
throw new Error(
`Claude query failed (${message.subtype})${detail ? `: ${detail}` : ""}`,
);
}
}
return await readClaudeResult(session);
} finally {
session.close();
}
if (!result) {
throw new Error("Claude run completed without a final result.");
}
return result;
});
console.log(finalResponse);
writeOutput(finalResponse);
} finally {
await sessionContext.close();
}