opalserve 0.1.0

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.
Files changed (158) hide show
  1. package/.env.example +19 -0
  2. package/AGENTS.md +23 -0
  3. package/README.md +109 -0
  4. package/config/servers.example.yaml +67 -0
  5. package/config/servers.yaml +2 -0
  6. package/dist/cli/discover.d.ts +3 -0
  7. package/dist/cli/discover.d.ts.map +1 -0
  8. package/dist/cli/discover.js +160 -0
  9. package/dist/cli/discover.js.map +1 -0
  10. package/dist/cli/index.d.ts +3 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +32 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/connectors/base.d.ts +49 -0
  15. package/dist/connectors/base.d.ts.map +1 -0
  16. package/dist/connectors/base.js +45 -0
  17. package/dist/connectors/base.js.map +1 -0
  18. package/dist/connectors/custom.d.ts +19 -0
  19. package/dist/connectors/custom.d.ts.map +1 -0
  20. package/dist/connectors/custom.js +129 -0
  21. package/dist/connectors/custom.js.map +1 -0
  22. package/dist/connectors/github.d.ts +18 -0
  23. package/dist/connectors/github.d.ts.map +1 -0
  24. package/dist/connectors/github.js +188 -0
  25. package/dist/connectors/github.js.map +1 -0
  26. package/dist/connectors/google-drive.d.ts +18 -0
  27. package/dist/connectors/google-drive.d.ts.map +1 -0
  28. package/dist/connectors/google-drive.js +209 -0
  29. package/dist/connectors/google-drive.js.map +1 -0
  30. package/dist/connectors/index.d.ts +11 -0
  31. package/dist/connectors/index.d.ts.map +1 -0
  32. package/dist/connectors/index.js +76 -0
  33. package/dist/connectors/index.js.map +1 -0
  34. package/dist/connectors/postgres.d.ts +18 -0
  35. package/dist/connectors/postgres.d.ts.map +1 -0
  36. package/dist/connectors/postgres.js +140 -0
  37. package/dist/connectors/postgres.js.map +1 -0
  38. package/dist/connectors/slack.d.ts +18 -0
  39. package/dist/connectors/slack.d.ts.map +1 -0
  40. package/dist/connectors/slack.js +181 -0
  41. package/dist/connectors/slack.js.map +1 -0
  42. package/dist/core/auth.d.ts +26 -0
  43. package/dist/core/auth.d.ts.map +1 -0
  44. package/dist/core/auth.js +81 -0
  45. package/dist/core/auth.js.map +1 -0
  46. package/dist/core/registry.d.ts +33 -0
  47. package/dist/core/registry.d.ts.map +1 -0
  48. package/dist/core/registry.js +237 -0
  49. package/dist/core/registry.js.map +1 -0
  50. package/dist/core/tokenizer.d.ts +16 -0
  51. package/dist/core/tokenizer.d.ts.map +1 -0
  52. package/dist/core/tokenizer.js +29 -0
  53. package/dist/core/tokenizer.js.map +1 -0
  54. package/dist/governance/audit.d.ts +27 -0
  55. package/dist/governance/audit.d.ts.map +1 -0
  56. package/dist/governance/audit.js +149 -0
  57. package/dist/governance/audit.js.map +1 -0
  58. package/dist/governance/index.d.ts +5 -0
  59. package/dist/governance/index.d.ts.map +1 -0
  60. package/dist/governance/index.js +5 -0
  61. package/dist/governance/index.js.map +1 -0
  62. package/dist/governance/policy.d.ts +20 -0
  63. package/dist/governance/policy.d.ts.map +1 -0
  64. package/dist/governance/policy.js +162 -0
  65. package/dist/governance/policy.js.map +1 -0
  66. package/dist/governance/rate-limiter.d.ts +20 -0
  67. package/dist/governance/rate-limiter.d.ts.map +1 -0
  68. package/dist/governance/rate-limiter.js +73 -0
  69. package/dist/governance/rate-limiter.js.map +1 -0
  70. package/dist/governance/types.d.ts +246 -0
  71. package/dist/governance/types.d.ts.map +1 -0
  72. package/dist/governance/types.js +72 -0
  73. package/dist/governance/types.js.map +1 -0
  74. package/dist/identity/access-control.d.ts +15 -0
  75. package/dist/identity/access-control.d.ts.map +1 -0
  76. package/dist/identity/access-control.js +81 -0
  77. package/dist/identity/access-control.js.map +1 -0
  78. package/dist/identity/index.d.ts +4 -0
  79. package/dist/identity/index.d.ts.map +1 -0
  80. package/dist/identity/index.js +4 -0
  81. package/dist/identity/index.js.map +1 -0
  82. package/dist/identity/manager.d.ts +29 -0
  83. package/dist/identity/manager.d.ts.map +1 -0
  84. package/dist/identity/manager.js +167 -0
  85. package/dist/identity/manager.js.map +1 -0
  86. package/dist/identity/types.d.ts +237 -0
  87. package/dist/identity/types.d.ts.map +1 -0
  88. package/dist/identity/types.js +80 -0
  89. package/dist/identity/types.js.map +1 -0
  90. package/dist/index.d.ts +13 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +10 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/registry/server.d.ts +14 -0
  95. package/dist/registry/server.d.ts.map +1 -0
  96. package/dist/registry/server.js +173 -0
  97. package/dist/registry/server.js.map +1 -0
  98. package/dist/types/index.d.ts +639 -0
  99. package/dist/types/index.d.ts.map +1 -0
  100. package/dist/types/index.js +76 -0
  101. package/dist/types/index.js.map +1 -0
  102. package/dist/utils/config.d.ts +29 -0
  103. package/dist/utils/config.d.ts.map +1 -0
  104. package/dist/utils/config.js +47 -0
  105. package/dist/utils/config.js.map +1 -0
  106. package/dist/utils/index.d.ts +7 -0
  107. package/dist/utils/index.d.ts.map +1 -0
  108. package/dist/utils/index.js +44 -0
  109. package/dist/utils/index.js.map +1 -0
  110. package/dist/workflow/engine.d.ts +18 -0
  111. package/dist/workflow/engine.d.ts.map +1 -0
  112. package/dist/workflow/engine.js +155 -0
  113. package/dist/workflow/engine.js.map +1 -0
  114. package/dist/workflow/index.d.ts +4 -0
  115. package/dist/workflow/index.d.ts.map +1 -0
  116. package/dist/workflow/index.js +4 -0
  117. package/dist/workflow/index.js.map +1 -0
  118. package/dist/workflow/templates.d.ts +4 -0
  119. package/dist/workflow/templates.d.ts.map +1 -0
  120. package/dist/workflow/templates.js +218 -0
  121. package/dist/workflow/templates.js.map +1 -0
  122. package/dist/workflow/types.d.ts +255 -0
  123. package/dist/workflow/types.d.ts.map +1 -0
  124. package/dist/workflow/types.js +48 -0
  125. package/dist/workflow/types.js.map +1 -0
  126. package/eslint.config.js +25 -0
  127. package/package.json +78 -0
  128. package/src/cli/discover.ts +223 -0
  129. package/src/cli/index.ts +40 -0
  130. package/src/connectors/base.ts +75 -0
  131. package/src/connectors/custom.ts +139 -0
  132. package/src/connectors/github.ts +195 -0
  133. package/src/connectors/google-drive.ts +217 -0
  134. package/src/connectors/index.ts +86 -0
  135. package/src/connectors/postgres.ts +148 -0
  136. package/src/connectors/slack.ts +188 -0
  137. package/src/core/auth.ts +109 -0
  138. package/src/core/registry.ts +301 -0
  139. package/src/core/tokenizer.ts +40 -0
  140. package/src/governance/audit.ts +182 -0
  141. package/src/governance/index.ts +4 -0
  142. package/src/governance/policy.ts +187 -0
  143. package/src/governance/rate-limiter.ts +95 -0
  144. package/src/governance/types.ts +100 -0
  145. package/src/identity/access-control.ts +119 -0
  146. package/src/identity/index.ts +3 -0
  147. package/src/identity/manager.ts +207 -0
  148. package/src/identity/types.ts +91 -0
  149. package/src/index.ts +16 -0
  150. package/src/registry/server.ts +195 -0
  151. package/src/types/index.ts +128 -0
  152. package/src/utils/config.ts +78 -0
  153. package/src/utils/index.ts +47 -0
  154. package/src/workflow/engine.ts +187 -0
  155. package/src/workflow/index.ts +3 -0
  156. package/src/workflow/templates.ts +220 -0
  157. package/src/workflow/types.ts +89 -0
  158. package/tsconfig.json +25 -0
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import inquirer from 'inquirer';
6
+
7
+ const DEFAULT_REGISTRY_URL = 'http://localhost:3000';
8
+
9
+ interface Tool {
10
+ id: string;
11
+ name: string;
12
+ description: string;
13
+ serverName: string;
14
+ tags: string[];
15
+ capabilities: string[];
16
+ inputSchema: Record<string, unknown>;
17
+ }
18
+
19
+ interface Server {
20
+ id: string;
21
+ name: string;
22
+ type: string;
23
+ status: string;
24
+ health: {
25
+ status: string;
26
+ lastChecked: string;
27
+ };
28
+ }
29
+
30
+ interface DiscoveryResult {
31
+ tools: Array<{
32
+ tool: Tool;
33
+ relevanceScore: number;
34
+ matchReasons: string[];
35
+ tokenEstimate: number;
36
+ }>;
37
+ servers: Server[];
38
+ totalMatches: number;
39
+ contextUsed: number;
40
+ contextBudget: number;
41
+ suggestions: string[];
42
+ queryInterpretation: string;
43
+ }
44
+
45
+ async function httpGet<T>(url: string, timeout = 10000): Promise<T> {
46
+ const controller = new AbortController();
47
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
48
+
49
+ try {
50
+ const response = await fetch(url, { signal: controller.signal });
51
+ clearTimeout(timeoutId);
52
+
53
+ if (!response.ok) {
54
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
55
+ }
56
+
57
+ return response.json() as Promise<T>;
58
+ } catch (error) {
59
+ clearTimeout(timeoutId);
60
+ if (error instanceof Error) {
61
+ if (error.name === 'AbortError') {
62
+ throw new Error('Request timeout');
63
+ }
64
+ throw error;
65
+ }
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ async function discover(query: string, options: { registryUrl?: string; limit?: number; json?: boolean }): Promise<DiscoveryResult> {
71
+ const url = options.registryUrl || DEFAULT_REGISTRY_URL;
72
+ const spinner = ora('Discovering available tools...').start();
73
+
74
+ try {
75
+ const params = new URLSearchParams();
76
+ params.append('query', query);
77
+ if (options.limit) params.append('limit', String(options.limit));
78
+ const result = await httpGet<DiscoveryResult>(`${url}/api/v1/discover?${params}`);
79
+ spinner.succeed();
80
+ return result;
81
+ } catch (error) {
82
+ spinner.fail();
83
+ if (error instanceof Error) {
84
+ if (error.message.includes('ECONNREFUSED') || error.message.includes('fetch')) {
85
+ console.error(chalk.red(`\nError: Cannot connect to OpalServe registry at ${url}`));
86
+ console.log(chalk.yellow('\nMake sure the registry is running:'));
87
+ console.log(chalk.cyan(' opalserve start\n'));
88
+ } else {
89
+ console.error(chalk.red(`\nError: ${error.message}`));
90
+ }
91
+ }
92
+ throw error;
93
+ }
94
+ }
95
+
96
+ function formatResults(result: DiscoveryResult, json: boolean) {
97
+ if (json) {
98
+ console.log(JSON.stringify(result, null, 2));
99
+ return;
100
+ }
101
+
102
+ console.log(chalk.bold('\n🔍 Query Interpretation:'));
103
+ console.log(` ${result.queryInterpretation}\n`);
104
+
105
+ console.log(chalk.bold(`📊 Found ${result.totalMatches} matching tools`));
106
+ console.log(chalk.gray(` Context used: ${result.contextUsed} / ${result.contextBudget} tokens\n`));
107
+
108
+ if (result.tools.length === 0) {
109
+ console.log(chalk.yellow('No tools matched your query. Try a different search term.'));
110
+ return;
111
+ }
112
+
113
+ console.log(chalk.bold('\n🛠️ Available Tools:\n'));
114
+
115
+ for (const { tool, relevanceScore, matchReasons } of result.tools) {
116
+ const scoreColor = relevanceScore > 0.7 ? 'green' : relevanceScore > 0.4 ? 'yellow' : 'gray';
117
+
118
+ console.log(` ${chalk[scoreColor](`[${Math.round(relevanceScore * 100)}%]`)} ${chalk.cyan(tool.name)}`);
119
+ console.log(` ${tool.description}`);
120
+ console.log(` ${chalk.gray('Server:')} ${chalk.white(tool.serverName)}`);
121
+
122
+ if (tool.tags.length > 0) {
123
+ console.log(` ${chalk.gray('Tags:')} ${tool.tags.map(t => chalk.magenta(t)).join(', ')}`);
124
+ }
125
+
126
+ if (matchReasons.length > 0) {
127
+ console.log(` ${chalk.gray('Matched:')} ${matchReasons.join(', ')}`);
128
+ }
129
+ console.log();
130
+ }
131
+
132
+ if (result.servers.length > 0) {
133
+ console.log(chalk.bold('\n🖥️ Connected Servers:\n'));
134
+ for (const server of result.servers) {
135
+ const statusColor = server.health.status === 'healthy' ? 'green' : server.health.status === 'degraded' ? 'yellow' : 'red';
136
+ console.log(` ${chalk[statusColor]('●')} ${chalk.white(server.name)} (${server.type})`);
137
+ }
138
+ console.log();
139
+ }
140
+
141
+ if (result.suggestions.length > 0) {
142
+ console.log(chalk.bold('\n💡 Suggestions:'));
143
+ for (const suggestion of result.suggestions) {
144
+ console.log(` • ${suggestion}`);
145
+ }
146
+ console.log();
147
+ }
148
+ }
149
+
150
+ async function interactiveMode(registryUrl: string) {
151
+ console.log(chalk.bold('\n🎯 OpalServe Discovery CLI - Interactive Mode\n'));
152
+ console.log(chalk.gray('Type your query or "exit" to quit\n'));
153
+
154
+ const { query } = await inquirer.prompt([
155
+ {
156
+ type: 'input',
157
+ name: 'query',
158
+ message: 'What would you like to do?',
159
+ default: 'what can I do?',
160
+ },
161
+ ]);
162
+
163
+ if (query.toLowerCase() === 'exit') {
164
+ return;
165
+ }
166
+
167
+ const result = await discover(query, { registryUrl });
168
+ formatResults(result, false);
169
+
170
+ await interactiveMode(registryUrl);
171
+ }
172
+
173
+ async function main() {
174
+ const args = process.argv.slice(2);
175
+
176
+ if (args.includes('--help') || args.includes('-h')) {
177
+ console.log(`
178
+ ${chalk.bold('OpalServe Discovery CLI')}
179
+
180
+ ${chalk.underline('Usage:')}
181
+ opalserve-discover [query] [options]
182
+
183
+ ${chalk.underline('Options:')}
184
+ --url <url> Registry URL (default: http://localhost:3000)
185
+ --limit <n> Maximum tools to return (default: 20)
186
+ --json Output as JSON
187
+ --interactive Interactive mode
188
+ --help, -h Show this help
189
+
190
+ ${chalk.underline('Examples:')}
191
+ opalserve-discover "what can I do?"
192
+ opalserve-discover "github repository" --limit 10
193
+ opalserve-discover --json
194
+ opalserve-discover --interactive
195
+ `);
196
+ return;
197
+ }
198
+
199
+ const interactive = args.includes('--interactive');
200
+ const json = args.includes('--json');
201
+
202
+ const urlArg = args.indexOf('--url');
203
+ const registryUrl = urlArg !== -1 ? args[urlArg + 1] : DEFAULT_REGISTRY_URL;
204
+
205
+ const limitArg = args.indexOf('--limit');
206
+ const limit = limitArg !== -1 ? parseInt(args[limitArg + 1], 10) : 20;
207
+
208
+ if (interactive) {
209
+ await interactiveMode(registryUrl);
210
+ return;
211
+ }
212
+
213
+ const query = args.filter(a => !a.startsWith('--')).join(' ') || 'what can I do?';
214
+
215
+ try {
216
+ const result = await discover(query, { registryUrl, limit, json });
217
+ formatResults(result, json);
218
+ } catch {
219
+ process.exit(1);
220
+ }
221
+ }
222
+
223
+ main().catch(console.error);
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { RegistryServer } from '../registry/server.js';
4
+ import { RegistryConfig } from '../types/index.js';
5
+ import 'dotenv/config';
6
+
7
+ const config: RegistryConfig = {
8
+ port: parseInt(process.env.PORT || '3000', 10),
9
+ host: process.env.HOST || 'localhost',
10
+ jwtSecret: process.env.JWT_SECRET || 'dev-secret-change-in-production',
11
+ apiKey: process.env.API_KEY,
12
+ maxToolsPerResponse: parseInt(process.env.MAX_TOOLS_PER_RESPONSE || '50', 10),
13
+ contextTokenBudget: parseInt(process.env.CONTEXT_TOKEN_BUDGET || '128000', 10),
14
+ enableAuth: process.env.ENABLE_AUTH === 'true',
15
+ };
16
+
17
+ const server = new RegistryServer(config);
18
+
19
+ process.on('SIGINT', async () => {
20
+ await server.stop();
21
+ process.exit(0);
22
+ });
23
+
24
+ process.on('SIGTERM', async () => {
25
+ await server.stop();
26
+ process.exit(0);
27
+ });
28
+
29
+ console.log(`
30
+ ╔══════════════════════════════════════════════════════════════╗
31
+ ║ O P A L S E R V E ║
32
+ ║ MCP Tool Registry & Discovery Hub ║
33
+ ║ Enterprise Edition ║
34
+ ╚══════════════════════════════════════════════════════════════╝
35
+ `);
36
+
37
+ server.start().catch((error) => {
38
+ console.error('Failed to start server:', error);
39
+ process.exit(1);
40
+ });
@@ -0,0 +1,75 @@
1
+ import type { MCPServer, Tool, ConnectorConfig } from '../types/index.js';
2
+
3
+ export abstract class BaseConnector implements MCPServer {
4
+ abstract id: string;
5
+ abstract name: string;
6
+ abstract type: string;
7
+ protected config: ConnectorConfig;
8
+ protected connected: boolean = false;
9
+ tools: Tool[] = [];
10
+
11
+ constructor(config: ConnectorConfig) {
12
+ this.config = config;
13
+ }
14
+
15
+ abstract connect(): Promise<void>;
16
+ abstract disconnect(): Promise<void>;
17
+ abstract healthCheck(): Promise<{ healthy: boolean; latencyMs?: number }>;
18
+ abstract refreshTools(): Promise<Tool[]>;
19
+
20
+ getTools(): Tool[] {
21
+ return this.tools;
22
+ }
23
+
24
+ isConnected(): boolean {
25
+ return this.connected;
26
+ }
27
+
28
+ protected createTool(partial: {
29
+ name: string;
30
+ description: string;
31
+ inputSchema: Record<string, unknown>;
32
+ tags?: string[];
33
+ capabilities?: string[];
34
+ authRequirements?: { required?: string[]; optional?: string[] };
35
+ contextRequirements?: { minTokens?: number; maxTokens?: number };
36
+ metadata?: Record<string, unknown>;
37
+ }): Tool {
38
+ const now = new Date().toISOString();
39
+ return {
40
+ id: `${this.id}:${partial.name}`,
41
+ serverId: this.id,
42
+ serverName: this.name,
43
+ createdAt: now,
44
+ updatedAt: now,
45
+ name: partial.name,
46
+ description: partial.description,
47
+ inputSchema: partial.inputSchema,
48
+ tags: partial.tags || [],
49
+ capabilities: partial.capabilities || [],
50
+ authRequirements: {
51
+ required: partial.authRequirements?.required || [],
52
+ optional: partial.authRequirements?.optional || [],
53
+ },
54
+ contextRequirements: {
55
+ minTokens: partial.contextRequirements?.minTokens || 0,
56
+ maxTokens: partial.contextRequirements?.maxTokens || 1000,
57
+ },
58
+ metadata: partial.metadata || {},
59
+ };
60
+ }
61
+
62
+ protected async measureLatency<T>(fn: () => Promise<T>): Promise<{ result: T; latencyMs: number }> {
63
+ const start = performance.now();
64
+ const result = await fn();
65
+ const latencyMs = Math.round(performance.now() - start);
66
+ return { result, latencyMs };
67
+ }
68
+ }
69
+
70
+ export interface Connector {
71
+ connect(): Promise<void>;
72
+ disconnect(): Promise<void>;
73
+ healthCheck(): Promise<{ healthy: boolean; latencyMs?: number }>;
74
+ getTools(): Tool[];
75
+ }
@@ -0,0 +1,139 @@
1
+ import { BaseConnector } from './base.js';
2
+ import { ConnectorConfig, Tool, ToolDefinition } from '../types/index.js';
3
+
4
+ const CUSTOM_TOOL_DEFS: ToolDefinition[] = [
5
+ {
6
+ name: 'custom_http_request',
7
+ description: 'Make HTTP requests to external APIs',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], default: 'GET' },
12
+ url: { type: 'string', description: 'Request URL' },
13
+ headers: { type: 'object', description: 'HTTP headers' },
14
+ body: { type: 'object', description: 'Request body' },
15
+ timeout: { type: 'number', default: 30000 },
16
+ },
17
+ required: ['method', 'url'],
18
+ },
19
+ tags: ['http', 'api', 'custom', 'request'],
20
+ capabilities: ['read', 'write', 'http'],
21
+ authRequirements: { optional: ['api_key', 'bearer_token'] },
22
+ contextRequirements: { minTokens: 50, maxTokens: 500 },
23
+ },
24
+ {
25
+ name: 'custom_webhook_trigger',
26
+ description: 'Trigger a webhook with custom payload',
27
+ inputSchema: {
28
+ type: 'object',
29
+ properties: {
30
+ url: { type: 'string', description: 'Webhook URL' },
31
+ event: { type: 'string', description: 'Event name' },
32
+ payload: { type: 'object', description: 'Event payload' },
33
+ secret: { type: 'string', description: 'Webhook secret for signing' },
34
+ },
35
+ required: ['url', 'event'],
36
+ },
37
+ tags: ['webhook', 'custom', 'trigger', 'events'],
38
+ capabilities: ['write', 'trigger'],
39
+ authRequirements: { optional: ['webhook_secret'] },
40
+ contextRequirements: { minTokens: 30, maxTokens: 300 },
41
+ },
42
+ {
43
+ name: 'custom_transform_data',
44
+ description: 'Transform and process data with custom logic',
45
+ inputSchema: {
46
+ type: 'object',
47
+ properties: {
48
+ operation: { type: 'string', enum: ['map', 'filter', 'reduce', 'sort', 'group'] },
49
+ data: { type: 'array', description: 'Input data array' },
50
+ expression: { type: 'string', description: 'Transformation expression' },
51
+ },
52
+ required: ['operation', 'data'],
53
+ },
54
+ tags: ['transform', 'custom', 'data', 'process'],
55
+ capabilities: ['read', 'transform'],
56
+ contextRequirements: { minTokens: 50, maxTokens: 500 },
57
+ },
58
+ {
59
+ name: 'custom_aggregate_metrics',
60
+ description: 'Aggregate and summarize metrics from input data',
61
+ inputSchema: {
62
+ type: 'object',
63
+ properties: {
64
+ data: { type: 'array', description: 'Data to aggregate' },
65
+ metrics: { type: 'array', items: { type: 'string' }, description: 'Metric names to calculate' },
66
+ groupBy: { type: 'array', items: { type: 'string' }, description: 'Fields to group by' },
67
+ },
68
+ required: ['data', 'metrics'],
69
+ },
70
+ tags: ['metrics', 'custom', 'aggregate', 'analytics'],
71
+ capabilities: ['read', 'analyze'],
72
+ contextRequirements: { minTokens: 50, maxTokens: 400 },
73
+ },
74
+ {
75
+ name: 'custom_send_email',
76
+ description: 'Send an email via configured email service',
77
+ inputSchema: {
78
+ type: 'object',
79
+ properties: {
80
+ to: { type: 'array', items: { type: 'string' }, description: 'Recipient emails' },
81
+ subject: { type: 'string', description: 'Email subject' },
82
+ body: { type: 'string', description: 'Email body (plain text)' },
83
+ html: { type: 'string', description: 'Email body (HTML)' },
84
+ from: { type: 'string', description: 'Sender email' },
85
+ replyTo: { type: 'string', description: 'Reply-to email' },
86
+ attachments: { type: 'array', description: 'Attachment URLs' },
87
+ },
88
+ required: ['to', 'subject', 'body'],
89
+ },
90
+ tags: ['email', 'custom', 'notification', 'send'],
91
+ capabilities: ['write', 'notify'],
92
+ authRequirements: { optional: ['smtp_credentials', 'email_api_key'] },
93
+ contextRequirements: { minTokens: 30, maxTokens: 300 },
94
+ },
95
+ ];
96
+
97
+ export class CustomConnector extends BaseConnector {
98
+ id = 'custom';
99
+ name = 'Custom Tools';
100
+ type = 'custom';
101
+
102
+ constructor(config: ConnectorConfig) {
103
+ super(config);
104
+ this.tools = this.buildTools();
105
+ }
106
+
107
+ async connect(): Promise<void> {
108
+ this.connected = true;
109
+ }
110
+
111
+ async disconnect(): Promise<void> {
112
+ this.connected = false;
113
+ }
114
+
115
+ async healthCheck(): Promise<{ healthy: boolean; latencyMs?: number }> {
116
+ return { healthy: true };
117
+ }
118
+
119
+ async refreshTools(): Promise<Tool[]> {
120
+ return this.tools;
121
+ }
122
+
123
+ registerCustomTool(definition: ToolDefinition): void {
124
+ this.tools.push(this.createTool(definition));
125
+ }
126
+
127
+ unregisterCustomTool(name: string): boolean {
128
+ const index = this.tools.findIndex(t => t.name === name);
129
+ if (index !== -1) {
130
+ this.tools.splice(index, 1);
131
+ return true;
132
+ }
133
+ return false;
134
+ }
135
+
136
+ private buildTools(): Tool[] {
137
+ return CUSTOM_TOOL_DEFS.map(def => this.createTool(def));
138
+ }
139
+ }
@@ -0,0 +1,195 @@
1
+ import { BaseConnector } from './base.js';
2
+ import { ConnectorConfig, Tool, ToolDefinition } from '../types/index.js';
3
+
4
+ const GITHUB_TOOL_DEFS: ToolDefinition[] = [
5
+ {
6
+ name: 'github_search_repositories',
7
+ description: 'Search for GitHub repositories by query, language, stars, and other criteria',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ q: { type: 'string', description: 'Search query' },
12
+ language: { type: 'string', description: 'Programming language filter' },
13
+ sort: { type: 'string', enum: ['stars', 'forks', 'updated'] },
14
+ },
15
+ required: ['q'],
16
+ },
17
+ tags: ['search', 'repositories', 'github'],
18
+ capabilities: ['read', 'search'],
19
+ authRequirements: { required: ['github_token'] },
20
+ contextRequirements: { minTokens: 50, maxTokens: 500 },
21
+ },
22
+ {
23
+ name: 'github_get_file_contents',
24
+ description: 'Get the contents of a file or directory from a GitHub repository',
25
+ inputSchema: {
26
+ type: 'object',
27
+ properties: {
28
+ owner: { type: 'string', description: 'Repository owner' },
29
+ repo: { type: 'string', description: 'Repository name' },
30
+ path: { type: 'string', description: 'Path to file or directory' },
31
+ branch: { type: 'string', description: 'Branch name' },
32
+ },
33
+ required: ['owner', 'repo', 'path'],
34
+ },
35
+ tags: ['files', 'repository', 'github'],
36
+ capabilities: ['read'],
37
+ authRequirements: { required: ['github_token'] },
38
+ contextRequirements: { minTokens: 50, maxTokens: 1000 },
39
+ },
40
+ {
41
+ name: 'github_list_issues',
42
+ description: 'List issues in a GitHub repository with filtering options',
43
+ inputSchema: {
44
+ type: 'object',
45
+ properties: {
46
+ owner: { type: 'string', description: 'Repository owner' },
47
+ repo: { type: 'string', description: 'Repository name' },
48
+ state: { type: 'string', enum: ['open', 'closed', 'all'] },
49
+ labels: { type: 'array', items: { type: 'string' } },
50
+ sort: { type: 'string', enum: ['created', 'updated', 'comments'] },
51
+ },
52
+ required: ['owner', 'repo'],
53
+ },
54
+ tags: ['issues', 'tickets', 'github'],
55
+ capabilities: ['read', 'list'],
56
+ authRequirements: { required: ['github_token'] },
57
+ contextRequirements: { minTokens: 50, maxTokens: 500 },
58
+ },
59
+ {
60
+ name: 'github_create_issue',
61
+ description: 'Create a new issue in a GitHub repository',
62
+ inputSchema: {
63
+ type: 'object',
64
+ properties: {
65
+ owner: { type: 'string', description: 'Repository owner' },
66
+ repo: { type: 'string', description: 'Repository name' },
67
+ title: { type: 'string', description: 'Issue title' },
68
+ body: { type: 'string', description: 'Issue body' },
69
+ labels: { type: 'array', items: { type: 'string' } },
70
+ assignees: { type: 'array', items: { type: 'string' } },
71
+ },
72
+ required: ['owner', 'repo', 'title'],
73
+ },
74
+ tags: ['issues', 'create', 'github'],
75
+ capabilities: ['write', 'create'],
76
+ authRequirements: { required: ['github_token'] },
77
+ contextRequirements: { minTokens: 50, maxTokens: 200 },
78
+ },
79
+ {
80
+ name: 'github_list_pull_requests',
81
+ description: 'List and filter pull requests in a GitHub repository',
82
+ inputSchema: {
83
+ type: 'object',
84
+ properties: {
85
+ owner: { type: 'string', description: 'Repository owner' },
86
+ repo: { type: 'string', description: 'Repository name' },
87
+ state: { type: 'string', enum: ['open', 'closed', 'all'] },
88
+ sort: { type: 'string', enum: ['created', 'updated', 'popularity'] },
89
+ head: { type: 'string', description: 'Filter by head branch' },
90
+ },
91
+ required: ['owner', 'repo'],
92
+ },
93
+ tags: ['pull-requests', 'code-review', 'github'],
94
+ capabilities: ['read', 'list'],
95
+ authRequirements: { required: ['github_token'] },
96
+ contextRequirements: { minTokens: 50, maxTokens: 500 },
97
+ },
98
+ {
99
+ name: 'github_create_pull_request',
100
+ description: 'Create a new pull request in a GitHub repository',
101
+ inputSchema: {
102
+ type: 'object',
103
+ properties: {
104
+ owner: { type: 'string', description: 'Repository owner' },
105
+ repo: { type: 'string', description: 'Repository name' },
106
+ title: { type: 'string', description: 'PR title' },
107
+ body: { type: 'string', description: 'PR body' },
108
+ head: { type: 'string', description: 'Head branch' },
109
+ base: { type: 'string', description: 'Base branch', default: 'main' },
110
+ draft: { type: 'boolean', description: 'Create as draft PR' },
111
+ },
112
+ required: ['owner', 'repo', 'title', 'head', 'base'],
113
+ },
114
+ tags: ['pull-requests', 'create', 'github'],
115
+ capabilities: ['write', 'create'],
116
+ authRequirements: { required: ['github_token'] },
117
+ contextRequirements: { minTokens: 50, maxTokens: 300 },
118
+ },
119
+ {
120
+ name: 'github_list_commits',
121
+ description: 'Get list of commits from a branch in a GitHub repository',
122
+ inputSchema: {
123
+ type: 'object',
124
+ properties: {
125
+ owner: { type: 'string', description: 'Repository owner' },
126
+ repo: { type: 'string', description: 'Repository name' },
127
+ sha: { type: 'string', description: 'Branch or commit SHA' },
128
+ per_page: { type: 'number', description: 'Results per page', default: 30 },
129
+ page: { type: 'number', description: 'Page number', default: 1 },
130
+ },
131
+ required: ['owner', 'repo'],
132
+ },
133
+ tags: ['commits', 'history', 'github'],
134
+ capabilities: ['read', 'list'],
135
+ authRequirements: { required: ['github_token'] },
136
+ contextRequirements: { minTokens: 50, maxTokens: 800 },
137
+ },
138
+ {
139
+ name: 'github_search_code',
140
+ description: 'Search for code across GitHub repositories',
141
+ inputSchema: {
142
+ type: 'object',
143
+ properties: {
144
+ q: { type: 'string', description: 'Search query with code-specific syntax' },
145
+ language: { type: 'string', description: 'Language filter' },
146
+ repo: { type: 'string', description: 'Limit search to repository' },
147
+ },
148
+ required: ['q'],
149
+ },
150
+ tags: ['search', 'code', 'github'],
151
+ capabilities: ['read', 'search'],
152
+ authRequirements: { required: ['github_token'] },
153
+ contextRequirements: { minTokens: 50, maxTokens: 500 },
154
+ },
155
+ ];
156
+
157
+ export class GitHubConnector extends BaseConnector {
158
+ id = 'github';
159
+ name = 'GitHub';
160
+ type = 'github';
161
+ private token?: string;
162
+
163
+ constructor(config: ConnectorConfig) {
164
+ super(config);
165
+ this.token = config.auth?.credentials?.token;
166
+ this.tools = this.buildTools();
167
+ }
168
+
169
+ async connect(): Promise<void> {
170
+ this.connected = true;
171
+ }
172
+
173
+ async disconnect(): Promise<void> {
174
+ this.connected = false;
175
+ }
176
+
177
+ async healthCheck(): Promise<{ healthy: boolean; latencyMs?: number }> {
178
+ const { result, latencyMs } = await this.measureLatency(async () => {
179
+ if (!this.token) return true;
180
+ const response = await fetch('https://api.github.com/rate_limit', {
181
+ headers: { Authorization: `Bearer ${this.token}` },
182
+ });
183
+ return response.ok;
184
+ });
185
+ return { healthy: result, latencyMs };
186
+ }
187
+
188
+ async refreshTools(): Promise<Tool[]> {
189
+ return this.tools;
190
+ }
191
+
192
+ private buildTools(): Tool[] {
193
+ return GITHUB_TOOL_DEFS.map(def => this.createTool(def));
194
+ }
195
+ }