loopengine 0.1.97 → 0.1.99

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.
Files changed (35) hide show
  1. package/dist/bin/ability-manager.d.ts +35 -2
  2. package/dist/bin/ability-manager.js +168 -35
  3. package/dist/core/ability-details.js +11 -0
  4. package/dist/core/mcp-abilities.d.ts +91 -0
  5. package/dist/core/mcp-abilities.js +400 -0
  6. package/dist/core/mcp-ability-generator.d.ts +59 -0
  7. package/dist/core/mcp-ability-generator.js +193 -0
  8. package/dist/core/mcp-apps.d.ts +95 -0
  9. package/dist/core/mcp-apps.js +254 -0
  10. package/dist/core/mcp-directory.d.ts +32 -0
  11. package/dist/core/mcp-directory.js +191 -0
  12. package/dist/core/run-agent.js +5 -2
  13. package/dist/core/system-agents/interviewer.d.ts +1 -1
  14. package/dist/core/system-agents/interviewer.js +1 -1
  15. package/dist/core/system-agents/templates.d.ts +11 -0
  16. package/dist/core/system-agents/templates.js +22 -16
  17. package/dist/core/system-agents/workflow-planner.d.ts +36 -3
  18. package/dist/core/system-agents/workflow-planner.js +24 -10
  19. package/dist/core/workspace-abilities.js +1 -1
  20. package/dist/index.d.ts +3 -0
  21. package/dist/index.js +3 -0
  22. package/dist/web/abilities-admin.d.ts +1 -1
  23. package/dist/web/abilities-admin.js +1 -1
  24. package/dist/web/agents-config-page.js +187 -11
  25. package/dist/web/console-shell.js +7 -5
  26. package/dist/web/dev-ui-styles.js +1 -1
  27. package/dist/web/global-config-page.js +293 -13
  28. package/dist/web/interview-admin.js +2 -0
  29. package/dist/web/templates-admin.d.ts +10 -2
  30. package/dist/web/templates-admin.js +36 -3
  31. package/dist/web/templates-page.js +1 -0
  32. package/dist/web/workflow-admin.d.ts +46 -0
  33. package/dist/web/workflow-admin.js +195 -3
  34. package/dist/web/workflow-page.js +33 -3
  35. package/package.json +10 -1
@@ -1,7 +1,7 @@
1
1
  import { nextTimeOfDay, parseTimeOfDay, workspaceTimeZone } from '../time-zone.js';
2
2
  const GMAIL_KEYS = ['GMAIL_CLIENT_ID', 'GMAIL_CLIENT_SECRET', 'GMAIL_REFRESH_TOKEN'];
3
3
  const SHOPIFY_KEYS = ['SHOPIFY_STORE_DOMAIN', 'SHOPIFY_ADMIN_TOKEN'];
4
- const GITHUB_KEYS = ['GITHUB_TOKEN', 'GITHUB_DEFAULT_REPO'];
4
+ const GITHUB_APP = 'io.github.github/github-mcp-server';
5
5
  const SLACK_KEYS = ['SLACK_BOT_TOKEN', 'SLACK_DEFAULT_CHANNEL'];
6
6
  const WEB_SEARCH_KEYS = [['TAVILY_API_KEY', 'BRAVE_API_KEY', 'FIRECRAWL_API_KEY']];
7
7
  const DOCS_NOTE = 'Put your policies and help articles in the project\'s docs folder, as Markdown or text files (or point DOCS_SEARCH_PATH at another folder)';
@@ -128,20 +128,22 @@ Rules:
128
128
  risk: 'medium',
129
129
  asksFirst: ['opening or commenting on issues'],
130
130
  needs: [
131
- { kind: 'ability', name: 'lp-github', description: 'your repository\'s commits, deploys and issues' },
131
+ { kind: 'app', name: 'GitHub', description: 'your repository\'s commits, deploys and issues' },
132
132
  { kind: 'ability', name: 'lp-slack-notifier', description: 'posting the summary to Slack' },
133
133
  ],
