@oneshot-agent/mcp-server 0.33.0 → 0.34.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
@@ -102,7 +102,7 @@ Read tools (inbox, SMS inbox, notifications, balance, browser profiles) return p
102
102
  ## Remote (hosted)
103
103
 
104
104
  Clients that run in someone else's cloud (Grok Bot, hosted runners) cannot spawn this
105
- server and must not hold a wallet key. For them, OneShot hosts the same 78 tools over
105
+ server and must not hold a wallet key. For them, OneShot hosts the same 81 tools over
106
106
  Streamable HTTP at **`https://win.oneshotagent.com/mcp`**, authenticated with an
107
107
  **agent access token** and billed to the agent's credit balance:
108
108
 
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.33.0", // keep in sync with package.json; guarded by version.test.ts
53
+ version: "0.34.0", // keep in sync with package.json; guarded by version.test.ts
54
54
  }, {
55
55
  capabilities: {
56
56
  tools: {},
@@ -62,7 +62,10 @@ export const TOOL_ANNOTATIONS = {
62
62
  oneshot_linkedin_sync: write("Sync LinkedIn messages", OPEN), // paid; imports history into the account
63
63
  oneshot_linkedin_conversations: read("List LinkedIn conversations", OWN),
64
64
  oneshot_linkedin_messages: read("Read LinkedIn messages", OWN),
65
- oneshot_linkedin_reply: write("Send a LinkedIn message", OPEN),
65
+ oneshot_linkedin_reply: write("Reply in a LinkedIn conversation", OPEN),
66
+ oneshot_linkedin_message: write("Message a LinkedIn connection", OPEN),
67
+ oneshot_linkedin_invitation: read("Check a LinkedIn invitation", OWN),
68
+ oneshot_linkedin_invitations: read("List LinkedIn invitations", OWN),
66
69
  oneshot_linkedin_invite: write("Send a LinkedIn invitation", OPEN),
67
70
  oneshot_linkedin_withdraw_invitation: write("Withdraw a LinkedIn invitation", OPEN, DESTRUCTIVE),
68
71
  oneshot_linkedin_view_profile: write("View a LinkedIn profile", OPEN), // notify=true is visible to the person
@@ -71,7 +71,7 @@ export async function handleApprovalCancel(agent, args) {
71
71
  }
72
72
  export const actionPolicyGetTool = {
73
73
  name: "oneshot_action_policy",
74
- description: "Read-only: this agent's action policy — which email/SMS/voice/commerce calls run, are denied, or need a human approval " +
74
+ description: "Read-only: this agent's action policy — which side-effect calls (email, SMS, voice, commerce, physical mail, LinkedIn actions, builds, browser tasks) run, are denied, or need a human approval " +
75
75
  "(rules, approver channel, timeout). The policy is set by the operator from a wallet session and cannot be changed from here. Free.",
76
76
  inputSchema: { type: "object", properties: {} },
77
77
  };
@@ -1,6 +1,7 @@
1
1
  export const emailTool = {
2
2
  name: "oneshot_email",
3
- description: "Send an email. Supports HTML body, attachments, and threaded replies (set reply_to_email_id to an inbound email id from oneshot_inbox_list/oneshot_inbox_get). Paid tool — quote provided before payment.",
3
+ description: "Send an email. Supports HTML body, attachments, and threaded replies (set reply_to_email_id to an inbound email id from oneshot_inbox_list/oneshot_inbox_get). Paid tool — quote provided before payment.\n\n" +
4
+ "Sending requires a domain the agent owns. A fresh account has none — call oneshot_list_domains first to see what's already owned. If there is no usable domain, pass from_domain with a new domain to register it. A call with a new from_domain only quotes — nothing is charged — and returns status 'confirmation_required' with the exact cost (registrar price + a service fee, plus a mailbox fee if mailbox_mode is 'mailbox') and a quote_id. Tell the user that exact computed number, never a guessed or approximate one, and only once they say to proceed, call this again with the same arguments and confirm_new_domain set to that quote_id: that registers the domain, charges exactly the quoted total (the stored quote is used, there is no re-quote), and sends the email in one call. The workspace balance must cover it, or the call fails with insufficient_credits.",
4
5
  inputSchema: {
5
6
  type: "object",
6
7
  properties: {
@@ -22,7 +23,7 @@ export const emailTool = {
22
23
  },
23
24
  from_domain: {
24
25
  type: "string",
25
- description: "Domain to send from (must be one you own). Omit both from_domain and from_mailbox to rotate across your warmed domains — the chosen address comes back on the quote.",
26
+ description: "Domain to send from. If you already own it, pass it to pin that domain. If you don't, pass a new domain here to register it — a call with a new from_domain only quotes (status 'confirmation_required' with a quote_id, nothing charged); repeat the call with confirm_new_domain set to that quote_id to register it, charge exactly the quoted registration + service fee (and a mailbox fee if mailbox_mode is 'mailbox'), and send. Only confirm once the user has seen that amount and asked to proceed. Omit both from_domain and from_mailbox to rotate across your warmed domains — the chosen address comes back on the result. Call oneshot_list_domains first to see what you already own.",
26
27
  },
27
28
  from_mailbox: {
28
29
  type: "string",
@@ -54,6 +55,10 @@ export const emailTool = {
54
55
  type: "string",
55
56
  description: "Only when retrying a call the action policy held: the approval_id from the approval_required error, once a human approved it. The call then runs once.",
56
57
  },
58
+ confirm_new_domain: {
59
+ type: "string",
60
+ description: "The quote_id from a 'confirmation_required' result, and nothing else. A call with a from_domain you don't own yet always stops before charging and returns that result with the exact total. Only after showing that total to the user and getting their go-ahead, call this again with the same arguments and confirm_new_domain set to the quote_id: the domain is registered, exactly that quote's total is charged, and the email is sent in that one call. Passing true never charges; it only quotes again. A wrong, expired or already-used quote id is rejected by the server without charge: drop it and call again without confirm_new_domain for a fresh quote.",
61
+ },
57
62
  },
58
63
  required: ["body"],
59
64
  },
@@ -70,5 +75,7 @@ export async function handleEmail(agent, args) {
70
75
  mailbox_mode: args.mailbox_mode,
71
76
  attachments: args.attachments,
72
77
  approvalId: args.approval_id,
78
+ // Only the quote id confirms; `true` or anything else is not a confirmation.
79
+ confirmNewDomain: typeof args.confirm_new_domain === "string" ? args.confirm_new_domain : undefined,
73
80
  });
74
81
  }
@@ -19,7 +19,7 @@ import { deepResearchPersonTool, socialProfilesTool, articleSearchTool, personNe
19
19
  import { companySearchTool, enrichCompanyTool, handleCompanySearch, handleEnrichCompany } from "./company.js";
20
20
  import { localSearchTool, localResolveTool, handleLocalSearch, handleLocalResolve } from "./local.js";
21
21
  import { govSolicitationsTool, handleGovSolicitations } from "./gov.js";
22
- import { linkedinConnectTool, linkedinAccountsTool, linkedinSyncTool, linkedinConversationsTool, linkedinMessagesTool, linkedinReplyTool, linkedinViewProfileTool, linkedinReactTool, linkedinInviteTool, linkedinWithdrawInvitationTool, handleLinkedinConnect, handleLinkedinAccounts, handleLinkedinSync, handleLinkedinConversations, handleLinkedinMessages, handleLinkedinReply, handleLinkedinViewProfile, handleLinkedinReact, handleLinkedinInvite, handleLinkedinWithdrawInvitation, } from "./linkedin.js";
22
+ import { linkedinConnectTool, linkedinAccountsTool, linkedinSyncTool, linkedinConversationsTool, linkedinMessagesTool, linkedinReplyTool, linkedinViewProfileTool, linkedinReactTool, linkedinInviteTool, linkedinWithdrawInvitationTool, linkedinMessageTool, linkedinInvitationTool, linkedinInvitationsTool, handleLinkedinConnect, handleLinkedinAccounts, handleLinkedinSync, handleLinkedinConversations, handleLinkedinMessages, handleLinkedinReply, handleLinkedinViewProfile, handleLinkedinReact, handleLinkedinInvite, handleLinkedinWithdrawInvitation, handleLinkedinMessage, handleLinkedinInvitation, handleLinkedinInvitations, } from "./linkedin.js";
23
23
  import { computeTool, computeStatusTool, computeTasksTool, computeCancelTool, computeRespondTool, computePauseTool, computeResumeTool, computeFundTool, handleCompute, handleComputeStatus, handleComputeTasks, handleComputeCancel, handleComputeRespond, handleComputePause, handleComputeResume, handleComputeFund } from "./compute.js";
24
24
  import { listDomainsTool, pauseDomainTool, resumeDomainTool, handleListDomains, handlePauseDomain, handleResumeDomain } from "./domains.js";
25
25
  import { huntListTool, huntStatusTool, huntPredictTool, huntCollectionTool, huntLeaderboardTool, handleHuntList, handleHuntStatus, handleHuntPredict, handleHuntCollection, handleHuntLeaderboard } from "./hunt.js";
@@ -79,6 +79,7 @@ const toolDefinitions = [
79
79
  linkedinMessagesTool,
80
80
  linkedinReplyTool,
81
81
  linkedinInviteTool, linkedinWithdrawInvitationTool,
82
+ linkedinMessageTool, linkedinInvitationTool, linkedinInvitationsTool,
82
83
  linkedinViewProfileTool,
83
84
  linkedinReactTool,
84
85
  listDomainsTool,
@@ -167,6 +168,9 @@ const handlers = {
167
168
  "oneshot_linkedin_reply": handleLinkedinReply,
168
169
  "oneshot_linkedin_invite": handleLinkedinInvite,
169
170
  "oneshot_linkedin_withdraw_invitation": handleLinkedinWithdrawInvitation,
171
+ "oneshot_linkedin_message": handleLinkedinMessage,
172
+ "oneshot_linkedin_invitation": handleLinkedinInvitation,
173
+ "oneshot_linkedin_invitations": handleLinkedinInvitations,
170
174
  "oneshot_linkedin_view_profile": handleLinkedinViewProfile,
171
175
  "oneshot_linkedin_react": handleLinkedinReact,
172
176
  "oneshot_list_domains": handleListDomains,
@@ -246,6 +250,36 @@ export function structuredToolError(error) {
246
250
  };
247
251
  case "PaymentError":
248
252
  return { error: "payment_failed", message: e.message, reason: e.reason };
253
+ case "ToolError": {
254
+ // Every 4xx the SDK doesn't recognize as one of the typed errors above
255
+ // (budget/credits/payment) lands here as a plain ToolError, and its
256
+ // `message` is the generic "Tool request failed" — the API's actual
257
+ // `error`/`reason`/`message` body only survives in `responseBody`
258
+ // (issue #1070). Parse it so the model sees e.g. `no_sending_domain`
259
+ // instead of a dead end. Only these four bounded, validated fields are
260
+ // ever surfaced — never the raw body, which could carry anything.
261
+ const bodyText = typeof e.responseBody === "string" ? e.responseBody : undefined;
262
+ let body;
263
+ if (bodyText) {
264
+ try {
265
+ const parsed = JSON.parse(bodyText);
266
+ if (parsed && typeof parsed === "object" && typeof parsed.error === "string")
267
+ body = parsed;
268
+ }
269
+ catch {
270
+ // Malformed/non-JSON body: fall through to the generic prose fallback.
271
+ }
272
+ }
273
+ if (!body)
274
+ return undefined;
275
+ const status = typeof e.statusCode === "number" ? e.statusCode : undefined;
276
+ return {
277
+ error: body.error,
278
+ ...(typeof body.reason === "string" ? { reason: body.reason } : {}),
279
+ message: typeof body.message === "string" ? body.message : e.message,
280
+ ...(status !== undefined ? { status } : {}),
281
+ };
282
+ }
249
283
  default:
250
284
  return undefined;
251
285
  }
@@ -30,3 +30,11 @@ export declare const linkedinInviteTool: Tool;
30
30
  export declare function handleLinkedinInvite(agent: OneShot, args: Record<string, unknown>): Promise<import("@oneshot-agent/sdk").LinkedInInviteResult>;
31
31
  export declare const linkedinWithdrawInvitationTool: Tool;
32
32
  export declare function handleLinkedinWithdrawInvitation(agent: OneShot, args: Record<string, unknown>): Promise<import("@oneshot-agent/sdk").LinkedInWithdrawInvitationResult>;
33
+ export declare const linkedinMessageTool: Tool;
34
+ export declare function handleLinkedinMessage(agent: OneShot, args: Record<string, unknown>): Promise<import("@oneshot-agent/sdk").LinkedInMessageResult>;
35
+ export declare const linkedinInvitationTool: Tool;
36
+ export declare function handleLinkedinInvitation(agent: OneShot, args: Record<string, unknown>): Promise<import("@oneshot-agent/sdk").LinkedInInvitation>;
37
+ export declare const linkedinInvitationsTool: Tool;
38
+ export declare function handleLinkedinInvitations(agent: OneShot, args: Record<string, unknown>): Promise<{
39
+ invitations: import("@oneshot-agent/sdk").LinkedInInvitation[];
40
+ }>;
@@ -215,3 +215,64 @@ export async function handleLinkedinWithdrawInvitation(agent, args) {
215
215
  return agent.linkedinWithdrawInvitation({ accountId: args.account_id, invitationId: args.invitation_id,
216
216
  memo: args.memo, idempotencyKey: args.idempotency_key });
217
217
  }
218
+ export const linkedinMessageTool = {
219
+ name: "oneshot_linkedin_message",
220
+ description: "Message a first-degree LinkedIn connection through a connected account that granted 'reply': into the 1:1 conversation the account already has with them, or a new one. Use it once an invitation is accepted, or for a connection the account has never messaged. Returns conversation_id. The human's account sends it; a message LinkedIn accepted cannot be recalled. Shares the reply daily cap (default 50); sends are paced. A failed send with not_connected (invite them first) or rate_limited means defer, not give up. An Idempotency-Key is generated automatically so a retry never double-sends. Paid tool.",
221
+ inputSchema: {
222
+ type: "object",
223
+ properties: {
224
+ account_id: { type: "string", description: "Connected LinkedIn account id (required)" },
225
+ profile: { type: "string", description: "The connection: a linkedin.com/in/<slug> URL, a slug or a profile id, as oneshot_linkedin_invite takes (required)" },
226
+ text: { type: "string", description: "Message text, 1-4000 characters (required)" },
227
+ memo: { type: "string", description: "Why this message is being sent (audit trail)" },
228
+ idempotency_key: { type: "string", description: "Optional caller-supplied key; reuse it to safely retry the same send" },
229
+ },
230
+ required: ["account_id", "profile", "text"],
231
+ },
232
+ };
233
+ export async function handleLinkedinMessage(agent, args) {
234
+ return agent.linkedinMessage({
235
+ accountId: args.account_id,
236
+ profile: args.profile,
237
+ text: args.text,
238
+ memo: args.memo,
239
+ idempotencyKey: args.idempotency_key,
240
+ });
241
+ }
242
+ export const linkedinInvitationTool = {
243
+ name: "oneshot_linkedin_invitation",
244
+ description: "Status of one invitation sent with oneshot_linkedin_invite: pending, accepted (with accepted_at), withdrawn, declined_or_unknown or not_sent. Reads accepted as soon as the person is a connection, without a message being exchanged. Free tool.",
245
+ inputSchema: {
246
+ type: "object",
247
+ properties: {
248
+ account_id: { type: "string", description: "Connected LinkedIn account id (required)" },
249
+ invitation_id: { type: "string", description: "invitation_id returned by oneshot_linkedin_invite (required)" },
250
+ },
251
+ required: ["account_id", "invitation_id"],
252
+ },
253
+ };
254
+ export async function handleLinkedinInvitation(agent, args) {
255
+ return agent.linkedinInvitation({ accountId: args.account_id, invitationId: args.invitation_id });
256
+ }
257
+ export const linkedinInvitationsTool = {
258
+ name: "oneshot_linkedin_invitations",
259
+ description: "List invitations sent with oneshot_linkedin_invite for a connected account, most recently changed first. Filter by status; poll with changed_since to learn which were accepted. Free tool.",
260
+ inputSchema: {
261
+ type: "object",
262
+ properties: {
263
+ account_id: { type: "string", description: "Connected LinkedIn account id (required)" },
264
+ status: { type: "string", enum: ["pending", "accepted", "withdrawn", "declined_or_unknown", "not_sent"] },
265
+ changed_since: { type: "string", description: "ISO time: only invitations whose status changed after this" },
266
+ limit: { type: "number", description: "1-100, default 50" },
267
+ },
268
+ required: ["account_id"],
269
+ },
270
+ };
271
+ export async function handleLinkedinInvitations(agent, args) {
272
+ return agent.linkedinInvitations({
273
+ accountId: args.account_id,
274
+ status: args.status,
275
+ changedSince: args.changed_since,
276
+ limit: args.limit,
277
+ });
278
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oneshot-agent/mcp-server",
3
- "version": "0.33.0",
4
- "description": "MCP server for OneShot - 78 tools for AI agents",
3
+ "version": "0.34.0",
4
+ "description": "MCP server for OneShot - 81 tools for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -11,7 +11,8 @@
11
11
  "scripts": {
12
12
  "build": "tsc",
13
13
  "dev": "tsc --watch",
14
- "prepublishOnly": "npm run build"
14
+ "prepublishOnly": "npm run build",
15
+ "typecheck": "tsc --noEmit --incremental false"
15
16
  },
16
17
  "keywords": [
17
18
  "mcp",
@@ -25,7 +26,7 @@
25
26
  "dependencies": {
26
27
  "@coinbase/cdp-sdk": "1.45.0",
27
28
  "@modelcontextprotocol/sdk": "1.27.1",
28
- "@oneshot-agent/sdk": "^0.47.0",
29
+ "@oneshot-agent/sdk": "^0.48.0",
29
30
  "zod": "3.25.76"
30
31
  },
31
32
  "devDependencies": {
@@ -39,5 +40,19 @@
39
40
  "dist",
40
41
  "bin",
41
42
  "README.md"
42
- ]
43
+ ],
44
+ "nx": {
45
+ "targets": {
46
+ "build": {
47
+ "cache": true,
48
+ "inputs": [
49
+ "default",
50
+ "^default"
51
+ ],
52
+ "outputs": [
53
+ "{projectRoot}/dist"
54
+ ]
55
+ }
56
+ }
57
+ }
43
58
  }