feat: add missing tools and polish descriptions. Add inspect_workspace, list_workspaces, inspect_session, get_session_message, and get_session_summaries tools. Fix CORS preflight handling and default route path. Extract shared formatting helpers for messages and summaries. Trim redundant tool descriptions.

This commit is contained in:
ajspig 2026-03-11 17:54:21 -04:00
parent 4bbdb4e20c
commit 1bc9da865b
7 changed files with 237 additions and 44 deletions

View File

@ -19,6 +19,10 @@ export default {
env: unknown,
executionCtx: ExecutionContext,
): Promise<Response> {
if (request.method === "OPTIONS") {
return new Response(null, { status: 204, headers: CORS_HEADERS });
}
let config;
try {
config = parseConfig(request);
@ -35,6 +39,7 @@ export default {
const honcho = createClient(config);
const server = createServer({ honcho, config });
const handler = createMcpHandler(server, {
route: "/",
corsOptions: {
origin: CORS_ORIGIN,
methods: CORS_METHODS,

View File

@ -149,7 +149,6 @@ export function register(server: McpServer, ctx: ToolContext) {
description: [
"Delete a specific conclusion by ID.",
"Use this to remove incorrect or outdated knowledge.",
"Returns a confirmation message.",
].join("\n"),
inputSchema: {
peer_id: z.string().describe("The observer peer."),

View File

@ -281,9 +281,7 @@ export function register(server: McpServer, ctx: ToolContext) {
"get_peer_metadata",
{
description: [
"Get the metadata dictionary for a peer.",
"Use this to read custom attributes stored on a peer.",
"Returns a JSON object of key-value pairs.",
"Get metadata for a peer.",
].join("\n"),
inputSchema: {
peer_id: z.string().describe("The peer to get metadata for."),
@ -307,9 +305,8 @@ export function register(server: McpServer, ctx: ToolContext) {
"set_peer_metadata",
{
description: [
"Set metadata for a peer (overwrites existing metadata).",
"Use this to store custom attributes on a peer.",
"Returns a confirmation message.",
"Set metadata for a peer.",
"Overwrites existing metadata.",
].join("\n"),
inputSchema: {
peer_id: z.string().describe("The peer to set metadata for."),

View File

@ -1,7 +1,14 @@
import { z } from "zod";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { Message } from "@honcho-ai/sdk";
import type { ToolContext } from "../types.js";
import { textResult, errorResult, formatMessages } from "../types.js";
import {
textResult,
errorResult,
formatMessage,
formatMessages,
formatSessionSummaries,
} from "../types.js";
export function register(server: McpServer, ctx: ToolContext) {
// ── create_session ──────────────────────────────────────────────────
@ -61,9 +68,8 @@ export function register(server: McpServer, ctx: ToolContext) {
"delete_session",
{
description: [
"Permanently delete a session and all its messages.",
"Use this to clean up conversations that are no longer needed. This cannot be undone.",
"Returns a confirmation message.",
"Delete a session and all its messages.",
"This cannot be undone.",
].join("\n"),
inputSchema: {
session_id: z.string().describe("The session to delete."),
@ -121,7 +127,6 @@ export function register(server: McpServer, ctx: ToolContext) {
description: [
"Add one or more peers to a session.",
"Use this to bring participants into a conversation.",
"Returns a confirmation message.",
].join("\n"),
inputSchema: {
session_id: z.string().describe("The session to add peers to."),
@ -175,8 +180,6 @@ export function register(server: McpServer, ctx: ToolContext) {
{
description: [
"Remove one or more peers from a session.",
"Use this to remove participants from a conversation.",
"Returns a confirmation message.",
].join("\n"),
inputSchema: {
session_id: z.string().describe("The session to remove peers from."),
@ -224,6 +227,42 @@ export function register(server: McpServer, ctx: ToolContext) {
},
);
// ── inspect_session ─────────────────────────────────────────────────
server.registerTool(
"inspect_session",
{
description: [
"Inspect a session at a glance.",
"Aggregates peer IDs, message count, and available summaries.",
"Returns a single JSON object.",
].join("\n"),
inputSchema: {
session_id: z.string().describe("The session to inspect."),
},
},
async ({ session_id }) => {
try {
const session = await ctx.honcho.session(session_id);
const [peers, messagePage, summaries] = await Promise.all([
session.peers(),
session.messages(),
session.summaries(),
]);
return textResult({
session_id,
peers: peers.map((peer) => ({ id: peer.id })),
message_count: messagePage.total,
summaries: formatSessionSummaries(summaries),
});
} catch (e) {
return errorResult(
`Failed to inspect session: ${e instanceof Error ? e.message : String(e)}`,
);
}
},
);
// ── add_messages_to_session ─────────────────────────────────────────
server.registerTool(
"add_messages_to_session",
@ -232,7 +271,6 @@ export function register(server: McpServer, ctx: ToolContext) {
"Add messages to a session from specific peers.",
"Use this for multi-peer conversations where you need to attribute messages to specific peers.",
"For the simple user/assistant flow, use add_turn instead.",
"Returns a confirmation message.",
].join("\n"),
inputSchema: {
session_id: z.string().describe("The session to add messages to."),
@ -311,6 +349,46 @@ export function register(server: McpServer, ctx: ToolContext) {
},
);
// ── get_session_message ─────────────────────────────────────────────
server.registerTool(
"get_session_message",
{
description: [
"Get a single message from a session by ID.",
"Use this when you already know the message ID and need the exact record.",
"Returns the message object.",
].join("\n"),
inputSchema: {
session_id: z.string().describe("The session the message belongs to."),
message_id: z.string().describe("The message ID to fetch."),
},
},
async ({ session_id, message_id }) => {
try {
// Workaround: the current @honcho-ai/sdk Session API does not expose
// a single-message getter, so we fetch the message by ID via raw HTTP.
const messageData = await ctx.honcho.http.get<{
id: string;
content: string;
peer_id: string;
session_id: string;
workspace_id: string;
metadata: Record<string, unknown>;
created_at: string;
token_count: number;
}>(
`/v3/workspaces/${ctx.honcho.workspaceId}/sessions/${session_id}/messages/${message_id}`,
);
const message = Message.fromApiResponse(messageData);
return textResult(formatMessage(message));
} catch (e) {
return errorResult(
`Failed to get message: ${e instanceof Error ? e.message : String(e)}`,
);
}
},
);
// ── search_session_messages ─────────────────────────────────────────
server.registerTool(
"search_session_messages",
@ -367,13 +445,7 @@ export function register(server: McpServer, ctx: ToolContext) {
return textResult({
session_id: context.sessionId,
summary: context.summary,
messages: context.messages.map((msg) => ({
id: msg.id,
content: msg.content,
peer_id: msg.peerId,
metadata: msg.metadata,
created_at: msg.createdAt,
})),
messages: formatMessages(context.messages),
});
} catch (e) {
return errorResult(
@ -383,6 +455,32 @@ export function register(server: McpServer, ctx: ToolContext) {
},
);
// ── get_session_summaries ───────────────────────────────────────────
server.registerTool(
"get_session_summaries",
{
description: [
"Get the short and long summaries available for a session.",
"Use this when you want Honcho's generated rollups of the conversation so far.",
"Returns the session ID plus short and long summary objects when available.",
].join("\n"),
inputSchema: {
session_id: z.string().describe("The session to get summaries for."),
},
},
async ({ session_id }) => {
try {
const session = await ctx.honcho.session(session_id);
const summaries = await session.summaries();
return textResult(formatSessionSummaries(summaries));
} catch (e) {
return errorResult(
`Failed to get session summaries: ${e instanceof Error ? e.message : String(e)}`,
);
}
},
);
// ── get_session_representation ──────────────────────────────────────
server.registerTool(
"get_session_representation",
@ -425,9 +523,7 @@ export function register(server: McpServer, ctx: ToolContext) {
"get_session_metadata",
{
description: [
"Get the metadata dictionary for a session.",
"Use this to read custom attributes stored on a session.",
"Returns a JSON object.",
"Get metadata for a session.",
].join("\n"),
inputSchema: {
session_id: z.string().describe("The session to get metadata for."),
@ -451,9 +547,8 @@ export function register(server: McpServer, ctx: ToolContext) {
"set_session_metadata",
{
description: [
"Set metadata for a session (overwrites existing metadata).",
"Use this to store custom attributes on a session.",
"Returns a confirmation message.",
"Set metadata for a session.",
"Overwrites existing metadata.",
].join("\n"),
inputSchema: {
session_id: z.string().describe("The session to set metadata for."),

View File

@ -12,7 +12,6 @@ export function register(server: McpServer, ctx: ToolContext) {
"Schedule a dream — a background memory-consolidation task for a peer.",
"Dreams consolidate observations into higher-level insights and update peer cards.",
"Use this after a long conversation to improve Honcho's memory quality.",
"Returns a confirmation message.",
].join("\n"),
inputSchema: {
peer_id: z.string().describe("The observer peer to dream for."),

View File

@ -4,6 +4,78 @@ import type { ToolContext } from "../types.js";
import { textResult, errorResult, formatMessages } from "../types.js";
export function register(server: McpServer, ctx: ToolContext) {
// ── inspect_workspace ───────────────────────────────────────────────
server.registerTool(
"inspect_workspace",
{
description: [
"Inspect the current workspace at a glance.",
"Aggregates workspace metadata, configuration, peer IDs, and session IDs.",
"Returns a single JSON object.",
].join("\n"),
inputSchema: {},
},
async () => {
try {
const [metadata, configuration, peerPage, sessionPage] = await Promise.all([
ctx.honcho.getMetadata(),
ctx.honcho.getConfiguration(),
ctx.honcho.peers(),
ctx.honcho.sessions(),
]);
const peers: { id: string }[] = [];
for await (const peer of peerPage) {
peers.push({ id: peer.id });
}
const sessions: { id: string }[] = [];
for await (const session of sessionPage) {
sessions.push({ id: session.id });
}
return textResult({
workspace_id: ctx.honcho.workspaceId,
metadata,
configuration,
peers,
sessions,
});
} catch (e) {
return errorResult(
`Failed to inspect workspace: ${e instanceof Error ? e.message : String(e)}`,
);
}
},
);
// ── list_workspaces ─────────────────────────────────────────────────
server.registerTool(
"list_workspaces",
{
description: [
"List all workspaces accessible to the current credentials.",
"Use this to discover available workspaces before selecting or switching context.",
"Returns an array of workspace IDs.",
].join("\n"),
inputSchema: {},
},
async () => {
try {
const page = await ctx.honcho.workspaces();
const workspaces: { id: string }[] = [];
for await (const workspace of page) {
workspaces.push({ id: workspace });
}
return textResult(workspaces);
} catch (e) {
return errorResult(
`Failed to list workspaces: ${e instanceof Error ? e.message : String(e)}`,
);
}
},
);
// ── search_workspace ────────────────────────────────────────────────
server.registerTool(
"search_workspace",
@ -34,9 +106,7 @@ export function register(server: McpServer, ctx: ToolContext) {
"get_workspace_metadata",
{
description: [
"Get the metadata dictionary for the current workspace.",
"Use this to read workspace-level settings or custom attributes.",
"Returns a JSON object of key-value pairs.",
"Get metadata for the current workspace.",
].join("\n"),
inputSchema: {},
},
@ -57,9 +127,8 @@ export function register(server: McpServer, ctx: ToolContext) {
"set_workspace_metadata",
{
description: [
"Set metadata for the current workspace (overwrites existing metadata).",
"Use this to store workspace-level settings or custom attributes.",
"Returns a confirmation message.",
"Set metadata for the current workspace.",
"Overwrites existing metadata.",
].join("\n"),
inputSchema: {
metadata: z

View File

@ -1,4 +1,4 @@
import type { Honcho, Message } from "@honcho-ai/sdk";
import type { Honcho, Message, Summary, SessionSummaries } from "@honcho-ai/sdk";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import type { HonchoConfig } from "./config.js";
@ -18,14 +18,43 @@ export function errorResult(msg: string): CallToolResult {
return { content: [{ type: "text", text: msg }], isError: true };
}
/** Serialize a Message to a plain JSON-safe object. */
export function formatMessage(message: Message) {
return {
id: message.id,
content: message.content,
peer_id: message.peerId,
session_id: message.sessionId,
metadata: message.metadata,
created_at: message.createdAt,
};
}
/** Serialize a Summary to a plain JSON-safe object. */
export function formatSummary(summary: Summary) {
return {
content: summary.content,
message_id: summary.messageId,
summary_type: summary.summaryType,
created_at: summary.createdAt,
token_count: summary.tokenCount,
};
}
/** Serialize SessionSummaries to a plain JSON-safe object. */
export function formatSessionSummaries(summaries: SessionSummaries) {
return {
session_id: summaries.sessionId,
short_summary: summaries.shortSummary
? formatSummary(summaries.shortSummary)
: null,
long_summary: summaries.longSummary
? formatSummary(summaries.longSummary)
: null,
};
}
/** Serialize a Message[] to a plain JSON-safe array. */
export function formatMessages(messages: Message[]) {
return messages.map((m) => ({
id: m.id,
content: m.content,
peer_id: m.peerId,
session_id: m.sessionId,
metadata: m.metadata,
created_at: m.createdAt,
}));
return messages.map(formatMessage);
}