@@ -5,8 +5,7 @@ import dotenv from "dotenv";
5
5
dotenv . config ( ) ;
6
6
7
7
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" ;
8
- import { createServer } from "./server.js" ;
9
- import { resolveConfig , type CLIOptions } from "./config.js" ;
8
+ import { Config } from "../config.js" ;
10
9
import type { Tool } from "./tools/tool.js" ;
11
10
12
11
import navigate from "./tools/navigate.js" ;
@@ -18,8 +17,13 @@ import common from "./tools/common.js";
18
17
import drag from "./tools/drag.js" ;
19
18
import hover from "./tools/hover.js" ;
20
19
import selectOption from "./tools/selectOption.js" ;
21
- import context from "./tools/context.js" ;
20
+ import contextTools from "./tools/context.js" ;
22
21
import cookies from "./tools/cookies.js" ;
22
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js" ;
23
+ import { CallToolRequestSchema , ListResourcesRequestSchema , ListToolsRequestSchema , ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js" ;
24
+ import { z } from "zod" ;
25
+ import { zodToJsonSchema } from "zod-to-json-schema" ;
26
+ import { Context } from "./context.js" ;
23
27
24
28
// Environment variables configuration
25
29
const requiredEnvVars = {
@@ -32,15 +36,28 @@ Object.entries(requiredEnvVars).forEach(([name, value]) => {
32
36
if ( ! value ) throw new Error ( `${ name } environment variable is required` ) ;
33
37
} ) ;
34
38
35
- const serverVersion = "0.5.1" ;
36
-
37
- async function main ( ) {
38
- const cliOptions : CLIOptions = { } ;
39
- const config = await resolveConfig ( cliOptions ) ;
39
+ // const serverVersion = "0.5.1";
40
40
41
+ export async function createServer ( config : Config ) : Promise < Server > {
41
42
// Assume true for captureSnapshot for keyboard, adjust if needed
42
43
const captureSnapshotFlag = true ;
43
44
45
+ // Create the server
46
+ const server = new Server (
47
+ { name : "mcp-server-browserbase" , version : "0.5.1" } ,
48
+ {
49
+ capabilities : {
50
+ resources : { list : true , read : true } ,
51
+ tools : { list : true , call : true } ,
52
+ prompts : { list : true , get : true } ,
53
+ notifications : { resources : { list_changed : true } } ,
54
+ } ,
55
+ }
56
+ ) ;
57
+
58
+ // Create the context, passing server instance and config
59
+ const context = new Context ( server , config ) ;
60
+
44
61
const tools : Tool < any > [] = [
45
62
...common,
46
63
...snapshot,
@@ -51,58 +68,103 @@ async function main() {
51
68
...getText, // Spread the array exported by getText.ts
52
69
...navigate, // Spread the array exported by navigate.ts
53
70
session, // Add the single tool object directly
54
- ...context ,
71
+ ...contextTools ,
55
72
...cookies,
56
73
];
57
74
58
- const toolsToUse = tools;
75
+ const toolsMap = new Map(tools.map(tool => [ tool . schema . name , tool ] ) ) ;
76
+ // --- Setup Request Handlers ---
59
77
60
- const server = createServer(
61
- {
62
- name : "Browserbase" ,
63
- version : serverVersion ,
64
- tools : toolsToUse ,
65
- } ,
66
- config
67
- );
68
-
69
- const signals: NodeJS.Signals[] = ["SIGINT", "SIGTERM"];
70
- signals.forEach((signal) => {
71
- process . on ( signal , async ( ) => {
72
- console . error ( `
73
- Received ${ signal } . Shutting down gracefully...` ) ;
74
- try {
75
- await server . close ( ) ;
76
- console . error ( "Server closed." ) ;
77
- } catch ( shutdownError ) {
78
- console . error ( "Error during shutdown:" , shutdownError ) ;
79
- } finally {
80
- process . exit ( 0 ) ;
81
- }
82
- } ) ;
78
+ server . setRequestHandler ( ListResourcesRequestSchema , async ( ) => {
79
+ return { resources : context . listResources ( ) } ;
83
80
} ) ;
84
81
85
- try {
86
- const transport = new StdioServerTransport ( ) ;
87
- await server . connect ( transport ) ;
88
- console . log ( "Browserbase MCP server connected via stdio." ) ;
89
- } catch (error) {
90
- console . error ( "Failed to connect server:" , error ) ;
91
- process . exit ( 1 ) ;
92
- }
93
- }
94
-
95
- main ( ) . catch ( ( err ) => {
96
- console . error ( "Error starting server:" , err ) ;
97
- process . exit ( 1 ) ;
98
- } );
82
+ server . setRequestHandler ( ReadResourceRequestSchema , async ( request ) => {
83
+ try {
84
+ const resourceContent = context . readResource ( request . params . uri . toString ( ) ) ;
85
+ return { contents : [ resourceContent ] } ;
86
+ } catch ( error ) {
87
+ console . error ( `Error reading resource via context: ${ error } ` ) ;
88
+ throw error ;
89
+ }
90
+ } ) ;
99
91
100
- process.on("uncaughtException", (err) => {
101
- console . error ( "Unhandled exception:" , err ) ;
102
- process . exit ( 1 ) ;
103
- } );
92
+ server . setRequestHandler ( ListToolsRequestSchema , async ( ) => {
93
+ return {
94
+ tools : tools . map ( tool => {
95
+ let finalInputSchema ;
96
+ // Check if inputSchema is a Zod schema before converting
97
+ if ( tool . schema . inputSchema instanceof z . Schema ) {
98
+ // Add type assertion to help compiler
99
+ finalInputSchema = zodToJsonSchema ( tool . schema . inputSchema as any ) ;
100
+ } else if ( typeof tool . schema . inputSchema === 'object' && tool . schema . inputSchema !== null ) {
101
+ // Assume it's already a valid JSON schema object
102
+ finalInputSchema = tool . schema . inputSchema ;
103
+ } else {
104
+ // Fallback or error handling if schema is neither
105
+ console . error ( `Warning: Tool '${ tool . schema . name } ' has an unexpected inputSchema type.` ) ;
106
+ finalInputSchema = { type : "object" } ; // Default to empty object schema
107
+ }
108
+
109
+ return {
110
+ name : tool . schema . name ,
111
+ description : tool . schema . description ,
112
+ inputSchema : finalInputSchema ,
113
+ } ;
114
+ } ) ,
115
+ } ;
116
+ } ) ;
104
117
105
- process.on("unhandledRejection", (reason, promise) => {
106
- console . error ( "Unhandled Rejection at:" , promise , "reason:" , reason ) ;
107
- process . exit ( 1 ) ;
108
- } );
118
+ server . setRequestHandler ( CallToolRequestSchema , async ( request ) => {
119
+ const logError = ( message : string ) => {
120
+ // Ensure error logs definitely go to stderr
121
+ process . stderr . write ( `[server.ts Error] ${ new Date ( ) . toISOString ( ) } ${ message } \\n` ) ;
122
+ } ;
123
+
124
+ const errorResult = ( ...messages : string [ ] ) => {
125
+ const result = {
126
+ content : [ { type : 'text' , text : messages . join ( '\\n' ) } ] ,
127
+ isError : true ,
128
+ } ;
129
+ logError ( `Returning error: ${ JSON . stringify ( result ) } ` ) ; // Log the error structure
130
+ return result ;
131
+ } ;
132
+
133
+ // Use the map built from the passed-in tools
134
+ const tool = toolsMap . get ( request . params . name ) ;
135
+
136
+ if ( ! tool ) {
137
+ // Use the explicit error logger
138
+ logError ( `Tool "${ request . params . name } " not found.` ) ;
139
+ // Check if it was a placeholder tool that wasn't implemented
140
+ // This requires access to the original placeholder definitions,
141
+ // maybe pass placeholder names/schemas separately or handle in Context?
142
+ // For now, just return not found.
143
+ return errorResult ( `Tool "${ request . params . name } " not found` ) ;
144
+ }
145
+
146
+ try {
147
+ // Delegate execution to the context
148
+ const result = await context . run ( tool , request . params . arguments ?? { } ) ;
149
+ // Log the successful result structure just before returning
150
+ process . stderr . write ( `[server.ts Success] ${ new Date ( ) . toISOString ( ) } Returning result for ${ request . params . name } : ${ JSON . stringify ( result ) } \\n` ) ;
151
+ return result ;
152
+ } catch ( error ) {
153
+ // Use the explicit error logger
154
+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
155
+ logError ( `Error running tool ${ request . params . name } via context: ${ errorMessage } ` ) ;
156
+ logError ( `Original error stack (if available): ${ error instanceof Error ? error . stack : 'N/A' } ` ) ; // Log stack trace
157
+ return errorResult ( `Failed to run tool '${ request . params . name } ': ${ errorMessage } ` ) ;
158
+ }
159
+ } ) ;
160
+
161
+ // Wrap server close to also close context
162
+ const originalClose = server . close . bind ( server ) ;
163
+ server . close = async ( ) => {
164
+ await context . close ( ) ;
165
+ await originalClose ( ) ;
166
+ } ;
167
+
168
+ // Return the configured server instance, DO NOT connect here
169
+ return server ;
170
+ }
0 commit comments