agenthub-multiagent-mcp 1.29.0 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -723,6 +723,64 @@ export function registerTools() {
723
723
  required: ["id"],
724
724
  },
725
725
  },
726
+ {
727
+ name: "calendar_schedule_meeting",
728
+ description: "Schedule a Google Calendar meeting with an auto-generated Google Meet link. Idempotent on dedupe_key (same key returns the same event). Requires the calendar capability to be enabled on the server.",
729
+ inputSchema: {
730
+ type: "object",
731
+ properties: {
732
+ title: { type: "string", description: "Meeting title" },
733
+ start: { type: "string", description: "Start time (RFC3339, e.g. 2026-06-02T10:00:00Z)" },
734
+ end: { type: "string", description: "End time (RFC3339); must be after start" },
735
+ attendee_emails: {
736
+ type: "array",
737
+ items: { type: "string" },
738
+ description: "Invitee email addresses",
739
+ },
740
+ description: { type: "string", description: "Optional meeting description" },
741
+ timezone: { type: "string", description: "Optional IANA timezone; defaults to the server default (UTC)" },
742
+ recurrence: { type: "string", description: "Optional RRULE, e.g. RRULE:FREQ=WEEKLY;BYDAY=MO" },
743
+ dedupe_key: { type: "string", description: "Idempotency key; same key returns the same event" },
744
+ source: {
745
+ type: "object",
746
+ description: "Optional linkage to a blocker/task; a task source resolves the assignee as an attendee",
747
+ properties: {
748
+ kind: { type: "string", enum: ["blocker", "task", "manual"] },
749
+ id: { type: "string" },
750
+ },
751
+ },
752
+ },
753
+ required: ["title", "start", "end", "dedupe_key"],
754
+ },
755
+ },
756
+ {
757
+ name: "email_send",
758
+ description: "Send an email via Gmail to an allowlisted recipient (external partners not in Slack). Recipient must pass the allowlist; per-org daily cap applies. Supports templates (onboarding_invite, task_assignment, digest) or raw body_text/body_html.",
759
+ inputSchema: {
760
+ type: "object",
761
+ properties: {
762
+ to: {
763
+ type: "array",
764
+ items: { type: "string" },
765
+ description: "Recipient email addresses (each must pass the allowlist)",
766
+ },
767
+ subject: { type: "string", description: "Email subject (required for ad-hoc sends)" },
768
+ body_text: { type: "string", description: "Plain-text body (required for ad-hoc sends)" },
769
+ body_html: { type: "string", description: "Optional HTML body" },
770
+ template: {
771
+ type: "string",
772
+ enum: ["onboarding_invite", "task_assignment", "digest"],
773
+ description: "Optional template; when set, body_* are derived from template_vars",
774
+ },
775
+ template_vars: {
776
+ type: "object",
777
+ description: "Variables for the chosen template (e.g. {name, login_url})",
778
+ },
779
+ dedupe_key: { type: "string", description: "Optional idempotency key; a repeat is a no-op" },
780
+ },
781
+ required: ["to"],
782
+ },
783
+ },
726
784
  {
727
785
  name: "list_channels",
728
786
  description: "List all available channels",
@@ -731,6 +789,156 @@ export function registerTools() {
731
789
  properties: {},
732
790
  },
733
791
  },
