diff --git a/docs/v2/guides/mcp.mdx b/docs/v2/guides/mcp.mdx index 22daf109..08a3af8b 100644 --- a/docs/v2/guides/mcp.mdx +++ b/docs/v2/guides/mcp.mdx @@ -7,16 +7,16 @@ sidebarTitle: 'MCP Integration' You can let Claude use Honcho to manage its own memory in the native desktop app by using the Honcho MCP integration! Follow these steps: -1. Clone the `honcho-mcp` repo: +1. Clone the `honcho` repo: ``` -git clone git@github.com:plastic-labs/honcho-mcp.git +git clone https://github.com/plastic-labs/honcho.git ``` -2. Navigate into the `honcho-mcp` folder. +2. Navigate into the `mcp` folder. ``` -cd honcho-mcp +cd mcp ``` 3. Sync the virtual environment. This package uses [uv](https://docs.astral.sh/uv/), [install](https://docs.astral.sh/uv/#installation) if you haven't. @@ -25,34 +25,21 @@ cd honcho-mcp uv sync ``` -4. In Claude Desktop, go to the *top left Mac Toolbar* Settings > Developer and click "Edit Config" +4. Get an API key from [honcho.dev](https://app.honcho.dev) -5. Add the following (and update paths!): +5. Run FastMCP to install the Honcho MCP server in Claude Desktop: ``` -{ - "mcpServers": { - "Honcho": { - "command": "/path/to/uv", - "args": [ - "run", - "--with", - "mcp[cli]", - "mcp", - "run", - "/path/to/honcho-mcp/main.py" - ] - } - } -} +uv pip install fastmcp +fastmcp install claude-desktop server.py --env-var HONCHO_API_KEY= ``` -You probably will need to put the full path to the uv executable in the command field. You can get this by running `which uv` on MacOS/Linux or `where uv` on Windows. +You can easily install Honcho MCP in claude-code or cursor by replacing `claude-desktop` in the above command with either of those values + +Make sure Claude Desktop has access to `uv` on your system. You can get the location by running `which uv` on MacOS/Linux or `where uv` on Windows. 6. Restart the Claude Desktop app. Upon relaunch, it should start Honcho and the tools should be available! -Just note that, by default, the MCP server is set up to use the Honcho Demo server, which only persists data for 7 days. If you're using the hosted version of Honcho, copy the `.env.template` to a proper `.env` file and update the URL and API key variables accordingly. - ## Project Instructions Finally, Claude needs instructions on how to use Honcho. The Desktop app doesn't allow you to add system prompts directly, but you can create a project and paste these [instructions](https://github.com/plastic-labs/honcho-mcp/blob/main/instructions.txt) into the "Project Instructions" field. diff --git a/mcp/.gitignore b/mcp/.gitignore new file mode 100644 index 00000000..83036c9e --- /dev/null +++ b/mcp/.gitignore @@ -0,0 +1,27 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +dist/ +.wrangler/ + +# Environment files +.env +.env.local +.env.production + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log \ No newline at end of file diff --git a/mcp/README.md b/mcp/README.md new file mode 100644 index 00000000..1496538e --- /dev/null +++ b/mcp/README.md @@ -0,0 +1,331 @@ +# Honcho MCP Server + +## Quickstart: Use the Hosted MCP Server + +Go to https://app.honcho.dev and get an API key. Then go to Claude Desktop and navigate to custom MCP servers. + +If you don't have node/bun installed you will need to do that. You can also use npm if you already have that installed. If not, Claude Desktop or Claude Code can help! + +Add Honcho to your Claude desktop config. You must provide a username for Honcho to refer to you as -- preferably what you want Claude to actually call you. +```json +{ + "mcpServers": { + "honcho": { + "command": "bunx", + "args": [ + "mcp-remote", + "https://mcp.honcho.dev", + "--header", + "Authorization:${AUTH_HEADER}", + "--header", + "X-Honcho-User-Name:${USER_NAME}" + ], + "env": { + "AUTH_HEADER": "Bearer ", + "USER_NAME": "" + } + } + } +} +``` + +You may customize your assistant name and/or workspace ID. Both are optional. + +```json +{ + "mcpServers": { + "honcho": { + "command": "bunx", + "args": [ + "mcp-remote", + "https://mcp.honcho.dev", + "--header", + "Authorization:${AUTH_HEADER}", + "--header", + "X-Honcho-User-Name:${USER_NAME}", + "--header", + "X-Honcho-Assistant-Name:${ASSISTANT_NAME}", + "--header", + "X-Honcho-Workspace-ID:${WORKSPACE_ID}" + ], + "env": { + "AUTH_HEADER": "Bearer ", + "USER_NAME": "", + "ASSISTANT_NAME": "", + "WORKSPACE_ID": "" + } + } + } +} +``` + +## Available Tools + +### start_conversation +Start a new conversation session with Honcho. This initializes a session for tracking conversation history and context. + +**Returns:** A session ID that you must store and use for all subsequent interactions in this conversation. + +### add_turn +Add a conversation turn (user and assistant messages) to the current session. This stores the conversation in Honcho for context tracking. + +**Parameters:** +- `session_id`: The ID of the session to add the turn to +- `messages`: Array of message objects with `role` ("user" or "assistant") and `content` + +**Example usage:** +```json +{ + "session_id": "session-uuid", + "messages": [ + { + "role": "user", + "content": "Hello, how are you?" + }, + { + "role": "assistant", + "content": "I'm doing well, thank you!" + } + ] +} +``` + +### get_personalization_insights +Get personalization insights from Honcho based on conversation history. This queries the user's conversation context to provide personalized responses. + +**Parameters:** +- `session_id`: The ID of the session for context +- `query`: The question about the user's preferences, habits, etc. + +**Example queries:** +- "What does this message reveal about the user's communication preferences?" +- "How formal or casual should I be with the user based on our history?" +- "What emotional state might the user be in right now?" + +### search_workspace +Search for messages across the entire workspace. + +**Parameters:** +- `query`: The search query to use + +### get_workspace_metadata +Get metadata for the current workspace. + +**Parameters:** None + +### set_workspace_metadata +Set metadata for the current workspace. + +**Parameters:** +- `metadata`: A dictionary of metadata to associate with the workspace + +### create_peer +Create or get a peer with the specified ID and optional configuration. + +**Parameters:** +- `peer_id`: Unique identifier for the peer +- `config`: Optional configuration dictionary for the peer + +### get_peer_metadata +Get metadata for a specific peer. + +**Parameters:** +- `peer_id`: The ID of the peer to get metadata for + +### set_peer_metadata +Set metadata for a specific peer. + +**Parameters:** +- `peer_id`: The ID of the peer to set metadata for +- `metadata`: A dictionary of metadata to associate with the peer + +### search_peer_messages +Search for messages sent by a peer. + +**Parameters:** +- `peer_id`: The ID of the peer to search messages for +- `query`: The search query to use + +### chat +Query a peer's representation with natural language questions. + +**Parameters:** +- `peer_id`: The ID of the peer to query +- `query`: The natural language question to ask +- `target_peer_id`: Optional target peer ID for local representation queries +- `session_id`: Optional session ID to scope the query to a specific session + +### list_peers +Get all peers in the current workspace. + +**Parameters:** None + +### create_session +Create or get a session with the specified ID and optional configuration. + +**Parameters:** +- `session_id`: Unique identifier for the session +- `config`: Optional configuration dictionary for the session + +### get_session_metadata +Get metadata for a specific session. + +**Parameters:** +- `session_id`: The ID of the session to get metadata for + +### set_session_metadata +Set metadata for a specific session. + +**Parameters:** +- `session_id`: The ID of the session to set metadata for +- `metadata`: A dictionary of metadata to associate with the session + +### add_peers_to_session +Add peers to a session. + +**Parameters:** +- `session_id`: The ID of the session to add peers to +- `peer_ids`: List of peer IDs to add to the session + +### remove_peers_from_session +Remove peers from a session. + +**Parameters:** +- `session_id`: The ID of the session to remove peers from +- `peer_ids`: List of peer IDs to remove from the session + +### get_session_peers +Get all peer IDs in a session. + +**Parameters:** +- `session_id`: The ID of the session to get peers from + +### add_messages_to_session +Add messages to a session. + +**Parameters:** +- `session_id`: The ID of the session to add messages to +- `messages`: List of message dictionaries with `peer_id`, `content`, and optional `metadata` + +### get_session_messages +Get messages from a session with optional filtering. + +**Parameters:** +- `session_id`: The ID of the session to get messages from +- `filters`: Optional dictionary of filter criteria + +### get_session_context +Get optimized context for a session within a token limit. + +**Parameters:** +- `session_id`: The ID of the session to get context for +- `summary`: Whether to include summary information (default: true) +- `tokens`: Maximum number of tokens to include in the context + +### search_session_messages +Search for messages in a specific session. + +**Parameters:** +- `session_id`: The ID of the session to search messages in +- `query`: The search query to use + +### get_working_representation +Get the current working representation of a peer in a session. + +**Parameters:** +- `session_id`: The ID of the session +- `peer_id`: The ID of the peer to get the working representation of +- `target_peer_id`: Optional target peer ID to get the representation of what peer_id knows about target_peer_id + +### list_sessions +Get all sessions in the current workspace. + +**Parameters:** None + +## Contributing or Self Hosting + +A Cloudflare Worker that implements the Model Context Protocol (MCP) to provide Honcho functionality as tools for AI assistants like Claude Desktop. + +### Deploy MCP Worker + +1. **Install dependencies:** + ```bash + bun i + ``` + +2. **Login to Cloudflare (if not already done):** + ```bash + bun wrangler login + ``` + +3. **Configure your worker name in `wrangler.toml`:** + - Update the `name` field to your desired worker name + - Update the worker names in the `[env.production]` and `[env.staging]` sections + +4. **Test locally:** + ```bash + bun dev + ``` + +5. **Deploy to production:** + ```bash + bun run deploy + ``` + +### Configuration Options + +You can customize the behavior using HTTP headers: + +**Available Configuration:** +- `apiKey`: Your Honcho API key +- `baseUrl`: Custom Honcho API base URL (default: https://api.honcho.dev) +- `workspaceId`: Workspace ID (default: "default") +- `userName`: User identifier (default: "User") +- `assistantName`: Assistant identifier (default: "Assistant") + +#### Using HTTP Headers: + +Pass configuration to mcp-remote via custom headers: + +```bash +bunx mcp-remote https://YOUR_WORKER_NAME.YOUR_SUBDOMAIN.workers.dev \ + --header "Authorization:Bearer YOUR_HONCHO_API_KEY" \ + --header "X-Honcho-Workspace-ID:my-workspace" \ + --header "X-Honcho-User-Name:john" \ + --header "X-Honcho-Assistant-Name:Claude" \ + --header "X-Honcho-Base-URL:https://custom.honcho.dev" +``` + +**Supported Custom Headers:** +- `Authorization: Bearer YOUR_API_KEY` - Your Honcho API key +- `X-Honcho-Base-URL` - Custom Honcho API base URL +- `X-Honcho-Workspace-ID` - Workspace identifier +- `X-Honcho-User-Name` - User identifier +- `X-Honcho-Assistant-Name` - Assistant identifier + +### Authentication + +The MCP server requires a valid Honcho API key provided via the Authorization header. + +### Testing + +You can test the MCP server using `mcp-remote` with the local URL: + +```bash +bunx mcp-remote http://localhost:8787 --header "Authorization:Bearer your-api-key" +``` + +### Error Handling + +The server provides proper JSON-RPC 2.0 error responses: + +- `-32700`: Parse error +- `-32600`: Invalid Request +- `-32601`: Method not found +- `-32602`: Invalid params +- `-32603`: Internal error + +Common issues: +- **Missing API key**: Ensure you provide a valid Honcho API key via header or URL parameter +- **Invalid tool parameters**: Check that required parameters are provided and properly formatted +- **Network errors**: Verify the worker is deployed and accessible \ No newline at end of file diff --git a/mcp/bun.lock b/mcp/bun.lock new file mode 100644 index 00000000..61644df3 --- /dev/null +++ b/mcp/bun.lock @@ -0,0 +1,304 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "honcho-mcp-proxy", + "dependencies": { + "@honcho-ai/sdk": "^1.2.1", + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20241002.0", + "only-allow": "^1.2.1", + "typescript": "^5.3.3", + "wrangler": "^4.24.3", + }, + }, + }, + "packages": { + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], + + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.4.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.17", "workerd": "^1.20250521.0" }, "optionalPeers": ["workerd"] }, "sha512-70mk5GPv+ozJ5XcIhFpq4ps7HvQYu+As7vwasUy9LcBadsTcWA2iFis/7aFJmQehfKerDwVOHfMYpgTTC+u24Q=="], + + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250712.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-M6S6a/LQ0Jb0R+g0XhlYi1adGifvYmxA5mD/i9TuZZgjs2bIm5ELuka/n3SCnI98ltvlx3HahRaHagAtOilsFg=="], + + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250712.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7sFzn6rvAcnLy7MktFL42dYtzL0Idw/kiUmNf2P3TvsBRoShhLK5ZKhbw+NAhvU8e4pXWm5lkE0XmpieA0zNjw=="], + + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250712.0", "", { "os": "linux", "cpu": "x64" }, "sha512-EFRrGe/bqK7NHtht7vNlbrDpfvH3eRvtJOgsTpEQEysDjVmlK6pVJxSnLy9Hg1zlLY15IfhfGC+K2qisseHGJQ=="], + + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250712.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-rG8JUleddhUHQVwpXOYv0VbL0S9kOtR9PNKecgVhFpxEhC8aTeg2HNBBjo8st7IfcUvY8WaW3pD3qdAMZ05UwQ=="], + + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250712.0", "", { "os": "win32", "cpu": "x64" }, "sha512-qS8H5RCYwE21Om9wo5/F807ClBJIfknhuLBj16eYxvJcj9JqgAKWi12BGgjyGxHuJJjeoQ63lr4wHAdbFntDDg=="], + + "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20250724.0", "", {}, "sha512-TRuz+kMArBpW3lR7xoPR7Ek+/ymvukE5JC7RITlaRyHbs6vWPfwpH9TWQ1dfztYFmSQl5ZiAbLcK8UDRjgiW7g=="], + + "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.4.5", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], + + "@honcho-ai/core": ["@honcho-ai/core@1.2.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-VPHCFIGfC00GeE4P83DDIT7hkuMnMVkWlMTmMd2tw4HSEUciqLBh09AX/6aMKfJAzprg1diub6pJJ6LJP6eJ+g=="], + + "@honcho-ai/sdk": ["@honcho-ai/sdk@1.2.1", "", { "dependencies": { "@honcho-ai/core": "1.2.0", "@types/node": "^24.0.1" } }, "sha512-/RFHq9R9XsD1uj3KPZkZ4aGMuyan6gmIXu3HFND7Aipe3PGFeMEmteFmcMVGi3FO3F5S+NJsfqppV9Z3dNfeyQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + + "@poppinss/colors": ["@poppinss/colors@4.1.5", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw=="], + + "@poppinss/dumper": ["@poppinss/dumper@0.6.4", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ=="], + + "@poppinss/exception": ["@poppinss/exception@1.2.2", "", {}, "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg=="], + + "@sindresorhus/is": ["@sindresorhus/is@7.0.2", "", {}, "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw=="], + + "@speed-highlight/core": ["@speed-highlight/core@1.2.7", "", {}, "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g=="], + + "@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], + + "@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="], + + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + + "acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], + + "acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="], + + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], + + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], + + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + + "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], + + "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], + + "form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="], + + "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], + + "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + + "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], + + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "miniflare": ["miniflare@4.20250712.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^7.10.0", "workerd": "1.20250712.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-cZ8WyQBwqfjYLjd61fDR4/j0nAVbjB3Wxbun/brL9S5FAi4RlTR0LyMTKsIVA0s+nL4Pg9VjVMki4M/Jk2cz+Q=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "only-allow": ["only-allow@1.2.1", "", { "dependencies": { "which-pm-runs": "^1.1.0" }, "bin": { "only-allow": "bin.js" } }, "sha512-M7CJbmv7UCopc0neRKdzfoGWaVZC+xC1925GitKH9EAqYFzX9//25Q7oX4+jw0tiCCj+t5l6VZh8UPH23NZkMA=="], + + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], + + "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], + + "stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="], + + "supports-color": ["supports-color@10.0.0", "", {}, "sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ=="], + + "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "undici": ["undici@7.12.0", "", {}, "sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug=="], + + "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], + + "unenv": ["unenv@2.0.0-rc.17", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-B06u0wXkEd+o5gOCMl/ZHl5cfpYbDZKAT+HWTL+Hws6jWu7dCiqBBXXXzMFcFVJb8D4ytAnYmxJA83uwOQRSsg=="], + + "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], + + "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + + "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], + + "workerd": ["workerd@1.20250712.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250712.0", "@cloudflare/workerd-darwin-arm64": "1.20250712.0", "@cloudflare/workerd-linux-64": "1.20250712.0", "@cloudflare/workerd-linux-arm64": "1.20250712.0", "@cloudflare/workerd-windows-64": "1.20250712.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-7h+k1OxREpiZW0849g0uQNexRWMcs5i5gUGhJzCY8nIx6Tv4D/ndlXJ47lEFj7/LQdp165IL9dM2D5uDiedZrg=="], + + "wrangler": ["wrangler@4.26.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.4.1", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20250712.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.17", "workerd": "1.20250712.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250712.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-EXuwyWlgYQZv6GJlyE0lVGk9hHqASssuECECT1XC5aIijTwNLQhsj/TOZ0hKSFlMbVr1E+OAdevAxd0kaF4ovA=="], + + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + + "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], + + "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], + + "zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], + + "@honcho-ai/core/@types/node": ["@types/node@18.19.120", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-WtCGHFXnVI8WHLxDAt5TbnCM4eSE+nI0QN2NJtwzcgMhht2eNz6V9evJrk+lwC8bCY8OWV5Ym8Jz7ZEyGnKnMA=="], + + "@honcho-ai/core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + } +} diff --git a/mcp/instructions.md b/mcp/instructions.md new file mode 100644 index 00000000..e9432c1d --- /dev/null +++ b/mcp/instructions.md @@ -0,0 +1,156 @@ +# Comprehensive Honcho MCP Integration Instructions + +## What is Honcho? + +Honcho is an infrastructure layer for building AI agents with social cognition and theory of mind capabilities. It enables personalized AI interactions by building coherent models of user psychology over time. The Honcho MCP server simplifies the integration to just 3 essential functions. Here's how to use them: + +### Step 1: Start New Conversation (First Message Only) + +When a user begins a new conversation, always call `start_conversation`: + +```text +start_conversation +``` + +**Returns**: A session ID that you must store and use for all subsequent interactions in this conversation. + +### Step 2: Get Personalized Insights (When Helpful) + +Before responding to any user message, you can query for personalization insights: + +```text +get_personalization_insights +session_id: [SESSION_ID_FROM_STEP_1] +query: [YOUR_THEORY_OF_MIND_QUESTION] +``` + +This query takes a bit of time, so it's best to only perform it when you need personalized insights. If the query can be responded to effectively using what you already know about the user, just go ahead and answer it. However, the insights endpoint is extremely perceptive. It has the capability to reveal aspects of the user's personality, historical use of the application you are operating in, and more. + +**Returns**: Personalized insights about the user based on accumulated knowledge. + +**Example Queries**: + +- "What does this message reveal about the user's communication preferences?" +- "How formal or casual should I be with the user based on our history?" +- "What is the user really asking for beyond her explicit question?" +- "What emotional state might the user be in right now?" +- "How can I best help the user with her current request?" + +### Step 3: Respond to User + +Craft your response using any insights gained from Step 2. + +### Step 4: Store the Conversation Turn (After Each Exchange) + +**CRITICAL**: Always store both the user's message AND your response using `add_turn`: + +```text +add_turn +session_id: [SESSION_ID_FROM_STEP_1] +messages: [ + { + "role": "user", + "content": "[USER'S_EXACT_MESSAGE]" + }, + { + "role": "assistant", + "content": "[YOUR_EXACT_RESPONSE]" + } +] +``` + +## Complete Example Flow + +Here's exactly what to do for a new conversation: + +1. **User says**: "Hi Claude! My name is Sarah and I'm feeling overwhelmed with work" + +2. **Start conversation**: + + ```text + start_conversation + ``` + + → Returns: `session_abc123` + +3. **Get insights** (optional but recommended): + + ```text + get_personalization_insights + session_id: "session_abc123" + query: "What does the user's message about feeling overwhelmed tell me about her current state and how should I respond?" + ``` + + → Returns insights about Sarah's emotional state and preferred communication style + +4. **Respond to Sarah**: "Hi Sarah! I can hear that you're feeling overwhelmed with work right now..." + +5. **Store the turn**: + + ```text + add_turn + session_id: "session_abc123" + messages: [ + { + "role": "user", + "content": "Hi Claude! My name is Sarah and I'm feeling overwhelmed with work" + }, + { + "role": "assistant", + "content": "Hi Sarah! I can hear that you're feeling overwhelmed with work right now..." + } + ] + ``` + +## Continuing an Existing Conversation + +For subsequent messages in the same conversation: + +1. **User says**: "Thanks for listening. Can you help me prioritize my tasks?" + +2. **Respond**: "Based on our conversation, I can see you value..." + +3. **Store the turn**: + + ```text + add_turn + session_id: "session_abc123" + messages: [ + { + "role": "user", + "content": "Thanks for listening. Can you help me prioritize my tasks?" + }, + { + "role": "assistant", + "content": "Based on our conversation, I can see you value..." + } + ] + ``` + +## Best Practices for Personalization Queries + +Ask theory-of-mind questions that reveal: + +**Communication Style**: "How formal/casual should I be?" "What does this reveal about their preferences?" + +**User Needs**: "What are they really asking for?" "What emotional state are they in?" + +**Relationship**: "How can I build rapport?" "What engages them most?" + +**Task Approach**: "How do they prefer problem-solving?" "What detail level do they want?" + +## Error Handling + +- **Authorization Errors and Timeouts**: Make sure user has configured API key and URL for Honcho +- **ValueError**: Messages were incorrectly formatted, make sure to include role and content +- **"No personalization insights found"**: Normal when there's limited history with the user +- **Session management**: The MCP server handles all session persistence automatically + +## Key Principles + +1. **Always start with `start_conversation` for new conversations** +2. **Store every message exchange with `add_turn`** +3. **Use `get_personalization_insights` strategically for better responses** +4. **Ask thoughtful theory-of-mind questions** +5. **Never expose technical details to the user** +6. **The system maintains context automatically between sessions** \ No newline at end of file diff --git a/mcp/package.json b/mcp/package.json new file mode 100644 index 00000000..c77cd31e --- /dev/null +++ b/mcp/package.json @@ -0,0 +1,25 @@ +{ + "name": "honcho-mcp-proxy", + "version": "1.0.0", + "description": "Cloudflare Worker proxy for Honcho MCP Server", + "main": "worker.ts", + "packageManager": "bun@1.2.0", + "engines": { + "node": ">=18.0.0", + "bun": ">=1.2.0" + }, + "scripts": { + "preinstall": "node -e \"if(process.env.npm_config_user_agent?.includes('npm')){console.error('āŒ Please use bun instead of npm!\\nšŸ“¦ Run: bun install\\n🌐 Install bun: https://bun.sh/');process.exit(1)}\"", + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "deploy:staging": "wrangler deploy --env staging" + }, + "dependencies": { + "@honcho-ai/sdk": "^1.2.1" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20241002.0", + "typescript": "^5.3.3", + "wrangler": "^4.24.3" + } +} diff --git a/mcp/tsconfig.json b/mcp/tsconfig.json new file mode 100644 index 00000000..a364d373 --- /dev/null +++ b/mcp/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ES2022", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true, + "skipLibCheck": true, + "types": ["@cloudflare/workers-types"] + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/mcp/worker.ts b/mcp/worker.ts new file mode 100644 index 00000000..a12dc9b7 --- /dev/null +++ b/mcp/worker.ts @@ -0,0 +1,1344 @@ +import { Honcho } from '@honcho-ai/sdk'; + +interface HonchoConfig { + apiKey: string; + userName: string; + baseUrl?: string; + workspaceId?: string; + assistantName?: string; +} + +interface Message { + role: 'user' | 'assistant'; + content: string; + metadata?: Record; +} + +/** + * JSON-RPC 2.0 request interface + */ +interface JsonRpcRequest { + jsonrpc: '2.0'; + method: string; + params?: any; + id?: string | number; +} + +/** + * JSON-RPC 2.0 response interface + */ +interface JsonRpcResponse { + jsonrpc: '2.0'; + id?: string | number | null; + result?: any; + error?: { + code: number; + message: string; + data?: any; + }; +} + +// MCP Tool definitions +interface Tool { + name: string; + description: string; + inputSchema: { + type: 'object'; + properties: Record; + required?: string[]; + }; +} + +/** + * Helper function to validate required arguments and create error responses + */ +function validateArguments(args: Record, required: string[], requestId: string | number | null): Response | null { + for (const param of required) { + if (!args[param]) { + return createErrorResponse(requestId, -32602, `${param} is required`); + } + } + + // Special validation for arrays + if (args.messages && !Array.isArray(args.messages)) { + return createErrorResponse(requestId, -32602, 'messages must be an array'); + } + if (args.peer_ids && !Array.isArray(args.peer_ids)) { + return createErrorResponse(requestId, -32602, 'peer_ids must be an array'); + } + + return null; +} + +/** + * Helper function to create error responses + */ +function createErrorResponse(id: string | number | null, code: number, message: string): Response { + return new Response(JSON.stringify(createJsonRpcResponse(id, undefined, createJsonRpcError(code, message))), { + status: code === -32602 ? 400 : (code === -32601 ? 404 : 500), + headers: { 'Content-Type': 'application/json' }, + }); +} + +/** + * Helper function to format messages for async iteration + */ +async function formatMessages(messagesPage: any): Promise { + const messages = []; + for await (const message of messagesPage) { + messages.push({ + id: message.id, + content: message.content, + peer_id: message.peer_id, + session_id: message.session_id, + metadata: message.metadata, + created_at: message.created_at, + }); + } + return messages; +} + +class HonchoWorker { + private honcho: Honcho; + private config: HonchoConfig; + + constructor(config: HonchoConfig) { + this.config = { + baseUrl: 'https://api.honcho.dev', + workspaceId: 'default', + assistantName: 'Assistant', + ...config, + }; + + this.honcho = new Honcho({ + apiKey: this.config.apiKey, + baseURL: this.config.baseUrl, + workspaceId: this.config.workspaceId, + }); + } + + //////////////////////////////////////////////////////////////////////////////// + /// /// + /// "Bespoke" tools: easy to use for user-assistant conversation paradigms /// + /// /// + //////////////////////////////////////////////////////////////////////////////// + + /** + * Start a new conversation with a user. Call this when a user starts a new conversation. + * @returns A session ID for the conversation + */ + async startConversation(): Promise { + // Get/create the assistant peer with observe_me=false + const assistant = this.honcho.peer(this.config.assistantName!, { config: { observe_me: false } }); + + // Create a new session + const sessionId = crypto.randomUUID(); + const session = this.honcho.session(sessionId); + + // Add the user and assistant peers to the session + // @ts-expect-error - API accepts null for observe_me despite type definition + await session.addPeers([this.config.userName, [assistant, { observe_me: null, observe_others: false }]]); + + return sessionId; + } + + /** + * Add a turn to a conversation. Call this after a user has sent a message and the assistant has responded. + * @param sessionId - The ID of the session to add the turn to + * @param messages - A list of messages to add to the session + */ + async addTurn(sessionId: string, messages: Message[]): Promise { + const session = this.honcho.session(sessionId); + const userPeer = this.honcho.peer(this.config.userName); + const assistantPeer = this.honcho.peer(this.config.assistantName!); + + const sessionMessages = []; + + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + + // Validate required fields + if (!message || typeof message !== 'object') { + throw new Error(`Message at index ${i} must be a dictionary`); + } + + if (!message.role) { + throw new Error(`Message at index ${i} is missing required field 'role'`); + } + + if (!message.content) { + throw new Error(`Message at index ${i} is missing required field 'content'`); + } + + const { role, content, metadata } = message; + + // Create message with appropriate peer + if (role === 'user') { + if (metadata) { + sessionMessages.push(userPeer.message(content, { metadata })); + } else { + sessionMessages.push(userPeer.message(content)); + } + } else if (role === 'assistant') { + if (metadata) { + sessionMessages.push(assistantPeer.message(content, { metadata })); + } else { + sessionMessages.push(assistantPeer.message(content)); + } + } else { + throw new Error(`Invalid role '${role}' at message index ${i}. Role must be one of: 'user' or 'assistant'`); + } + } + + await session.addMessages(sessionMessages); + } + + /** + * Get personalization insights about the user, based on the query and the accumulated knowledge of the user across all conversations. + * @param sessionId - The ID of the session for context + * @param query - The question about the user's preferences, habits, etc. + * @returns A string with the personalization insights + */ + async getPersonalizationInsights(sessionId: string, query: string): Promise { + const userPeer = this.honcho.peer(this.config.userName); + + // Get the personalization insights + const personalizationInsights = await userPeer.chat(query, { sessionId }); + + if (!personalizationInsights) { + return "No personalization insights found."; + } + + return personalizationInsights; + } + + //////////////////////////////////////////////////////////////////////////////// + /// /// + /// General tools for using Honcho /// + /// /// + //////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////// + /// /// + /// Workspace operations /// + /// /// + ////////////////////////////////////////////////////// + + /** + * Search for messages across the entire workspace. + * @param query - The search query to use + * @returns A list of message dictionaries matching the search query + */ + async searchWorkspace(query: string): Promise { + const messagesPage = await this.honcho.search(query); + return await formatMessages(messagesPage); + } + + /** + * Get metadata for the current workspace. + * @returns A dictionary containing the workspace's metadata + */ + async getWorkspaceMetadata(): Promise> { + return await this.honcho.getMetadata(); + } + + /** + * Set metadata for the current workspace. + * @param metadata - A dictionary of metadata to associate with the workspace + */ + async setWorkspaceMetadata(metadata: Record): Promise { + await this.honcho.setMetadata(metadata); + } + + ////////////////////////////////////////////////////// + /// /// + /// Peer operations /// + /// /// + ////////////////////////////////////////////////////// + + /** + * Create or get a peer with the specified ID and optional configuration. + * @param peerId - Unique identifier for the peer + * @param config - Optional configuration dictionary for the peer + * @returns A dictionary with the peer ID and confirmation of creation + */ + async createPeer(peerId: string, config?: Record): Promise<{ peer_id: string; config?: Record }> { + const peer = this.honcho.peer(peerId, { config }); + return { + peer_id: peer.id, + config, + }; + } + + /** + * Get metadata for a specific peer. + * @param peerId - The ID of the peer to get metadata for + * @returns A dictionary containing the peer's metadata + */ + async getPeerMetadata(peerId: string): Promise> { + const peer = this.honcho.peer(peerId); + return await peer.getMetadata(); + } + + /** + * Set metadata for a specific peer. + * @param peerId - The ID of the peer to set metadata for + * @param metadata - A dictionary of metadata to associate with the peer + */ + async setPeerMetadata(peerId: string, metadata: Record): Promise { + const peer = this.honcho.peer(peerId); + await peer.setMetadata(metadata); + } + + /** + * Search for messages sent by a peer. + * @param peerId - The ID of the peer to search messages for + * @param query - The search query to use + * @returns A list of message dictionaries matching the search query + */ + async searchPeerMessages(peerId: string, query: string): Promise { + const peer = this.honcho.peer(peerId); + const messagesPage = await peer.search(query); + return await formatMessages(messagesPage); + } + + /** + * Query a peer's representation with natural language questions. + * @param peerId - The ID of the peer to query + * @param query - The natural language question to ask + * @param targetPeerId - Optional target peer ID for local representation queries + * @param sessionId - Optional session ID to scope the query to a specific session + * @returns Response string containing the answer to the query, or "None" if no relevant information + */ + async chat(peerId: string, query: string, targetPeerId?: string, sessionId?: string): Promise { + const peer = this.honcho.peer(peerId); + let targetPeer; + if (targetPeerId) { + targetPeer = this.honcho.peer(targetPeerId); + } + + const result = await peer.chat(query, { target: targetPeer, sessionId }); + return result || "None"; + } + + /** + * Get all peers in the current workspace. + * @returns A list of peer dictionaries with their IDs + */ + async listPeers(): Promise<{ id: string }[]> { + const peersPage = await this.honcho.getPeers(); + const peers = []; + + for await (const peer of peersPage) { + peers.push({ + id: peer.id, + }); + } + + return peers; + } + + ////////////////////////////////////////////////////// + /// /// + /// Session operations /// + /// /// + ////////////////////////////////////////////////////// + + /** + * Create or get a session with the specified ID and optional configuration. + * @param sessionId - Unique identifier for the session + * @param config - Optional configuration dictionary for the session + * @returns A dictionary with the session ID and confirmation of creation + */ + async createSession(sessionId: string, config?: Record): Promise<{ session_id: string; config?: Record }> { + const session = this.honcho.session(sessionId, { config }); + return { + session_id: session.id, + config, + }; + } + + /** + * Get metadata for a specific session. + * @param sessionId - The ID of the session to get metadata for + * @returns A dictionary containing the session's metadata + */ + async getSessionMetadata(sessionId: string): Promise> { + const session = this.honcho.session(sessionId); + return await session.getMetadata(); + } + + /** + * Set metadata for a specific session. + * @param sessionId - The ID of the session to set metadata for + * @param metadata - A dictionary of metadata to associate with the session + */ + async setSessionMetadata(sessionId: string, metadata: Record): Promise { + const session = this.honcho.session(sessionId); + await session.setMetadata(metadata); + } + + /** + * Add peers to a session. + * @param sessionId - The ID of the session to add peers to + * @param peerIds - List of peer IDs to add to the session + */ + async addPeersToSession(sessionId: string, peerIds: string[]): Promise { + const session = this.honcho.session(sessionId); + await session.addPeers(peerIds); + } + + /** + * Remove peers from a session. + * @param sessionId - The ID of the session to remove peers from + * @param peerIds - List of peer IDs to remove from the session + */ + async removePeersFromSession(sessionId: string, peerIds: string[]): Promise { + const session = this.honcho.session(sessionId); + await session.removePeers(peerIds); + } + + /** + * Get all peer IDs in a session. + * @param sessionId - The ID of the session to get peers from + * @returns A list of peer IDs that are members of the session + */ + async getSessionPeers(sessionId: string): Promise { + const session = this.honcho.session(sessionId); + const peers = await session.getPeers(); + return peers.map(peer => peer.id); + } + + /** + * Add messages to a session. + * @param sessionId - The ID of the session to add messages to + * @param messages - List of message dictionaries + */ + async addMessagesToSession(sessionId: string, messages: { peer_id: string; content: string; metadata?: Record }[]): Promise { + const session = this.honcho.session(sessionId); + + const sessionMessages = []; + for (const message of messages) { + const peer = this.honcho.peer(message.peer_id); + if (message.metadata) { + sessionMessages.push(peer.message(message.content, { metadata: message.metadata })); + } else { + sessionMessages.push(peer.message(message.content)); + } + } + + await session.addMessages(sessionMessages); + } + + /** + * Get messages from a session with optional filtering. + * @param sessionId - The ID of the session to get messages from + * @param filters - Optional dictionary of filter criteria + * @returns A list of message dictionaries + */ + async getSessionMessages(sessionId: string, filters?: Record): Promise { + const session = this.honcho.session(sessionId); + const messagesPage = await session.getMessages({ filter: filters }); + return await formatMessages(messagesPage); + } + + /** + * Get optimized context for a session within a token limit. + * @param sessionId - The ID of the session to get context for + * @param summary - Whether to include summary information + * @param tokens - Maximum number of tokens to include in the context + * @returns A dictionary containing the session context with messages and optional summary + */ + async getSessionContext(sessionId: string, summary: boolean = true, tokens?: number): Promise { + const session = this.honcho.session(sessionId); + const context = await session.getContext({ summary, tokens }); + + return { + session_id: context.sessionId, + summary: context.summary, + messages: context.messages.map(msg => ({ + id: msg.id, + content: msg.content, + peer_id: msg.peer_id, + metadata: msg.metadata, + created_at: msg.created_at, + })), + }; + } + + /** + * Search for messages in a specific session. + * @param sessionId - The ID of the session to search messages in + * @param query - The search query to use + * @returns A list of message dictionaries matching the search query + */ + async searchSessionMessages(sessionId: string, query: string): Promise { + const session = this.honcho.session(sessionId); + const messagesPage = await session.search(query); + return await formatMessages(messagesPage); + } + + /** + * Get the current working representation of a peer in a session. + * @param sessionId - The ID of the session + * @param peerId - The ID of the peer to get the working representation of + * @param targetPeerId - Optional target peer ID to get the representation of what peer_id knows about target_peer_id + * @returns A dictionary containing information about the peer + */ + async getWorkingRepresentation(sessionId: string, peerId: string, targetPeerId?: string): Promise> { + const session = this.honcho.session(sessionId); + if (targetPeerId) { + return await session.workingRep(peerId, targetPeerId); + } else { + return await session.workingRep(peerId); + } + } + + /** + * Get all sessions in the current workspace. + * @returns A list of session dictionaries with their IDs + */ + async listSessions(): Promise<{ id: string }[]> { + const sessionsPage = await this.honcho.getSessions(); + const sessions = []; + + for await (const session of sessionsPage) { + sessions.push({ + id: session.id, + }); + } + + return sessions; + } +} + +/** + * Parse configuration from request headers + * @param request - The incoming request + * @returns Configuration object or null if invalid + */ +function parseConfig(request: Request): HonchoConfig | null { + // Get API key from Authorization header + const authHeader = request.headers.get('Authorization'); + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return null; + } + const apiKey = authHeader.substring(7); + + if (!apiKey) { + return null; + } + + const userName = request.headers.get('X-Honcho-User-Name'); + if (!userName) { + return null; + } + + // Get configuration from headers with proper defaults + const config: HonchoConfig = { + apiKey, + userName, + baseUrl: request.headers.get('X-Honcho-Base-URL') || 'https://api.honcho.dev', + workspaceId: request.headers.get('X-Honcho-Workspace-ID') || 'default', + assistantName: request.headers.get('X-Honcho-Assistant-Name') || 'Assistant', + }; + + return config; +} + +/** + * Create a JSON-RPC 2.0 response + * @param id - Request ID + * @param result - Response result + * @param error - Error object if any + * @returns JSON-RPC response object + */ +function createJsonRpcResponse(id: string | number | null, result?: any, error?: { code: number; message: string; data?: any }): JsonRpcResponse { + const response: JsonRpcResponse = { + jsonrpc: '2.0', + id, + }; + + if (error) { + response.error = error; + } else { + response.result = result; + } + + return response; +} + +/** + * Create a JSON-RPC 2.0 error object + * @param code - Error code + * @param message - Error message + * @param data - Optional error data + * @returns Error object + */ +function createJsonRpcError(code: number, message: string, data?: any): { code: number; message: string; data?: any } { + return { code, message, data }; +} + +// Define all MCP tools based on the Python server.py functions +const tools: Tool[] = [ + // Bespoke tools + { + name: 'start_conversation', + description: 'Start a new conversation with a user. Call this when a user starts a new conversation.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, + }, + { + name: 'add_turn', + description: 'Add a turn to a conversation. Call this after a user has sent a message and the assistant has responded.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session to add the turn to.', + }, + messages: { + type: 'array', + description: 'A list of messages to add to the session.', + items: { + type: 'object', + properties: { + role: { + type: 'string', + enum: ['user', 'assistant'], + description: 'The role of the message author.', + }, + content: { + type: 'string', + description: 'The content of the message.', + }, + metadata: { + type: 'object', + description: 'Optional metadata about the message.', + }, + }, + required: ['role', 'content'], + }, + }, + }, + required: ['session_id', 'messages'], + }, + }, + { + name: 'get_personalization_insights', + description: 'Get personalization insights about the user, based on the query and the accumulated knowledge of the user across all conversations.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session for context.', + }, + query: { + type: 'string', + description: 'The question about the user\'s preferences, habits, etc.', + }, + }, + required: ['session_id', 'query'], + }, + }, + + // Workspace operations + { + name: 'search_workspace', + description: 'Search for messages across the entire workspace.', + inputSchema: { + type: 'object', + properties: { + query: { + type: 'string', + description: 'The search query to use.', + }, + }, + required: ['query'], + }, + }, + { + name: 'get_workspace_metadata', + description: 'Get metadata for the current workspace.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, + }, + { + name: 'set_workspace_metadata', + description: 'Set metadata for the current workspace.', + inputSchema: { + type: 'object', + properties: { + metadata: { + type: 'object', + description: 'A dictionary of metadata to associate with the workspace.', + }, + }, + required: ['metadata'], + }, + }, + + // Peer operations + { + name: 'create_peer', + description: 'Create or get a peer with the specified ID and optional configuration.', + inputSchema: { + type: 'object', + properties: { + peer_id: { + type: 'string', + description: 'Unique identifier for the peer.', + }, + config: { + type: 'object', + description: 'Optional configuration dictionary for the peer.', + }, + }, + required: ['peer_id'], + }, + }, + { + name: 'get_peer_metadata', + description: 'Get metadata for a specific peer.', + inputSchema: { + type: 'object', + properties: { + peer_id: { + type: 'string', + description: 'The ID of the peer to get metadata for.', + }, + }, + required: ['peer_id'], + }, + }, + { + name: 'set_peer_metadata', + description: 'Set metadata for a specific peer.', + inputSchema: { + type: 'object', + properties: { + peer_id: { + type: 'string', + description: 'The ID of the peer to set metadata for.', + }, + metadata: { + type: 'object', + description: 'A dictionary of metadata to associate with the peer.', + }, + }, + required: ['peer_id', 'metadata'], + }, + }, + { + name: 'search_peer_messages', + description: 'Search for messages sent by a peer.', + inputSchema: { + type: 'object', + properties: { + peer_id: { + type: 'string', + description: 'The ID of the peer to search messages for.', + }, + query: { + type: 'string', + description: 'The search query to use.', + }, + }, + required: ['peer_id', 'query'], + }, + }, + { + name: 'chat', + description: 'Query a peer\'s representation with natural language questions.', + inputSchema: { + type: 'object', + properties: { + peer_id: { + type: 'string', + description: 'The ID of the peer to query.', + }, + query: { + type: 'string', + description: 'The natural language question to ask.', + }, + target_peer_id: { + type: 'string', + description: 'Optional target peer ID for local representation queries.', + }, + session_id: { + type: 'string', + description: 'Optional session ID to scope the query to a specific session.', + }, + }, + required: ['peer_id', 'query'], + }, + }, + { + name: 'list_peers', + description: 'Get all peers in the current workspace.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, + }, + + // Session operations + { + name: 'create_session', + description: 'Create or get a session with the specified ID and optional configuration.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'Unique identifier for the session.', + }, + config: { + type: 'object', + description: 'Optional configuration dictionary for the session.', + }, + }, + required: ['session_id'], + }, + }, + { + name: 'get_session_metadata', + description: 'Get metadata for a specific session.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session to get metadata for.', + }, + }, + required: ['session_id'], + }, + }, + { + name: 'set_session_metadata', + description: 'Set metadata for a specific session.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session to set metadata for.', + }, + metadata: { + type: 'object', + description: 'A dictionary of metadata to associate with the session.', + }, + }, + required: ['session_id', 'metadata'], + }, + }, + { + name: 'add_peers_to_session', + description: 'Add peers to a session.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session to add peers to.', + }, + peer_ids: { + type: 'array', + items: { type: 'string' }, + description: 'List of peer IDs to add to the session.', + }, + }, + required: ['session_id', 'peer_ids'], + }, + }, + { + name: 'remove_peers_from_session', + description: 'Remove peers from a session.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session to remove peers from.', + }, + peer_ids: { + type: 'array', + items: { type: 'string' }, + description: 'List of peer IDs to remove from the session.', + }, + }, + required: ['session_id', 'peer_ids'], + }, + }, + { + name: 'get_session_peers', + description: 'Get all peer IDs in a session.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session to get peers from.', + }, + }, + required: ['session_id'], + }, + }, + { + name: 'add_messages_to_session', + description: 'Add messages to a session.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session to add messages to.', + }, + messages: { + type: 'array', + items: { + type: 'object', + properties: { + peer_id: { + type: 'string', + description: 'ID of the peer sending the message', + }, + content: { + type: 'string', + description: 'Message content', + }, + metadata: { + type: 'object', + description: 'Optional metadata dictionary', + }, + }, + required: ['peer_id', 'content'], + }, + description: 'List of message dictionaries.', + }, + }, + required: ['session_id', 'messages'], + }, + }, + { + name: 'get_session_messages', + description: 'Get messages from a session with optional filtering.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session to get messages from.', + }, + filters: { + type: 'object', + description: 'Optional dictionary of filter criteria.', + }, + }, + required: ['session_id'], + }, + }, + { + name: 'get_session_context', + description: 'Get optimized context for a session within a token limit.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session to get context for.', + }, + summary: { + type: 'boolean', + description: 'Whether to include summary information.', + default: true, + }, + tokens: { + type: 'integer', + description: 'Maximum number of tokens to include in the context.', + }, + }, + required: ['session_id'], + }, + }, + { + name: 'search_session_messages', + description: 'Search for messages in a specific session.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session to search messages in.', + }, + query: { + type: 'string', + description: 'The search query to use.', + }, + }, + required: ['session_id', 'query'], + }, + }, + { + name: 'get_working_representation', + description: 'Get the current working representation of a peer in a session.', + inputSchema: { + type: 'object', + properties: { + session_id: { + type: 'string', + description: 'The ID of the session.', + }, + peer_id: { + type: 'string', + description: 'The ID of the peer to get the working representation of.', + }, + target_peer_id: { + type: 'string', + description: 'Optional target peer ID to get the representation of what peer_id knows about target_peer_id.', + }, + }, + required: ['session_id', 'peer_id'], + }, + }, + { + name: 'list_sessions', + description: 'Get all sessions in the current workspace.', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, + }, +]; + +/** + * Execute a tool with validation and consistent response handling + */ +async function executeToolCall(honcho: HonchoWorker, toolName: string, toolArguments: any, requestId: string | number | null): Promise { + let result: any; + + switch (toolName) { + // Bespoke tools + case 'start_conversation': + result = await honcho.startConversation(); + break; + + case 'add_turn': { + const validation = validateArguments(toolArguments, ['session_id', 'messages'], requestId); + if (validation) return validation; + + await honcho.addTurn(toolArguments.session_id, toolArguments.messages); + result = 'Turn added successfully'; + break; + } + + case 'get_personalization_insights': { + const validation = validateArguments(toolArguments, ['session_id', 'query'], requestId); + if (validation) return validation; + + result = await honcho.getPersonalizationInsights(toolArguments.session_id, toolArguments.query); + break; + } + + // Workspace operations + case 'search_workspace': { + const validation = validateArguments(toolArguments, ['query'], requestId); + if (validation) return validation; + + result = await honcho.searchWorkspace(toolArguments.query); + break; + } + + case 'get_workspace_metadata': + result = await honcho.getWorkspaceMetadata(); + break; + + case 'set_workspace_metadata': { + const validation = validateArguments(toolArguments, ['metadata'], requestId); + if (validation) return validation; + + await honcho.setWorkspaceMetadata(toolArguments.metadata); + result = 'Workspace metadata set successfully'; + break; + } + + // Peer operations + case 'create_peer': { + const validation = validateArguments(toolArguments, ['peer_id'], requestId); + if (validation) return validation; + + result = await honcho.createPeer(toolArguments.peer_id, toolArguments.config); + break; + } + + case 'get_peer_metadata': { + const validation = validateArguments(toolArguments, ['peer_id'], requestId); + if (validation) return validation; + + result = await honcho.getPeerMetadata(toolArguments.peer_id); + break; + } + + case 'set_peer_metadata': { + const validation = validateArguments(toolArguments, ['peer_id', 'metadata'], requestId); + if (validation) return validation; + + await honcho.setPeerMetadata(toolArguments.peer_id, toolArguments.metadata); + result = 'Peer metadata set successfully'; + break; + } + + case 'search_peer_messages': { + const validation = validateArguments(toolArguments, ['peer_id', 'query'], requestId); + if (validation) return validation; + + result = await honcho.searchPeerMessages(toolArguments.peer_id, toolArguments.query); + break; + } + + case 'chat': { + const validation = validateArguments(toolArguments, ['peer_id', 'query'], requestId); + if (validation) return validation; + + result = await honcho.chat(toolArguments.peer_id, toolArguments.query, toolArguments.target_peer_id, toolArguments.session_id); + break; + } + + case 'list_peers': + result = await honcho.listPeers(); + break; + + // Session operations + case 'create_session': { + const validation = validateArguments(toolArguments, ['session_id'], requestId); + if (validation) return validation; + + result = await honcho.createSession(toolArguments.session_id, toolArguments.config); + break; + } + + case 'get_session_metadata': { + const validation = validateArguments(toolArguments, ['session_id'], requestId); + if (validation) return validation; + + result = await honcho.getSessionMetadata(toolArguments.session_id); + break; + } + + case 'set_session_metadata': { + const validation = validateArguments(toolArguments, ['session_id', 'metadata'], requestId); + if (validation) return validation; + + await honcho.setSessionMetadata(toolArguments.session_id, toolArguments.metadata); + result = 'Session metadata set successfully'; + break; + } + + case 'add_peers_to_session': { + const validation = validateArguments(toolArguments, ['session_id', 'peer_ids'], requestId); + if (validation) return validation; + + await honcho.addPeersToSession(toolArguments.session_id, toolArguments.peer_ids); + result = 'Peers added to session successfully'; + break; + } + + case 'remove_peers_from_session': { + const validation = validateArguments(toolArguments, ['session_id', 'peer_ids'], requestId); + if (validation) return validation; + + await honcho.removePeersFromSession(toolArguments.session_id, toolArguments.peer_ids); + result = 'Peers removed from session successfully'; + break; + } + + case 'get_session_peers': { + const validation = validateArguments(toolArguments, ['session_id'], requestId); + if (validation) return validation; + + result = await honcho.getSessionPeers(toolArguments.session_id); + break; + } + + case 'add_messages_to_session': { + const validation = validateArguments(toolArguments, ['session_id', 'messages'], requestId); + if (validation) return validation; + + await honcho.addMessagesToSession(toolArguments.session_id, toolArguments.messages); + result = 'Messages added to session successfully'; + break; + } + + case 'get_session_messages': { + const validation = validateArguments(toolArguments, ['session_id'], requestId); + if (validation) return validation; + + result = await honcho.getSessionMessages(toolArguments.session_id, toolArguments.filters); + break; + } + + case 'get_session_context': { + const validation = validateArguments(toolArguments, ['session_id'], requestId); + if (validation) return validation; + + result = await honcho.getSessionContext(toolArguments.session_id, toolArguments.summary, toolArguments.tokens); + break; + } + + case 'search_session_messages': { + const validation = validateArguments(toolArguments, ['session_id', 'query'], requestId); + if (validation) return validation; + + result = await honcho.searchSessionMessages(toolArguments.session_id, toolArguments.query); + break; + } + + case 'get_working_representation': { + const validation = validateArguments(toolArguments, ['session_id', 'peer_id'], requestId); + if (validation) return validation; + + result = await honcho.getWorkingRepresentation(toolArguments.session_id, toolArguments.peer_id, toolArguments.target_peer_id); + break; + } + + case 'list_sessions': + result = await honcho.listSessions(); + break; + + default: + return createErrorResponse(requestId, -32601, `Method not found: ${toolName}`); + } + + const responseData = typeof result === 'string' ? result : JSON.stringify(result); + return new Response(JSON.stringify(createJsonRpcResponse(requestId, { + content: [{ + type: 'text', + text: responseData, + }], + })), { + status: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + }); +} + +/** + * Main Cloudflare Worker export + */ +export default { + async fetch(request: Request): Promise { + // Handle CORS preflight requests + if (request.method === 'OPTIONS') { + return new Response(null, { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Honcho-User-Name, X-Honcho-Base-URL, X-Honcho-Workspace-ID, X-Honcho-Assistant-Name', + }, + }); + } + + // Only accept POST requests for JSON-RPC + if (request.method !== 'POST') { + return createErrorResponse(null, -32600, 'Invalid Request'); + } + + let requestData: JsonRpcRequest; + + try { + requestData = await request.json() as JsonRpcRequest; + } catch (error) { + return createErrorResponse(null, -32700, 'Parse error'); + } + + // Validate JSON-RPC format + if (requestData.jsonrpc !== '2.0') { + return createErrorResponse(requestData.id ?? null, -32600, 'Invalid Request'); + } + + if (!requestData.method) { + return createErrorResponse(requestData.id ?? null, -32600, 'Invalid Request'); + } + + // Parse configuration + const config = parseConfig(request); + if (!config && requestData.method !== 'initialize') { + return createErrorResponse(requestData.id ?? null, -32602, 'Missing or invalid API key'); + } + + const honcho = config ? new HonchoWorker(config) : null; + + try { + switch (requestData.method) { + case 'initialize': + return new Response(JSON.stringify(createJsonRpcResponse(requestData.id ?? null, { + protocolVersion: '2024-11-05', + capabilities: { + tools: {} + }, + serverInfo: { + name: 'Honcho MCP Server', + version: '1.0.0', + }, + })), { + status: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + }); + + case 'notifications/initialized': + // MCP initialized notification - no response needed + return new Response(null, { + status: 204, + headers: { + 'Access-Control-Allow-Origin': '*', + }, + }); + + case 'tools/list': + return new Response(JSON.stringify(createJsonRpcResponse(requestData.id ?? null, { + tools: tools, + })), { + status: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + }); + + case 'tools/call': + if (!honcho) { + return createErrorResponse(requestData.id ?? null, -32602, 'Missing API key'); + } + + const toolName = requestData.params?.name; + const toolArguments = requestData.params?.arguments || {}; + + return await executeToolCall(honcho, toolName, toolArguments, requestData.id ?? null); + + default: + return createErrorResponse(requestData.id ?? null, -32601, `Method not found: ${requestData.method}`); + } + } catch (error) { + console.error('Worker error:', error); + const errorMessage = error instanceof Error ? error.message : 'Internal server error'; + return createErrorResponse(requestData.id ?? null, -32603, errorMessage); + } + }, +}; \ No newline at end of file diff --git a/mcp/wrangler.toml b/mcp/wrangler.toml new file mode 100644 index 00000000..8a443964 --- /dev/null +++ b/mcp/wrangler.toml @@ -0,0 +1,13 @@ +name = "honcho-mcp" +main = "worker.ts" +compatibility_date = "2024-12-09" +compatibility_flags = ["nodejs_compat"] + +[env.production] +name = "honcho-mcp" + +[env.staging] +name = "honcho-mcp-staging" + +[observability.logs] +enabled = true \ No newline at end of file diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml index 9de432b2..3e329952 100644 --- a/sdks/python/pyproject.toml +++ b/sdks/python/pyproject.toml @@ -30,8 +30,8 @@ classifiers = [ ] [project.urls] -Homepage = "https://github.com/plastic-labs/honcho-sdks" -Repository = "https://github.com/plastic-labs/honcho-sdks" +Homepage = "https://github.com/plastic-labs/honcho" +Repository = "https://github.com/plastic-labs/honcho" [dependency-groups] dev = [