From a9a33f3f3c62dd93177e92f384f7612380a502c6 Mon Sep 17 00:00:00 2001 From: Chojan Shang Date: Sun, 26 Oct 2025 01:24:26 +0800 Subject: [PATCH 1/3] feat: follow protocol 0.6.2 Signed-off-by: Chojan Shang --- schema/VERSION | 2 +- schema/schema.json | 49 +++++++++++++++++++++++++++++++++++++++++++- src/acp/meta.py | 2 +- src/acp/schema.py | 51 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 99 insertions(+), 5 deletions(-) diff --git a/schema/VERSION b/schema/VERSION index 866919d..0e55109 100644 --- a/schema/VERSION +++ b/schema/VERSION @@ -1 +1 @@ -refs/tags/v0.5.0 +refs/tags/v0.6.2 diff --git a/schema/schema.json b/schema/schema.json index 9c5e26a..e94b521 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -1071,6 +1071,31 @@ ], "type": "object" }, + "Implementation": { + "description": "Describes the name and version of an MCP implementation, with an optional\ntitle for UI representation.", + "properties": { + "name": { + "description": "Intended for programmatic or logical use, but can be used as a display\nname fallback if title isn\u2019t present.", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts \u2014 optimized to be human-readable\nand easily understood.\n\nIf not provided, the name should be used for display.", + "type": [ + "string", + "null" + ] + }, + "version": { + "description": "Version of the implementation. Can be displayed to the user or used\nfor debugging or metrics purposes.", + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, "InitializeRequest": { "description": "Request parameters for the initialize method.\n\nSent by the client to establish connection and negotiate capabilities.\n\nSee protocol docs: [Initialization](https://agentclientprotocol.com/protocol/initialization)", "properties": { @@ -1088,6 +1113,17 @@ }, "description": "Capabilities supported by the client." }, + "clientInfo": { + "anyOf": [ + { + "$ref": "#/$defs/Implementation" + }, + { + "type": "null" + } + ], + "description": "Information about the Client name and version sent to the Agent.\n\nNote: in future versions of the protocol, this will be required." + }, "protocolVersion": { "$ref": "#/$defs/ProtocolVersion", "description": "The latest protocol version supported by the client." @@ -1122,6 +1158,17 @@ }, "description": "Capabilities supported by the agent." }, + "agentInfo": { + "anyOf": [ + { + "$ref": "#/$defs/Implementation" + }, + { + "type": "null" + } + ], + "description": "Information about the Agent name and version sent to the Client.\n\nNote: in future versions of the protocol, this will be required." + }, "authMethods": { "default": [], "description": "Authentication methods supported by the agent.", @@ -2304,7 +2351,7 @@ "SetSessionModeResponse": { "description": "Response to `session/set_mode` method.", "properties": { - "meta": true + "_meta": true }, "type": "object", "x-method": "session/set_mode", diff --git a/src/acp/meta.py b/src/acp/meta.py index f9b189f..dad9daf 100644 --- a/src/acp/meta.py +++ b/src/acp/meta.py @@ -1,5 +1,5 @@ # Generated from schema/meta.json. Do not edit by hand. -# Schema ref: refs/tags/v0.5.0 +# Schema ref: refs/tags/v0.6.2 AGENT_METHODS = {'authenticate': 'authenticate', 'initialize': 'initialize', 'session_cancel': 'session/cancel', 'session_load': 'session/load', 'session_new': 'session/new', 'session_prompt': 'session/prompt', 'session_set_mode': 'session/set_mode', 'session_set_model': 'session/set_model'} CLIENT_METHODS = {'fs_read_text_file': 'fs/read_text_file', 'fs_write_text_file': 'fs/write_text_file', 'session_request_permission': 'session/request_permission', 'session_update': 'session/update', 'terminal_create': 'terminal/create', 'terminal_kill': 'terminal/kill', 'terminal_output': 'terminal/output', 'terminal_release': 'terminal/release', 'terminal_wait_for_exit': 'terminal/wait_for_exit'} PROTOCOL_VERSION = 1 diff --git a/src/acp/schema.py b/src/acp/schema.py index 9050d15..b65a63e 100644 --- a/src/acp/schema.py +++ b/src/acp/schema.py @@ -1,5 +1,5 @@ # Generated from schema/schema.json. Do not edit by hand. -# Schema ref: refs/tags/v0.5.0 +# Schema ref: refs/tags/v0.6.2 from __future__ import annotations @@ -151,6 +151,35 @@ class HttpHeader(BaseModel): value: Annotated[str, Field(description="The value to set for the HTTP header.")] +class Implementation(BaseModel): + # Intended for programmatic or logical use, but can be used as a display + # name fallback if title isn’t present. + name: Annotated[ + str, + Field( + description="Intended for programmatic or logical use, but can be used as a display\nname fallback if title isn’t present." + ), + ] + # Intended for UI and end-user contexts — optimized to be human-readable + # and easily understood. + # + # If not provided, the name should be used for display. + title: Annotated[ + Optional[str], + Field( + description="Intended for UI and end-user contexts — optimized to be human-readable\nand easily understood.\n\nIf not provided, the name should be used for display." + ), + ] = None + # Version of the implementation. Can be displayed to the user or used + # for debugging or metrics purposes. + version: Annotated[ + str, + Field( + description="Version of the implementation. Can be displayed to the user or used\nfor debugging or metrics purposes." + ), + ] + + class KillTerminalCommandResponse(BaseModel): # Extension point for implementations field_meta: Annotated[ @@ -349,7 +378,7 @@ class SetSessionModeRequest(BaseModel): class SetSessionModeResponse(BaseModel): - meta: Optional[Any] = None + field_meta: Annotated[Optional[Any], Field(alias="_meta")] = None class SetSessionModelRequest(BaseModel): @@ -767,6 +796,15 @@ class InitializeRequest(BaseModel): Optional[ClientCapabilities], Field(description="Capabilities supported by the client."), ] = ClientCapabilities(fs=FileSystemCapability(readTextFile=False, writeTextFile=False), terminal=False) + # Information about the Client name and version sent to the Agent. + # + # Note: in future versions of the protocol, this will be required. + clientInfo: Annotated[ + Optional[Implementation], + Field( + description="Information about the Client name and version sent to the Agent.\n\nNote: in future versions of the protocol, this will be required." + ), + ] = None # The latest protocol version supported by the client. protocolVersion: Annotated[ int, @@ -793,6 +831,15 @@ class InitializeResponse(BaseModel): mcpCapabilities=McpCapabilities(http=False, sse=False), promptCapabilities=PromptCapabilities(audio=False, embeddedContext=False, image=False), ) + # Information about the Agent name and version sent to the Client. + # + # Note: in future versions of the protocol, this will be required. + agentInfo: Annotated[ + Optional[Implementation], + Field( + description="Information about the Agent name and version sent to the Client.\n\nNote: in future versions of the protocol, this will be required." + ), + ] = None # Authentication methods supported by the agent. authMethods: Annotated[ Optional[List[AuthMethod]], From f1286e60b0c0f0ea0e30baa5e90ae5aa24fd19f8 Mon Sep 17 00:00:00 2001 From: Chojan Shang Date: Sun, 26 Oct 2025 02:09:26 +0800 Subject: [PATCH 2/3] feat: update examples Signed-off-by: Chojan Shang --- examples/agent.py | 47 +++++++++++++++++++--------------------------- examples/client.py | 44 +++++++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/examples/agent.py b/examples/agent.py index 1356c37..a75e1a5 100644 --- a/examples/agent.py +++ b/examples/agent.py @@ -24,69 +24,60 @@ update_agent_message, PROTOCOL_VERSION, ) -from acp.schema import AgentCapabilities, McpCapabilities, PromptCapabilities +from acp.schema import AgentCapabilities, AgentMessageChunk, Implementation class ExampleAgent(Agent): def __init__(self, conn: AgentSideConnection) -> None: self._conn = conn self._next_session_id = 0 + self._sessions: set[str] = set() - async def _send_chunk(self, session_id: str, content: Any) -> None: - await self._conn.sessionUpdate( - session_notification( - session_id, - update_agent_message(content), - ) - ) + async def _send_agent_message(self, session_id: str, content: Any) -> None: + update = content if isinstance(content, AgentMessageChunk) else update_agent_message(content) + await self._conn.sessionUpdate(session_notification(session_id, update)) async def initialize(self, params: InitializeRequest) -> InitializeResponse: # noqa: ARG002 logging.info("Received initialize request") - mcp_caps: McpCapabilities = McpCapabilities(http=False, sse=False) - prompt_caps: PromptCapabilities = PromptCapabilities(audio=False, embeddedContext=False, image=False) - agent_caps: AgentCapabilities = AgentCapabilities( - loadSession=False, - mcpCapabilities=mcp_caps, - promptCapabilities=prompt_caps, - ) return InitializeResponse( protocolVersion=PROTOCOL_VERSION, - agentCapabilities=agent_caps, + agentCapabilities=AgentCapabilities(), + agentInfo=Implementation(name="example-agent", title="Example Agent", version="0.1.0"), ) async def authenticate(self, params: AuthenticateRequest) -> AuthenticateResponse | None: # noqa: ARG002 - logging.info("Received authenticate request") + logging.info("Received authenticate request %s", params.methodId) return AuthenticateResponse() async def newSession(self, params: NewSessionRequest) -> NewSessionResponse: # noqa: ARG002 logging.info("Received new session request") session_id = str(self._next_session_id) self._next_session_id += 1 - return NewSessionResponse(sessionId=session_id) + self._sessions.add(session_id) + return NewSessionResponse(sessionId=session_id, modes=None) async def loadSession(self, params: LoadSessionRequest) -> LoadSessionResponse | None: # noqa: ARG002 - logging.info("Received load session request") + logging.info("Received load session request %s", params.sessionId) + self._sessions.add(params.sessionId) return LoadSessionResponse() async def setSessionMode(self, params: SetSessionModeRequest) -> SetSessionModeResponse | None: # noqa: ARG002 - logging.info("Received set session mode request") + logging.info("Received set session mode request %s -> %s", params.sessionId, params.modeId) return SetSessionModeResponse() async def prompt(self, params: PromptRequest) -> PromptResponse: - logging.info("Received prompt request") + logging.info("Received prompt request for session %s", params.sessionId) + if params.sessionId not in self._sessions: + self._sessions.add(params.sessionId) - # Notify the client what it just sent and then echo each content block back. - await self._send_chunk( - params.sessionId, - text_block("Client sent:"), - ) + await self._send_agent_message(params.sessionId, text_block("Client sent:")) for block in params.prompt: - await self._send_chunk(params.sessionId, block) + await self._send_agent_message(params.sessionId, block) return PromptResponse(stopReason="end_turn") async def cancel(self, params: CancelNotification) -> None: # noqa: ARG002 - logging.info("Received cancel notification") + logging.info("Received cancel notification for session %s", params.sessionId) async def extMethod(self, method: str, params: dict) -> dict: # noqa: ARG002 logging.info("Received extension method call: %s", method) diff --git a/examples/client.py b/examples/client.py index bdb2ae9..8c62462 100644 --- a/examples/client.py +++ b/examples/client.py @@ -17,6 +17,16 @@ text_block, PROTOCOL_VERSION, ) +from acp.schema import ( + AgentMessageChunk, + AudioContentBlock, + ClientCapabilities, + EmbeddedResourceContentBlock, + ImageContentBlock, + Implementation, + ResourceContentBlock, + TextContentBlock, +) class ExampleClient(Client): @@ -46,20 +56,24 @@ async def killTerminal(self, params): # type: ignore[override] async def sessionUpdate(self, params: SessionNotification) -> None: update = params.update - if isinstance(update, dict): - kind = update.get("sessionUpdate") - content = update.get("content") - else: - kind = getattr(update, "sessionUpdate", None) - content = getattr(update, "content", None) - - if kind != "agent_message_chunk" or content is None: + if not isinstance(update, AgentMessageChunk): return - if isinstance(content, dict): - text = content.get("text", "") + content = update.content + text: str + if isinstance(content, TextContentBlock): + text = content.text + elif isinstance(content, ImageContentBlock): + text = "" + elif isinstance(content, AudioContentBlock): + text = "