feat(cli+broker): expose mesh skills as MCP prompts and skill:// resources
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Claudemesh MCP server now declares prompts:{} and resources:{} capabilities.
Mesh skills auto-appear as /claudemesh:skill-name slash commands in Claude Code
via prompts/list+get, and as skill://claudemesh/{name} resources for the
upcoming MCP_SKILLS protocol. share_skill accepts optional metadata (when_to_use,
allowed_tools, model, context, agent) stored in the manifest jsonb column.
Change notifications sent on share/remove so Claude Code refreshes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-09 00:01:06 +01:00
parent 5db9842261
commit b31aab8aeb
5 changed files with 336 additions and 16 deletions

View File

@@ -720,6 +720,7 @@ export async function shareSkill(
tags: string[],
memberId?: string,
memberName?: string,
manifest?: unknown,
): Promise<string> {
const existing = await db
.select({ id: meshSkill.id })
@@ -734,6 +735,7 @@ export async function shareSkill(
description,
instructions,
tags,
manifest: manifest ?? null,
authorMemberId: memberId ?? null,
authorName: memberName ?? null,
updatedAt: new Date(),
@@ -750,6 +752,7 @@ export async function shareSkill(
description,
instructions,
tags,
manifest: manifest ?? null,
authorMemberId: memberId ?? null,
authorName: memberName ?? null,
})
@@ -770,6 +773,7 @@ export async function getSkill(
instructions: string;
tags: string[];
author: string;
manifest: unknown;
createdAt: Date;
} | null> {
const rows = await db
@@ -779,6 +783,7 @@ export async function getSkill(
instructions: meshSkill.instructions,
tags: meshSkill.tags,
authorName: meshSkill.authorName,
manifest: meshSkill.manifest,
createdAt: meshSkill.createdAt,
})
.from(meshSkill)
@@ -793,6 +798,7 @@ export async function getSkill(
instructions: r.instructions,
tags: r.tags ?? [],
author: r.authorName ?? "unknown",
manifest: r.manifest,
createdAt: r.createdAt,
};
}
@@ -1800,13 +1806,18 @@ export async function joinMesh(args: {
if (!claimed) return { ok: false, error: "invite_exhausted" };
// 6. Insert the member with the role from the payload.
// Apply invite preset overrides (displayName, roleTag, groups, messageMode).
const preset = (inv.preset as any) ?? {};
const [row] = await db
.insert(memberTable)
.values({
meshId: invitePayload.mesh_id,
peerPubkey,
displayName,
displayName: preset.displayName ?? displayName,
role: invitePayload.role,
roleTag: preset.roleTag ?? null,
defaultGroups: preset.groups ?? [],
messageMode: preset.messageMode ?? "push",
})
.returning({ id: memberTable.id });
if (!row) return { ok: false, error: "member_insert_failed" };
@@ -1820,12 +1831,24 @@ export async function joinMesh(args: {
export async function findMemberByPubkey(
meshId: string,
pubkey: string,
): Promise<{ id: string; displayName: string; role: string } | null> {
): Promise<{
id: string;
displayName: string;
role: string;
roleTag: string | null;
defaultGroups: Array<{ name: string; role?: string }>;
messageMode: string | null;
dashboardUserId: string | null;
} | null> {
const [row] = await db
.select({
id: memberTable.id,
displayName: memberTable.displayName,
role: memberTable.role,
roleTag: memberTable.roleTag,
defaultGroups: memberTable.defaultGroups,
messageMode: memberTable.messageMode,
dashboardUserId: memberTable.dashboardUserId,
})
.from(memberTable)
.where(