134
134
  agent: {
135
135
  name: 'incident-triage',
136
136
  systemPrompt: `You help the on-call engineer triage incidents.
137
137
 
138
+ Our repository on GitHub: [owner/name] (edit this). Pass its owner and repo to every GitHub tool.
139
+
138
140
  When you get an alert, error or bug report:
139
141
  1. Summarise it in two sentences: what is failing and who is affected.
140
142
  2. Rate the severity: SEV1 (customers can't use the product), SEV2 (a major feature is broken), SEV3 (degraded or a workaround exists), SEV4 (minor).
141
- 3. Find what changed shortly before it started: github_list_workflow_runs for recent deploys and failed builds, github_list_pull_requests for recently merged changes, and github_list_commits (narrowed with since_hours and path) for detail. Match times carefully.
142
- 4. Check whether it's already known with github_search_issues; read a match with github_get_issue.
143
+ 3. Find what changed shortly before it started: github_actions_list (method list_workflow_runs) for recent deploys and failed builds, github_list_pull_requests (state closed) for recently merged changes, and github_list_commits (narrowed with since and path) for detail. Match times carefully.
144
+ 4. Check whether it's already known with github_search_issues; read a match with github_issue_read (method get).
143
145
  5. Post a short summary to Slack with post_to_slack: what's broken, severity, the most likely cause with links, and one next step (who should look, what to check, or whether to roll back).
144
- 6. If an issue already tracks it, add your findings with github_comment_issue; otherwise, for SEV1 or SEV2, open one with github_create_issue. Both ask a person first.
146
+ 6. If an issue already tracks it, add your findings with github_add_issue_comment; otherwise, for SEV1 or SEV2, open one with github_issue_write (method create). Both ask a person first.
145
147
 
146
148
  Rules:
147
149
  - Only read on your own; never roll anything back yourself.
@@ -151,12 +153,13 @@ Rules:
151
153
  },
152
154
  },
153
155
  setup: {
154
- abilities: ['lp-github', 'lp-slack-notifier'],
155
- keys: [...GITHUB_KEYS, ...SLACK_KEYS],
156
+ abilities: ['lp-slack-notifier'],
157
+ apps: [{ id: GITHUB_APP, title: 'GitHub', tools: ['list_commits', 'list_pull_requests', 'actions_list', 'search_issues', 'issue_read', 'issue_write', 'add_issue_comment'] }],
158
+ keys: [...SLACK_KEYS],
156
159
  rules: [
157
160
  { name: 'template-slack-summary-allowed', tool: 'post_to_slack', decision: 'allow' },
158
- { name: 'template-new-issues-need-approval', tool: 'github_create_issue', decision: 'ask' },
159
- { name: 'template-comments-need-approval', tool: 'github_comment_issue', decision: 'ask' },
161
+ { name: 'template-new-issues-need-approval', tool: 'github_issue_write', decision: 'ask' },
162
+ { name: 'template-comments-need-approval', tool: 'github_add_issue_comment', decision: 'ask' },
160
163
  ],
161
164
  needsApprovals: true,
162
165
  notes: ['Send it alerts: paste one into Chat, have your alerting tool call it over HTTP, or reply to it in Slack'],
@@ -280,12 +283,14 @@ Rules:
280
283
  asksFirst: ['replying to the sender'],
281
284
  needs: [
282
285
  { kind: 'ability', name: 'lp-gmail', description: 'the shared inbox' },
283
- { kind: 'ability', name: 'lp-github', description: 'tasks, as GitHub issues' },
286
+ { kind: 'app', name: 'GitHub', description: 'tasks, as GitHub issues' },
284
287
  ],
285
288
  agent: {
286
289
  name: 'inbox-to-tasks',
287
290
  systemPrompt: `You turn requests in our shared inbox into tasks, kept as GitHub issues.
288
291
 
292
+ Tasks live in this GitHub repository: [owner/name] (edit this). Pass its owner and repo to every GitHub tool.
293
+
289
294
  Who handles what (edit this; GitHub usernames):
290
295
  - Billing and invoices: [username]
291
296
  - Technical problems: [username]
@@ -294,8 +299,8 @@ Who handles what (edit this; GitHub usernames):
294
299
  When asked to handle new emails:
295
300
  1. Find them with gmail_search, query "is:unread in:inbox". Read each with gmail_get_thread.
296
301
  2. Decide whether it's a request someone needs to act on. Newsletters, receipts, out-of-office replies and spam are not: mark them read and move on.
297
- 3. Check whether a task already exists with github_search_issues (the sender and subject). If so, add the new details with github_comment_issue instead.
298
- 4. Otherwise open one with github_create_issue: a short title starting with a verb, a two-line summary, the sender, the due date (the date they asked for, or 2 working days if they didn't say), and the owner from the list above as the assignee.
302
+ 3. Check whether a task already exists with github_search_issues (the sender and subject). If so, add the new details with github_add_issue_comment instead.
303
+ 4. Otherwise open one with github_issue_write (method create): a short title starting with a verb, a two-line summary, the sender, the due date (the date they asked for, or 2 working days if they didn't say), and the owner from the list above as the assignee.
299
304
  5. Reply to the sender with gmail_send (reply_to_message_id set): who is handling it and by when. A person approves it first.
300
305
  6. Mark the email read with gmail_modify_labels.
301
306
 
@@ -307,14 +312,15 @@ Rules:
307
312
  },
308
313
  },
309
314
  setup: {
310
- abilities: ['lp-gmail', 'lp-github'],
311
- keys: [...GMAIL_KEYS, ...GITHUB_KEYS],
315
+ abilities: ['lp-gmail'],
316
+ apps: [{ id: GITHUB_APP, title: 'GitHub', tools: ['search_issues', 'issue_write', 'add_issue_comment'] }],
317
+ keys: [...GMAIL_KEYS],
312
318
  rules: [
313
319
  { name: 'template-read-mail-allowed', tool: 'gmail_search', decision: 'allow' },
314
320
  { name: 'template-read-thread-allowed', tool: 'gmail_get_thread', decision: 'allow' },
315
321
  { name: 'template-labels-allowed', tool: 'gmail_modify_labels', decision: 'allow' },
316
- { name: 'template-tasks-allowed', tool: 'github_create_issue', decision: 'allow' },
317
- { name: 'template-task-updates-allowed', tool: 'github_comment_issue', decision: 'allow' },
322
+ { name: 'template-tasks-allowed', tool: 'github_issue_write', decision: 'allow' },
323
+ { name: 'template-task-updates-allowed', tool: 'github_add_issue_comment', decision: 'allow' },
318
324
  { name: 'template-replies-need-approval', tool: 'gmail_send', decision: 'ask' },
319
325
  ],
320
326
  schedule: { every: '10m', name: 'Check the shared inbox', message: 'Turn the new emails in the shared inbox into tasks.' },
@@ -1,6 +1,6 @@
1
1
  import type { ToolSchema } from '#core/agent-config.js';
2
2
  import type { ModelCall } from '#core/run-agent.js';
3
- import type { StarterTemplate } from './templates.js';
3
+ import type { StarterTemplate, TemplateApp } from './templates.js';
4
4
  export type Decision = 'allow' | 'ask' | 'deny';
5
5
  export interface WorkflowStep {
6
6
  title: string;
@@ -18,6 +18,30 @@ export interface WorkflowStep {
18
18
  * an ability): the source's name. It's copied to the agent that runs the
19
19
  * workflow, not installed. */
20
20
  connection?: string;
21
+ /** For a step done by an MCP server's tool, when no ability has it: the
22
+ * server's id (search_mcp_servers). `tool` is then the server's own
23
+ * tool name, if the planner could see them. On Create the server
24
+ * becomes a private ability (core/mcp-ability-generator.ts) and the
25
+ * step points at its tool instead. */
26
+ mcp?: string;
27
+ }
28
+ /** An MCP server a plan would set up as a private ability. */
29
+ export interface PlannedMcpServer {
30
+ id: string;
31
+ title: string;
32
+ url: string;
33
+ description: string;
34
+ /** Saved by the workspace, or on its publisher's verified domain. */
35
+ verified: boolean;
36
+ source: 'saved' | 'registry';
37
+ /** The private ability it becomes, e.g. mcp-linear. */
38
+ ability: string;
39
+ /** The setting its key is saved as, when it takes one. */
40
+ keyEnv: string;
41
+ /** Whether that setting already has a value. */
42
+ keySet: boolean;
43
+ /** The workspace already has an ability made from this server. */
44
+ existing?: string;
21
45
  }
22
46
  export interface WorkflowPlan {
23
47
  name: string;
@@ -64,6 +88,11 @@ export interface WorkflowPlan {
64
88
  };
65
89
  /** What the workspace can't do yet, in plain words. */
66
90
  gaps: string[];
91
+ /** MCP servers its steps use, set up on Create (filled in after planning). */
92
+ mcpServers?: PlannedMcpServer[];
93
+ /** Connected apps it adds to the agent, with just the tools its steps
94
+ * use (filled in on Create, from mcpServers). */
95
+ apps?: TemplateApp[];
67
96
  }
68
97
  export interface WorkflowPlannerTools {
69
98
  /** Every agent: its tools, installed abilities, rules and messaging. */
@@ -81,9 +110,13 @@ export interface WorkflowPlannerTools {
81
110
  /** An ability's tools (with the decision each ships with) and settings. */
82
111
  abilityDetails(spec: string): Promise<unknown>;
83
112
  listConnections(): Promise<Record<string, unknown>>;
113
+ /** MCP servers matching a query (core/mcp-directory.ts). Optional: without it the planner only plans with abilities. */
114
+ searchMcpServers?(query: string): Promise<unknown>;
115
+ /** A server's tools, when it can be listed without a key (or with one already set). */
116
+ mcpServerTools?(id: string): Promise<unknown>;
84
117
  }
85
- export declare const WORKFLOW_PLANNER_PROMPT = "You turn a workflow a person describes into a plan for a LoopEngine agent.\n\nLoopEngine agents call tools. Tools come from abilities (installable packages: public ones on npm, or the workspace's own private ones) or are built in. Each tool has a permission: allow (runs on its own), ask (waits for a person to approve, edit or deny it) or deny. When nobody is in Chat, questions and approvals go to the agent's messaging app (Slack, Telegram, Lark or email) and the run waits, for days if needed, then carries on.\n\nHow to plan:\n1. Look before you plan: list_agents (what exists, and what each agent's tools and rules are), list_workspace_abilities, search_public_abilities for each kind of step (even when an agent already has a connected tool for it), ability_details for any ability you might use (its exact tool names and the settings it needs), and list_connections.\n2. Split the workflow into steps, in order. For each step:\n - something to fetch or do: kind \"tool\", with the exact tool name and where it comes from. Pick the source in this order:\n a. A tool the target agent already has (from list_agents): use it, nothing to install.\n b. An ability: a workspace ability (list_workspace_abilities) or a public one (search_public_abilities, e.g. \"firecrawl\" or \"web search\" for searching and reading the web). Set \"ability\" to its install spec (the npm name, or a workspace ability's spec) and use the exact tool name from ability_details.\n c. Only when no ability does it: a tool in some agent's connectedTools (a connected service, usually one that needs a person to sign in, like Gmail or HubSpot). Give its exact tool name, set \"connection\" to that source's name and leave \"ability\" empty (it's copied to the agent that runs the workflow). Never put a connection's name in abilities.\n An ability beats a connected tool even when another agent already has the connected one: it's faster, needs only a key, and travels with the agent. If you chose a connected tool, say why in gaps.\n - the person chooses or answers (\"ask me which\", \"let me pick\"): kind \"ask\", tool \"system_ask_user\" (built in, no ability). Say what options are offered. It reaches them through the agent's messaging app by itself, so don't add a separate step to post to Slack or another chat first, and don't install a chat ability just to ask.\n - the person reviews or approves before something happens (\"let me review it\", \"before it's sent\"): kind \"approval\", on the tool that does that thing; its rule becomes \"ask\".\n - the agent's own work (writing, summarising, choosing): kind \"other\".\n - nothing found can do it: set \"missing\" to what's needed, and add it to gaps.\n3. Rules: every tool the workflow uses gets one. Reading and searching: allow. Anything a person should see first (sending, publishing, paying, creating something others see): ask. Never set deny unless asked.\n4. Skill: the steps in order, as numbered instructions the agent follows, naming the tools and saying exactly when to stop and ask. Write it so the agent keeps going after each answer or decision.\n5. Trigger: a schedule (\"every Monday at 9\" is every \"7d\", at \"09:00\", weekday 1) or on_request (started from Chat or a message). startMessage is what the agent receives when it starts.\n6. Target: an existing agent only when it already has (or can install) the tools, none of the workflow's rules would change a decision it already has, and the same people would approve. Otherwise a new agent, with a short lowercase-hyphenated name and a system prompt saying who it is and what it's for. Give the reason either way.\n7. keys: the setting names its abilities need that list_connections or the agents don't show as set (from ability_details settings).\n\nThen call save_plan once, with the whole plan. Don't ask the person questions: if something is unclear, plan the most reasonable version and note the assumption in gaps.";
86
- export declare function workflowPlannerTools(): ToolSchema[];
118
+ export declare const WORKFLOW_PLANNER_PROMPT = "You turn a workflow a person describes into a plan for a LoopEngine agent.\n\nLoopEngine agents call tools. Tools come from abilities (installable packages: public ones on npm, or the workspace's own private ones) or are built in. Each tool has a permission: allow (runs on its own), ask (waits for a person to approve, edit or deny it) or deny. When nobody is in Chat, questions and approvals go to the agent's messaging app (Slack, Telegram, Lark or email) and the run waits, for days if needed, then carries on.\n\nHow to plan:\n1. Look before you plan: list_agents (what exists, and what each agent's tools and rules are), list_workspace_abilities, search_public_abilities for each kind of step, ability_details for any ability you might use (its exact tool names and the settings it needs), and list_connections.\n2. Split the workflow into steps, in order. For each step:\n - something to fetch or do: kind \"tool\", with the exact tool name and where it comes from. Pick the source in this order:\n a. A tool the target agent already has (from list_agents): use it, nothing to install.\n b. An ability: a workspace ability (list_workspace_abilities) or a public one (search_public_abilities, e.g. \"firecrawl\" or \"web search\" for searching and reading the web). Set \"ability\" to its install spec (the npm name, or a workspace ability's spec) and use the exact tool name from ability_details.\n c. Only when no ability can do it, and search_mcp_servers is available: an app's MCP server. Search for the app (e.g. \"linear\", \"stripe\"), pick the app's own server (official), else a saved or verified one, then call mcp_server_tools. Set \"mcp\" to the server's id, and \"tool\" to its exact tool name when mcp_server_tools lists them (leave \"tool\" empty when it needs a key first, and say in \"detail\" what the step does). It's set up as a private ability when the workflow is created, which picks its tools and their permissions, so add no rules for MCP tools.\n - the person chooses or answers (\"ask me which\", \"let me pick\"): kind \"ask\", tool \"system_ask_user\" (built in, no ability). Say what options are offered. It reaches them through the agent's messaging app by itself, so don't add a separate step to post to Slack or another chat first, and don't install a chat ability just to ask.\n - the person reviews or approves before something happens (\"let me review it\", \"before it's sent\"): kind \"approval\", on the tool that does that thing; its rule becomes \"ask\".\n - the agent's own work (writing, summarising, choosing): kind \"other\".\n - nothing found can do it: set \"missing\" to what's needed, and add it to gaps.\n3. Rules: every tool the workflow uses gets one. Reading and searching: allow. Anything a person should see first (sending, publishing, paying, creating something others see): ask. Never set deny unless asked.\n4. Skill: the steps in order, as numbered instructions the agent follows, naming the tools and saying exactly when to stop and ask. Write it so the agent keeps going after each answer or decision.\n5. Trigger: a schedule (\"every Monday at 9\" is every \"7d\", at \"09:00\", weekday 1) or on_request (started from Chat or a message). startMessage is what the agent receives when it starts.\n6. Target: an existing agent only when it already has (or can install) the tools, none of the workflow's rules would change a decision it already has, and the same people would approve. Otherwise a new agent, with a short lowercase-hyphenated name and a system prompt saying who it is and what it's for. Give the reason either way.\n7. keys: the setting names its abilities need that list_connections or the agents don't show as set (from ability_details settings). Not for MCP servers: their keys are asked for on the plan.\n\nThen call save_plan once, with the whole plan. Don't ask the person questions: if something is unclear, plan the most reasonable version and note the assumption in gaps.";
119
+ export declare function workflowPlannerTools(withMcp?: boolean): ToolSchema[];
87
120
  /** lowercase-hyphenated, 2–40 characters. */
88
121
  export declare function slugify(value: string, fallback?: string): string;
89
122
  /** A plan from the model's save_plan input, or undefined when it lacks a
@@ -4,13 +4,12 @@ export const WORKFLOW_PLANNER_PROMPT = `You turn a workflow a person describes i
4
4
  LoopEngine agents call tools. Tools come from abilities (installable packages: public ones on npm, or the workspace's own private ones) or are built in. Each tool has a permission: allow (runs on its own), ask (waits for a person to approve, edit or deny it) or deny. When nobody is in Chat, questions and approvals go to the agent's messaging app (Slack, Telegram, Lark or email) and the run waits, for days if needed, then carries on.
5
5
 
6
6
  How to plan:
7
- 1. Look before you plan: list_agents (what exists, and what each agent's tools and rules are), list_workspace_abilities, search_public_abilities for each kind of step (even when an agent already has a connected tool for it), ability_details for any ability you might use (its exact tool names and the settings it needs), and list_connections.
7
+ 1. Look before you plan: list_agents (what exists, and what each agent's tools and rules are), list_workspace_abilities, search_public_abilities for each kind of step, ability_details for any ability you might use (its exact tool names and the settings it needs), and list_connections.
8
8
  2. Split the workflow into steps, in order. For each step:
9
9
  - something to fetch or do: kind "tool", with the exact tool name and where it comes from. Pick the source in this order:
10
10
  a. A tool the target agent already has (from list_agents): use it, nothing to install.
11
11
  b. An ability: a workspace ability (list_workspace_abilities) or a public one (search_public_abilities, e.g. "firecrawl" or "web search" for searching and reading the web). Set "ability" to its install spec (the npm name, or a workspace ability's spec) and use the exact tool name from ability_details.
12
- c. Only when no ability does it: a tool in some agent's connectedTools (a connected service, usually one that needs a person to sign in, like Gmail or HubSpot). Give its exact tool name, set "connection" to that source's name and leave "ability" empty (it's copied to the agent that runs the workflow). Never put a connection's name in abilities.
13
- An ability beats a connected tool even when another agent already has the connected one: it's faster, needs only a key, and travels with the agent. If you chose a connected tool, say why in gaps.
12
+ c. Only when no ability can do it, and search_mcp_servers is available: an app's MCP server. Search for the app (e.g. "linear", "stripe"), pick the app's own server (official), else a saved or verified one, then call mcp_server_tools. Set "mcp" to the server's id, and "tool" to its exact tool name when mcp_server_tools lists them (leave "tool" empty when it needs a key first, and say in "detail" what the step does). It's set up as a private ability when the workflow is created, which picks its tools and their permissions, so add no rules for MCP tools.
14
13
  - the person chooses or answers ("ask me which", "let me pick"): kind "ask", tool "system_ask_user" (built in, no ability). Say what options are offered. It reaches them through the agent's messaging app by itself, so don't add a separate step to post to Slack or another chat first, and don't install a chat ability just to ask.
15
14
  - the person reviews or approves before something happens ("let me review it", "before it's sent"): kind "approval", on the tool that does that thing; its rule becomes "ask".
16
15
  - the agent's own work (writing, summarising, choosing): kind "other".
@@ -19,10 +18,10 @@ How to plan:
19
18
  4. Skill: the steps in order, as numbered instructions the agent follows, naming the tools and saying exactly when to stop and ask. Write it so the agent keeps going after each answer or decision.
20
19
  5. Trigger: a schedule ("every Monday at 9" is every "7d", at "09:00", weekday 1) or on_request (started from Chat or a message). startMessage is what the agent receives when it starts.
21
20
  6. Target: an existing agent only when it already has (or can install) the tools, none of the workflow's rules would change a decision it already has, and the same people would approve. Otherwise a new agent, with a short lowercase-hyphenated name and a system prompt saying who it is and what it's for. Give the reason either way.
22
- 7. keys: the setting names its abilities need that list_connections or the agents don't show as set (from ability_details settings).
21
+ 7. keys: the setting names its abilities need that list_connections or the agents don't show as set (from ability_details settings). Not for MCP servers: their keys are asked for on the plan.
23
22
 
24
23
  Then call save_plan once, with the whole plan. Don't ask the person questions: if something is unclear, plan the most reasonable version and note the assumption in gaps.`;
25
- export function workflowPlannerTools() {
24
+ export function workflowPlannerTools(withMcp = false) {
26
25
  const step = {
27
26
  type: 'object',
28
27
  properties: {
@@ -30,9 +29,9 @@ export function workflowPlannerTools() {
30
29
  kind: { type: 'string', enum: ['tool', 'ask', 'approval', 'other'] },
31
30
  tool: { type: 'string' },
32
31
  ability: { type: 'string' },
32
+ mcp: { type: 'string', description: "an MCP server's id, for a step no ability can do" },
33
33
  detail: { type: 'string' },
34
34
  missing: { type: 'string' },
35
- connection: { type: 'string' },
36
35
  },
37
36
  required: ['title', 'kind'],
38
37
  };
@@ -42,6 +41,10 @@ export function workflowPlannerTools() {
42
41
  { name: 'search_public_abilities', description: 'Search public LoopEngine abilities on npm, e.g. "web search", "wordpress", "slack".', input_schema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] } },
43
42
  { name: 'ability_details', description: "An ability's exact tool names, what each does, the permission each ships with, and the settings it needs. spec: its npm name or workspace spec.", input_schema: { type: 'object', properties: { spec: { type: 'string' } }, required: ['spec'] } },
44
43
  { name: 'list_connections', description: 'What is set up: model providers with keys, messaging apps per agent.', input_schema: { type: 'object', properties: {} } },
44
+ ...(withMcp ? [
45
+ { name: 'search_mcp_servers', description: "Search apps' MCP servers (the workspace's saved ones, then the public MCP Registry), for a step no ability can do. query: the app's name, e.g. \"linear\".", input_schema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] } },
46
+ { name: 'mcp_server_tools', description: "An MCP server's tools (name, description, read-only or not), or a note that it needs a key before they can be seen. id: from search_mcp_servers.", input_schema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
47
+ ] : []),
45
48
  {
46
49
  name: 'save_plan',
47
50
  description: 'Save the workflow plan. Call once, at the end.',
@@ -113,6 +116,7 @@ export function validateWorkflowPlan(raw) {
113
116
  ...(str(step.detail, 600) ? { detail: str(step.detail, 600) } : {}),
114
117
  ...(str(step.missing, 300) ? { missing: str(step.missing, 300) } : {}),
115
118
  ...(str(step.connection, 120) ? { connection: str(step.connection, 120) } : {}),
119
+ ...(str(step.mcp, 200) && !str(step.ability, 300) ? { mcp: str(step.mcp, 200) } : {}),
116
120
  }];
117
121
  });
118
122
  if (!steps.length)
@@ -128,14 +132,17 @@ export function validateWorkflowPlan(raw) {
128
132
  ...(typeof triggerRaw.weekday === 'number' && triggerRaw.weekday >= 0 && triggerRaw.weekday <= 7 ? { weekday: triggerRaw.weekday } : {}),
129
133
  }
130
134
  : { kind: 'on_request', description: str(triggerRaw.description, 200) ?? 'when asked' };
135
+ // An MCP step's permissions are set when its server becomes an ability.
136
+ const mcpTools = new Set(steps.flatMap((s) => (s.mcp && s.tool ? [s.tool] : [])));
131
137
  const rules = (Array.isArray(r.rules) ? r.rules : []).flatMap((x) => {
132
138
  const rule = (x ?? {});
133
139
  const tool = str(rule.tool, 120);
134
140
  return tool && isDecision(rule.decision) && tool !== 'system_ask_user' ? [{ tool, decision: rule.decision }] : [];
135
- });
136
- // An approval step's tool always asks first.
141
+ }).filter((rule) => !mcpTools.has(rule.tool));
142
+ // An approval step's tool always asks first (an MCP step's tool gets
143
+ // that when its ability is made).
137
144
  for (const step of steps) {
138
- if (step.kind !== 'approval' || !step.tool)
145
+ if (step.kind !== 'approval' || !step.tool || step.mcp)
139
146
  continue;
140
147
  const rule = rules.find((x) => x.tool === step.tool);
141
148
  if (rule)
@@ -178,7 +185,7 @@ export async function planWorkflowWithModel(modelCall, description, tools) {
178
185
  for (let round = 0; round <= MAX_TOOL_ROUNDS; round++) {
179
186
  let response;
180
187
  try {
181
- response = await modelCall(messages, WORKFLOW_PLANNER_PROMPT, workflowPlannerTools());
188
+ response = await modelCall(messages, WORKFLOW_PLANNER_PROMPT, workflowPlannerTools(Boolean(tools.searchMcpServers)));
182
189
  }
183
190
  catch (err) {
184
191
  // A reply cut off part-way (its tool arguments end mid-JSON) usually
@@ -229,6 +236,12 @@ export async function planWorkflowWithModel(modelCall, description, tools) {
229
236
  case 'list_connections':
230
237
  output = await tools.listConnections();
231
238
  break;
239
+ case 'search_mcp_servers':
240
+ output = tools.searchMcpServers ? await tools.searchMcpServers(String(input.query ?? '')) : 'not available';
241
+ break;
242
+ case 'mcp_server_tools':
243
+ output = tools.mcpServerTools ? await tools.mcpServerTools(String(input.id ?? '')) : 'not available';
244
+ break;
232
245
  case 'save_plan':
233
246
  plan = validateWorkflowPlan(input);
234
247
  output = plan ? 'saved' : 'that plan needs a name, summary, at least one step, a skill body and newAgent.systemPrompt; fix and call again';
@@ -266,6 +279,7 @@ export function workflowTemplate(plan) {
266
279
  },
267
280
  setup: {
268
281
  abilities: plan.abilities,
282
+ ...(plan.apps?.length ? { apps: plan.apps } : {}),
269
283
  keys: plan.keys,
270
284
  skills: [{ id: plan.id, description: plan.skill.description, body: plan.skill.body }],
271
285
  rules: plan.rules.map((r) => ({ name: slugify(`${plan.id}-${r.tool}`, `${plan.id}-rule`), tool: r.tool, decision: r.decision })),
@@ -1,6 +1,6 @@
1
1
  // The workspace's own copies of private abilities: installed once from a
2
2
  // private source (core/ability-sources.ts) on the console's Abilities
3
- // page, then added to any agent from its Abilities tab. Each copy is a
3
+ // page, then added to any agent from its Apps tab. Each copy is a
4
4
  // folder under abilities/ in the project root (or the folder
5
5
  // LOOPENGINE_ABILITIES_DIR names), and abilities/workspace.json says
6
6
  // where each came from:
package/dist/index.d.ts CHANGED
@@ -99,6 +99,9 @@ export type { PublicAbilitySearchResult } from './web/abilities-admin.js';
99
99
  export { AbilitySourceError, abilityOrigin, readAbilitySources, saveAbilitySource, removeAbilitySource, listSourceAbilities, sourceTokenVar, type AbilitySource, type SourceAbility, type AbilityOrigin } from './core/ability-sources.js';
100
100
  export { describeAbilitySources, listPrivateAbilities, saveAbilitySourceFromConsole, removeAbilitySourceFromConsole } from './web/ability-sources-admin.js';
101
101
  export { readAbilityDetails, readToolDefinitions, type AbilityDetails } from './core/ability-details.js';
102
+ export { loadMcpAbilityTools, mcpAbilityLoadError, closeMcpConnections, resolveMcpManifest, listMcpServerTools, isMcpAuthError, type McpServerManifest, type McpAuth, type InstalledMcpRecord, type McpToolInfo } from './core/mcp-abilities.js';
103
+ export { searchMcpServers, getMcpServer, savedMcpServers, type McpDirectoryServer } from './core/mcp-directory.js';
104
+ export { generateMcpAbility, mcpAbilityNames, McpAbilityGenerationError, type GeneratedMcpAbility, type McpAbilityRequest } from './core/mcp-ability-generator.js';
102
105
  export { abilityDetails } from './web/ability-details-admin.js';
103
106
  export { WorkspaceAbilityError, listWorkspaceAbilities, addWorkspaceAbility, removeWorkspaceAbility, workspaceAbilitiesDir, type WorkspaceAbility } from './core/workspace-abilities.js';
104
107
  export { installWorkspaceAbility, installUploadedAbility, MAX_UPLOAD_BYTES, type AbilityUpload } from './web/workspace-abilities-admin.js';
package/dist/index.js CHANGED
@@ -89,6 +89,9 @@ export { searchPublicAbilities, fetchLatestAbilityVersion } from './web/abilitie
89
89
  export { AbilitySourceError, abilityOrigin, readAbilitySources, saveAbilitySource, removeAbilitySource, listSourceAbilities, sourceTokenVar } from './core/ability-sources.js';
90
90
  export { describeAbilitySources, listPrivateAbilities, saveAbilitySourceFromConsole, removeAbilitySourceFromConsole } from './web/ability-sources-admin.js';
91
91
  export { readAbilityDetails, readToolDefinitions } from './core/ability-details.js';
92
+ export { loadMcpAbilityTools, mcpAbilityLoadError, closeMcpConnections, resolveMcpManifest, listMcpServerTools, isMcpAuthError } from './core/mcp-abilities.js';
93
+ export { searchMcpServers, getMcpServer, savedMcpServers } from './core/mcp-directory.js';
94
+ export { generateMcpAbility, mcpAbilityNames, McpAbilityGenerationError } from './core/mcp-ability-generator.js';
92
95
  export { abilityDetails } from './web/ability-details-admin.js';
93
96
  export { WorkspaceAbilityError, listWorkspaceAbilities, addWorkspaceAbility, removeWorkspaceAbility, workspaceAbilitiesDir } from './core/workspace-abilities.js';
94
97
  export { installWorkspaceAbility, installUploadedAbility, MAX_UPLOAD_BYTES } from './web/workspace-abilities-admin.js';
@@ -8,7 +8,7 @@ export declare function searchPublicAbilities(query?: string): Promise<PublicAbi
8
8
  * `packageName`, or null if it can't be determined — not published
9
9
  * there at all (a private/file:/git-installed ability, the common case
10
10
  * during local development), a network hiccup, or a timeout. Best-effort
11
- * only: the Abilities tab uses this to decide whether an installed
11
+ * only: the Apps tab uses this to decide whether an installed
12
12
  * ability's own "Upgrade" button is worth showing, not to block
13
13
  * anything, so a failure here surfaces as "can't tell, show the button
14
14
  * anyway" rather than an error the operator has to deal with. */
@@ -43,7 +43,7 @@ const NPM_LATEST_VERSION_TIMEOUT_MS = 5000;
43
43
  * `packageName`, or null if it can't be determined — not published
44
44
  * there at all (a private/file:/git-installed ability, the common case
45
45
  * during local development), a network hiccup, or a timeout. Best-effort
46
- * only: the Abilities tab uses this to decide whether an installed
46
+ * only: the Apps tab uses this to decide whether an installed
47
47
  * ability's own "Upgrade" button is worth showing, not to block
48
48
  * anything, so a failure here surfaces as "can't tell, show the button
49
49
  * anyway" rather than an error the operator has to deal with. */