Skip to content

Commit 3be1a8e

Browse files
authored
Merge pull request #47 from brand-dot-dev/release-please--branches--main--changes--next--components--brand.dev
release: 0.29.1
2 parents ce48186 + f2ccf68 commit 3be1a8e

File tree

16 files changed

+194
-53
lines changed

16 files changed

+194
-53
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-
".": "0.29.0"
2+
".": "0.29.1"
33
}

CHANGELOG.md

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

3+
## 0.29.1 (2026-03-06)
4+
5+
Full Changelog: [v0.29.0...v0.29.1](https://github.com/brand-dot-dev/typescript-sdk/compare/v0.29.0...v0.29.1)
6+
7+
### Chores
8+
9+
* **internal:** codegen related update ([836c5e9](https://github.com/brand-dot-dev/typescript-sdk/commit/836c5e9cac2ad66c6f4b65484ec34ff4286e845b))
10+
* **internal:** codegen related update ([aa92d3f](https://github.com/brand-dot-dev/typescript-sdk/commit/aa92d3f5602f7a1869501c13186985b3dc7d926e))
11+
* **internal:** use x-stainless-mcp-client-envs header for MCP remote code tool calls ([4ef071f](https://github.com/brand-dot-dev/typescript-sdk/commit/4ef071f664f0bbf4e2d5b2dffc549c1821f95e89))
12+
* **mcp-server:** return access instructions for 404 without API key ([476d055](https://github.com/brand-dot-dev/typescript-sdk/commit/476d05574f82aff4ad98725ac9d0aa3207ef793a))
13+
314
## 0.29.0 (2026-03-01)
415

516
Full Changelog: [v0.28.0...v0.29.0](https://github.com/brand-dot-dev/typescript-sdk/compare/v0.28.0...v0.29.0)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "brand.dev",
3-
"version": "0.29.0",
3+
"version": "0.29.1",
44
"description": "The official TypeScript library for the Brand Dev API",
55
"author": "Brand Dev <hello@brand.dev>",
66
"types": "dist/index.d.ts",

packages/mcp-server/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"dxt_version": "0.2",
33
"name": "brand.dev-mcp",
4-
"version": "0.29.0",
4+
"version": "0.29.1",
55
"description": "The official MCP Server for the Brand Dev API",
66
"author": {
77
"name": "Brand Dev",

packages/mcp-server/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "brand.dev-mcp",
3-
"version": "0.29.0",
3+
"version": "0.29.1",
44
"description": "The official MCP Server for the Brand Dev API",
55
"author": "Brand Dev <hello@brand.dev>",
66
"types": "dist/index.d.ts",
@@ -39,8 +39,9 @@
3939
"express": "^5.1.0",
4040
"fuse.js": "^7.1.0",
4141
"jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz",
42-
"morgan": "^1.10.0",
43-
"morgan-body": "^2.6.9",
42+
"pino": "^10.3.1",
43+
"pino-http": "^11.0.0",
44+
"pino-pretty": "^13.1.3",
4445
"qs": "^6.14.1",
4546
"typescript": "5.8.3",
4647
"yargs": "^17.7.2",
@@ -57,7 +58,6 @@
5758
"@types/cors": "^2.8.19",
5859
"@types/express": "^5.0.3",
5960
"@types/jest": "^29.4.0",
60-
"@types/morgan": "^1.9.10",
6161
"@types/qs": "^6.14.0",
6262
"@types/yargs": "^17.0.8",
6363
"@typescript-eslint/eslint-plugin": "8.31.1",

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

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
import { Tool } from '@modelcontextprotocol/sdk/types.js';
1818
import { readEnv, requireValue } from './util';
1919
import { WorkerInput, WorkerOutput } from './code-tool-types';
20+
import { getLogger } from './logger';
2021
import { SdkMethod } from './methods';
2122
import { McpCodeExecutionMode } from './options';
2223
import { ClientOptions } from 'brand.dev';
@@ -83,6 +84,8 @@ export function codeTool({
8384
},
8485
};
8586

87+
const logger = getLogger();
88+
8689
const handler = async ({
8790
reqContext,
8891
args,
@@ -107,11 +110,27 @@ export function codeTool({
107110
}
108111
}
109112

113+
let result: ToolCallResult;
114+
const startTime = Date.now();
115+
110116
if (codeExecutionMode === 'local') {
111-
return await localDenoHandler({ reqContext, args });
117+
logger.debug('Executing code in local Deno environment');
118+
result = await localDenoHandler({ reqContext, args });
112119
} else {
113-
return await remoteStainlessHandler({ reqContext, args });
120+
logger.debug('Executing code in remote Stainless environment');
121+
result = await remoteStainlessHandler({ reqContext, args });
114122
}
123+
124+
logger.info(
125+
{
126+
codeExecutionMode,
127+
durationMs: Date.now() - startTime,
128+
isError: result.isError,
129+
contentRows: result.content?.length ?? 0,
130+
},
131+
'Got code tool execution result',
132+
);
133+
return result;
115134
};
116135

117136
return { metadata, tool, handler };
@@ -136,7 +155,7 @@ const remoteStainlessHandler = async ({
136155
headers: {
137156
...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }),
138157
'Content-Type': 'application/json',
139-
client_envs: JSON.stringify({
158+
'x-stainless-mcp-client-envs': JSON.stringify({
140159
BRAND_DEV_API_KEY: requireValue(
141160
readEnv('BRAND_DEV_API_KEY') ?? client.apiKey,
142161
'set BRAND_DEV_API_KEY environment variable or provide apiKey client option',
@@ -153,6 +172,11 @@ const remoteStainlessHandler = async ({
153172
});
154173

155174
if (!res.ok) {
175+
if (res.status === 404 && !reqContext.stainlessApiKey) {
176+
throw new Error(
177+
'Could not access code tool for this project. You may need to provide a Stainless API key via the STAINLESS_API_KEY environment variable, the --stainless-api-key flag, or the x-stainless-api-key HTTP header.',
178+
);
179+
}
156180
throw new Error(
157181
`${res.status}: ${
158182
res.statusText

packages/mcp-server/src/docs-search-tool.ts

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

3-
import { Metadata, McpRequestContext, asTextContentResult } from './types';
43
import { Tool } from '@modelcontextprotocol/sdk/types.js';
4+
import { Metadata, McpRequestContext, asTextContentResult } from './types';
5+
import { getLogger } from './logger';
56

67
export const metadata: Metadata = {
78
resource: 'all',
@@ -50,19 +51,49 @@ export const handler = async ({
5051
}) => {
5152
const body = args as any;
5253
const query = new URLSearchParams(body).toString();
54+
55+
const startTime = Date.now();
5356
const result = await fetch(`${docsSearchURL}?${query}`, {
5457
headers: {
5558
...(reqContext.stainlessApiKey && { Authorization: reqContext.stainlessApiKey }),
5659
},
5760
});
5861

62+
const logger = getLogger();
63+
5964
if (!result.ok) {
65+
const errorText = await result.text();
66+
logger.warn(
67+
{
68+
durationMs: Date.now() - startTime,
69+
query: body.query,
70+
status: result.status,
71+
statusText: result.statusText,
72+
errorText,
73+
},
74+
'Got error response from docs search tool',
75+
);
76+
77+
if (result.status === 404 && !reqContext.stainlessApiKey) {
78+
throw new Error(
79+
'Could not find docs for this project. You may need to provide a Stainless API key via the STAINLESS_API_KEY environment variable, the --stainless-api-key flag, or the x-stainless-api-key HTTP header.',
80+
);
81+
}
82+
6083
throw new Error(
61-
`${result.status}: ${result.statusText} when using doc search tool. Details: ${await result.text()}`,
84+
`${result.status}: ${result.statusText} when using doc search tool. Details: ${errorText}`,
6285
);
6386
}
6487

65-
return asTextContentResult(await result.json());
88+
const resultBody = await result.json();
89+
logger.info(
90+
{
91+
durationMs: Date.now() - startTime,
92+
query: body.query,
93+
},
94+
'Got docs search result',
95+
);
96+
return asTextContentResult(resultBody);
6697
};
6798

6899
export default { metadata, tool, handler };

packages/mcp-server/src/http.ts

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
44
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
55
import { ClientOptions } from 'brand.dev';
66
import express from 'express';
7-
import morgan from 'morgan';
8-
import morganBody from 'morgan-body';
7+
import pino from 'pino';
8+
import pinoHttp from 'pino-http';
99
import { getStainlessApiKey, parseClientAuthHeaders } from './auth';
10+
import { getLogger } from './logger';
1011
import { McpOptions } from './options';
1112
import { initMcpServer, newMcpServer } from './server';
1213

@@ -70,29 +71,60 @@ const del = async (req: express.Request, res: express.Response) => {
7071
});
7172
};
7273

74+
const redactHeaders = (headers: Record<string, any>) => {
75+
const hiddenHeaders = /auth|cookie|key|token/i;
76+
const filtered = { ...headers };
77+
Object.keys(filtered).forEach((key) => {
78+
if (hiddenHeaders.test(key)) {
79+
filtered[key] = '[REDACTED]';
80+
}
81+
});
82+
return filtered;
83+
};
84+
7385
export const streamableHTTPApp = ({
7486
clientOptions = {},
7587
mcpOptions,
76-
debug,
7788
}: {
7889
clientOptions?: ClientOptions;
7990
mcpOptions: McpOptions;
80-
debug: boolean;
8191
}): express.Express => {
8292
const app = express();
8393
app.set('query parser', 'extended');
8494
app.use(express.json());
85-
86-
if (debug) {
87-
morganBody(app, {
88-
logAllReqHeader: true,
89-
logAllResHeader: true,
90-
logRequestBody: true,
91-
logResponseBody: true,
92-
});
93-
} else {
94-
app.use(morgan('combined'));
95-
}
95+
app.use(
96+
pinoHttp({
97+
logger: getLogger(),
98+
customLogLevel: (req, res) => {
99+
if (res.statusCode >= 500) {
100+
return 'error';
101+
} else if (res.statusCode >= 400) {
102+
return 'warn';
103+
}
104+
return 'info';
105+
},
106+
customSuccessMessage: function (req, res) {
107+
return `Request ${req.method} to ${req.url} completed with status ${res.statusCode}`;
108+
},
109+
customErrorMessage: function (req, res, err) {
110+
return `Request ${req.method} to ${req.url} errored with status ${res.statusCode}`;
111+
},
112+
serializers: {
113+
req: pino.stdSerializers.wrapRequestSerializer((req) => {
114+
return {
115+
...req,
116+
headers: redactHeaders(req.raw.headers),
117+
};
118+
}),
119+
res: pino.stdSerializers.wrapResponseSerializer((res) => {
120+
return {
121+
...res,
122+
headers: redactHeaders(res.headers),
123+
};
124+
}),
125+
},
126+
}),
127+
);
96128

97129
app.get('/health', async (req: express.Request, res: express.Response) => {
98130
res.status(200).send('OK');
@@ -106,22 +138,22 @@ export const streamableHTTPApp = ({
106138

107139
export const launchStreamableHTTPServer = async ({
108140
mcpOptions,
109-
debug,
110141
port,
111142
}: {
112143
mcpOptions: McpOptions;
113-
debug: boolean;
114144
port: number | string | undefined;
115145
}) => {
116-
const app = streamableHTTPApp({ mcpOptions, debug });
146+
const app = streamableHTTPApp({ mcpOptions });
117147
const server = app.listen(port);
118148
const address = server.address();
119149

150+
const logger = getLogger();
151+
120152
if (typeof address === 'string') {
121-
console.error(`MCP Server running on streamable HTTP at ${address}`);
153+
logger.info(`MCP Server running on streamable HTTP at ${address}`);
122154
} else if (address !== null) {
123-
console.error(`MCP Server running on streamable HTTP on port ${address.port}`);
155+
logger.info(`MCP Server running on streamable HTTP on port ${address.port}`);
124156
} else {
125-
console.error(`MCP Server running on streamable HTTP on port ${port}`);
157+
logger.info(`MCP Server running on streamable HTTP on port ${port}`);
126158
}
127159
};

packages/mcp-server/src/index.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ import { McpOptions, parseCLIOptions } from './options';
55
import { launchStdioServer } from './stdio';
66
import { launchStreamableHTTPServer } from './http';
77
import type { McpTool } from './types';
8+
import { configureLogger, getLogger } from './logger';
89

910
async function main() {
1011
const options = parseOptionsOrError();
12+
configureLogger({
13+
level: options.debug ? 'debug' : 'info',
14+
pretty: options.logFormat === 'pretty',
15+
});
1116

1217
const selectedTools = await selectToolsOrError(options);
1318

14-
console.error(
15-
`MCP Server starting with ${selectedTools.length} tools:`,
16-
selectedTools.map((e) => e.tool.name),
19+
getLogger().info(
20+
{ tools: selectedTools.map((e) => e.tool.name) },
21+
`MCP Server starting with ${selectedTools.length} tools`,
1722
);
1823

1924
switch (options.transport) {
@@ -23,7 +28,6 @@ async function main() {
2328
case 'http':
2429
await launchStreamableHTTPServer({
2530
mcpOptions: options,
26-
debug: options.debug,
2731
port: options.socket ?? options.port,
2832
});
2933
break;
@@ -32,7 +36,8 @@ async function main() {
3236

3337
if (require.main === module) {
3438
main().catch((error) => {
35-
console.error('Fatal error in main():', error);
39+
// Logger might not be initialized yet
40+
console.error('Fatal error in main()', error);
3641
process.exit(1);
3742
});
3843
}
@@ -41,7 +46,8 @@ function parseOptionsOrError() {
4146
try {
4247
return parseCLIOptions();
4348
} catch (error) {
44-
console.error('Error parsing options:', error);
49+
// Logger is initialized after options, so use console.error here
50+
console.error('Error parsing options', error);
4551
process.exit(1);
4652
}
4753
}
@@ -50,16 +56,12 @@ async function selectToolsOrError(options: McpOptions): Promise<McpTool[]> {
5056
try {
5157
const includedTools = selectTools(options);
5258
if (includedTools.length === 0) {
53-
console.error('No tools match the provided filters.');
59+
getLogger().error('No tools match the provided filters');
5460
process.exit(1);
5561
}
5662
return includedTools;
5763
} catch (error) {
58-
if (error instanceof Error) {
59-
console.error('Error filtering tools:', error.message);
60-
} else {
61-
console.error('Error filtering tools:', error);
62-
}
64+
getLogger().error({ error }, 'Error filtering tools');
6365
process.exit(1);
6466
}
6567
}

0 commit comments

Comments
 (0)