Skip to content

Commit d2bfda3

Browse files
authored
Merge pull request #19 from beeper/release-please--branches--main--changes--next--components--desktop-api
release: 4.2.3
2 parents 17942df + d7fc713 commit d2bfda3

File tree

14 files changed

+308
-42
lines changed

14 files changed

+308
-42
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "4.2.2"
2+
".": "4.2.3"
33
}

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 15
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-0763b61997721da6f4514241bf0f7bb5f7a88c7298baf0f1b2d58036aaf7e2f1.yml
3-
openapi_spec_hash: 5158475919c04bb52fb03c6a4582188d
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-bea2e5f3b01053a66261a824c75c2640856d0ba00ad795ab71734c4ab9ae33b0.yml
3+
openapi_spec_hash: d766f6e344c12ca6d23e6ef6713b38c4
44
config_hash: 5fa7ded4bfdffe4cc1944a819da87f9f

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
# Changelog
22

3+
## 4.2.3 (2025-11-08)
4+
5+
Full Changelog: [v4.2.2...v4.2.3](https://github.com/beeper/desktop-api-js/compare/v4.2.2...v4.2.3)
6+
7+
### Features
8+
9+
* **api:** add `description` field to chats, make `title` optional ([b18ee1e](https://github.com/beeper/desktop-api-js/commit/b18ee1edc0d59fedaf31c71f7d5cae677061d1e6))
10+
* **mcp:** enable optional code execution tool on http mcp servers ([02de91e](https://github.com/beeper/desktop-api-js/commit/02de91e3fd9b820b471fa31690da7b1de98dda07))
11+
12+
13+
### Bug Fixes
14+
15+
* **mcpb:** pin @anthropic-ai/mcpb version ([7592b25](https://github.com/beeper/desktop-api-js/commit/7592b25fd496231d0991b74ef1b4887235edba5e))
16+
17+
18+
### Chores
19+
20+
* **internal:** codegen related update ([e45eb2c](https://github.com/beeper/desktop-api-js/commit/e45eb2ce64c47e5e1558d9558ead0fd21e40a447))
21+
* **internal:** codegen related update ([06efc95](https://github.com/beeper/desktop-api-js/commit/06efc9525352b8deec8fa9cc8180c02cd1ebb139))
22+
* **internal:** grammar fix (it's -> its) ([10090e9](https://github.com/beeper/desktop-api-js/commit/10090e9af4b4aefa51df078357710493f518888f))
23+
* mcp code tool explicit error message when missing a run function ([1bc0f3e](https://github.com/beeper/desktop-api-js/commit/1bc0f3ee57009ecb1e4153165feac2c5fc74ff3b))
24+
* **mcp:** add friendlier MCP code tool errors on incorrect method invocations ([a2c4ebe](https://github.com/beeper/desktop-api-js/commit/a2c4ebef0fb8d807348281385a856bb73f1bc0e2))
25+
* **mcp:** add line numbers to code tool errors ([e9eb036](https://github.com/beeper/desktop-api-js/commit/e9eb036ce4bef49f1a9f2c18c1509e4a83f1e604))
26+
* use structured error when code execution tool errors ([ee2d35b](https://github.com/beeper/desktop-api-js/commit/ee2d35be66aa8dd332c69ac8ec1b83fd2cbb8d49))
27+
28+
29+
### Documentation
30+
31+
* **mcp:** add a README button for one-click add to Cursor ([238e178](https://github.com/beeper/desktop-api-js/commit/238e1787c5d6e34cf49def65b2de48fefd2822c7))
32+
* **mcp:** add a README link to add server to VS Code or Claude Code ([0f7efe8](https://github.com/beeper/desktop-api-js/commit/0f7efe85bbbbcfd50c472f4654bb8b2bf8fb991a))
33+
334
## 4.2.2 (2025-10-18)
435

536
Full Changelog: [v4.2.1...v4.2.2](https://github.com/beeper/desktop-api-js/compare/v4.2.1...v4.2.2)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@beeper/desktop-api",
3-
"version": "4.2.2",
3+
"version": "4.2.3",
44
"description": "The official TypeScript library for the Beeper Desktop API",
55
"author": "Beeper Desktop <[email protected]>",
66
"types": "dist/index.d.ts",

packages/mcp-server/README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,36 @@ For clients with a configuration JSON, it might look something like this:
3636
}
3737
```
3838

39+
### Cursor
40+
41+
If you use Cursor, you can install the MCP server by using the button below. You will need to set your environment variables
42+
in Cursor's `mcp.json`, which can be found in Cursor Settings > Tools & MCP > New MCP Server.
43+
44+
[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=@beeper/desktop-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBiZWVwZXIvZGVza3RvcC1tY3AiXSwiZW52Ijp7IkJFRVBFUl9BQ0NFU1NfVE9LRU4iOiJTZXQgeW91ciBCRUVQRVJfQUNDRVNTX1RPS0VOIGhlcmUuIn19)
45+
46+
### VS Code
47+
48+
If you use MCP, you can install the MCP server by clicking the link below. You will need to set your environment variables
49+
in VS Code's `mcp.json`, which can be found via Command Palette > MCP: Open User Configuration.
50+
51+
[Open VS Code](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40beeper%2Fdesktop-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40beeper%2Fdesktop-mcp%22%5D%2C%22env%22%3A%7B%22BEEPER_ACCESS_TOKEN%22%3A%22Set%20your%20BEEPER_ACCESS_TOKEN%20here.%22%7D%7D)
52+
53+
### Claude Code
54+
55+
If you use Claude Code, you can install the MCP server by running the command below in your terminal. You will need to set your
56+
environment variables in Claude Code's `.claude.json`, which can be found in your home directory.
57+
58+
```
59+
claude mcp add --transport stdio beeper_desktop_api_api --env BEEPER_ACCESS_TOKEN="Your BEEPER_ACCESS_TOKEN here." -- npx -y @beeper/desktop-mcp
60+
```
61+
3962
## Exposing endpoints to your MCP Client
4063

41-
There are two ways to expose endpoints as tools in the MCP server:
64+
There are three ways to expose endpoints as tools in the MCP server:
4265

4366
1. Exposing one tool per endpoint, and filtering as necessary
4467
2. Exposing a set of tools to dynamically discover and invoke endpoints from the API
68+
3. Exposing a docs search tool and a code execution tool, allowing the client to write code to be executed against the TypeScript client
4569

4670
### Filtering endpoints and tools
4771

@@ -77,6 +101,18 @@ All of these command-line options can be repeated, combined together, and have c
77101

78102
Use `--list` to see the list of available tools, or see below.
79103

104+
### Code execution
105+
106+
If you specify `--tools=code` to the MCP server, it will expose just two tools:
107+
108+
- `search_docs` - Searches the API documentation and returns a list of markdown results
109+
- `execute` - Runs code against the TypeScript client
110+
111+
This allows the LLM to implement more complex logic by chaining together many API calls without loading
112+
intermediary results into its context window.
113+
114+
The code execution itself happens in a Deno sandbox that has network access only to the base URL for the API.
115+
80116
### Specifying the MCP Client
81117

82118
Different clients have varying abilities to handle arbitrary tools and schemas.

packages/mcp-server/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@beeper/desktop-mcp",
3-
"version": "4.2.2",
3+
"version": "4.2.3",
44
"description": "The official MCP Server for the Beeper Desktop API",
55
"author": "Beeper Desktop <[email protected]>",
66
"types": "dist/index.d.ts",
@@ -37,8 +37,10 @@
3737
"cors": "^2.8.5",
3838
"date-fns": "^4.1.0",
3939
"express": "^5.1.0",
40+
"fuse.js": "^7.1.0",
4041
"jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.6/jq-web.tar.gz",
4142
"qs": "^6.14.0",
43+
"typescript": "5.8.3",
4244
"yargs": "^17.7.2",
4345
"zod": "^3.25.20",
4446
"zod-to-json-schema": "^3.24.5",
@@ -48,7 +50,7 @@
4850
"mcp-server": "dist/index.js"
4951
},
5052
"devDependencies": {
51-
"@anthropic-ai/mcpb": "^1.1.0",
53+
"@anthropic-ai/mcpb": "1.1.0",
5254
"@types/cors": "^2.8.19",
5355
"@types/express": "^5.0.3",
5456
"@types/jest": "^29.4.0",
@@ -65,8 +67,7 @@
6567
"ts-morph": "^19.0.0",
6668
"ts-node": "^10.5.0",
6769
"tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz",
68-
"tsconfig-paths": "^4.0.0",
69-
"typescript": "5.8.3"
70+
"tsconfig-paths": "^4.0.0"
7071
},
7172
"imports": {
7273
"@beeper/desktop-mcp": ".",

packages/mcp-server/src/code-tool-worker.ts

Lines changed: 170 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,178 @@
11
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
import util from 'node:util';
4+
5+
import Fuse from 'fuse.js';
6+
import ts from 'typescript';
7+
48
import { WorkerInput, WorkerSuccess, WorkerError } from './code-tool-types';
59
import { BeeperDesktop } from '@beeper/desktop-api';
610

11+
function getRunFunctionNode(
12+
code: string,
13+
): ts.FunctionDeclaration | ts.FunctionExpression | ts.ArrowFunction | null {
14+
const sourceFile = ts.createSourceFile('code.ts', code, ts.ScriptTarget.Latest, true);
15+
16+
for (const statement of sourceFile.statements) {
17+
// Check for top-level function declarations
18+
if (ts.isFunctionDeclaration(statement)) {
19+
if (statement.name?.text === 'run') {
20+
return statement;
21+
}
22+
}
23+
24+
// Check for variable declarations: const run = () => {} or const run = function() {}
25+
if (ts.isVariableStatement(statement)) {
26+
for (const declaration of statement.declarationList.declarations) {
27+
if (ts.isIdentifier(declaration.name) && declaration.name.text === 'run') {
28+
// Check if it's initialized with a function
29+
if (
30+
declaration.initializer &&
31+
(ts.isFunctionExpression(declaration.initializer) || ts.isArrowFunction(declaration.initializer))
32+
) {
33+
return declaration.initializer;
34+
}
35+
}
36+
}
37+
}
38+
}
39+
40+
return null;
41+
}
42+
43+
const fuse = new Fuse(
44+
[
45+
'client.focus',
46+
'client.search',
47+
'client.accounts.list',
48+
'client.accounts.contacts.search',
49+
'client.chats.archive',
50+
'client.chats.create',
51+
'client.chats.list',
52+
'client.chats.retrieve',
53+
'client.chats.search',
54+
'client.chats.reminders.create',
55+
'client.chats.reminders.delete',
56+
'client.messages.list',
57+
'client.messages.search',
58+
'client.messages.send',
59+
'client.assets.download',
60+
],
61+
{ threshold: 1, shouldSort: true },
62+
);
63+
64+
function getMethodSuggestions(fullyQualifiedMethodName: string): string[] {
65+
return fuse
66+
.search(fullyQualifiedMethodName)
67+
.map(({ item }) => item)
68+
.slice(0, 5);
69+
}
70+
71+
const proxyToObj = new WeakMap<any, any>();
72+
const objToProxy = new WeakMap<any, any>();
73+
74+
type ClientProxyConfig = {
75+
path: string[];
76+
isBelievedBad?: boolean;
77+
};
78+
79+
function makeSdkProxy<T extends object>(obj: T, { path, isBelievedBad = false }: ClientProxyConfig): T {
80+
let proxy: T = objToProxy.get(obj);
81+
82+
if (!proxy) {
83+
proxy = new Proxy(obj, {
84+
get(target, prop, receiver) {
85+
const propPath = [...path, String(prop)];
86+
const value = Reflect.get(target, prop, receiver);
87+
88+
if (isBelievedBad || (!(prop in target) && value === undefined)) {
89+
// If we're accessing a path that doesn't exist, it will probably eventually error.
90+
// Let's proxy it and mark it bad so that we can control the error message.
91+
// We proxy an empty class so that an invocation or construction attempt is possible.
92+
return makeSdkProxy(class {}, { path: propPath, isBelievedBad: true });
93+
}
94+
95+
if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
96+
return makeSdkProxy(value, { path: propPath, isBelievedBad });
97+
}
98+
99+
return value;
100+
},
101+
102+
apply(target, thisArg, args) {
103+
if (isBelievedBad || typeof target !== 'function') {
104+
const fullyQualifiedMethodName = path.join('.');
105+
const suggestions = getMethodSuggestions(fullyQualifiedMethodName);
106+
throw new Error(
107+
`${fullyQualifiedMethodName} is not a function. Did you mean: ${suggestions.join(', ')}`,
108+
);
109+
}
110+
111+
return Reflect.apply(target, proxyToObj.get(thisArg) ?? thisArg, args);
112+
},
113+
114+
construct(target, args, newTarget) {
115+
if (isBelievedBad || typeof target !== 'function') {
116+
const fullyQualifiedMethodName = path.join('.');
117+
const suggestions = getMethodSuggestions(fullyQualifiedMethodName);
118+
throw new Error(
119+
`${fullyQualifiedMethodName} is not a constructor. Did you mean: ${suggestions.join(', ')}`,
120+
);
121+
}
122+
123+
return Reflect.construct(target, args, newTarget);
124+
},
125+
});
126+
127+
objToProxy.set(obj, proxy);
128+
proxyToObj.set(proxy, obj);
129+
}
130+
131+
return proxy;
132+
}
133+
134+
function parseError(code: string, error: unknown): string | undefined {
135+
if (!(error instanceof Error)) return;
136+
const message = error.name ? `${error.name}: ${error.message}` : error.message;
137+
try {
138+
// Deno uses V8; the first "<anonymous>:LINE:COLUMN" is the top of stack.
139+
const lineNumber = error.stack?.match(/<anonymous>:([0-9]+):[0-9]+/)?.[1];
140+
// -1 for the zero-based indexing
141+
const line =
142+
lineNumber &&
143+
code
144+
.split('\n')
145+
.at(parseInt(lineNumber, 10) - 1)
146+
?.trim();
147+
return line ? `${message}\n at line ${lineNumber}\n ${line}` : message;
148+
} catch {
149+
return message;
150+
}
151+
}
152+
7153
const fetch = async (req: Request): Promise<Response> => {
8154
const { opts, code } = (await req.json()) as WorkerInput;
155+
if (code == null) {
156+
return Response.json(
157+
{
158+
message:
159+
'The code param is missing. Provide one containing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```',
160+
} satisfies WorkerError,
161+
{ status: 400, statusText: 'Code execution error' },
162+
);
163+
}
164+
165+
const runFunctionNode = getRunFunctionNode(code);
166+
if (!runFunctionNode) {
167+
return Response.json(
168+
{
169+
message:
170+
'The code is missing a top-level `run` function. Write code within this template:\n\n```\nasync function run(client) {\n // Fill this out\n}\n```',
171+
} satisfies WorkerError,
172+
{ status: 400, statusText: 'Code execution error' },
173+
);
174+
}
175+
9176
const client = new BeeperDesktop({
10177
...opts,
11178
});
@@ -22,21 +189,17 @@ const fetch = async (req: Request): Promise<Response> => {
22189
};
23190
try {
24191
let run_ = async (client: any) => {};
25-
eval(`
26-
${code}
27-
run_ = run;
28-
`);
29-
const result = await run_(client);
192+
eval(`${code}\nrun_ = run;`);
193+
const result = await run_(makeSdkProxy(client, { path: ['client'] }));
30194
return Response.json({
31195
result,
32196
logLines,
33197
errLines,
34198
} satisfies WorkerSuccess);
35199
} catch (e) {
36-
const message = e instanceof Error ? e.message : undefined;
37200
return Response.json(
38201
{
39-
message,
202+
message: parseError(code, e),
40203
} satisfies WorkerError,
41204
{ status: 400, statusText: 'Code execution error' },
42205
);

0 commit comments

Comments
 (0)