@ottocode/sdk 0.1.200 → 0.1.202
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/package.json +2 -1
- package/src/config/src/paths.ts +16 -14
- package/src/core/src/index.ts +30 -0
- package/src/core/src/mcp/client.ts +229 -0
- package/src/core/src/mcp/index.ts +32 -0
- package/src/core/src/mcp/lifecycle.ts +168 -0
- package/src/core/src/mcp/oauth/callback.ts +83 -0
- package/src/core/src/mcp/oauth/index.ts +6 -0
- package/src/core/src/mcp/oauth/provider.ts +149 -0
- package/src/core/src/mcp/oauth/store.ts +115 -0
- package/src/core/src/mcp/server-manager.ts +332 -0
- package/src/core/src/mcp/tools.ts +97 -0
- package/src/core/src/mcp/types.ts +42 -0
- package/src/core/src/tools/builtin/ripgrep.ts +12 -4
- package/src/core/src/tools/loader.ts +10 -3
- package/src/index.ts +32 -0
- package/src/core/src/tools/builtin/grep.ts +0 -134
- package/src/core/src/tools/builtin/grep.txt +0 -9
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type MCPTransport = 'stdio' | 'http' | 'sse';
|
|
2
|
+
export type MCPScope = 'global' | 'project';
|
|
3
|
+
|
|
4
|
+
export interface MCPOAuthConfig {
|
|
5
|
+
clientId?: string;
|
|
6
|
+
callbackPort?: number;
|
|
7
|
+
scopes?: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface MCPServerConfig {
|
|
11
|
+
name: string;
|
|
12
|
+
transport?: MCPTransport;
|
|
13
|
+
|
|
14
|
+
command?: string;
|
|
15
|
+
args?: string[];
|
|
16
|
+
env?: Record<string, string>;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
|
|
19
|
+
url?: string;
|
|
20
|
+
headers?: Record<string, string>;
|
|
21
|
+
|
|
22
|
+
oauth?: MCPOAuthConfig;
|
|
23
|
+
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
|
|
26
|
+
scope?: MCPScope;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MCPConfig {
|
|
30
|
+
servers: MCPServerConfig[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MCPServerStatus {
|
|
34
|
+
name: string;
|
|
35
|
+
connected: boolean;
|
|
36
|
+
tools: string[];
|
|
37
|
+
error?: string;
|
|
38
|
+
transport?: MCPTransport;
|
|
39
|
+
url?: string;
|
|
40
|
+
authRequired?: boolean;
|
|
41
|
+
authenticated?: boolean;
|
|
42
|
+
}
|
|
@@ -24,14 +24,14 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
24
24
|
.array(z.string())
|
|
25
25
|
.optional()
|
|
26
26
|
.describe('One or more glob patterns to include'),
|
|
27
|
-
maxResults: z.number().int().min(1).max(5000).optional().default(
|
|
27
|
+
maxResults: z.number().int().min(1).max(5000).optional().default(100),
|
|
28
28
|
}),
|
|
29
29
|
async execute({
|
|
30
30
|
query,
|
|
31
31
|
path = '.',
|
|
32
32
|
ignoreCase,
|
|
33
33
|
glob,
|
|
34
|
-
maxResults =
|
|
34
|
+
maxResults = 100,
|
|
35
35
|
}: {
|
|
36
36
|
query: string;
|
|
37
37
|
path?: string;
|
|
@@ -95,12 +95,20 @@ export function buildRipgrepTool(projectRoot: string): {
|
|
|
95
95
|
.split('\n')
|
|
96
96
|
.filter(Boolean)
|
|
97
97
|
.slice(0, maxResults);
|
|
98
|
+
const TEXT_MAX = 200;
|
|
98
99
|
const matches = lines.map((l) => {
|
|
99
100
|
const m = l.match(/^(.+?):(\d+):(.*)$/s);
|
|
100
|
-
if (!m)
|
|
101
|
+
if (!m)
|
|
102
|
+
return {
|
|
103
|
+
file: '',
|
|
104
|
+
line: 0,
|
|
105
|
+
text: l.length > TEXT_MAX ? l.slice(0, TEXT_MAX) + '…' : l,
|
|
106
|
+
};
|
|
101
107
|
const file = m[1];
|
|
102
108
|
const line = Number.parseInt(m[2], 10);
|
|
103
|
-
const
|
|
109
|
+
const raw = m[3];
|
|
110
|
+
const text =
|
|
111
|
+
raw.length > TEXT_MAX ? raw.slice(0, TEXT_MAX) + '…' : raw;
|
|
104
112
|
return { file, line, text };
|
|
105
113
|
});
|
|
106
114
|
resolve({ ok: true, count: matches.length, matches });
|
|
@@ -6,7 +6,6 @@ import { buildGitTools } from './builtin/git.ts';
|
|
|
6
6
|
import { progressUpdateTool } from './builtin/progress.ts';
|
|
7
7
|
import { buildBashTool } from './builtin/bash.ts';
|
|
8
8
|
import { buildRipgrepTool } from './builtin/ripgrep.ts';
|
|
9
|
-
import { buildGrepTool } from './builtin/grep.ts';
|
|
10
9
|
import { buildGlobTool } from './builtin/glob.ts';
|
|
11
10
|
import { buildApplyPatchTool } from './builtin/patch.ts';
|
|
12
11
|
import { buildEditTool } from './builtin/edit.ts';
|
|
@@ -16,6 +15,8 @@ import { buildWebSearchTool } from './builtin/websearch.ts';
|
|
|
16
15
|
import { buildTerminalTool } from './builtin/terminal.ts';
|
|
17
16
|
import type { TerminalManager } from '../terminals/index.ts';
|
|
18
17
|
import { initializeSkills, buildSkillTool } from '../../../skills/index.ts';
|
|
18
|
+
import { getMCPManager } from '../mcp/index.ts';
|
|
19
|
+
import { convertMCPToolsToAISDK } from '../mcp/tools.ts';
|
|
19
20
|
import fg from 'fast-glob';
|
|
20
21
|
import { dirname, isAbsolute, join } from 'node:path';
|
|
21
22
|
import { pathToFileURL } from 'node:url';
|
|
@@ -120,8 +121,6 @@ export async function discoverProjectTools(
|
|
|
120
121
|
// Search
|
|
121
122
|
const rg = buildRipgrepTool(projectRoot);
|
|
122
123
|
tools.set(rg.name, rg.tool);
|
|
123
|
-
const grep = buildGrepTool(projectRoot);
|
|
124
|
-
tools.set(grep.name, grep.tool);
|
|
125
124
|
const glob = buildGlobTool(projectRoot);
|
|
126
125
|
tools.set(glob.name, glob.tool);
|
|
127
126
|
// Patch/apply
|
|
@@ -148,6 +147,14 @@ export async function discoverProjectTools(
|
|
|
148
147
|
const skillTool = buildSkillTool();
|
|
149
148
|
tools.set(skillTool.name, skillTool.tool);
|
|
150
149
|
|
|
150
|
+
const mcpManager = getMCPManager();
|
|
151
|
+
if (mcpManager?.started) {
|
|
152
|
+
const mcpTools = convertMCPToolsToAISDK(mcpManager);
|
|
153
|
+
for (const { name, tool } of mcpTools) {
|
|
154
|
+
tools.set(name, tool);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
151
158
|
async function loadFromBase(base: string | null | undefined) {
|
|
152
159
|
if (!base) return;
|
|
153
160
|
try {
|
package/src/index.ts
CHANGED
|
@@ -171,6 +171,8 @@ export {
|
|
|
171
171
|
getGlobalToolsDir,
|
|
172
172
|
getGlobalCommandsDir,
|
|
173
173
|
getSecureAuthPath,
|
|
174
|
+
getSecureBaseDir,
|
|
175
|
+
getSecureOAuthDir,
|
|
174
176
|
getHomeDir,
|
|
175
177
|
} from './config/src/paths.ts';
|
|
176
178
|
export {
|
|
@@ -333,3 +335,33 @@ export {
|
|
|
333
335
|
} from './tunnel/index.ts';
|
|
334
336
|
|
|
335
337
|
export type { TunnelConnection, TunnelEvents } from './tunnel/index.ts';
|
|
338
|
+
|
|
339
|
+
// =======================
|
|
340
|
+
// MCP (Model Context Protocol)
|
|
341
|
+
// =======================
|
|
342
|
+
export {
|
|
343
|
+
MCPClientWrapper,
|
|
344
|
+
MCPServerManager,
|
|
345
|
+
convertMCPToolsToAISDK,
|
|
346
|
+
getMCPManager,
|
|
347
|
+
initializeMCP,
|
|
348
|
+
shutdownMCP,
|
|
349
|
+
loadMCPConfig,
|
|
350
|
+
addMCPServerToConfig,
|
|
351
|
+
removeMCPServerFromConfig,
|
|
352
|
+
OAuthCredentialStore,
|
|
353
|
+
OttoOAuthProvider,
|
|
354
|
+
OAuthCallbackServer,
|
|
355
|
+
} from './core/src/index.ts';
|
|
356
|
+
export type {
|
|
357
|
+
MCPServerConfig,
|
|
358
|
+
MCPConfig,
|
|
359
|
+
MCPServerStatus,
|
|
360
|
+
MCPToolInfo,
|
|
361
|
+
MCPTransport,
|
|
362
|
+
MCPOAuthConfig,
|
|
363
|
+
MCPScope,
|
|
364
|
+
StoredOAuthData,
|
|
365
|
+
OttoOAuthProviderOptions,
|
|
366
|
+
CallbackResult,
|
|
367
|
+
} from './core/src/index.ts';
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { tool, type Tool } from 'ai';
|
|
2
|
-
import { z } from 'zod/v3';
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import DESCRIPTION from './grep.txt' with { type: 'text' };
|
|
6
|
-
import { defaultIgnoreGlobs } from './ignore.ts';
|
|
7
|
-
import { createToolError, type ToolResponse } from '../error.ts';
|
|
8
|
-
import { resolveBinary } from '../bin-manager.ts';
|
|
9
|
-
|
|
10
|
-
function expandTilde(p: string) {
|
|
11
|
-
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
12
|
-
if (!home) return p;
|
|
13
|
-
if (p === '~') return home;
|
|
14
|
-
if (p.startsWith('~/')) return `${home}/${p.slice(2)}`;
|
|
15
|
-
return p;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function buildGrepTool(projectRoot: string): {
|
|
19
|
-
name: string;
|
|
20
|
-
tool: Tool;
|
|
21
|
-
} {
|
|
22
|
-
const grep = tool({
|
|
23
|
-
description: DESCRIPTION,
|
|
24
|
-
inputSchema: z.object({
|
|
25
|
-
pattern: z
|
|
26
|
-
.string()
|
|
27
|
-
.describe('Regex pattern to search for in file contents'),
|
|
28
|
-
path: z
|
|
29
|
-
.string()
|
|
30
|
-
.optional()
|
|
31
|
-
.describe('Directory to search in (default: project root).'),
|
|
32
|
-
include: z
|
|
33
|
-
.string()
|
|
34
|
-
.optional()
|
|
35
|
-
.describe('File glob to include (e.g., "*.js", "*.{ts,tsx}")'),
|
|
36
|
-
ignore: z
|
|
37
|
-
.array(z.string())
|
|
38
|
-
.optional()
|
|
39
|
-
.describe('Glob patterns to exclude from search'),
|
|
40
|
-
}),
|
|
41
|
-
async execute(params: {
|
|
42
|
-
pattern: string;
|
|
43
|
-
path?: string;
|
|
44
|
-
include?: string;
|
|
45
|
-
ignore?: string[];
|
|
46
|
-
}): Promise<
|
|
47
|
-
ToolResponse<{
|
|
48
|
-
count: number;
|
|
49
|
-
matches: Array<{ file: string; line: number; text: string }>;
|
|
50
|
-
}>
|
|
51
|
-
> {
|
|
52
|
-
const pattern = String(params.pattern || '');
|
|
53
|
-
if (!pattern) {
|
|
54
|
-
return createToolError('pattern is required', 'validation', {
|
|
55
|
-
parameter: 'pattern',
|
|
56
|
-
suggestion: 'Provide a regex pattern to search for',
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const p = expandTilde(String(params.path || '')).trim();
|
|
61
|
-
const isAbs = p.startsWith('/') || /^[A-Za-z]:[\\/]/.test(p);
|
|
62
|
-
const searchPath = p ? (isAbs ? p : join(projectRoot, p)) : projectRoot;
|
|
63
|
-
|
|
64
|
-
const rgBin = await resolveBinary('rg');
|
|
65
|
-
const args: string[] = ['-n', '--color', 'never'];
|
|
66
|
-
for (const g of defaultIgnoreGlobs(params.ignore)) {
|
|
67
|
-
args.push('--glob', g);
|
|
68
|
-
}
|
|
69
|
-
if (params.include) {
|
|
70
|
-
args.push('--glob', params.include);
|
|
71
|
-
}
|
|
72
|
-
args.push(pattern, searchPath);
|
|
73
|
-
|
|
74
|
-
let output = '';
|
|
75
|
-
try {
|
|
76
|
-
output = await new Promise<string>((resolve, reject) => {
|
|
77
|
-
const proc = spawn(rgBin, args, { cwd: projectRoot });
|
|
78
|
-
let stdout = '';
|
|
79
|
-
let stderr = '';
|
|
80
|
-
proc.stdout.on('data', (d) => {
|
|
81
|
-
stdout += d.toString();
|
|
82
|
-
});
|
|
83
|
-
proc.stderr.on('data', (d) => {
|
|
84
|
-
stderr += d.toString();
|
|
85
|
-
});
|
|
86
|
-
proc.on('close', (code) => {
|
|
87
|
-
if (code === 1) resolve('');
|
|
88
|
-
else if (code !== 0)
|
|
89
|
-
reject(new Error(stderr.trim() || 'ripgrep failed'));
|
|
90
|
-
else resolve(stdout);
|
|
91
|
-
});
|
|
92
|
-
proc.on('error', reject);
|
|
93
|
-
});
|
|
94
|
-
} catch (error: unknown) {
|
|
95
|
-
const err2 = error as { message?: string };
|
|
96
|
-
return createToolError(`ripgrep failed: ${err2.message}`, 'execution', {
|
|
97
|
-
parameter: 'pattern',
|
|
98
|
-
value: pattern,
|
|
99
|
-
suggestion:
|
|
100
|
-
'Check if ripgrep (rg) is installed and the pattern is valid',
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const lines = output.trim().split('\n');
|
|
105
|
-
const matches: Array<{
|
|
106
|
-
file: string;
|
|
107
|
-
line: number;
|
|
108
|
-
text: string;
|
|
109
|
-
}> = [];
|
|
110
|
-
|
|
111
|
-
for (const line of lines) {
|
|
112
|
-
if (!line) continue;
|
|
113
|
-
const m = line.match(/^(.+?):(\d+):(.*)$/s);
|
|
114
|
-
if (!m) continue;
|
|
115
|
-
const filePath = m[1];
|
|
116
|
-
const lineNum = parseInt(m[2], 10);
|
|
117
|
-
const lineText = m[3];
|
|
118
|
-
if (!filePath || !Number.isFinite(lineNum)) continue;
|
|
119
|
-
matches.push({ file: filePath, line: lineNum, text: lineText });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const limit = 500;
|
|
123
|
-
const truncated = matches.length > limit;
|
|
124
|
-
const finalMatches = truncated ? matches.slice(0, limit) : matches;
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
ok: true,
|
|
128
|
-
count: finalMatches.length,
|
|
129
|
-
matches: finalMatches,
|
|
130
|
-
};
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
return { name: 'grep', tool: grep };
|
|
134
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
- Fast content search tool powered by ripgrep (rg)
|
|
2
|
-
- Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
|
|
3
|
-
- Optional include glob to filter files (e.g., "*.js", "*.{ts,tsx}")
|
|
4
|
-
- Returns files with at least one match and line previews, sorted by modification time
|
|
5
|
-
- Skips common build and cache folders by default; add 'ignore' patterns to refine
|
|
6
|
-
|
|
7
|
-
Usage tips:
|
|
8
|
-
- For counting matches, use the Bash tool with rg directly (do not use grep)
|
|
9
|
-
- Batch multiple searches when exploring a codebase broadly
|