@@ -4,141 +4,141 @@ import { Global } from "../global"
44import { Effect , Layer , Context } from "effect"
55import { AppFileSystem } from "@opencode-ai/shared/filesystem"
66
7- export namespace McpAuth {
8- export const Tokens = z . object ( {
9- accessToken : z . string ( ) ,
10- refreshToken : z . string ( ) . optional ( ) ,
11- expiresAt : z . number ( ) . optional ( ) ,
12- scope : z . string ( ) . optional ( ) ,
13- } )
14- export type Tokens = z . infer < typeof Tokens >
15-
16- export const ClientInfo = z . object ( {
17- clientId : z . string ( ) ,
18- clientSecret : z . string ( ) . optional ( ) ,
19- clientIdIssuedAt : z . number ( ) . optional ( ) ,
20- clientSecretExpiresAt : z . number ( ) . optional ( ) ,
21- } )
22- export type ClientInfo = z . infer < typeof ClientInfo >
23-
24- export const Entry = z . object ( {
25- tokens : Tokens . optional ( ) ,
26- clientInfo : ClientInfo . optional ( ) ,
27- codeVerifier : z . string ( ) . optional ( ) ,
28- oauthState : z . string ( ) . optional ( ) ,
29- serverUrl : z . string ( ) . optional ( ) ,
30- } )
31- export type Entry = z . infer < typeof Entry >
32-
33- const filepath = path . join ( Global . Path . data , "mcp-auth.json" )
34-
35- export interface Interface {
36- readonly all : ( ) => Effect . Effect < Record < string , Entry > >
37- readonly get : ( mcpName : string ) => Effect . Effect < Entry | undefined >
38- readonly getForUrl : ( mcpName : string , serverUrl : string ) => Effect . Effect < Entry | undefined >
39- readonly set : ( mcpName : string , entry : Entry , serverUrl ?: string ) => Effect . Effect < void >
40- readonly remove : ( mcpName : string ) => Effect . Effect < void >
41- readonly updateTokens : ( mcpName : string , tokens : Tokens , serverUrl ?: string ) => Effect . Effect < void >
42- readonly updateClientInfo : ( mcpName : string , clientInfo : ClientInfo , serverUrl ?: string ) => Effect . Effect < void >
43- readonly updateCodeVerifier : ( mcpName : string , codeVerifier : string ) => Effect . Effect < void >
44- readonly clearCodeVerifier : ( mcpName : string ) => Effect . Effect < void >
45- readonly updateOAuthState : ( mcpName : string , oauthState : string ) => Effect . Effect < void >
46- readonly getOAuthState : ( mcpName : string ) => Effect . Effect < string | undefined >
47- readonly clearOAuthState : ( mcpName : string ) => Effect . Effect < void >
48- readonly isTokenExpired : ( mcpName : string ) => Effect . Effect < boolean | null >
49- }
50-
51- export class Service extends Context . Service < Service , Interface > ( ) ( "@opencode/McpAuth" ) { }
52-
53- export const layer = Layer . effect (
54- Service ,
55- Effect . gen ( function * ( ) {
56- const fs = yield * AppFileSystem . Service
57-
58- const all = Effect . fn ( "McpAuth.all" ) ( function * ( ) {
59- return yield * fs . readJson ( filepath ) . pipe (
60- Effect . map ( ( data ) => data as Record < string , Entry > ) ,
61- Effect . catch ( ( ) => Effect . succeed ( { } as Record < string , Entry > ) ) ,
62- )
63- } )
64-
65- const get = Effect . fn ( "McpAuth.get" ) ( function * ( mcpName : string ) {
66- const data = yield * all ( )
67- return data [ mcpName ]
68- } )
69-
70- const getForUrl = Effect . fn ( "McpAuth.getForUrl" ) ( function * ( mcpName : string , serverUrl : string ) {
71- const entry = yield * get ( mcpName )
72- if ( ! entry ) return undefined
73- if ( ! entry . serverUrl ) return undefined
74- if ( entry . serverUrl !== serverUrl ) return undefined
75- return entry
76- } )
77-
78- const set = Effect . fn ( "McpAuth.set" ) ( function * ( mcpName : string , entry : Entry , serverUrl ?: string ) {
79- const data = yield * all ( )
80- if ( serverUrl ) entry . serverUrl = serverUrl
81- yield * fs . writeJson ( filepath , { ...data , [ mcpName ] : entry } , 0o600 ) . pipe ( Effect . orDie )
82- } )
83-
84- const remove = Effect . fn ( "McpAuth.remove" ) ( function * ( mcpName : string ) {
85- const data = yield * all ( )
86- delete data [ mcpName ]
87- yield * fs . writeJson ( filepath , data , 0o600 ) . pipe ( Effect . orDie )
88- } )
7+ export const Tokens = z . object ( {
8+ accessToken : z . string ( ) ,
9+ refreshToken : z . string ( ) . optional ( ) ,
10+ expiresAt : z . number ( ) . optional ( ) ,
11+ scope : z . string ( ) . optional ( ) ,
12+ } )
13+ export type Tokens = z . infer < typeof Tokens >
14+
15+ export const ClientInfo = z . object ( {
16+ clientId : z . string ( ) ,
17+ clientSecret : z . string ( ) . optional ( ) ,
18+ clientIdIssuedAt : z . number ( ) . optional ( ) ,
19+ clientSecretExpiresAt : z . number ( ) . optional ( ) ,
20+ } )
21+ export type ClientInfo = z . infer < typeof ClientInfo >
22+
23+ export const Entry = z . object ( {
24+ tokens : Tokens . optional ( ) ,
25+ clientInfo : ClientInfo . optional ( ) ,
26+ codeVerifier : z . string ( ) . optional ( ) ,
27+ oauthState : z . string ( ) . optional ( ) ,
28+ serverUrl : z . string ( ) . optional ( ) ,
29+ } )
30+ export type Entry = z . infer < typeof Entry >
31+
32+ const filepath = path . join ( Global . Path . data , "mcp-auth.json" )
33+
34+ export interface Interface {
35+ readonly all : ( ) => Effect . Effect < Record < string , Entry > >
36+ readonly get : ( mcpName : string ) => Effect . Effect < Entry | undefined >
37+ readonly getForUrl : ( mcpName : string , serverUrl : string ) => Effect . Effect < Entry | undefined >
38+ readonly set : ( mcpName : string , entry : Entry , serverUrl ?: string ) => Effect . Effect < void >
39+ readonly remove : ( mcpName : string ) => Effect . Effect < void >
40+ readonly updateTokens : ( mcpName : string , tokens : Tokens , serverUrl ?: string ) => Effect . Effect < void >
41+ readonly updateClientInfo : ( mcpName : string , clientInfo : ClientInfo , serverUrl ?: string ) => Effect . Effect < void >
42+ readonly updateCodeVerifier : ( mcpName : string , codeVerifier : string ) => Effect . Effect < void >
43+ readonly clearCodeVerifier : ( mcpName : string ) => Effect . Effect < void >
44+ readonly updateOAuthState : ( mcpName : string , oauthState : string ) => Effect . Effect < void >
45+ readonly getOAuthState : ( mcpName : string ) => Effect . Effect < string | undefined >
46+ readonly clearOAuthState : ( mcpName : string ) => Effect . Effect < void >
47+ readonly isTokenExpired : ( mcpName : string ) => Effect . Effect < boolean | null >
48+ }
8949
90- const updateField = < K extends keyof Entry > ( field : K , spanName : string ) =>
91- Effect . fn ( `McpAuth.${ spanName } ` ) ( function * ( mcpName : string , value : NonNullable < Entry [ K ] > , serverUrl ?: string ) {
92- const entry = ( yield * get ( mcpName ) ) ?? { }
93- entry [ field ] = value
94- yield * set ( mcpName , entry , serverUrl )
95- } )
96-
97- const clearField = < K extends keyof Entry > ( field : K , spanName : string ) =>
98- Effect . fn ( `McpAuth.${ spanName } ` ) ( function * ( mcpName : string ) {
99- const entry = yield * get ( mcpName )
100- if ( entry ) {
101- delete entry [ field ]
102- yield * set ( mcpName , entry )
103- }
104- } )
105-
106- const updateTokens = updateField ( "tokens" , "updateTokens" )
107- const updateClientInfo = updateField ( "clientInfo" , "updateClientInfo" )
108- const updateCodeVerifier = updateField ( "codeVerifier" , "updateCodeVerifier" )
109- const updateOAuthState = updateField ( "oauthState" , "updateOAuthState" )
110- const clearCodeVerifier = clearField ( "codeVerifier" , "clearCodeVerifier" )
111- const clearOAuthState = clearField ( "oauthState" , "clearOAuthState" )
112-
113- const getOAuthState = Effect . fn ( "McpAuth.getOAuthState" ) ( function * ( mcpName : string ) {
114- const entry = yield * get ( mcpName )
115- return entry ?. oauthState
50+ export class Service extends Context . Service < Service , Interface > ( ) ( "@opencode/McpAuth" ) { }
51+
52+ export const layer = Layer . effect (
53+ Service ,
54+ Effect . gen ( function * ( ) {
55+ const fs = yield * AppFileSystem . Service
56+
57+ const all = Effect . fn ( "McpAuth.all" ) ( function * ( ) {
58+ return yield * fs . readJson ( filepath ) . pipe (
59+ Effect . map ( ( data ) => data as Record < string , Entry > ) ,
60+ Effect . catch ( ( ) => Effect . succeed ( { } as Record < string , Entry > ) ) ,
61+ )
62+ } )
63+
64+ const get = Effect . fn ( "McpAuth.get" ) ( function * ( mcpName : string ) {
65+ const data = yield * all ( )
66+ return data [ mcpName ]
67+ } )
68+
69+ const getForUrl = Effect . fn ( "McpAuth.getForUrl" ) ( function * ( mcpName : string , serverUrl : string ) {
70+ const entry = yield * get ( mcpName )
71+ if ( ! entry ) return undefined
72+ if ( ! entry . serverUrl ) return undefined
73+ if ( entry . serverUrl !== serverUrl ) return undefined
74+ return entry
75+ } )
76+
77+ const set = Effect . fn ( "McpAuth.set" ) ( function * ( mcpName : string , entry : Entry , serverUrl ?: string ) {
78+ const data = yield * all ( )
79+ if ( serverUrl ) entry . serverUrl = serverUrl
80+ yield * fs . writeJson ( filepath , { ...data , [ mcpName ] : entry } , 0o600 ) . pipe ( Effect . orDie )
81+ } )
82+
83+ const remove = Effect . fn ( "McpAuth.remove" ) ( function * ( mcpName : string ) {
84+ const data = yield * all ( )
85+ delete data [ mcpName ]
86+ yield * fs . writeJson ( filepath , data , 0o600 ) . pipe ( Effect . orDie )
87+ } )
88+
89+ const updateField = < K extends keyof Entry > ( field : K , spanName : string ) =>
90+ Effect . fn ( `McpAuth.${ spanName } ` ) ( function * ( mcpName : string , value : NonNullable < Entry [ K ] > , serverUrl ?: string ) {
91+ const entry = ( yield * get ( mcpName ) ) ?? { }
92+ entry [ field ] = value
93+ yield * set ( mcpName , entry , serverUrl )
11694 } )
11795
118- const isTokenExpired = Effect . fn ( "McpAuth.isTokenExpired" ) ( function * ( mcpName : string ) {
96+ const clearField = < K extends keyof Entry > ( field : K , spanName : string ) =>
97+ Effect . fn ( `McpAuth.${ spanName } ` ) ( function * ( mcpName : string ) {
11998 const entry = yield * get ( mcpName )
120- if ( ! entry ?. tokens ) return null
121- if ( ! entry . tokens . expiresAt ) return false
122- return entry . tokens . expiresAt < Date . now ( ) / 1000
99+ if ( entry ) {
100+ delete entry [ field ]
101+ yield * set ( mcpName , entry )
102+ }
123103 } )
124104
125- return Service . of ( {
126- all,
127- get,
128- getForUrl,
129- set,
130- remove,
131- updateTokens,
132- updateClientInfo,
133- updateCodeVerifier,
134- clearCodeVerifier,
135- updateOAuthState,
136- getOAuthState,
137- clearOAuthState,
138- isTokenExpired,
139- } )
140- } ) ,
141- )
142-
143- export const defaultLayer = layer . pipe ( Layer . provide ( AppFileSystem . defaultLayer ) )
144- }
105+ const updateTokens = updateField ( "tokens" , "updateTokens" )
106+ const updateClientInfo = updateField ( "clientInfo" , "updateClientInfo" )
107+ const updateCodeVerifier = updateField ( "codeVerifier" , "updateCodeVerifier" )
108+ const updateOAuthState = updateField ( "oauthState" , "updateOAuthState" )
109+ const clearCodeVerifier = clearField ( "codeVerifier" , "clearCodeVerifier" )
110+ const clearOAuthState = clearField ( "oauthState" , "clearOAuthState" )
111+
112+ const getOAuthState = Effect . fn ( "McpAuth.getOAuthState" ) ( function * ( mcpName : string ) {
113+ const entry = yield * get ( mcpName )
114+ return entry ?. oauthState
115+ } )
116+
117+ const isTokenExpired = Effect . fn ( "McpAuth.isTokenExpired" ) ( function * ( mcpName : string ) {
118+ const entry = yield * get ( mcpName )
119+ if ( ! entry ?. tokens ) return null
120+ if ( ! entry . tokens . expiresAt ) return false
121+ return entry . tokens . expiresAt < Date . now ( ) / 1000
122+ } )
123+
124+ return Service . of ( {
125+ all,
126+ get,
127+ getForUrl,
128+ set,
129+ remove,
130+ updateTokens,
131+ updateClientInfo,
132+ updateCodeVerifier,
133+ clearCodeVerifier,
134+ updateOAuthState,
135+ getOAuthState,
136+ clearOAuthState,
137+ isTokenExpired,
138+ } )
139+ } ) ,
140+ )
141+
142+ export const defaultLayer = layer . pipe ( Layer . provide ( AppFileSystem . defaultLayer ) )
143+
144+ export * as McpAuth from "./auth"
0 commit comments