diff --git a/src/mcp-server.ts b/src/mcp-server.ts
index 9c5dd77c..dca2e90a 100644
--- a/src/mcp-server.ts
+++ b/src/mcp-server.ts
@@ -6,6 +6,12 @@ import { z } from "zod";
import { Server } from "node:http";
import { ClaudeAcpAgent } from "./acp-agent.js";
+export const SYSTEM_REMINDER = `
+
+
+Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
+`;
+
export function createMcpServer(
agent: ClaudeAcpAgent,
sessionId: string,
@@ -70,13 +76,7 @@ In sessions with mcp__acp__read always use it instead of Read as it contains the
content: [
{
type: "text",
- text:
- content.content +
- `
-
-
-Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
-`,
+ text: content.content + SYSTEM_REMINDER,
},
],
};
diff --git a/src/tests/acp-agent.test.ts b/src/tests/acp-agent.test.ts
index 2afe1efe..8cd1f7e1 100644
--- a/src/tests/acp-agent.test.ts
+++ b/src/tests/acp-agent.test.ts
@@ -13,7 +13,11 @@ import {
WriteTextFileResponse,
} from "@zed-industries/agent-client-protocol";
import { nodeToWebWritable, nodeToWebReadable } from "../utils.js";
-import { toolInfoFromToolUse, toolUpdateFromToolResult } from "../tools.js";
+import {
+ markdownEscape,
+ toolInfoFromToolUse,
+ toolUpdateFromToolResult,
+} from "../tools.js";
import { toAcpNotifications } from "../acp-agent.js";
import { SDKAssistantMessage } from "@anthropic-ai/claude-code";
@@ -776,3 +780,17 @@ describe("tool conversions", () => {
expect(update).toEqual({});
});
});
+
+describe("escape markdown", () => {
+ it("should escape markdown characters", () => {
+ let text = "Hello *world*!";
+ let escaped = markdownEscape(text);
+ expect(escaped).toEqual("```\nHello *world*!\n```");
+
+ text = "for example:\n```markdown\nHello *world*!\n```\n";
+ escaped = markdownEscape(text);
+ expect(escaped).toEqual(
+ "````\nfor example:\n```markdown\nHello *world*!\n```\n````",
+ );
+ });
+});
diff --git a/src/tools.ts b/src/tools.ts
index f488dd9b..1c85602f 100644
--- a/src/tools.ts
+++ b/src/tools.ts
@@ -6,7 +6,8 @@ import {
ToolCallLocation,
ToolKind,
} from "@zed-industries/agent-client-protocol";
-import { replaceAndCalculateLocation } from "./mcp-server.js";
+import { replaceAndCalculateLocation, SYSTEM_REMINDER } from "./mcp-server.js";
+import { tool } from "@anthropic-ai/claude-code";
interface ToolInfo {
title: string;
@@ -489,6 +490,43 @@ export function toolUpdateFromToolResult(
case "Write":
return {};
+ case "Read":
+ case "mcp__acp__read":
+ if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
+ return {
+ content: toolResult.content.map((content: any) => ({
+ type: "content",
+ content:
+ content.type == "text"
+ ? {
+ type: "text",
+ text: markdownEscape(
+ content.text.replace(SYSTEM_REMINDER, ""),
+ ),
+ }
+ : content,
+ })),
+ };
+ } else if (
+ typeof toolResult.content === "string" &&
+ toolResult.content.length > 0
+ ) {
+ return {
+ content: [
+ {
+ type: "content",
+ content: {
+ type: "text",
+ text: markdownEscape(
+ toolResult.content.replace(SYSTEM_REMINDER, ""),
+ ),
+ },
+ },
+ ],
+ };
+ }
+ return {};
+
case "Task":
case "NotebookEdit":
case "NotebookRead":
@@ -502,12 +540,8 @@ export function toolUpdateFromToolResult(
case "Grep":
case "WebFetch":
case "WebSearch":
- case "Read":
- case "mcp__acp__read":
case "Other":
default: {
- // This happens for the mcp__acp__read tool,
- // but may also for others...
if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
return {
content: toolResult.content.map((content: any) => ({
@@ -523,7 +557,10 @@ export function toolUpdateFromToolResult(
content: [
{
type: "content",
- content: { type: "text", text: toolResult.content },
+ content: {
+ type: "text",
+ text: toolResult.content,
+ },
},
],
};
@@ -546,3 +583,13 @@ export function planEntries(input: { todos: ClaudePlanEntry[] }): PlanEntry[] {
priority: "medium",
}));
}
+
+export function markdownEscape(text: string): string {
+ let escape = "```";
+ for (let [m] of text.matchAll(/^```+/gm)) {
+ while (m.length >= escape.length) {
+ escape += "`";
+ }
+ }
+ return escape + "\n" + text + (text.endsWith("\n") ? "" : "\n") + escape;
+}