Skip to content

Commit cb4d23e

Browse files
release: 9.1.1 (#645)
* chore(internal): cache fetch instruction calls in MCP server * fix(mcp): initialize SDK lazily to avoid failing the connection on init errors * chore: update mock server docs * chore(mcp): correctly update version in sync with sdk * fix(docs/contributing): correct pnpm link command * chore(internal): upgrade @modelcontextprotocol/sdk and hono * release: 9.1.1 --------- Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
1 parent 5bae442 commit cb4d23e

File tree

12 files changed

+175
-83
lines changed

12 files changed

+175
-83
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-
".": "9.1.0"
2+
".": "9.1.1"
33
}

CHANGELOG.md

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

3+
## 9.1.1 (2026-02-23)
4+
5+
Full Changelog: [v9.1.0...v9.1.1](https://github.com/Finch-API/finch-api-node/compare/v9.1.0...v9.1.1)
6+
7+
### Bug Fixes
8+
9+
* **docs/contributing:** correct pnpm link command ([fbc6228](https://github.com/Finch-API/finch-api-node/commit/fbc622849196606ea0610d656d80d9906b674c50))
10+
* **mcp:** initialize SDK lazily to avoid failing the connection on init errors ([b8baaa7](https://github.com/Finch-API/finch-api-node/commit/b8baaa724f8ecd0f8302642f43794ebe8d8f65f0))
11+
12+
13+
### Chores
14+
15+
* **internal:** cache fetch instruction calls in MCP server ([3949456](https://github.com/Finch-API/finch-api-node/commit/3949456cc64a6d002468cf4094726a0149361373))
16+
* **internal:** upgrade @modelcontextprotocol/sdk and hono ([df73805](https://github.com/Finch-API/finch-api-node/commit/df73805ff5245a55778bf67b3d19c7df3265da91))
17+
* **mcp:** correctly update version in sync with sdk ([d0c7741](https://github.com/Finch-API/finch-api-node/commit/d0c7741ce567161f542c2ef795ee8a7965e7b069))
18+
* update mock server docs ([5934a67](https://github.com/Finch-API/finch-api-node/commit/5934a6731510047f2829f0e7fa412d387f8c3fdd))
19+
320
## 9.1.0 (2026-02-17)
421

522
Full Changelog: [v9.0.0...v9.1.0](https://github.com/Finch-API/finch-api-node/compare/v9.0.0...v9.1.0)

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ $ yarn link @tryfinch/finch-api
6060
# With pnpm
6161
$ pnpm link --global
6262
$ cd ../my-package
63-
$ pnpm link -global @tryfinch/finch-api
63+
$ pnpm link --global @tryfinch/finch-api
6464
```
6565

6666
## Running tests
6767

6868
Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests.
6969

7070
```sh
71-
$ npx prism mock path/to/your/openapi.yml
71+
$ ./scripts/mock
7272
```
7373

7474
```sh

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tryfinch/finch-api",
3-
"version": "9.1.0",
3+
"version": "9.1.1",
44
"description": "The official TypeScript library for the Finch API",
55
"author": "Finch <founders@tryfinch.com>",
66
"types": "dist/index.d.ts",

packages/mcp-server/manifest.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"dxt_version": "0.2",
33
"name": "@tryfinch/finch-api-mcp",
4-
"version": "6.37.0",
4+
"version": "9.1.1",
55
"description": "The official MCP Server for the Finch API",
66
"author": {
77
"name": "Finch",
@@ -18,7 +18,9 @@
1818
"entry_point": "index.js",
1919
"mcp_config": {
2020
"command": "node",
21-
"args": ["${__dirname}/index.js"],
21+
"args": [
22+
"${__dirname}/index.js"
23+
],
2224
"env": {
2325
"FINCH_ACCESS_TOKEN": "${user_config.FINCH_ACCESS_TOKEN}",
2426
"FINCH_CLIENT_ID": "${user_config.FINCH_CLIENT_ID}",
@@ -60,5 +62,7 @@
6062
"node": ">=18.0.0"
6163
}
6264
},
63-
"keywords": ["api"]
65+
"keywords": [
66+
"api"
67+
]
6468
}

packages/mcp-server/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tryfinch/finch-api-mcp",
3-
"version": "9.1.0",
3+
"version": "9.1.1",
44
"description": "The official MCP Server for the Finch API",
55
"author": "Finch <founders@tryfinch.com>",
66
"types": "dist/index.d.ts",
@@ -32,7 +32,7 @@
3232
"dependencies": {
3333
"@tryfinch/finch-api": "file:../../dist/",
3434
"@cloudflare/cabidela": "^0.2.4",
35-
"@modelcontextprotocol/sdk": "^1.25.2",
35+
"@modelcontextprotocol/sdk": "^1.26.0",
3636
"@valtown/deno-http-worker": "^0.0.21",
3737
"cookie-parser": "^1.4.6",
3838
"cors": "^2.8.5",

packages/mcp-server/src/http.ts

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,17 @@ const newServer = async ({
2424
const stainlessApiKey = getStainlessApiKey(req, mcpOptions);
2525
const server = await newMcpServer(stainlessApiKey);
2626

27-
try {
28-
const authOptions = parseClientAuthHeaders(req, false);
27+
const authOptions = parseClientAuthHeaders(req, false);
2928

30-
await initMcpServer({
31-
server: server,
32-
mcpOptions: mcpOptions,
33-
clientOptions: {
34-
...clientOptions,
35-
...authOptions,
36-
},
37-
stainlessApiKey: stainlessApiKey,
38-
});
39-
} catch (error) {
40-
res.status(401).json({
41-
jsonrpc: '2.0',
42-
error: {
43-
code: -32000,
44-
message: `Unauthorized: ${error instanceof Error ? error.message : error}`,
45-
},
46-
});
47-
return null;
48-
}
29+
await initMcpServer({
30+
server: server,
31+
mcpOptions: mcpOptions,
32+
clientOptions: {
33+
...clientOptions,
34+
...authOptions,
35+
},
36+
stainlessApiKey: stainlessApiKey,
37+
});
4938

5039
return server;
5140
};

packages/mcp-server/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ async function main() {
2424
await launchStreamableHTTPServer({
2525
mcpOptions: options,
2626
debug: options.debug,
27-
port: options.port ?? options.socket,
27+
port: options.socket ?? options.port,
2828
});
2929
break;
3030
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
import { readEnv } from './util';
4+
5+
const INSTRUCTIONS_CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
6+
7+
interface InstructionsCacheEntry {
8+
fetchedInstructions: string;
9+
fetchedAt: number;
10+
}
11+
12+
const instructionsCache = new Map<string, InstructionsCacheEntry>();
13+
14+
// Periodically evict stale entries so the cache doesn't grow unboundedly.
15+
const _cacheCleanupInterval = setInterval(() => {
16+
const now = Date.now();
17+
for (const [key, entry] of instructionsCache) {
18+
if (now - entry.fetchedAt > INSTRUCTIONS_CACHE_TTL_MS) {
19+
instructionsCache.delete(key);
20+
}
21+
}
22+
}, INSTRUCTIONS_CACHE_TTL_MS);
23+
24+
// Don't keep the process alive just for cleanup.
25+
_cacheCleanupInterval.unref();
26+
27+
export async function getInstructions(stainlessApiKey: string | undefined): Promise<string> {
28+
const cacheKey = stainlessApiKey ?? '';
29+
const cached = instructionsCache.get(cacheKey);
30+
31+
if (cached && Date.now() - cached.fetchedAt <= INSTRUCTIONS_CACHE_TTL_MS) {
32+
return cached.fetchedInstructions;
33+
}
34+
35+
const fetchedInstructions = await fetchLatestInstructions(stainlessApiKey);
36+
instructionsCache.set(cacheKey, { fetchedInstructions, fetchedAt: Date.now() });
37+
return fetchedInstructions;
38+
}
39+
40+
async function fetchLatestInstructions(stainlessApiKey: string | undefined): Promise<string> {
41+
// Setting the stainless API key is optional, but may be required
42+
// to authenticate requests to the Stainless API.
43+
const response = await fetch(
44+
readEnv('CODE_MODE_INSTRUCTIONS_URL') ?? 'https://api.stainless.com/api/ai/instructions/finch',
45+
{
46+
method: 'GET',
47+
headers: { ...(stainlessApiKey && { Authorization: stainlessApiKey }) },
48+
},
49+
);
50+
51+
let instructions: string | undefined;
52+
if (!response.ok) {
53+
console.warn(
54+
'Warning: failed to retrieve MCP server instructions. Proceeding with default instructions...',
55+
);
56+
57+
instructions = `
58+
This is the finch MCP server. You will use Code Mode to help the user perform
59+
actions. You can use search_docs tool to learn about how to take action with this server. Then,
60+
you will write TypeScript code using the execute tool take action. It is CRITICAL that you be
61+
thoughtful and deliberate when executing code. Always try to entirely solve the problem in code
62+
block: it can be as long as you need to get the job done!
63+
`;
64+
}
65+
66+
instructions ??= ((await response.json()) as { instructions: string }).instructions;
67+
instructions = `
68+
If needed, you can get the current time by executing Date.now().
69+
70+
${instructions}
71+
`;
72+
73+
return instructions;
74+
}

packages/mcp-server/src/server.ts

Lines changed: 54 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,52 +11,17 @@ import { ClientOptions } from '@tryfinch/finch-api';
1111
import Finch from '@tryfinch/finch-api';
1212
import { codeTool } from './code-tool';
1313
import docsSearchTool from './docs-search-tool';
14+
import { getInstructions } from './instructions';
1415
import { McpOptions } from './options';
1516
import { blockedMethodsForCodeTool } from './methods';
1617
import { HandlerFunction, McpRequestContext, ToolCallResult, McpTool } from './types';
1718
import { readEnv } from './util';
1819

19-
async function getInstructions(stainlessApiKey: string | undefined): Promise<string> {
20-
// Setting the stainless API key is optional, but may be required
21-
// to authenticate requests to the Stainless API.
22-
const response = await fetch(
23-
readEnv('CODE_MODE_INSTRUCTIONS_URL') ?? 'https://api.stainless.com/api/ai/instructions/finch',
24-
{
25-
method: 'GET',
26-
headers: { ...(stainlessApiKey && { Authorization: stainlessApiKey }) },
27-
},
28-
);
29-
30-
let instructions: string | undefined;
31-
if (!response.ok) {
32-
console.warn(
33-
'Warning: failed to retrieve MCP server instructions. Proceeding with default instructions...',
34-
);
35-
36-
instructions = `
37-
This is the finch MCP server. You will use Code Mode to help the user perform
38-
actions. You can use search_docs tool to learn about how to take action with this server. Then,
39-
you will write TypeScript code using the execute tool take action. It is CRITICAL that you be
40-
thoughtful and deliberate when executing code. Always try to entirely solve the problem in code
41-
block: it can be as long as you need to get the job done!
42-
`;
43-
}
44-
45-
instructions ??= ((await response.json()) as { instructions: string }).instructions;
46-
instructions = `
47-
The current time in Unix timestamps is ${Date.now()}.
48-
49-
${instructions}
50-
`;
51-
52-
return instructions;
53-
}
54-
5520
export const newMcpServer = async (stainlessApiKey: string | undefined) =>
5621
new McpServer(
5722
{
5823
name: 'tryfinch_finch_api_api',
59-
version: '9.1.0',
24+
version: '9.1.1',
6025
},
6126
{
6227
instructions: await getInstructions(stainlessApiKey),
@@ -91,15 +56,33 @@ export async function initMcpServer(params: {
9156
error: logAtLevel('error'),
9257
};
9358

94-
let client = new Finch({
95-
...{ accessToken: readEnv('FINCH_ACCESS_TOKEN') },
96-
logger,
97-
...params.clientOptions,
98-
defaultHeaders: {
99-
...params.clientOptions?.defaultHeaders,
100-
'X-Stainless-MCP': 'true',
101-
},
102-
});
59+
let _client: Finch | undefined;
60+
let _clientError: Error | undefined;
61+
let _logLevel: 'debug' | 'info' | 'warn' | 'error' | 'off' | undefined;
62+
63+
const getClient = (): Finch => {
64+
if (_clientError) throw _clientError;
65+
if (!_client) {
66+
try {
67+
_client = new Finch({
68+
...{ accessToken: readEnv('FINCH_ACCESS_TOKEN') },
69+
logger,
70+
...params.clientOptions,
71+
defaultHeaders: {
72+
...params.clientOptions?.defaultHeaders,
73+
'X-Stainless-MCP': 'true',
74+
},
75+
});
76+
if (_logLevel) {
77+
_client = _client.withOptions({ logLevel: _logLevel });
78+
}
79+
} catch (e) {
80+
_clientError = e instanceof Error ? e : new Error(String(e));
81+
throw _clientError;
82+
}
83+
}
84+
return _client;
85+
};
10386

10487
const providedTools = selectTools(params.mcpOptions);
10588
const toolMap = Object.fromEntries(providedTools.map((mcpTool) => [mcpTool.tool.name, mcpTool]));
@@ -117,6 +100,21 @@ export async function initMcpServer(params: {
117100
throw new Error(`Unknown tool: ${name}`);
118101
}
119102

103+
let client: Finch;
104+
try {
105+
client = getClient();
106+
} catch (error) {
107+
return {
108+
content: [
109+
{
110+
type: 'text' as const,
111+
text: `Failed to initialize client: ${error instanceof Error ? error.message : String(error)}`,
112+
},
113+
],
114+
isError: true,
115+
};
116+
}
117+
120118
return executeHandler({
121119
handler: mcpTool.handler,
122120
reqContext: {
@@ -129,24 +127,29 @@ export async function initMcpServer(params: {
129127

130128
server.setRequestHandler(SetLevelRequestSchema, async (request) => {
131129
const { level } = request.params;
130+
let logLevel: 'debug' | 'info' | 'warn' | 'error' | 'off';
132131
switch (level) {
133132
case 'debug':
134-
client = client.withOptions({ logLevel: 'debug' });
133+
logLevel = 'debug';
135134
break;
136135
case 'info':
137-
client = client.withOptions({ logLevel: 'info' });
136+
logLevel = 'info';
138137
break;
139138
case 'notice':
140139
case 'warning':
141-
client = client.withOptions({ logLevel: 'warn' });
140+
logLevel = 'warn';
142141
break;
143142
case 'error':
144-
client = client.withOptions({ logLevel: 'error' });
143+
logLevel = 'error';
145144
break;
146145
default:
147-
client = client.withOptions({ logLevel: 'off' });
146+
logLevel = 'off';
148147
break;
149148
}
149+
_logLevel = logLevel;
150+
if (_client) {
151+
_client = _client.withOptions({ logLevel });
152+
}
150153
return {};
151154
});
152155
}

0 commit comments

Comments
 (0)