@withone/mcp 2.1.0 → 2.2.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.
package/README.md CHANGED
@@ -66,7 +66,7 @@ The server exposes three MCP tools:
66
66
 
67
67
  ### Finding actions
68
68
 
69
- `find_one_actions` takes one `requests` entry per operation, each a kebab-case `platform` and a short `intent` naming the operation alone (`"send a message to a channel"`, not the message). An optional `task` describes the whole job in general terms, which helps choose between similar actions. For each intent, One's decision model picks the action to use; the answer documents it, sets apart a substitute when the model was unsure, and lists a few alternatives. Large documents come back as a digest; call `find_one_actions` again with `load: [{ action_id, section }]` for a section it left out, `full: true` for the whole document, or `toc: true` for its table of contents. `ONE_ACTION_IDS`, `ONE_PERMISSIONS` and `ONE_CONNECTION_KEYS` apply to every answer: an action they refuse is never offered, and when they refuse the model's pick, the next allowed candidate takes its place.
69
+ `find_one_actions` takes one `requests` entry per operation, each a kebab-case `platform` and a short `intent` naming the operation alone (`"send a message to a channel"`, not the message). A `task` describes the whole job in general terms, and an `ai_model` names the model asking; agents are asked to send both with every find, since the task helps tell similar actions apart. For each intent, One's decision model picks the action to use; the answer documents it, sets apart a substitute when the model was unsure, and lists a few alternatives. A `platform` no active platform has (`clickup` for `click-up`) is not searched: the answer names the platforms it may have meant, closest first, so the agent can ask again with the right one. Large documents come back as a digest; call `find_one_actions` again with `load: [{ action_id, section }]` for a section it left out, `full: true` for the whole document, or `toc: true` for its table of contents. `ONE_ACTION_IDS`, `ONE_PERMISSIONS` and `ONE_CONNECTION_KEYS` apply to every answer: an action they refuse is never offered, and when they refuse the model's pick, the next allowed candidate takes its place.
70
70
 
71
71
  Upgrading from 1.x: `search_one_platform_actions` and `get_one_action_knowledge` are removed. `find_one_actions` replaces both.
72
72
 
package/build/find.js CHANGED
@@ -93,6 +93,13 @@ function status(answer, narrowed) {
93
93
  if (narrowed.replaced) {
94
94
  return 'The chosen action is not allowed on this server, so this is the next allowed candidate: check it fits before using it.';
95
95
  }
96
+ if (answer.selector === 'unknown_platform') {
97
+ const opening = `No active platform is named \`${answer.platform}\`.`;
98
+ const suggestions = answer.suggestions ?? [];
99
+ return suggestions.length === 0
100
+ ? `${opening} Check the platform name: \`list_one_integrations\` gives the exact name of each connected integration.`
101
+ : `${opening} Did you mean ${suggestions.map((s) => `\`${s}\``).join(', ')}? Ask again with the platform you meant.`;
102
+ }
96
103
  if (answer.selector === 'none_fit' || answer.selected.length === 0) {
97
104
  return answer.alternatives.length === 0
98
105
  ? 'No action fits this intent, and nothing close was found. Rephrase it, or check the platform name.'
@@ -131,6 +131,18 @@ describe('find_one_actions', () => {
131
131
  assert.ok(text.includes('No action fits this intent, and nothing close was found.'));
132
132
  assert.ok(!text.includes(FIND_EXECUTE_HINT));
133
133
  });
134
+ it('names the platforms core suggests for a name no active platform has', async () => {
135
+ const unknown = (suggestions) => answer({ platform: 'clickup', intent: 'create a task', selected: [], alternatives: [], selector: 'unknown_platform', confidence: undefined, suggestions });
136
+ const asked = [
137
+ { platform: 'clickup', intent: 'create a task' },
138
+ { platform: 'clickup', intent: 'create a task' },
139
+ ];
140
+ const text = await findOneActions({ requests: asked }, deps([unknown(['click-up', 'click-house']), unknown()]), settings());
141
+ assert.ok(text.includes('No active platform is named `clickup`. Did you mean `click-up`, `click-house`? Ask again with the platform you meant.'), text);
142
+ assert.ok(text.includes('Check the platform name: `list_one_integrations` gives the exact name'), text);
143
+ assert.ok(!text.includes('No action fits'), 'an unknown platform is not read as a platform with no fitting action');
144
+ assert.ok(!text.includes(FIND_EXECUTE_HINT), 'nothing was documented');
145
+ });
134
146
  it('loads what each entry asked for, refusing one this server may not use in the words for a missing one', async () => {
135
147
  const text = await findOneActions({ load: [{ action_id: 'a', section: 'Response Fields' }, { action_id: 'missing' }, { action_id: 'b' }] }, deps([], { a: LARGE }), settings({ actionIds: ['a', 'missing'] }));
136
148
  assert.ok(text.includes('### Action a'));
package/build/schemas.js CHANGED
@@ -21,14 +21,14 @@ export const findOneActionsInputSchema = {
21
21
  platform: z.string().describe('The kebab-case platform identifier (e.g. "gmail", "stripe").'),
22
22
  intent: z.string().describe('The operation alone, in a few words (e.g. "send a message to a channel"), without its data: no IDs, names or message text.')
23
23
  })).min(1).max(10).optional().describe('What to do, one entry per API operation, on any platforms (1 to 10). Each is the kebab-case platform (e.g. "gmail", "hubspot") and a short intent (e.g. "send an email", "find a contact by email").'),
24
- task: z.string().optional().describe('The whole task in one line, in general terms (e.g. "email a report to a contact"): what it does, without names, addresses, IDs or message text. It helps choose between similar actions. Never the chat history. Only with `requests`.'),
24
+ task: z.string().optional().describe('The whole task in one line, in general terms (e.g. "email a report to a contact"): what it does, without names, addresses, IDs or message text. Send it with every find: it helps tell similar actions apart. Never the chat history. Only with `requests`.'),
25
25
  load: z.array(z.object({
26
26
  action_id: z.string().describe("The actionId from a find_one_actions answer."),
27
27
  section: z.string().optional().describe('Section name(s) to load, comma-separated (e.g. "Response Fields"). Omit, with `full` and `toc` unset, for the digest. In knowledge mode every load returns the whole document, so `section` and `toc` do not narrow it.'),
28
28
  full: z.boolean().optional().describe("Load the whole document. Ignored when `section` is set."),
29
29
  toc: z.boolean().optional().describe("Load only the table of contents. Ignored when `section` is set.")
30
30
  })).min(1).max(10).optional().describe("More documentation for actions already found (1 to 10): a section the digest omitted, the whole document, the table of contents, or an alternative's documentation. Use instead of `requests`."),
31
- ai_model: z.string().optional().describe('The AI model you are running as (e.g. "claude-sonnet-5", "gpt-5"), recorded with this find so the account\'s usage can report which model asked. Optional.')
31
+ ai_model: z.string().optional().describe('Your model name as you know it (e.g. "claude-sonnet-5", "gpt-5"), recorded with this find. Send it with every find; leave it out only if you do not know which model you are.')
32
32
  };
33
33
  /**
34
34
  * Schema for executing One actions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/mcp",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "A Model Context Protocol Server for One",
5
5
  "type": "module",
6
6
  "bin": {