@@ -3,8 +3,8 @@ import { createClient } from "@openauthjs/openauth/client"
33import { Actor } from "@opencontrol/core/actor.js"
44import { Log } from "@opencontrol/core/util/log.js"
55import { Workspace } from "@opencontrol/core/workspace/index.js"
6- import { Database , eq } from "@opencontrol/core/drizzle/index.js"
7- import { Hono } from "hono"
6+ import { Database , eq , and } from "@opencontrol/core/drizzle/index.js"
7+ import { Hono , MiddlewareHandler } from "hono"
88import { handle } from "hono/aws-lambda"
99import { HTTPException } from "hono/http-exception"
1010import { Resource } from "sst"
@@ -16,6 +16,13 @@ import { WorkspaceTable } from "@opencontrol/core/workspace/workspace.sql.js"
1616import { AwsAccountTable } from "@opencontrol/core/aws.sql.js"
1717import { Identifier } from "@opencontrol/core/identifier.js"
1818import { Aws } from "@opencontrol/core/aws.js"
19+ import {
20+ ListToolsRequestSchema ,
21+ CallToolRequestSchema ,
22+ ListToolsResult ,
23+ CallToolResult ,
24+ } from "@modelcontextprotocol/sdk/types.js"
25+ import { UserTable } from "@opencontrol/core/user/user.sql.js"
1926
2027const model = createAnthropic ( {
2128 apiKey : Resource . AnthropicApiKey . value ,
@@ -30,30 +37,61 @@ const log = Log.create({
3037 namespace : "api" ,
3138} )
3239
33- const app = new Hono ( )
34- . use ( async ( c , next ) => {
35- const authorization = c . req . header ( "Authorization" )
36- if ( authorization ) {
37- const [ type , token ] = authorization . split ( " " )
38- if ( type !== "Bearer" ) {
39- throw new HTTPException ( 401 , {
40- message : "Invalid authorization header" ,
41- } )
42- }
43- const verified = await client . verify ( token )
44- if ( verified . err )
45- throw new HTTPException ( 401 , {
46- message : verified . err . message ,
47- } )
40+ export const AuthMiddleware : MiddlewareHandler = async ( c , next ) => {
41+ const authorization = c . req . header ( "authorization" )
42+ if ( ! authorization ) {
43+ return Actor . provide ( "public" , { } , next )
44+ }
45+ const token = authorization . split ( " " ) [ 1 ]
46+ if ( ! token )
47+ throw new HTTPException ( 403 , {
48+ message : "Bearer token is required." ,
49+ } )
4850
49- return Actor . provide (
50- verified . subject . type as any ,
51- verified . subject . properties ,
52- next ,
51+ const verified = await client . verify ( token )
52+ if ( verified . err ) {
53+ throw new HTTPException ( 403 , {
54+ message : "Invalid token." ,
55+ } )
56+ }
57+ let subject = verified . subject as Actor . Info
58+ if ( subject . type === "account" ) {
59+ const workspaceID = c . req . header ( "x-opencontrol-workspace" )
60+ const email = subject . properties . email
61+ if ( workspaceID ) {
62+ const user = await Database . use ( ( tx ) =>
63+ tx
64+ . select ( {
65+ id : UserTable . id ,
66+ workspaceID : UserTable . workspaceID ,
67+ } )
68+ . from ( UserTable )
69+ . where (
70+ and (
71+ eq ( UserTable . email , email ) ,
72+ eq ( UserTable . workspaceID , workspaceID ) ,
73+ ) ,
74+ )
75+ . then ( ( rows ) => rows [ 0 ] ) ,
5376 )
77+ if ( ! user )
78+ throw new HTTPException ( 403 , {
79+ message : "You do not have access to this workspace." ,
80+ } )
81+ subject = {
82+ type : "user" ,
83+ properties : {
84+ userID : user . id ,
85+ workspaceID : workspaceID ,
86+ } ,
87+ }
5488 }
55- return Actor . provide ( "public" , { } , next )
56- } )
89+ }
90+ await Actor . provide ( subject . type , subject . properties , next )
91+ }
92+
93+ const app = new Hono ( )
94+ . use ( AuthMiddleware )
5795 . get ( "/rest/account" , async ( c , next ) => {
5896 const account = Actor . assert ( "account" )
5997 let workspaces = await Workspace . list ( )
@@ -141,103 +179,132 @@ const app = new Hono()
141179 } )
142180 } ,
143181 )
144- . post ( "/tools/list" , async ( c ) => {
145- const user = Actor . assert ( "user" )
146- const tools = [ ]
147-
148- // Look up aws tool
149- const awsAccount = await Database . use ( ( tx ) =>
150- tx
151- . select ( { } )
152- . from ( AwsAccountTable )
153- . where ( eq ( AwsAccountTable . workspaceID , user . properties . workspaceID ) ) ,
154- )
155- if ( awsAccount ) {
156- tools . push ( {
157- name : "aws" ,
158- description : `This uses aws sdk v2 in javascript to execute aws commands
159- this is roughly how it works
160- \`\`\`js
161- import aws from "aws-sdk";
162- aws[service][method](params)
163- \`\`\`
164- ` ,
165- args : z . object ( {
166- service : z
167- . string ( )
168- . describe (
169- "name of the aws service in the format aws sdk v2 uses, like S3 or EC2" ,
170- ) ,
171- method : z
172- . string ( )
173- . describe ( "name of the aws method in the format aws sdk v2 uses" ) ,
174- params : z
175- . string ( )
176- . describe ( "params for the aws method in json format" ) ,
177- } ) ,
178- } )
179- }
180-
181- return c . json ( { tools } )
182- } )
183182 . post (
184- "/tools/call " ,
183+ "/mcp " ,
185184 zValidator (
186185 "json" ,
187- z . custom < {
188- name : string
189- service : string
190- method : string
191- params : string
192- } > ( ) ,
186+ z . discriminatedUnion ( "method" , [
187+ ListToolsRequestSchema ,
188+ CallToolRequestSchema ,
189+ ] ) ,
193190 ) ,
194191 async ( c ) => {
195192 const body = c . req . valid ( "json" )
196- const { name, service, method, params } = body
197-
198- if ( name === "aws" ) {
199- const awsAccount = await Database . use ( ( tx ) =>
200- tx
201- . select ( {
202- accountNumber : AwsAccountTable . accountNumber ,
203- region : AwsAccountTable . region ,
204- } )
205- . from ( AwsAccountTable )
206- . where ( eq ( AwsAccountTable . workspaceID , Actor . workspace ( ) ) ) ,
207- )
208- if ( ! awsAccount ) {
209- throw new Error (
210- "AWS integration not found. Please connect your AWS account first." ,
211- )
193+ switch ( body . method ) {
194+ case "tools/list" : {
195+ const result : ListToolsResult = {
196+ tools : await Database . use ( ( tx ) =>
197+ tx
198+ . select ( { } )
199+ . from ( AwsAccountTable )
200+ . where ( eq ( AwsAccountTable . workspaceID , Actor . workspace ( ) ) ) ,
201+ ) . then ( ( rows ) =>
202+ rows . map ( ( item ) : ListToolsResult [ "tools" ] [ number ] => ( {
203+ name : "aws" ,
204+ description : `This uses aws sdk v2 in javascript to execute aws commands
205+ this is roughly how it works
206+ \`\`\`js
207+ import aws from "aws-sdk";
208+ aws[service][method](params)
209+ \`\`\`` ,
210+ inputSchema : {
211+ type : "object" ,
212+ properties : {
213+ service : {
214+ type : "string" ,
215+ description :
216+ "name of the aws service in the format aws sdk v2 uses, like S3 or EC2" ,
217+ } ,
218+ method : {
219+ type : "string" ,
220+ description :
221+ "name of the aws method in the format aws sdk v2 uses" ,
222+ } ,
223+ params : {
224+ type : "string" ,
225+ description : "params for the aws method in json format" ,
226+ } ,
227+ } ,
228+ } ,
229+ } ) ) ,
230+ ) ,
231+ }
232+ return c . json ( result )
212233 }
234+ case "tools/call" : {
235+ try {
236+ if ( body . params . name === "aws" ) {
237+ const awsAccount = await Database . use ( ( tx ) =>
238+ tx
239+ . select ( {
240+ accountNumber : AwsAccountTable . accountNumber ,
241+ region : AwsAccountTable . region ,
242+ } )
243+ . from ( AwsAccountTable )
244+ . where ( eq ( AwsAccountTable . workspaceID , Actor . workspace ( ) ) ) ,
245+ )
246+ if ( ! awsAccount ) {
247+ throw new Error (
248+ "AWS integration not found. Please connect your AWS account first." ,
249+ )
250+ }
213251
214- // Assume role
215- const credentials = await Aws . assumeRole ( {
216- accountNumber : awsAccount [ 0 ] . accountNumber ,
217- region : awsAccount [ 0 ] . region ,
218- } )
252+ // Assume role
253+ const credentials = await Aws . assumeRole ( {
254+ accountNumber : awsAccount [ 0 ] . accountNumber ,
255+ region : awsAccount [ 0 ] . region ,
256+ } )
219257
220- /* @ts -expect-error */
221- const client = aws . default [ service ]
222- if ( ! client ) {
223- throw new HTTPException ( 500 , {
224- message : `service "${ service } " is not found in aws sdk v2` ,
225- } )
226- }
227- const instance = new client ( {
228- credentials : {
229- accessKeyId : credentials . AccessKeyId ,
230- secretAccessKey : credentials . SecretAccessKey ,
231- sessionToken : credentials . SessionToken ,
232- } ,
233- } )
234- if ( ! instance [ method ] ) {
258+ const { service, method, params } = body . params . arguments || { }
259+
260+ /* @ts -expect-error */
261+ const client = AWS [ service ]
262+ if ( ! client ) {
263+ throw new Error (
264+ `service "${ service } " is not found in aws sdk v2` ,
265+ )
266+ }
267+ const instance = new client ( {
268+ credentials : {
269+ accessKeyId : credentials . AccessKeyId ,
270+ secretAccessKey : credentials . SecretAccessKey ,
271+ sessionToken : credentials . SessionToken ,
272+ } ,
273+ } )
274+ if ( ! instance [ method ] ) {
275+ throw new Error (
276+ `method "${ method } " is not found in on the ${ service } service of aws sdk v2` ,
277+ )
278+ }
279+ const response = await instance [ method ] (
280+ JSON . parse ( params as string ) ,
281+ ) . promise ( )
282+ const result : CallToolResult = {
283+ content : [
284+ {
285+ type : "text" ,
286+ text : JSON . stringify ( response ) ,
287+ } ,
288+ ] ,
289+ }
290+ return c . json ( result )
291+ }
292+ } catch ( error : any ) {
293+ const result : CallToolResult = {
294+ isError : true ,
295+ content : [
296+ {
297+ type : "text" ,
298+ text : error . toString ( ) ,
299+ } ,
300+ ] ,
301+ }
302+ return c . json ( result )
303+ }
235304 throw new HTTPException ( 500 , {
236- message : `method "${ method } " is not found in on the ${ service } service of aws sdk v2 ` ,
305+ message : `tool "${ body . params . name } " is not found` ,
237306 } )
238307 }
239- const response = await instance [ method ] ( JSON . parse ( params ) ) . promise ( )
240- return c . json ( response )
241308 }
242309 } ,
243310 )
0 commit comments