@oneshot-agent/mcp-server 0.49.0 → 0.50.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
@@ -79,12 +79,12 @@ Every tool is named `oneshot_<name>`. Below are the names.
79
79
  | [Talk](https://docs.oneshotagent.com/api-reference/email/send) | `email` `voice` `sms` `inbox_list` `inbox_get` `sms_inbox_list` `sms_inbox_get` `notifications` `mark_notification_read` |
80
80
  | [Inbound calls](https://docs.oneshotagent.com/api-reference/voice/inbound) | `voice_number_provision` `voice_numbers` `voice_inbound_set` `voice_inbound_disable` `voice_inbound_calls` |
81
81
  | [Send from your domains](https://docs.oneshotagent.com/api-reference/email/domains) | `list_domains` `pause_domain` `resume_domain` |
82
- | [Find people and companies](https://docs.oneshotagent.com/api-reference/research/person-find) | `person_find` `person_profile` `person_email` `person_email_status` `person_dossier` `person_social_accounts` `person_articles` `person_posts` `person_interests` `person_followers` `company_find` `company_profile` `business_find` `business_resolve` `record_solicitations_find` |
82
+ | [Find people and companies](https://docs.oneshotagent.com/api-reference/research/person-find) | `person_find` `person_profile` `person_email` `person_email_status` `person_dossier` `person_social_accounts` `person_articles` `person_posts` `person_interests` `person_followers` `company_find` `company_profile` `company_dossier` `business_find` `business_resolve` `record_solicitations_find` |
83
83
  | [Lookups](https://docs.oneshotagent.com/tools-and-goals#lookups) | public records, places, products, vehicles, genes, proteins and more, one tool each |
84
84
  | [Think](https://docs.oneshotagent.com/api-reference/research) | `research` `predict` `solve` |
85
85
  | [Web and buying](https://docs.oneshotagent.com/api-reference/web-search) | `web_search` `web_read` `browser` `product_find` `commerce_buy` |
86
86
  | [Physical mail](https://docs.oneshotagent.com/api-reference/physical-mail/overview) | `physical_mail_upload` `physical_mail_validate_address` `physical_mail_preview` `physical_mail_quote` `physical_mail_approve` `physical_mail_send` `physical_mail_recover` `physical_mail_order` `physical_mail_cancel` |
87
- | [Connected systems](https://docs.oneshotagent.com/api-reference/connections/overview) | `connections_list` `ledger_invoices` `ledger_bills` `ledger_reports` `crm_accounts` `crm_contacts` `crm_opportunities` `tax_rates` `tax_calculate` `shipping_shipments` `shipping_tracking` `shipping_rates` `procurement_transactions` `procurement_bills` `procurement_suppliers` |
87
+ | [Connected systems](https://docs.oneshotagent.com/api-reference/connections/overview) | `connections_list` `ledger_invoices` `ledger_invoice_lines` `ledger_bills` `ledger_reports` `ledger_bank_transactions` `crm_accounts` `crm_contacts` `crm_opportunities` `tax_rates` `tax_calculate` `shipping_shipments` `shipping_tracking` `shipping_rates` `procurement_transactions` `procurement_bills` `procurement_suppliers` `meetings_list` `meetings_get` |
88
88
  | [LinkedIn](https://docs.oneshotagent.com/api-reference/linkedin/overview) | `linkedin_connect` `linkedin_accounts` `linkedin_invite` `linkedin_message` `linkedin_reply` and more |
89
89
  | [Build](https://docs.oneshotagent.com/api-reference/build/create) | `build` `update_build` |
90
90
  | [Goals](https://docs.oneshotagent.com/api-reference/compute/create) | `compute` `compute_status` `compute_tasks` `compute_respond` `compute_pause` `compute_resume` `compute_fund` `compute_cancel` |
@@ -149,7 +149,7 @@ wallet. The server signs it for you. A read with no proof and no valid token is
149
149
 
150
150
  ## Tool discovery
151
151
 
152
- The MCP catalog contains 193 tools. `oneshot_discover_tools` is a free, read-only
152
+ The MCP catalog contains 233 tools. `oneshot_discover_tools` is a free, read-only
153
153
  search over the tools your token can call. Call it with no arguments for category
154
154
  counts, `{ "query": "send a printed letter" }` to search, or `{ "category": "email" }`
155
155
  to browse. Results contain public tool names, purposes and required inputs.
package/dist/index.js CHANGED
@@ -50,7 +50,7 @@ async function main() {
50
50
  const agent = await initAgent();
51
51
  const server = new Server({
52
52
  name: "oneshot-mcp",
53
- version: "0.49.0", // keep in sync with package.json; guarded by version.test.ts
53
+ version: "0.50.0", // keep in sync with package.json; guarded by version.test.ts
54
54
  }, {
55
55
  capabilities: {
56
56
  tools: {},
@@ -6,8 +6,10 @@ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
6
6
  *
7
7
  * readOnlyHint true = retrieval only (paid lookups included — the only
8
8
  * side effect is our own billing)
9
- * destructiveHint true = deletes, overwrites or revokes something. Sends are
10
- * additive writes, so false — like any mail app's send.
9
+ * destructiveHint true = deletes, overwrites or revokes something, or is an
10
+ * irreversible send or transaction (a message, call, letter
11
+ * or purchase cannot be taken back). OpenAI's plugin
12
+ * guidelines ask for true on those.
11
13
  * openWorldHint true = reaches the public web or third parties;
12
14
  * false = only the caller's own OneShot account data.
13
15
  */
@@ -1,15 +1,22 @@
1
1
  import { LOOKUP_MANIFEST, ROUTE_MANIFEST } from "./lookup-manifest.generated.js";
2
2
  /** Retrieval only. */
3
3
  const read = (title, openWorld) => ({ title, readOnlyHint: true, destructiveHint: false, openWorldHint: openWorld });
4
- /** Changes something. `destructive` only for delete / overwrite / revoke. */
4
+ /** Changes something. `destructive` for delete / overwrite / revoke and for irreversible sends and transactions. */
5
5
  const write = (title, openWorld, destructive = false) => ({ title, readOnlyHint: false, destructiveHint: destructive, openWorldHint: openWorld });
6
+ /**
7
+ * Declared routes that cannot be taken back, so destructive. Approving a mail quote posts a letter and
8
+ * starting an identity check sends a person a link: both reach outside the account, so open-world too.
9
+ */
10
+ const IRREVERSIBLE_ROUTES = new Set(["physical_mail_approve", "physical_mail_cancel", "identity_start"]);
11
+ const OUTBOUND_ROUTES = new Set(["physical_mail_approve", "identity_start"]);
6
12
  /** The connected-system reads (#2403): paid, but each reads the caller's own connected business system, so closed-world. */
7
- const CONNECTED_SYSTEM_READ = /^\/v1\/tools\/(ledger|crm|shipping|tax|procurement)\//;
13
+ const CONNECTED_SYSTEM_READ = /^\/v1\/tools\/(ledger|crm|shipping|tax|procurement|meetings|mailbox|files)\//;
8
14
  const OPEN = true;
9
15
  const OWN = false;
10
16
  const DESTRUCTIVE = true;
11
17
  export const TOOL_ANNOTATIONS = {
12
18
  oneshot_discover_tools: read("Discover tools", OWN),
19
+ oneshot_lookup: read("Run a lookup", OPEN),
13
20
  oneshot_solve: read("Solve", OWN),
14
21
  oneshot_predict: read("Predict", OWN),
15
22
  oneshot_hunt_list: read("List Soul Hunts", OPEN),
@@ -17,22 +24,22 @@ export const TOOL_ANNOTATIONS = {
17
24
  oneshot_hunt_predict: write("Predict Soul Hunt", OPEN),
18
25
  oneshot_hunt_collection: read("List Collected Souls", OWN),
19
26
  oneshot_hunt_leaderboard: read("Soul Hunt Leaderboard", OPEN),
20
- oneshot_email: write("Send email", OPEN),
21
- oneshot_voice: write("Place a phone call", OPEN),
22
- oneshot_sms: write("Send a text message", OPEN),
27
+ oneshot_email: write("Send email", OPEN, DESTRUCTIVE),
28
+ oneshot_voice: write("Place a phone call", OPEN, DESTRUCTIVE),
29
+ oneshot_sms: write("Send a text message", OPEN, DESTRUCTIVE),
23
30
  oneshot_inbox_list: read("List received emails", OWN),
24
31
  oneshot_inbox_get: read("Read a received email", OWN),
25
32
  oneshot_sms_inbox_list: read("List received texts", OWN),
26
33
  oneshot_sms_inbox_get: read("Read a received text", OWN),
27
- oneshot_voice_number_provision: write("Get a phone number", OPEN),
34
+ oneshot_voice_number_provision: write("Get a phone number", OPEN, DESTRUCTIVE),
28
35
  oneshot_voice_numbers: read("List phone numbers", OWN),
29
36
  oneshot_voice_inbound_set: write("Set up call answering", OPEN, DESTRUCTIVE), // replaces the number's answering settings; outside callers reach it via the carrier
30
- oneshot_voice_inbound_disable: write("Stop answering calls", OPEN),
37
+ oneshot_voice_inbound_disable: write("Stop answering calls", OPEN, DESTRUCTIVE),
31
38
  oneshot_voice_inbound_calls: read("List answered calls", OWN),
32
39
  // Physical Mail (#1883): the upload stores artwork in the agent's own account; the send puts a letter in the post.
33
40
  // The generated route tools (validate, preview, quote, approve, recover, order, cancel) get their hints from the manifest below.
34
41
  oneshot_physical_mail_upload: write("Upload mail artwork", OWN),
35
- oneshot_physical_mail_send: write("Send approved physical mail", OPEN),
42
+ oneshot_physical_mail_send: write("Send approved physical mail", OPEN, DESTRUCTIVE),
36
43
  oneshot_research: read("Deep research", OPEN),
37
44
  oneshot_person_find: read("Find people", OPEN),
38
45
  oneshot_person_profile: read("Look up a person's profile", OPEN),
@@ -51,7 +58,7 @@ export const TOOL_ANNOTATIONS = {
51
58
  oneshot_business_resolve: read("Look up a local business", OPEN),
52
59
  oneshot_record_solicitations_find: read("Find government contracts", OPEN),
53
60
  oneshot_product_find: read("Search products", OPEN),
54
- oneshot_commerce_buy: write("Buy a product", OPEN),
61
+ oneshot_commerce_buy: write("Buy a product", OPEN, DESTRUCTIVE),
55
62
  oneshot_compute: write("Start a goal", OPEN),
56
63
  oneshot_compute_status: read("Check a goal", OWN),
57
64
  oneshot_compute_tasks: read("List a goal's tasks", OWN),
@@ -59,7 +66,7 @@ export const TOOL_ANNOTATIONS = {
59
66
  oneshot_compute_respond: write("Answer a goal's question", OWN),
60
67
  oneshot_compute_pause: write("Pause a goal", OWN),
61
68
  oneshot_compute_resume: write("Resume a goal", OWN),
62
- oneshot_compute_fund: write("Add budget to a goal", OWN),
69
+ oneshot_compute_fund: write("Add budget to a goal", OWN, DESTRUCTIVE),
63
70
  oneshot_build: write("Build a website", OPEN),
64
71
  oneshot_update_build: write("Update a website", OPEN, DESTRUCTIVE), // overwrites the live site
65
72
  oneshot_browser: write("Browse and act on a website", OPEN),
@@ -72,14 +79,14 @@ export const TOOL_ANNOTATIONS = {
72
79
  oneshot_linkedin_sync: write("Sync LinkedIn messages", OPEN), // paid; imports history into the account
73
80
  oneshot_linkedin_conversations: read("List LinkedIn conversations", OWN),
74
81
  oneshot_linkedin_messages: read("Read LinkedIn messages", OWN),
75
- oneshot_linkedin_reply: write("Reply in a LinkedIn conversation", OPEN),
76
- oneshot_linkedin_message: write("Message a LinkedIn connection", OPEN),
82
+ oneshot_linkedin_reply: write("Reply in a LinkedIn conversation", OPEN, DESTRUCTIVE),
83
+ oneshot_linkedin_message: write("Message a LinkedIn connection", OPEN, DESTRUCTIVE),
77
84
  oneshot_linkedin_invitation: read("Check a LinkedIn invitation", OWN),
78
85
  oneshot_linkedin_invitations: read("List LinkedIn invitations", OWN),
79
- oneshot_linkedin_invite: write("Send a LinkedIn invitation", OPEN),
86
+ oneshot_linkedin_invite: write("Send a LinkedIn invitation", OPEN, DESTRUCTIVE),
80
87
  oneshot_linkedin_withdraw_invitation: write("Withdraw a LinkedIn invitation", OPEN, DESTRUCTIVE),
81
88
  oneshot_linkedin_view_profile: write("View a LinkedIn profile", OPEN), // notify=true is visible to the person
82
- oneshot_linkedin_react: write("React to a LinkedIn post", OPEN),
89
+ oneshot_linkedin_react: write("React to a LinkedIn post", OPEN, DESTRUCTIVE),
83
90
  oneshot_list_domains: read("List sending domains", OWN),
84
91
  oneshot_pause_domain: write("Pause a sending domain", OWN),
85
92
  oneshot_resume_domain: write("Resume a sending domain", OWN),
@@ -92,7 +99,7 @@ export const TOOL_ANNOTATIONS = {
92
99
  oneshot_mark_notification_read: write("Mark a notification read", OWN),
93
100
  oneshot_get_balance: read("Check balance", OWN),
94
101
  oneshot_budget_status: read("Check budget", OWN),
95
- oneshot_approval_create: write("Ask a person to approve", OPEN),
102
+ oneshot_approval_create: write("Ask a person to approve", OPEN, DESTRUCTIVE),
96
103
  oneshot_approval_get: read("Check an approval", OWN),
97
104
  oneshot_approval_list: read("List approvals", OWN),
98
105
  oneshot_approval_cancel: write("Cancel an approval", OWN, DESTRUCTIVE),
@@ -105,10 +112,10 @@ export const TOOL_ANNOTATIONS = {
105
112
  // read (Changes, Benchmark) reaches recorded third-party data, a connected-system read the caller's own system; a
106
113
  // write changes something, and a DELETE removes it.
107
114
  ...Object.fromEntries(ROUTE_MANIFEST.map((entry) => {
108
- const own = !entry.paid || CONNECTED_SYSTEM_READ.test(entry.path);
115
+ const own = (!entry.paid || CONNECTED_SYSTEM_READ.test(entry.path)) && !OUTBOUND_ROUTES.has(entry.name);
109
116
  return [
110
117
  `oneshot_${entry.name}`,
111
- entry.readOnly ? read(entry.title, own ? OWN : OPEN) : write(entry.title, own ? OWN : OPEN, entry.method === "DELETE"),
118
+ entry.readOnly ? read(entry.title, own ? OWN : OPEN) : write(entry.title, own ? OWN : OPEN, entry.method === "DELETE" || IRREVERSIBLE_ROUTES.has(entry.name)),
112
119
  ];
113
120
  })),
114
121
  };
@@ -20,11 +20,6 @@ export declare function handleComputeCancel(agent: OneShot, args: Record<string,
20
20
  export declare function handleComputeRespond(agent: OneShot, args: Record<string, unknown>): Promise<{
21
21
  goal_id: string;
22
22
  status: string;
23
- end_reason: string | null;
24
- dispute: {
25
- at: string;
26
- reason: string;
27
- };
28
23
  } | {
29
24
  task_id: string;
30
25
  goal_id: string;
@@ -1,6 +1,28 @@
1
+ const KNOWN_GOAL_TOOL = { type: "string", description: 'A OneShot tool name, e.g. "email", "voice", "commerce_buy"' };
2
+ const SCOPE_USE = {
3
+ type: "object",
4
+ properties: {
5
+ tool: KNOWN_GOAL_TOOL,
6
+ qty: { type: "integer", minimum: 1, description: "How many times inside one unit" },
7
+ max_attempts: { type: "integer", minimum: 1, description: "Attempts each use may take" },
8
+ target: { type: "string", description: 'Whom or what the use is pointed at, e.g. "the carrier\'s billing desk"' },
9
+ persistence: {
10
+ type: "object",
11
+ description: "How long a use keeps trying to reach the world, and what happens when it cannot",
12
+ properties: {
13
+ max_attempts: { type: "integer", minimum: 1 },
14
+ spacing_seconds: { type: "integer", minimum: 0, description: "Seconds between two attempts at the same target" },
15
+ escalate_to: KNOWN_GOAL_TOOL,
16
+ then: { type: "string", enum: ["owner", "give_up"], description: "After the last attempt: back to you, or given up as unreached" },
17
+ },
18
+ required: ["max_attempts", "spacing_seconds", "then"],
19
+ },
20
+ },
21
+ required: ["tool", "qty", "max_attempts"],
22
+ };
1
23
  export const computeTool = {
2
24
  name: "oneshot_compute",
3
- description: "Create an autonomous compute goal: give OneShot an objective and a budget; it plans and runs the steps across OneShot tools. Returns a goal id to poll. Paid tool (quote-to-pay against the budget).",
25
+ description: "Create an autonomous compute goal: give OneShot an objective and a budget; it plans and runs the steps across OneShot tools. Name the unit of work, how work starts (a given list, an event, a schedule, or once) and what each unit needs, or pass `scope`. The quote returns `scope`, `price` (per unit expected and ceiling, overhead, total or units covered, unpriced, unmeasured) and the `done_check` the goal is held to. Returns a goal id to poll. Paid tool (quote-to-pay against the budget).",
4
26
  inputSchema: {
5
27
  type: "object",
6
28
  properties: {
@@ -43,6 +65,120 @@ export const computeTool = {
43
65
  },
44
66
  additionalProperties: false,
45
67
  },
68
+ done_check: {
69
+ description: "What done means, written before the goal runs and evaluated in code: the goal completes on its own when the check is met. Without one the goal waits in awaiting_confirmation for its owner to confirm. Give one shape.",
70
+ oneOf: [
71
+ {
72
+ type: "object",
73
+ description: "At least at_least tasks of a tool (of: tasks, needs tool), events of one type (of: events, needs event_type) or emails that reached a status (of: emails).",
74
+ properties: {
75
+ kind: { const: "count" },
76
+ of: { type: "string", enum: ["tasks", "events", "emails"] },
77
+ tool: KNOWN_GOAL_TOOL,
78
+ status: { type: "string", description: 'For tasks: "completed" or "failed"; for emails: "sent", "delivered" or "opened"' },
79
+ event_type: { type: "string", enum: ["email_reply", "sms_reply", "voice_completed", "task_completed", "task_failed"] },
80
+ at_least: { type: "integer", minimum: 1 },
81
+ },
82
+ required: ["kind", "of", "at_least"],
83
+ additionalProperties: false,
84
+ },
85
+ {
86
+ type: "object",
87
+ description: "The latest completed result of a tool, at a dot path (omit for the whole result), equals a value or is at least a number. Give exactly one of equals or at_least.",
88
+ properties: {
89
+ kind: { const: "tool_result" },
90
+ tool: KNOWN_GOAL_TOOL,
91
+ path: { type: "string", description: 'Dot path into the result, e.g. "result.status" (up to 8 keys)' },
92
+ equals: { type: ["string", "number", "boolean"] },
93
+ at_least: { type: "number" },
94
+ },
95
+ required: ["kind", "tool"],
96
+ // The API rejects a tool_result check with neither or both criteria; say so in the schema so a client fails before the quote.
97
+ oneOf: [{ required: ["equals"] }, { required: ["at_least"] }],
98
+ additionalProperties: false,
99
+ },
100
+ {
101
+ type: "object",
102
+ description: "No check can be written: the owner confirms completion.",
103
+ properties: { kind: { const: "owner" } },
104
+ required: ["kind"],
105
+ additionalProperties: false,
106
+ },
107
+ {
108
+ type: "object",
109
+ description: "Every case of the goal is in one of finished_states (a state in owner_required_for also needs an owner on the record); at least min_cases (default 1) must exist.",
110
+ properties: {
111
+ kind: { const: "cases" },
112
+ finished_states: { type: "array", items: { type: "string" }, minItems: 1, maxItems: 20, description: 'Lowercase state tokens such as "merged"' },
113
+ owner_required_for: { type: "array", items: { type: "string" }, maxItems: 20, description: "States from finished_states that only count when the case record names an owner" },
114
+ min_cases: { type: "integer", minimum: 1 },
115
+ },
116
+ required: ["kind", "finished_states"],
117
+ additionalProperties: false,
118
+ },
119
+ ],
120
+ },
121
+ egress_allowlist: {
122
+ type: "array",
123
+ items: { type: "string" },
124
+ maxItems: 50,
125
+ description: "Hosts the goal's sandboxes may reach besides OneShot's API: hostnames, IPv4 addresses or IPv4 ranges. Setting it (even to []) limits every sandbox of the goal to OneShot's API and these hosts.",
126
+ },
127
+ scope: {
128
+ type: "object",
129
+ description: "Your reading of the goal, replacing the quote's and priced again in code. Omit it and the quote proposes one. A tool the goal cannot call is refused.",
130
+ properties: {
131
+ unit: {
132
+ type: "object",
133
+ description: "What one unit is and what starts it",
134
+ properties: {
135
+ name: { type: "string", description: 'One unit in your words, e.g. "a carrier invoice"' },
136
+ starts_on: { type: "string", enum: ["list", "event", "schedule", "once"], description: "A given list, an event in the world, a schedule, or the goal itself once" },
137
+ },
138
+ required: ["name", "starts_on"],
139
+ },
140
+ per_unit: {
141
+ type: "object",
142
+ description: "What one unit takes",
143
+ properties: {
144
+ uses: { type: "array", maxItems: 50, items: SCOPE_USE, description: "Tool uses inside one unit" },
145
+ step_allowance: { type: "integer", minimum: 0, description: "Model steps one unit may take beyond its tool uses" },
146
+ done: { type: "array", items: { type: "string" }, minItems: 1, description: 'Case states that finish a unit, e.g. ["paid"]; "unreached" is always among them' },
147
+ },
148
+ required: ["uses", "step_allowance", "done"],
149
+ },
150
+ overhead: {
151
+ type: "object",
152
+ description: "Tool uses outside any unit: setting up, reporting",
153
+ properties: {
154
+ uses: { type: "array", maxItems: 50, items: { type: "object", properties: { tool: KNOWN_GOAL_TOOL, qty: { type: "integer", minimum: 1 }, max_attempts: { type: "integer", minimum: 1 } }, required: ["tool", "qty", "max_attempts"] } },
155
+ step_allowance: { type: "integer", minimum: 0 },
156
+ },
157
+ required: ["uses", "step_allowance"],
158
+ },
159
+ volume: {
160
+ type: "object",
161
+ description: 'How many units: {"kind": "list", "count": N} for a given list, {"kind": "open"} otherwise',
162
+ properties: { kind: { type: "string", enum: ["list", "open"] }, count: { type: "integer", minimum: 1 } },
163
+ required: ["kind"],
164
+ },
165
+ unpriced: {
166
+ type: "array",
167
+ maxItems: 50,
168
+ description: "What the price list cannot price: a connected system you pay for, or a tool quoted per call",
169
+ items: {
170
+ type: "object",
171
+ properties: {
172
+ name: { type: "string" },
173
+ reason: { type: "string", enum: ["connection", "quoted_tool"] },
174
+ assumption: { type: "string", description: 'What the price assumes, e.g. "5 minutes" for a call or "20 steps" for a browser run' },
175
+ },
176
+ required: ["name", "reason"],
177
+ },
178
+ },
179
+ },
180
+ required: ["unit", "per_unit", "overhead", "volume", "unpriced"],
181
+ },
46
182
  },
47
183
  required: ["objective"],
48
184
  },
@@ -84,14 +220,15 @@ export const computeCancelTool = {
84
220
  };
85
221
  export const computeRespondTool = {
86
222
  name: "oneshot_compute_respond",
87
- description: "Respond to a human-in-the-loop task that a compute goal is waiting on (approve/reject or provide input), or send `dispute: true` without task_id to dispute a goal that ended impossible_unverified, with `response` as the reason; nothing is refunded. Free.",
223
+ description: "Respond to a human-in-the-loop task that a compute goal is waiting on (approve/reject or provide input), send `continue_without_tool: true` without task_id to release a goal awaiting a requested tool (with `response` as the message it resumes with), or send `dispute: true` without task_id to dispute a goal that ended impossible_unverified, with `response` as the reason; nothing is refunded. Free.",
88
224
  inputSchema: {
89
225
  type: "object",
90
226
  properties: {
91
227
  goal_id: { type: "string", description: "Compute goal id" },
92
- task_id: { type: "string", description: "Id of the task awaiting a response (not with dispute)" },
93
- response: { type: "string", description: "Free-text response for the task, or the reason for a dispute" },
228
+ task_id: { type: "string", description: "Id of the task awaiting a response (not with continue_without_tool or dispute)" },
229
+ response: { type: "string", description: "Free-text response for the task, the message an awaiting_tool goal resumes with, or the reason for a dispute" },
94
230
  approved: { type: "boolean", description: "Approve (true) or reject (false) an approval task" },
231
+ continue_without_tool: { type: "boolean", description: "Continue an awaiting_tool goal without the requested tool; must be true, without task_id" },
95
232
  dispute: { type: "boolean", description: "Dispute an impossible_unverified ending; must be true, without task_id" },
96
233
  },
97
234
  required: ["goal_id"],
@@ -133,7 +270,7 @@ export const computeFundTool = {
133
270
  },
134
271
  };
135
272
  export async function handleCompute(agent, args) {
136
- return agent.compute({
273
+ const options = {
137
274
  objective: args.objective,
138
275
  budget_usdc: args.budget_usdc,
139
276
  params: args.params,
@@ -142,7 +279,12 @@ export async function handleCompute(agent, args) {
142
279
  soul_service_slug: args.soul_service_slug,
143
280
  schedule: args.schedule,
144
281
  approvals: args.approvals,
145
- });
282
+ done_check: args.done_check,
283
+ egress_allowlist: args.egress_allowlist,
284
+ };
285
+ if (args.scope !== undefined)
286
+ options.scope = args.scope;
287
+ return agent.compute(options);
146
288
  }
147
289
  export async function handleComputeStatus(agent, args) {
148
290
  return agent.getComputeGoal(args.goal_id);
@@ -163,6 +305,12 @@ export async function handleComputeRespond(agent, args) {
163
305
  throw new Error("Send dispute: true, with an optional response, and without task_id");
164
306
  return agent.disputeComputeGoal(args.goal_id, { reason: args.response });
165
307
  }
308
+ // The owner continues a goal held for a requested tool without it (#3173); no task answers it either.
309
+ if (args.continue_without_tool !== undefined) {
310
+ if (args.continue_without_tool !== true || args.task_id !== undefined)
311
+ throw new Error("Send continue_without_tool: true, with an optional response, and without task_id");
312
+ return agent.continueComputeGoalWithoutTool(args.goal_id, { message: args.response });
313
+ }
166
314
  return agent.respondToComputeTask(args.goal_id, {
167
315
  task_id: args.task_id,
168
316
  response: args.response,
@@ -3,7 +3,7 @@ import type { OneShot } from "@oneshot-agent/sdk";
3
3
  /**
4
4
  * The agent's connections (#2403). Hand-written because `GET /v1/connections` is not a declared route: the
5
5
  * connected-system read tools (`oneshot_ledger_*`, `oneshot_crm_*`, `oneshot_shipping_*`, `oneshot_tax_*`,
6
- * `oneshot_procurement_*`) are generated from their routes and each tells the model to call this one first.
6
+ * `oneshot_procurement_*`, `oneshot_meetings_*`) are generated from their routes and each tells the model to call this one first.
7
7
  * Connecting and revoking stay in the console.
8
8
  */
9
9
  export declare const connectionsListTool: Tool;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The agent's connections (#2403). Hand-written because `GET /v1/connections` is not a declared route: the
3
3
  * connected-system read tools (`oneshot_ledger_*`, `oneshot_crm_*`, `oneshot_shipping_*`, `oneshot_tax_*`,
4
- * `oneshot_procurement_*`) are generated from their routes and each tells the model to call this one first.
4
+ * `oneshot_procurement_*`, `oneshot_meetings_*`) are generated from their routes and each tells the model to call this one first.
5
5
  * Connecting and revoking stay in the console.
6
6
  */
7
7
  export const connectionsListTool = {
@@ -1,6 +1,6 @@
1
1
  export const discoverToolsTool = {
2
2
  name: 'oneshot_discover_tools',
3
- description: 'Find OneShot tools by task or category. Free. Returns public names, purposes and required inputs. With no query or category, lists categories. Category pages contain up to five tools; pass nextCursor with the same query and category.',
3
+ description: 'Find OneShot tools by task or category. Free. Returns public names, purposes and required inputs; a lookup that is not in your tool list runs with oneshot_lookup. With no query or category, lists categories. Category pages contain up to five tools; pass nextCursor with the same query and category.',
4
4
  inputSchema: {
5
5
  type: 'object',
6
6
  properties: {
@@ -1,5 +1,6 @@
1
1
  import { UNTRUSTED_TOOLS } from './untrusted-tools.generated.js';
2
2
  import { discoverToolsTool, handleDiscoverTools } from './discover.js';
3
+ import { handleLookupCall, lookupCallTool, lookupTarget } from './lookup-call.js';
3
4
  import { emailTool, handleEmail } from "./email.js";
4
5
  import { voiceTool, handleVoice } from "./voice.js";
5
6
  import { voiceNumberProvisionTool, voiceNumbersTool, voiceInboundSetTool, voiceInboundDisableTool, voiceInboundCallsTool, handleVoiceNumberProvision, handleVoiceNumbers, handleVoiceInboundSet, handleVoiceInboundDisable, handleVoiceInboundCalls, } from "./voice-inbound.js";
@@ -34,6 +35,7 @@ import { connectionsListTool, handleConnectionsList } from "./connections.js";
34
35
  import { spendBreakdownTool, rocsTool, rocsByGoalTool, receiptsListTool, tagReceiptValueTool, handleSpendBreakdown, handleRocs, handleRocsByGoal, handleReceiptsList, handleTagReceiptValue } from "./analytics.js";
35
36
  const toolDefinitions = [
36
37
  discoverToolsTool,
38
+ lookupCallTool,
37
39
  emailTool,
38
40
  voiceTool,
39
41
  smsTool,
@@ -130,6 +132,7 @@ export const tools = toolDefinitions.map((tool) => {
130
132
  });
131
133
  const handlers = {
132
134
  oneshot_discover_tools: handleDiscoverTools,
135
+ oneshot_lookup: handleLookupCall,
133
136
  "oneshot_email": handleEmail,
134
137
  "oneshot_voice": handleVoice,
135
138
  "oneshot_sms": handleSms,
@@ -227,7 +230,9 @@ export async function handleToolCall(agent, toolName, args) {
227
230
  isError: true,
228
231
  };
229
232
  }
230
- const untrusted = UNTRUSTED_TOOLS.has(toolName.replace(/^oneshot_/, ""));
233
+ // oneshot_lookup returns what the lookup it runs returns, so it is marked by that lookup.
234
+ const marked = toolName === "oneshot_lookup" && args.input !== undefined ? lookupTarget(args) ?? toolName : toolName;
235
+ const untrusted = UNTRUSTED_TOOLS.has(marked.replace(/^oneshot_/, ""));
231
236
  try {
232
237
  const data = await handler(agent, args);
233
238
  const acknowledgement = data && typeof data === "object" && "request_id" in data && "status" in data
@@ -6,7 +6,7 @@ export const businessFindTool = {
6
6
  type: "object",
7
7
  properties: {
8
8
  category: { type: "array", items: { type: "string" }, description: "Business categories, e.g. ['hvac contractor', 'dental practice']. category or keywords required." },
9
- keywords: { type: "array", items: { type: "string" }, description: "Free-text keywords (alternative to category)" },
9
+ keywords: { type: "array", items: { type: "string" }, description: "Free-text keywords (alternative to category). category or keywords required." },
10
10
  location: { type: "array", items: { type: "string" }, description: "Cities, neighborhoods, or 'City, ST' strings (required)" },
11
11
  min_rating: { type: "number", description: "Minimum star rating (0-5)" },
12
12
  min_review_count: { type: "number", description: "Minimum review count" },
@@ -17,7 +17,6 @@ export const businessFindTool = {
17
17
  ...FRESH_PROPERTY,
18
18
  },
19
19
  required: ["location"],
20
- anyOf: [{ required: ["category"] }, { required: ["keywords"] }],
21
20
  },
22
21
  };
23
22
  export const businessResolveTool = {
@@ -35,7 +34,6 @@ export const businessResolveTool = {
35
34
  ...FRESH_PROPERTY,
36
35
  },
37
36
  required: ["name"],
38
- anyOf: [{ required: ["address"] }, { required: ["city"] }, { required: ["postal_code"] }, { required: ["phone"] }],
39
37
  },
40
38
  };
41
39
  export async function handleBusinessFind(agent, args) {
@@ -0,0 +1,13 @@
1
+ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { OneShot } from "@oneshot-agent/sdk";
3
+ /**
4
+ * One tool that runs any lookup by name. A client with a short tool list (a
5
+ * platform's cap, or `?tools=compact` on hosted /mcp) lists this instead of
6
+ * every lookup; oneshot_discover_tools finds the names. Lookups only: a route
7
+ * or hand-written tool keeps its own annotations and confirmation, which a
8
+ * read-only meta-tool must not stand in for.
9
+ */
10
+ export declare const lookupCallTool: Tool;
11
+ /** The lookup a oneshot_lookup call targets, as `oneshot_<name>`; null when it names no lookup. */
12
+ export declare function lookupTarget(args: Record<string, unknown>): string | null;
13
+ export declare function handleLookupCall(agent: OneShot, args: Record<string, unknown>): Promise<unknown>;
@@ -0,0 +1,45 @@
1
+ import { LOOKUP_MANIFEST } from "./lookup-manifest.generated.js";
2
+ import { lookupHandlers } from "./lookup.js";
3
+ /**
4
+ * One tool that runs any lookup by name. A client with a short tool list (a
5
+ * platform's cap, or `?tools=compact` on hosted /mcp) lists this instead of
6
+ * every lookup; oneshot_discover_tools finds the names. Lookups only: a route
7
+ * or hand-written tool keeps its own annotations and confirmation, which a
8
+ * read-only meta-tool must not stand in for.
9
+ */
10
+ export const lookupCallTool = {
11
+ name: "oneshot_lookup",
12
+ description: "Run a OneShot lookup by name, including one that is not in your tool list. Find names with oneshot_discover_tools. " +
13
+ "Without input, returns the lookup's description, input schema and an example, free. With input, runs the lookup, " +
14
+ "exactly as calling that tool directly. Paid like the lookup it runs.",
15
+ inputSchema: {
16
+ type: "object",
17
+ properties: {
18
+ tool: { type: "string", description: "Lookup name, e.g. oneshot_company_filings (the oneshot_ prefix is optional)." },
19
+ input: { type: "object", description: "The lookup's input. Omit to get its input schema first." },
20
+ },
21
+ required: ["tool"],
22
+ additionalProperties: false,
23
+ },
24
+ };
25
+ const BY_NAME = new Map(LOOKUP_MANIFEST.map((entry) => [`oneshot_${entry.name}`, entry]));
26
+ /** The lookup a oneshot_lookup call targets, as `oneshot_<name>`; null when it names no lookup. */
27
+ export function lookupTarget(args) {
28
+ if (typeof args.tool !== "string")
29
+ return null;
30
+ const name = args.tool.startsWith("oneshot_") ? args.tool : `oneshot_${args.tool}`;
31
+ return BY_NAME.has(name) ? name : null;
32
+ }
33
+ export async function handleLookupCall(agent, args) {
34
+ const target = lookupTarget(args);
35
+ if (!target)
36
+ throw new Error(`${String(args.tool)} is not a lookup. Find lookup names with oneshot_discover_tools.`);
37
+ const entry = BY_NAME.get(target);
38
+ if (args.input === undefined) {
39
+ return { tool: target, description: entry.description, inputSchema: entry.inputSchema, example: entry.example };
40
+ }
41
+ if (!args.input || typeof args.input !== "object" || Array.isArray(args.input)) {
42
+ throw new Error("input must be an object of the lookup's arguments.");
43
+ }
44
+ return lookupHandlers[target](agent, args.input);
45
+ }
@@ -32,5 +32,7 @@ export interface RouteManifestEntry {
32
32
  example: Record<string, unknown>;
33
33
  /** `false` for a route whose 202 envelope is the answer (a verification_url to hand on): the handler returns it and never waits for the job. */
34
34
  wait?: false;
35
+ /** Seconds to wait for the job, for a long one (a tool backed by a goal runs up to its deadline); else the SDK default. */
36
+ timeout?: number;
35
37
  }
36
38
  export declare const ROUTE_MANIFEST: readonly RouteManifestEntry[];