@metorial/mcp-server 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/adapter.ts ADDED
@@ -0,0 +1,167 @@
1
+ import z from 'zod';
2
+ import { McpServerInstance } from './instance';
3
+
4
+ let discoverMessage = z.object({
5
+ type: z.literal('metorial-mcp.discover')
6
+ });
7
+ type DiscoverMessage = z.infer<typeof discoverMessage>;
8
+
9
+ let authUrlMessage = z.object({
10
+ type: z.literal('metorial-mcp.get-oauth-authorization-url'),
11
+ params: z.object({
12
+ authConfig: z.record(z.string(), z.any()),
13
+ clientId: z.string(),
14
+ clientSecret: z.string(),
15
+ state: z.string(),
16
+ redirectUri: z.string()
17
+ })
18
+ });
19
+ type AuthUrlMessage = z.infer<typeof authUrlMessage>;
20
+
21
+ let oauthCallbackMessage = z.object({
22
+ type: z.literal('metorial-mcp.handle-oauth-callback'),
23
+ params: z.object({
24
+ authConfig: z.record(z.string(), z.any()),
25
+ authState: z.record(z.string(), z.string()),
26
+ clientId: z.string(),
27
+ clientSecret: z.string(),
28
+ code: z.string(),
29
+ state: z.string(),
30
+ redirectUri: z.string(),
31
+ callbackUrl: z.string(),
32
+ authorizationUrl: z.string()
33
+ })
34
+ });
35
+ type OauthCallbackMessage = z.infer<typeof oauthCallbackMessage>;
36
+
37
+ let oauthTokenRefreshMessage = z.object({
38
+ type: z.literal('metorial-mcp.handle-oauth-token-refresh'),
39
+ params: z.object({
40
+ authConfig: z.record(z.string(), z.any()),
41
+ authState: z.record(z.string(), z.string()),
42
+ refreshToken: z.string(),
43
+ clientId: z.string(),
44
+ clientSecret: z.string()
45
+ })
46
+ });
47
+ type OauthTokenRefreshMessage = z.infer<typeof oauthTokenRefreshMessage>;
48
+
49
+ let allMessages = z.union([
50
+ discoverMessage,
51
+ authUrlMessage,
52
+ oauthCallbackMessage,
53
+ oauthTokenRefreshMessage
54
+ ]);
55
+
56
+ export type McpServerInstanceMessage =
57
+ | DiscoverMessage
58
+ | AuthUrlMessage
59
+ | OauthCallbackMessage
60
+ | OauthTokenRefreshMessage;
61
+
62
+ export let serverAdapter = async (
63
+ instance: McpServerInstance,
64
+ messages: McpServerInstanceMessage[]
65
+ ) =>
66
+ Promise.all(
67
+ messages.map(async message => {
68
+ let parsed = allMessages.parse(message);
69
+
70
+ switch (parsed.type) {
71
+ case 'metorial-mcp.discover': {
72
+ return instance.discover();
73
+ }
74
+ case 'metorial-mcp.get-oauth-authorization-url': {
75
+ return instance.getOauthAuthorizationUrl(parsed.params);
76
+ }
77
+ case 'metorial-mcp.handle-oauth-callback': {
78
+ return instance.handleOauthCallback(parsed.params);
79
+ }
80
+ case 'metorial-mcp.handle-oauth-token-refresh': {
81
+ return instance.handleOauthTokenRefresh(parsed.params);
82
+ }
83
+ }
84
+
85
+ throw new Error(`Unknown message type: ${(message as any).type}`);
86
+ })
87
+ );
88
+
89
+ export type McpServerInstanceAdapterResponses = Awaited<ReturnType<typeof serverAdapter>>;
90
+
91
+ export let clientAdapter = (
92
+ transport: (
93
+ messages: McpServerInstanceMessage[]
94
+ ) => Promise<McpServerInstanceAdapterResponses>
95
+ ) => {
96
+ let queue: {
97
+ msg: McpServerInstanceMessage;
98
+ resolve: (res: any) => void;
99
+ reject: (err: any) => void;
100
+ }[] = [];
101
+ let isSent = { current: false };
102
+ let isSending = { current: false };
103
+
104
+ let send = async (message: McpServerInstanceMessage) => {
105
+ let promise = new Promise<any>((resolve, reject) => {
106
+ queue.push({ msg: message, resolve, reject });
107
+ });
108
+
109
+ if (!isSending.current) {
110
+ isSending.current = true;
111
+
112
+ setTimeout(async () => {
113
+ isSent.current = true;
114
+
115
+ try {
116
+ let res = await transport(queue.map(q => q.msg));
117
+ res.forEach((r, i) => {
118
+ queue[i].resolve(r);
119
+ });
120
+ } catch (err) {
121
+ queue.forEach(q => {
122
+ q.reject(err);
123
+ });
124
+ } finally {
125
+ queue = [];
126
+ }
127
+ }, 2);
128
+ }
129
+
130
+ return promise;
131
+ };
132
+
133
+ return {
134
+ discover: async () => {
135
+ return send({ type: 'metorial-mcp.discover' }) as Promise<
136
+ Awaited<ReturnType<McpServerInstance['discover']>>
137
+ >;
138
+ },
139
+
140
+ getOauthAuthorizationUrl: async (
141
+ d: Parameters<McpServerInstance['getOauthAuthorizationUrl']>[0]
142
+ ) => {
143
+ return send({
144
+ type: 'metorial-mcp.get-oauth-authorization-url',
145
+ params: d
146
+ }) as Promise<Awaited<ReturnType<McpServerInstance['getOauthAuthorizationUrl']>>>;
147
+ },
148
+
149
+ handleOauthCallback: async (
150
+ d: Parameters<McpServerInstance['handleOauthCallback']>[0]
151
+ ) => {
152
+ return send({
153
+ type: 'metorial-mcp.handle-oauth-callback',
154
+ params: d
155
+ }) as Promise<Awaited<ReturnType<McpServerInstance['handleOauthCallback']>>>;
156
+ },
157
+
158
+ handleOauthTokenRefresh: async (
159
+ d: Parameters<McpServerInstance['handleOauthTokenRefresh']>[0]
160
+ ) => {
161
+ return send({
162
+ type: 'metorial-mcp.handle-oauth-token-refresh',
163
+ params: d
164
+ }) as Promise<Awaited<ReturnType<McpServerInstance['handleOauthTokenRefresh']>>>;
165
+ }
166
+ };
167
+ };
package/src/auth.ts ADDED
@@ -0,0 +1,165 @@
1
+ import z from 'zod';
2
+
3
+ export type McpServerAuthUrlHandlerParams<AuthConfig extends {}> = {
4
+ authConfig: AuthConfig;
5
+ clientId: string;
6
+ clientSecret: string;
7
+ state: string;
8
+ redirectUri: string;
9
+ };
10
+
11
+ export type McpServerAuthUrlHandlerResult = {
12
+ authorizationUrl: string;
13
+ authState?: Record<string, string> | null;
14
+ };
15
+
16
+ export type McpServerAuthUrlHandlerInternal = <AuthConfig extends {}>(
17
+ d: McpServerAuthUrlHandlerParams<AuthConfig>
18
+ ) => Promise<McpServerAuthUrlHandlerResult>;
19
+
20
+ export type McpServerAuthUrlHandler = <AuthConfig extends {}>(
21
+ d: McpServerAuthUrlHandlerParams<AuthConfig>
22
+ ) => Promise<string | McpServerAuthUrlHandlerResult>;
23
+
24
+ export type McpServerAuthCallbackHandlerParams<AuthConfig extends {}> = {
25
+ authConfig: AuthConfig;
26
+ authState: Record<string, string>;
27
+ clientId: string;
28
+ clientSecret: string;
29
+ code: string;
30
+ state: string;
31
+ redirectUri: string;
32
+ callbackUrl: string;
33
+ authorizationUrl: string;
34
+ };
35
+
36
+ export type McpServerAuthCallbackHandlerResult = {
37
+ accessToken: string;
38
+ refreshToken?: string;
39
+ expiresIn?: number;
40
+ scope?: string;
41
+ tokenType?: string;
42
+ [key: string]: any;
43
+ };
44
+
45
+ export type McpServerAuthCallbackHandler = <AuthConfig extends {}>(
46
+ d: McpServerAuthCallbackHandlerParams<AuthConfig>
47
+ ) => Promise<McpServerAuthCallbackHandlerResult>;
48
+
49
+ export type McpServerAuthTokenRefreshHandlerParams<AuthConfig extends {}> = {
50
+ authConfig: AuthConfig;
51
+ authState: Record<string, string>;
52
+ refreshToken: string;
53
+ clientId: string;
54
+ clientSecret: string;
55
+ };
56
+
57
+ export type McpServerAuthTokenRefreshHandlerResult = {
58
+ accessToken: string;
59
+ refreshToken?: string;
60
+ expiresIn?: number;
61
+ scope?: string;
62
+ tokenType?: string;
63
+ [key: string]: any;
64
+ };
65
+
66
+ export type McpServerAuthTokenRefreshHandler = <AuthConfig extends {}>(
67
+ d: McpServerAuthTokenRefreshHandlerParams<AuthConfig>
68
+ ) => Promise<McpServerAuthTokenRefreshHandlerResult>;
69
+
70
+ export type McpServerAuthValue = {
71
+ accessToken: string;
72
+ expiresIn?: number;
73
+ scope?: string;
74
+ tokenType?: string;
75
+ [key: string]: any;
76
+ };
77
+
78
+ export class McpServerAuthConfig<AuthConfig extends {}> {
79
+ #value: McpServerAuthValue | null = null;
80
+
81
+ #getAuthUrlHandler?: McpServerAuthUrlHandlerInternal;
82
+ #callbackHandler?: McpServerAuthCallbackHandler;
83
+ #tokenRefreshHandler?: McpServerAuthTokenRefreshHandler;
84
+
85
+ private constructor(private readonly schema: z.ZodType<AuthConfig>) {}
86
+
87
+ static create<AuthConfig extends {}>(schema?: z.ZodType<AuthConfig>) {
88
+ return new McpServerAuthConfig(schema ?? (z.object({}) as z.ZodType<AuthConfig>));
89
+ }
90
+
91
+ static getImplementation(config: McpServerAuthConfig<any>) {
92
+ if (!config.#getAuthUrlHandler || !config.#callbackHandler) {
93
+ throw new Error('MCP Server auth config is missing required handlers');
94
+ }
95
+
96
+ return {
97
+ getAuthUrlHandler: config.#getAuthUrlHandler,
98
+ callbackHandler: config.#callbackHandler,
99
+ tokenRefreshHandler: config.#tokenRefreshHandler
100
+ };
101
+ }
102
+
103
+ setValue(value: McpServerAuthValue) {
104
+ this.#value = value;
105
+ }
106
+
107
+ getAuthorizationUrl(cb: McpServerAuthUrlHandler) {
108
+ this.#getAuthUrlHandler = async d => {
109
+ let res = await cb(d);
110
+ if (typeof res == 'string') {
111
+ return { authorizationUrl: res };
112
+ }
113
+ return res;
114
+ };
115
+ return this;
116
+ }
117
+
118
+ handleCallback(cb: McpServerAuthCallbackHandler) {
119
+ this.#callbackHandler = cb;
120
+ return this;
121
+ }
122
+
123
+ refreshToken(cb: McpServerAuthTokenRefreshHandler) {
124
+ this.#tokenRefreshHandler = cb;
125
+ return this;
126
+ }
127
+
128
+ build(): AuthConfig {
129
+ if (!this.#getAuthUrlHandler || !this.#callbackHandler) {
130
+ throw new Error('MCP Server auth config is missing required handlers');
131
+ }
132
+
133
+ let self = this;
134
+
135
+ // Proxy for getting the config values
136
+ // or throwing an error if not set
137
+ return new Proxy(
138
+ {},
139
+ {
140
+ get: (target, prop, receiver) => {
141
+ if (prop == '__config__') {
142
+ return self;
143
+ }
144
+
145
+ if (self.#value === null) {
146
+ throw new Error('MCP Server auth config value not set');
147
+ }
148
+
149
+ return (self.#value as any)[prop];
150
+ }
151
+ }
152
+ ) as any as AuthConfig;
153
+ }
154
+ }
155
+
156
+ export let createAuth = <Config extends {}>(schema?: z.ZodType<Config>) => {
157
+ return McpServerAuthConfig.create(schema);
158
+ };
159
+
160
+ export let auth = <Config extends {}>(schema?: z.ZodType<Config>) => createAuth(schema);
161
+
162
+ export let getAuthConfigImplementation = <Config extends {}>(configValue: any) => {
163
+ let self = configValue['__config__'] as McpServerAuthConfig<Config>;
164
+ return McpServerAuthConfig.getImplementation(self);
165
+ };
package/src/client.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { createInMemoryTransport } from '@metorial/mcp-transport-memory';
2
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import {
5
+ ClientCapabilities,
6
+ JSONRPCMessage,
7
+ Notification
8
+ } from '@modelcontextprotocol/sdk/types.js';
9
+ import z from 'zod';
10
+
11
+ export interface ClientOpts {
12
+ client: {
13
+ name: string;
14
+ version: string;
15
+ };
16
+ capabilities: ClientCapabilities;
17
+ }
18
+
19
+ export let getClient = async (
20
+ server: McpServer,
21
+ notificationListener: (notification: Notification) => Promise<void>,
22
+ opts: ClientOpts
23
+ ) => {
24
+ let transport = createInMemoryTransport();
25
+ await server.connect(transport.server);
26
+
27
+ let client = new Client(opts.client);
28
+
29
+ client.registerCapabilities(opts.capabilities);
30
+ client.fallbackNotificationHandler = notificationListener;
31
+
32
+ await client.connect(transport.client);
33
+
34
+ return client;
35
+ };
36
+
37
+ export let handleMcpMessages = async (
38
+ server: McpServer,
39
+ opts: ClientOpts,
40
+ messages: JSONRPCMessage[]
41
+ ) => {
42
+ let responses: JSONRPCMessage[] = [];
43
+
44
+ let client = await getClient(
45
+ server,
46
+ async notification => {
47
+ responses.push(notification as JSONRPCMessage);
48
+ },
49
+ opts
50
+ );
51
+
52
+ let error: Error | null = null;
53
+
54
+ for (let message of messages) {
55
+ try {
56
+ if ('id' in message) {
57
+ let res = await client.request(message as any, z.any());
58
+ responses.push(res);
59
+ } else {
60
+ await client.notification(message as any);
61
+ }
62
+ } catch (err) {
63
+ error = err as Error;
64
+ break;
65
+ }
66
+ }
67
+
68
+ return { messages: responses, error };
69
+ };
package/src/config.ts ADDED
@@ -0,0 +1,68 @@
1
+ import z from 'zod';
2
+
3
+ export class McpServerConfig<Config extends {}> {
4
+ #value: Config | null = null;
5
+
6
+ private constructor(public readonly schema: z.ZodType<Config>) {}
7
+
8
+ static create<Config extends {}>(schema: z.ZodType<Config>) {
9
+ return new McpServerConfig(schema);
10
+ }
11
+
12
+ setValue(value: unknown) {
13
+ let parsed = this.schema.parse(value);
14
+ this.#value = parsed;
15
+ }
16
+
17
+ value(): Config {
18
+ let self = this;
19
+
20
+ // Proxy for getting the config values
21
+ // or throwing an error if not set
22
+ return new Proxy(
23
+ {},
24
+ {
25
+ get: (target, prop, receiver) => {
26
+ if (prop == '__config__') {
27
+ return self;
28
+ }
29
+
30
+ if (self.#value === null) {
31
+ throw new Error('MCP Server config value not set');
32
+ }
33
+
34
+ return (self.#value as any)[prop];
35
+ }
36
+ }
37
+ ) as any as Config;
38
+ }
39
+ }
40
+
41
+ export let createConfig = <Config extends {}>(schema: z.ZodType<Config>) => {
42
+ return McpServerConfig.create(schema).value();
43
+ };
44
+
45
+ export let config = <Config extends {}>(schema: z.ZodType<Config>) => createConfig(schema);
46
+
47
+ export let setConfigValue = <Config extends {}>(configValue: any, value: Config) => {
48
+ let self = configValue['__config__'] as McpServerConfig<Config>;
49
+ self.setValue(value);
50
+ };
51
+
52
+ export let getConfigSchema = <Config extends {}>(configValue: any) => {
53
+ let self = configValue['__config__'] as McpServerConfig<Config>;
54
+ return self.schema.toJSONSchema({
55
+ unrepresentable: 'any',
56
+ override: ctx => {
57
+ let def = ctx.zodSchema._zod.def;
58
+
59
+ if (def.type === 'date') {
60
+ ctx.jsonSchema.type = 'string';
61
+ ctx.jsonSchema.format = 'date-time';
62
+ }
63
+ if (def.type === 'bigint') {
64
+ ctx.jsonSchema.type = 'number';
65
+ }
66
+ }
67
+ });
68
+ };
package/src/index.ts ADDED
@@ -0,0 +1,32 @@
1
+ export {
2
+ createMcpServer,
3
+ type McpServerInstance,
4
+ type McpServerInstanceOpts,
5
+ type McpServerInstanceServer
6
+ } from './instance';
7
+
8
+ export {
9
+ clientAdapter,
10
+ serverAdapter,
11
+ type McpServerInstanceAdapterResponses,
12
+ type McpServerInstanceMessage
13
+ } from './adapter';
14
+
15
+ export {
16
+ auth,
17
+ createAuth,
18
+ type McpServerAuthCallbackHandler,
19
+ type McpServerAuthCallbackHandlerParams,
20
+ type McpServerAuthCallbackHandlerResult,
21
+ type McpServerAuthConfig,
22
+ type McpServerAuthTokenRefreshHandler,
23
+ type McpServerAuthTokenRefreshHandlerParams,
24
+ type McpServerAuthTokenRefreshHandlerResult,
25
+ type McpServerAuthUrlHandler,
26
+ type McpServerAuthUrlHandlerInternal,
27
+ type McpServerAuthUrlHandlerParams,
28
+ type McpServerAuthUrlHandlerResult,
29
+ type McpServerAuthValue
30
+ } from './auth';
31
+
32
+ export { config, createConfig, type McpServerConfig } from './config';
@@ -0,0 +1,90 @@
1
+ import {
2
+ createMcpServer as createMcpServerInternal,
3
+ CreateMcpServerOpts
4
+ } from '@metorial/mcp';
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
6
+ import { JSONRPCMessage } from '@modelcontextprotocol/sdk/types';
7
+ import {
8
+ getAuthConfigImplementation,
9
+ McpServerAuthCallbackHandlerParams,
10
+ McpServerAuthTokenRefreshHandlerParams,
11
+ McpServerAuthUrlHandlerParams
12
+ } from './auth';
13
+ import { ClientOpts, handleMcpMessages } from './client';
14
+ import { getConfigSchema, setConfigValue } from './config';
15
+
16
+ export type McpServerInstanceServer = CreateMcpServerOpts | { server: McpServer };
17
+ let getServer = (instance: McpServerInstanceServer) => {
18
+ if ('server' in instance) {
19
+ return instance.server;
20
+ } else {
21
+ return createMcpServerInternal(instance);
22
+ }
23
+ };
24
+
25
+ export type McpServerInstanceOpts = McpServerInstanceServer & {
26
+ config?: any;
27
+ authConfig?: any;
28
+ };
29
+
30
+ export class McpServerInstance {
31
+ #server: McpServer;
32
+
33
+ private constructor(private instance: McpServerInstanceOpts) {
34
+ this.#server = getServer(instance);
35
+ }
36
+
37
+ static create(instance: McpServerInstanceOpts) {
38
+ return new McpServerInstance(instance);
39
+ }
40
+
41
+ discover() {
42
+ return {
43
+ configSchema: this.instance.config ? getConfigSchema(this.instance.config) : null,
44
+ oauth: this.instance.authConfig
45
+ ? {
46
+ status: 'enabled' as const,
47
+ hasTokenRefresh: !!getAuthConfigImplementation(this.instance.authConfig)
48
+ .tokenRefreshHandler
49
+ }
50
+ : {
51
+ status: 'disabled' as const
52
+ }
53
+ };
54
+ }
55
+
56
+ handleMcpMessages(opts: { config: any; client: ClientOpts; message: JSONRPCMessage[] }) {
57
+ setConfigValue(this.instance.config, opts.config);
58
+
59
+ return handleMcpMessages(this.#server, opts.client, opts.message);
60
+ }
61
+
62
+ getOauthAuthorizationUrl(d: McpServerAuthUrlHandlerParams<any>) {
63
+ if (!this.instance.authConfig) return null;
64
+ let impl = getAuthConfigImplementation(this.instance.authConfig);
65
+
66
+ return impl.getAuthUrlHandler(d);
67
+ }
68
+
69
+ handleOauthCallback(d: McpServerAuthCallbackHandlerParams<any>) {
70
+ if (!this.instance.authConfig) return null;
71
+ let impl = getAuthConfigImplementation(this.instance.authConfig);
72
+
73
+ return impl.callbackHandler(d);
74
+ }
75
+
76
+ handleOauthTokenRefresh(d: McpServerAuthTokenRefreshHandlerParams<any>) {
77
+ if (!this.instance.authConfig) return null;
78
+ let impl = getAuthConfigImplementation(this.instance.authConfig);
79
+
80
+ if (!impl.tokenRefreshHandler) {
81
+ return null;
82
+ }
83
+
84
+ return impl.tokenRefreshHandler(d);
85
+ }
86
+ }
87
+
88
+ export let createMcpServer = (instance: McpServerInstanceOpts) => {
89
+ return McpServerInstance.create(instance);
90
+ };