- MCP Server ToolsUpdated
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type GenerationUpdated
- MCP Client InputUpdated
- MCP Server Tools: AdvancedNew
- MCP EventsNew
- MCP AppsUpdated
- Serve Tools over HTTPUpdated
- Serve Resources and PromptsUpdated
- Ask for InputUpdated
- Draft Text from a ToolUpdated
- MCP Server TasksUpdated
- MCP Server AuthUpdated
- MCP Server SessionsUpdated
- MCP Server on stdioUpdated
Persistence (Advanced)
- GenerationsUpdated
- Realtime Voice ChatUpdated
- Text-to-SpeechUpdated
- TranscriptionUpdated
- Transcription: AdvancedNew
- Voice CreationUpdated
- Audio RecordingUpdated
- Audio GenerationUpdated
- Image GenerationUpdated
- Image Generation: AdvancedNew
- Video GenerationUpdated
- Video Generation: AdvancedNew
- Live GenerationUpdated
- World GenerationUpdated
- Generation HooksUpdated
- Build your first harnessUpdated
- Host optionsNew
- Send and show mediaUpdated
- Connect clientsUpdated
- Write a client in any languageNew
- Access and tenantsNew
- Run in the terminalUpdated
- Deploy a harnessUpdated
- Send inputs safelyUpdated
- Run a tool without a model
- Durable sessionsUpdated
- Run background work on workersNew
- List, rename, and fork sessionsUpdated
- Lifecycle and retentionNew
- Run side effects onceUpdated
- Wait for a personNew
- Build on the session logUpdated
- Control how a turn endsUpdated
- Store settings per threadUpdated
- Compact a long sessionUpdated
- Fork and reset a threadUpdated
- One log for a session treeUpdated
- Write a pluginUpdated
- Track usage and costUpdated
- Ask before risky tool callsUpdated
- Give your agent coding toolsUpdated
- Sandbox and extend the coding toolsUpdated
- Run the coding tools for many threadsNew
- Undo a turnUpdated
- Build a coding agentUpdated
- Delegate to coding agentsUpdated
- Switch between agent profilesUpdated
- Auth and connectorsUpdated
- Share a thread between peopleUpdated
- Connect model providersUpdated
- Build your own UIUpdated
- Code mode in a harnessUpdated
- Work until a goal is metUpdated
- Run agents from a harnessUpdated
- Hand off a conversationNew
- Add skillsUpdated
- Use MCP serversUpdated
- Give your agent a computerNew
- Use from any MCP clientUpdated
- Use a harness from an editor (ACP)Updated
- OverviewUpdated
- Quick StartUpdated
- ProvidersUpdated
- HarnessesUpdated
- Harness AuthUpdated
- WorkspaceUpdated
- ToolsUpdated
- PolicyUpdated
- Lifecycle & SnapshotsUpdated
- Portable SnapshotsUpdated
- Keep Files After ReloadUpdated
- Save a Named VersionUpdated
- Branch From a VersionUpdated
- Send a Frozen FileUpdated
- Let the Agent Save and ForkUpdated
- Pick Which Files to KeepUpdated
- What a Snapshot StoresUpdated
- Instance DurabilityUpdated
- Durable RunsUpdated
- EventsUpdated
Sandboxes (Advanced)
- Runtime ContextUpdated
- Bring Your Own KeyUpdated
- Keep Keys on the ServerUpdated
- Debug LoggingUpdated
- Multimodal ContentUpdated
- Files APIUpdated
- Per-Model Type SafetyUpdated
- Runtime Adapter SwitchingUpdated
- Tree-ShakingUpdated
- Extend AdapterUpdated
- Extend Adapter: AdvancedNew
- Typed Pre-Configured OptionsUpdated
- Test with a Fake ModelUpdated
- Prompt CachingUpdated
- Mid-Conversation ChangesUpdated
- Approval Flow ProcessingUpdated
Use a harness from an editor (ACP)
Your users want your agent in their editor. Editors such as Zed start an agent as a process and talk the Agent Client Protocol (ACP) over stdin and stdout. serveAcp makes your harness an ACP v2 agent.
1. Install the package#
npm i @tanstack/ai-acppnpm add @tanstack/ai-acpyarn add @tanstack/ai-acpbun add @tanstack/ai-acp2. Serve the harness#
Make a script that the editor starts:
import { serveAcp } from '@tanstack/ai-acp/agent'
import { createHarnessHost, defineHarness } from '@tanstack/ai-harness'
import { openaiText } from '@tanstack/ai-openai'
import { memoryPersistence } from '@tanstack/ai-persistence'
export const assistant = defineHarness({
name: 'acme/assistant',
adapter: openaiText('gpt-6.1-sol'),
})
const host = createHarnessHost({ persistence: memoryPersistence() })
serveAcp({ host, harness: assistant, version: '1.0.0' })- Each ACP session is one harness session. The ACP session id is the thread id.
- user is the user of the editor, for example serveAcp({ host, harness: assistant, user: { id: 'ada' } }). Every session opens as that user, so /connect saves keys for them. On a host with access, user is required.
- The prompts of a session run one after another, in the order that the editor sent them. A prompt with a large file keeps its place while the file uploads. The harness busy setting does not apply.
- The agent holds a session only while a prompt runs or waits. An idle session closes after the host lifecycle.closeIdleSessionsAfterMs, and the next prompt opens it again.
- version is the version that initialize reports. Default: '0.0.0'.
- cwd is the working directory that the session list shows for each session. Default: process.cwd().
- Use a persistent store instead of memoryPersistence() if a session must stay after the process stops.
3. Add the agent to the editor#
Give the editor the command that runs the script, for example node agent.js. The editor then sends prompts, and the harness streams the answer back.
What the editor gets#
- The text and the reasoning of the main agent. Text from child agents stays out of the main message.
- Each tool call, of the main agent and of child agents, as a tool call update.
- Each wait of a turn, by its kind (see Waits in the editor).
- Cancel stops the running turn, also while the editor asks for an answer. A turn that fails ends with the error stop reason and the message.
- Image blocks, audio blocks, and embedded files of a prompt go to the media store of the session. A file that the harness refuses fails the prompt with an invalid params error. See Send and show media.
Waits in the editor#
The agent sends each wait to the editor by its kind:
| Kind | What the editor shows |
|---|---|
| approval | A permission request with the id of the real tool call. Allow always shows only for an approval of permissions(), because its answer schema takes always. |
| question, human | A form elicitation of the answer schema. |
| sign-in | A URL elicitation with the sign-in page. |
| client-tool | Nothing. The editor cannot run it, so the agent cancels it. |
- The editor must declare form and URL elicitation in initialize. If it does not, the agent cancels that wait, and the model continues without the answer.
- A question that a tool asks while it runs (step.ask, an MCP server) is a form elicitation too. An answer that does not fit the schema asks again.
- If the editor cancels the prompt while it asks, the turn stops. Its waits stay open until the next prompt.
Open an old session#
The editor lists the sessions of the harness, newest first. The user picks one, and the next prompt continues that thread.
- The list shows the top-level sessions of the harness that user can list. Sessions of child agents stay out.
- The list comes from the session index of the host. Use a persistent store, so that the list keeps the sessions after the process stops.
- A session id that the session index does not know, or that user cannot read, fails with a not found error.
Known limits#
- ACP v2 is still a draft, so this API is experimental.
- A client tool call, and a wait that the editor did not declare it can show, are cancelled. The model then continues without the answer.
- Each session in the list shows the same working directory: the cwd of the agent. If the editor asks for the sessions of another directory, the list is empty.
- An old session opens without its earlier messages. The editor shows only the new turns. The model still gets the full thread.
- A resource link in a prompt is only named. The harness does not read it. The model can open it with its own tools.
Use your own stream#
serveAcp uses stdin and stdout when you pass no stream. To use another transport, pass an ACP Stream, or call createAcpAgent({ host, harness }) and connect the app yourself:
import { createAcpAgent } from '@tanstack/ai-acp/agent'
export const app = createAcpAgent({ host, harness: assistant })One agent app serves one connection. It reads the editor capabilities from that connection, and a second connection to it fails with an error. To serve more editors, for example over HTTP, make one app for each connection. With the AcpServer of @agentclientprotocol/sdk/experimental/server, pass createAgent: () => createAcpAgent({ host, harness: assistant }).
What you have now#
- Your harness as an agent in any ACP v2 editor.
- Approvals, questions, and sign-ins in the editor, and cancel, media, and old sessions.