@withone/mcp 2.0.0 → 2.1.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/build/caller.js +28 -0
- package/build/caller.test.js +17 -0
- package/build/client.js +5 -2
- package/build/find.js +3 -3
- package/build/find.test.js +9 -1
- package/build/index.js +1 -0
- package/build/schemas.js +1 -1
- package/package.json +1 -1
package/build/caller.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a find names who asked, for One's record of what agents ask
|
|
3
|
+
* `find_one_actions` (pica-v2 #993).
|
|
4
|
+
*
|
|
5
|
+
* `x-one-source: cli` says the call came through One's own local tooling
|
|
6
|
+
* rather than a script holding the same key; this server reaches One through
|
|
7
|
+
* the same door as the CLI, and `cli` is the only surface core lets a client
|
|
8
|
+
* claim. `x-one-agent` names the MCP client that connected, as its
|
|
9
|
+
* `clientInfo` gave it (`claude-code/2.4.1`), and `x-one-agent-model` the model
|
|
10
|
+
* the agent says it is running as, which only the agent knows. All three are
|
|
11
|
+
* declared, never authenticated, and core drops a value it cannot read rather
|
|
12
|
+
* than refusing the find, so a wrong one can never break it.
|
|
13
|
+
*/
|
|
14
|
+
export const CALLER_SOURCE_HEADER = 'x-one-source';
|
|
15
|
+
export const CALLER_AGENT_HEADER = 'x-one-agent';
|
|
16
|
+
export const CALLER_MODEL_HEADER = 'x-one-agent-model';
|
|
17
|
+
export function callerHeaders(caller = {}) {
|
|
18
|
+
const headers = { [CALLER_SOURCE_HEADER]: 'cli' };
|
|
19
|
+
const name = caller.client?.name?.trim();
|
|
20
|
+
if (name) {
|
|
21
|
+
const version = caller.client?.version?.trim();
|
|
22
|
+
headers[CALLER_AGENT_HEADER] = version ? `${name}/${version}` : name;
|
|
23
|
+
}
|
|
24
|
+
const model = caller.model?.trim();
|
|
25
|
+
if (model)
|
|
26
|
+
headers[CALLER_MODEL_HEADER] = model;
|
|
27
|
+
return headers;
|
|
28
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { callerHeaders } from './caller.js';
|
|
4
|
+
describe('callerHeaders', () => {
|
|
5
|
+
it('always names the source, and nothing else when nothing is known', () => {
|
|
6
|
+
assert.deepEqual(callerHeaders(), { 'x-one-source': 'cli' });
|
|
7
|
+
});
|
|
8
|
+
it('names the MCP client with its version, as core reads it', () => {
|
|
9
|
+
assert.equal(callerHeaders({ client: { name: 'claude-code', version: '2.4.1' } })['x-one-agent'], 'claude-code/2.4.1');
|
|
10
|
+
assert.equal(callerHeaders({ client: { name: 'cursor' } })['x-one-agent'], 'cursor');
|
|
11
|
+
assert.equal(callerHeaders({ client: { name: ' ', version: '1.0' } })['x-one-agent'], undefined);
|
|
12
|
+
});
|
|
13
|
+
it('names the model only when the agent gave one', () => {
|
|
14
|
+
assert.equal(callerHeaders({ model: ' claude-sonnet-5 ' })['x-one-agent-model'], 'claude-sonnet-5');
|
|
15
|
+
assert.equal(callerHeaders({ model: ' ' })['x-one-agent-model'], undefined);
|
|
16
|
+
});
|
|
17
|
+
});
|
package/build/client.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import axios from 'axios';
|
|
10
10
|
import FormData from 'form-data';
|
|
11
|
+
import { callerHeaders } from './caller.js';
|
|
11
12
|
import { fetchPaginatedData, replacePathVariables } from './helpers.js';
|
|
12
13
|
/** Header names that only the server may set on an outgoing request. */
|
|
13
14
|
const RESERVED_HEADER_PREFIX = 'x-one-';
|
|
@@ -190,12 +191,14 @@ export class OneClient {
|
|
|
190
191
|
* @param task - The whole task in one line, which helps break ties
|
|
191
192
|
* @param knowledgeAgent - Search the catalog for writing integration code
|
|
192
193
|
* rather than the one for executing actions
|
|
194
|
+
* @param caller - The MCP client that connected and the model it named,
|
|
195
|
+
* sent so One records who asked
|
|
193
196
|
* @returns One answer per intent, in the order asked
|
|
194
197
|
* @throws {Error} Naming core's reason when it refuses the request
|
|
195
198
|
*/
|
|
196
|
-
async findActions(requests, task, knowledgeAgent) {
|
|
199
|
+
async findActions(requests, task, knowledgeAgent, caller = {}) {
|
|
197
200
|
try {
|
|
198
|
-
const response = await axios.post(`${this.baseUrl}/v1/available-actions/find`, { requests, ...(task ? { task } : {}), knowledgeAgent }, { headers: this.generateHeaders() });
|
|
201
|
+
const response = await axios.post(`${this.baseUrl}/v1/available-actions/find`, { requests, ...(task ? { task } : {}), knowledgeAgent }, { headers: { ...this.generateHeaders(), ...callerHeaders(caller) } });
|
|
199
202
|
return response.data || [];
|
|
200
203
|
}
|
|
201
204
|
catch (error) {
|
package/build/find.js
CHANGED
|
@@ -37,7 +37,7 @@ export class FindArgsError extends Error {
|
|
|
37
37
|
*/
|
|
38
38
|
export async function findOneActions(args, deps, settings) {
|
|
39
39
|
if (args.requests && !args.load) {
|
|
40
|
-
return find(args.requests, args.task, deps, settings);
|
|
40
|
+
return find(args.requests, args.task, deps, settings, { client: settings.client, model: args.ai_model });
|
|
41
41
|
}
|
|
42
42
|
if (args.load && !args.requests && args.task === undefined) {
|
|
43
43
|
return load(args.load, deps, settings);
|
|
@@ -141,12 +141,12 @@ function finish(sections, documented, settings) {
|
|
|
141
141
|
return body;
|
|
142
142
|
return `${body}\n\n---\n\n${settings.knowledgeAgent ? codeGuideRules() : FIND_EXECUTE_HINT}`;
|
|
143
143
|
}
|
|
144
|
-
async function find(requests, task, deps, settings) {
|
|
144
|
+
async function find(requests, task, deps, settings, caller) {
|
|
145
145
|
if (requests.length === 0 || requests.length > FIND_MAX_INTENTS) {
|
|
146
146
|
throw new FindArgsError(`Send between 1 and ${FIND_MAX_INTENTS} requests.`);
|
|
147
147
|
}
|
|
148
148
|
const reachable = requests.filter((r) => reaches(settings, r.platform));
|
|
149
|
-
const answers = reachable.length > 0 ? await deps.findActions(reachable, task, settings.knowledgeAgent) : [];
|
|
149
|
+
const answers = reachable.length > 0 ? await deps.findActions(reachable, task, settings.knowledgeAgent, caller) : [];
|
|
150
150
|
let next = 0;
|
|
151
151
|
const plans = requests.map((intent) => {
|
|
152
152
|
if (!reaches(settings, intent.platform)) {
|
package/build/find.test.js
CHANGED
|
@@ -37,10 +37,13 @@ const settings = (overrides = {}) => ({
|
|
|
37
37
|
/** Core answering `answers` in turn, and raw knowledge per action. */
|
|
38
38
|
function deps(answers, knowledge = {}) {
|
|
39
39
|
const asked = [];
|
|
40
|
+
const callers = [];
|
|
40
41
|
return {
|
|
41
42
|
asked,
|
|
42
|
-
|
|
43
|
+
callers,
|
|
44
|
+
async findActions(requests, _task, _knowledgeAgent, caller) {
|
|
43
45
|
asked.push(requests);
|
|
46
|
+
callers.push(caller);
|
|
44
47
|
return answers.slice(0, requests.length);
|
|
45
48
|
},
|
|
46
49
|
async getActionDetails(id) {
|
|
@@ -60,6 +63,11 @@ function deps(answers, knowledge = {}) {
|
|
|
60
63
|
}
|
|
61
64
|
const intent = [{ platform: 'gmail', intent: 'send an email' }];
|
|
62
65
|
describe('find_one_actions', () => {
|
|
66
|
+
it('tells core which client and model asked', async () => {
|
|
67
|
+
const found = deps([answer()]);
|
|
68
|
+
await findOneActions({ requests: intent, ai_model: 'claude-sonnet-5' }, found, settings({ client: { name: 'claude-code', version: '2.4.1' } }));
|
|
69
|
+
assert.deepEqual(found.callers, [{ client: { name: 'claude-code', version: '2.4.1' }, model: 'claude-sonnet-5' }]);
|
|
70
|
+
});
|
|
63
71
|
it('takes requests or load, never both, never neither, and no task with a load', async () => {
|
|
64
72
|
for (const args of [{}, { requests: intent, load: [{ action_id: 'a' }] }, { load: [{ action_id: 'a' }], task: 't' }]) {
|
|
65
73
|
await assert.rejects(findOneActions(args, deps([answer()]), settings()), (e) => e instanceof FindArgsError && e.message === FIND_ARGS_ERROR);
|
package/build/index.js
CHANGED
package/build/schemas.js
CHANGED
|
@@ -28,7 +28,7 @@ export const findOneActionsInputSchema = {
|
|
|
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"),
|
|
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.')
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
34
34
|
* Schema for executing One actions
|