792
+ {
793
+ name: "list_projects",
794
+ description: "List the projects in your org (id, key, name). Use this to discover your project_id (UUID) — required by project-scoped tools like create_epic, feature_graph_sync_openspec, and roadmap_get. The server scopes results to your org automatically.",
795
+ inputSchema: {
796
+ type: "object",
797
+ properties: {
798
+ include_archived: {
799
+ type: "boolean",
800
+ description: "Include archived projects (default false)",
801
+ },
802
+ },
803
+ },
804
+ },
805
+ {
806
+ name: "api_search",
807
+ description: "Search the API knowledge graph — endpoints (REST/MCP/GraphQL/gRPC) extracted from indexed repos across the org. Use this BEFORE building a new endpoint to check if it already exists or to match a sibling project's contract. Org-wide read.",
808
+ inputSchema: {
809
+ type: "object",
810
+ properties: {
811
+ query: { type: "string", description: "Free-text match over endpoint identity + description" },
812
+ project_id: { type: "string", description: "Filter to one project (optional)" },
813
+ surface: { type: "string", description: "Filter by surface: rest | mcp | graphql | grpc (optional)" },
814
+ },
815
+ },
816
+ },
817
+ {
818
+ name: "api_get_endpoint",
819
+ description: "Get one API endpoint's full contract (handler ref, schemas, auth, description) plus its edges, by identity (e.g. \"POST /api/x\").",
820
+ inputSchema: {
821
+ type: "object",
822
+ properties: {
823
+ identity: { type: "string", description: "Endpoint identity, e.g. \"POST /api/x\" (REST) or a tool name (MCP)" },
824
+ project_id: { type: "string", description: "Project to look in (optional; org-wide if omitted)" },
825
+ },
826
+ required: ["identity"],
827
+ },
828
+ },
829
+ {
830
+ name: "api_endpoint_impact",
831
+ description: "Show an endpoint's blast radius before you change it: the consumers, models, and database tables it touches (plus linked openspec changes / feature-graph nodes).",
832
+ inputSchema: {
833
+ type: "object",
834
+ properties: {
835
+ identity: { type: "string", description: "Endpoint identity, e.g. \"POST /api/x\"" },
836
+ project_id: { type: "string", description: "Project to look in (optional)" },
837
+ },
838
+ required: ["identity"],
839
+ },
840
+ },
841
+ {
842
+ name: "api_graph",
843
+ description: "Return a project's API endpoint subgraph (endpoints with their edges) — for an overview of what a project exposes.",
844
+ inputSchema: {
845
+ type: "object",
846
+ properties: {
847
+ project_id: { type: "string", description: "Project to render (optional; org-wide if omitted)" },
848
+ surface: { type: "string", description: "Filter by surface: rest | mcp | graphql | grpc (optional)" },
849
+ },
850
+ },
851
+ },
852
+ {
853
+ name: "action_item_create",
854
+ description: "Create a lightweight action item — a tracked to-do assigned to a person (e.g. a task captured from chat). Distinct from the openspec-linked ticket hierarchy; promote to a ticket task when it becomes real dev work. Requires the deployment to have action items enabled.",
855
+ inputSchema: {
856
+ type: "object",
857
+ properties: {
858
+ title: { type: "string", description: "Short description of the action item" },
859
+ assignee_user_id: {
860
+ type: "string",
861
+ description: "AgentHub user id to assign to (optional; created unassigned if omitted)",
862
+ },
863
+ details: { type: "string", description: "Optional longer details" },
864
+ due_at: { type: "string", description: "Optional due timestamp (RFC3339)" },
865
+ source_kind: {
866
+ type: "string",
867
+ description: "Origin: manual | slack_mention | slack_command | slack_shortcut | slack_reaction | slack_autoparse (default manual)",
868
+ },
869
+ source_ref: { type: "string", description: "Optional provenance link (e.g. a Slack permalink)" },
870
+ },
871
+ required: ["title"],
872
+ },
873
+ },
874
+ {
875
+ name: "action_item_list",
876
+ description: "List action items in your org, optionally filtered by assignee user id and/or status (open | snoozed | done | cancelled).",
877
+ inputSchema: {
878
+ type: "object",
879
+ properties: {
880
+ assignee: { type: "string", description: "Filter by assignee AgentHub user id" },
881
+ status: {
882
+ type: "string",
883
+ description: "Filter by status: open | snoozed | done | cancelled",
884
+ },
885
+ },
886
+ },
887
+ },
888
+ {
889
+ name: "action_item_complete",
890
+ description: "Mark an action item done.",
891
+ inputSchema: {
892
+ type: "object",
893
+ properties: {
894
+ id: { type: "string", description: "Action item id" },
895
+ },
896
+ required: ["id"],
897
+ },
898
+ },
899
+ {
900
+ name: "action_item_snooze",
901
+ description: "Snooze an action item until a given time (RFC3339).",
902
+ inputSchema: {
903
+ type: "object",
904
+ properties: {
905
+ id: { type: "string", description: "Action item id" },
906
+ until: { type: "string", description: "RFC3339 timestamp to snooze until" },
907
+ },
908
+ required: ["id", "until"],
909
+ },
910
+ },
911
+ {
912
+ name: "action_item_promote",
913
+ description: "Promote an action item into a tracked ticket task under a story (when an ad-hoc item turns out to be real dev work). Links the two; completing the ticket later marks the action item done.",
914
+ inputSchema: {
915
+ type: "object",
916
+ properties: {
917
+ id: { type: "string", description: "Action item id to promote" },
918
+ story_id: { type: "string", description: "Target story id the new task will live under" },
919
+ },
920
+ required: ["id", "story_id"],
921
+ },
922
+ },
923
+ {
924
+ name: "notify_prefs_set",
925
+ description: "Set your action-item digest + reminder preferences (the authenticated user's). Controls which channel the daily digest is delivered on and whether overdue reminders are sent.",
926
+ inputSchema: {
927
+ type: "object",
928
+ properties: {
929
+ digest_channel: {
930
+ type: "string",
931
+ description: "Where to deliver the digest: auto (Slack DM if linked, else email) | slack | email | both",
932
+ },
933
+ digest_hour_local: {
934
+ type: "number",
935
+ description: "Hour of day (0-23, local to tz) to send the daily digest",
936
+ },
937
+ tz: { type: "string", description: "IANA timezone name (e.g. Asia/Kolkata); default UTC" },
938
+ reminders_enabled: { type: "boolean", description: "Whether to send overdue reminders" },
939
+ },
940
+ },
941
+ },
734
942
  {
735
943
  name: "join_channel",
736
944
  description: "Subscribe to a channel to receive its messages",
@@ -1678,7 +1886,29 @@ export function registerTools() {
1678
1886
  description: "Get the TOON-encoded org brain context package for this agent. Includes own session history, blockers, org decisions, and alerts.",
1679
1887
  inputSchema: {
1680
1888
  type: "object",
1681
- properties: {},
1889
+ properties: {
1890
+ include_code: {
1891
+ type: "boolean",
1892
+ description: "add-code-brain: when true, append a code_index section with top org-wide code/doc matches relevant to recent sessions. Default false — behaviour is byte-identical to pre-change when omitted.",
1893
+ },
1894
+ },
1895
+ },
1896
+ },
1897
+ {
1898
+ name: "brain_code_recall",
1899
+ description: "add-code-brain: search the org-wide indexed code + docs (Markdown / RST / OpenAPI / HTML / Jupyter / PDF + tree-sitter symbols). Use when org_search returns nothing for a code or architecture question, or when you need to discover files/symbols that no agent has written down. Read-only.",
1900
+ inputSchema: {
1901
+ type: "object",
1902
+ properties: {
1903
+ query: { type: "string", description: "Free-text query — symbol name, doc title fragment, or natural-language phrase" },
1904
+ k: { type: "number", description: "Max hits per kind (default 5, max 50)" },
1905
+ kinds: {
1906
+ type: "array",
1907
+ items: { type: "string", enum: ["docs", "symbols"] },
1908
+ description: "Restrict to docs and/or symbols. Default both.",
1909
+ },
1910
+ },
1911
+ required: ["query"],
1682
1912
  },
1683
1913
  },
1684
1914
  {
@@ -2826,10 +3056,107 @@ export async function handleToolCall(name, args, client, context) {
2826
3056
  const result = await client.getAgent(args.id);
2827
3057
  return wrapWithPendingItems(client, agentId, result, context);
2828
3058
  }
3059
+ case "calendar_schedule_meeting": {
3060
+ const result = await client.scheduleMeeting({
3061
+ title: args.title,
3062
+ start: args.start,
3063
+ end: args.end,
3064
+ attendee_emails: args.attendee_emails,
3065
+ description: args.description,
3066
+ timezone: args.timezone,
3067
+ recurrence: args.recurrence,
3068
+ dedupe_key: args.dedupe_key,
3069
+ source: args.source,
3070
+ });
3071
+ return wrapWithPendingItems(client, agentId, result, context);
3072
+ }
3073
+ case "email_send": {
3074
+ const result = await client.sendEmail({
3075
+ to: args.to,
3076
+ subject: args.subject,
3077
+ body_text: args.body_text,
3078
+ body_html: args.body_html,
3079
+ template: args.template,
3080
+ template_vars: args.template_vars,
3081
+ dedupe_key: args.dedupe_key,
3082
+ });
3083
+ return wrapWithPendingItems(client, agentId, result, context);
3084
+ }
2829
3085
  case "list_channels": {
2830
3086
  const result = await client.listChannels();
2831
3087
  return wrapWithPendingItems(client, agentId, result, context);
2832
3088
  }
3089
+ case "list_projects": {
3090
+ // Org-scoped discovery — no agent registration required. Lets an agent
3091
+ // find its project_id (UUID) for project-scoped tools.
3092
+ const result = await client.listProjects(args.include_archived);
3093
+ return wrapWithPendingItems(client, agentId, result, context);
3094
+ }
3095
+ // add-api-knowledge-graph §3 — org-wide read of the API graph.
3096
+ case "api_search": {
3097
+ const result = await client.apiSearch({
3098
+ query: args.query,
3099
+ project_id: args.project_id,
3100
+ surface: args.surface,
3101
+ });
3102
+ return wrapWithPendingItems(client, agentId, result, context);
3103
+ }
3104
+ case "api_get_endpoint": {
3105
+ const result = await client.apiGetEndpoint(args.identity, args.project_id);
3106
+ return wrapWithPendingItems(client, agentId, result, context);
3107
+ }
3108
+ case "api_endpoint_impact": {
3109
+ const result = await client.apiEndpointImpact(args.identity, args.project_id);
3110
+ return wrapWithPendingItems(client, agentId, result, context);
3111
+ }
3112
+ case "api_graph": {
3113
+ const result = await client.apiGraph({
3114
+ project_id: args.project_id,
3115
+ surface: args.surface,
3116
+ });
3117
+ return wrapWithPendingItems(client, agentId, result, context);
3118
+ }
3119
+ // add-action-items Phase 1 — org-scoped (auth via the agent's connect
3120
+ // token); no agentId guard, mirroring list_projects.
3121
+ case "action_item_create": {
3122
+ const result = await client.createActionItem({
3123
+ title: args.title,
3124
+ assignee_user_id: args.assignee_user_id,
3125
+ details: args.details,
3126
+ due_at: args.due_at,
3127
+ source_kind: args.source_kind,
3128
+ source_ref: args.source_ref,
3129
+ });
3130
+ return wrapWithPendingItems(client, agentId, result, context);
3131
+ }
3132
+ case "action_item_list": {
3133
+ const result = await client.listActionItems({
3134
+ assignee: args.assignee,
3135
+ status: args.status,
3136
+ });
3137
+ return wrapWithPendingItems(client, agentId, result, context);
3138
+ }
3139
+ case "action_item_complete": {
3140
+ const result = await client.completeActionItem(args.id);
3141
+ return wrapWithPendingItems(client, agentId, result, context);
3142
+ }
3143
+ case "action_item_snooze": {
3144
+ const result = await client.snoozeActionItem(args.id, args.until);
3145
+ return wrapWithPendingItems(client, agentId, result, context);
3146
+ }
3147
+ case "action_item_promote": {
3148
+ const result = await client.promoteActionItem(args.id, args.story_id);
3149
+ return wrapWithPendingItems(client, agentId, result, context);
3150
+ }
3151
+ case "notify_prefs_set": {
3152
+ const result = await client.setNotifyPrefs({
3153
+ digest_channel: args.digest_channel,
3154
+ digest_hour_local: args.digest_hour_local,
3155
+ tz: args.tz,
3156
+ reminders_enabled: args.reminders_enabled,
3157
+ });
3158
+ return wrapWithPendingItems(client, agentId, result, context);
3159
+ }
2833
3160
  case "join_channel": {
2834
3161
  if (!agentId)
2835
3162
  throw new Error("Not registered. Call agent_register first.");
@@ -3710,7 +4037,35 @@ export async function handleToolCall(name, args, client, context) {
3710
4037
  case "brain_get_context": {
3711
4038
  if (!agentId)
3712
4039
  throw new Error("Not registered. Call agent_register first.");
3713
- return client.getBrainContext(agentId);
4040
+ const includeCode = Boolean(args.include_code);
4041
+ const qs = includeCode ? "?include_code=true" : "";
4042
+ // Use raw GET so the include_code flag round-trips without a typed
4043
+ // client helper. Server-side decides whether to append code_index.
4044
+ return client.get(`/brain/context/${encodeURIComponent(agentId)}${qs}`);
4045
+ }
4046
+ case "brain_code_recall": {
4047
+ const query = args.query || "";
4048
+ if (!query)
4049
+ throw new Error("query is required");
4050
+ const k = args.k || 5;
4051
+ const kindsArr = args.kinds || ["docs", "symbols"];
4052
+ const params = new URLSearchParams();
4053
+ params.set("q", query);
4054
+ params.set("k", String(k));
4055
+ params.set("kinds", kindsArr.join(","));
4056
+ try {
4057
+ return await client.get(`/brain/code/search?${params.toString()}`);
4058
+ }
4059
+ catch (e) {
4060
+ const msg = e instanceof Error ? e.message : String(e);
4061
+ if (msg.includes("code_brain_disabled")) {
4062
+ return {
4063
+ error: "code_brain_disabled",
4064
+ message: "The code brain is disabled on this server. It defaults to enabled — ask the operator to unset AGENTHUB_CODE_BRAIN_ENABLED (or set it back to true) and restart.",
4065
+ };
4066
+ }
4067
+ throw e;
4068
+ }
3714
4069
  }
3715
4070
  case "brain_list_sessions": {
3716
4071
  const targetAgent = args.agent_id || agentId;