@opticlm/connector 2.0.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.
- package/LICENSE +21 -0
- package/README.md +499 -0
- package/dist/capabilities-CQHv2shL.d.ts +379 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +6 -0
- package/dist/lsp/index.d.ts +85 -0
- package/dist/lsp/index.js +3 -0
- package/dist/mcp/index.d.ts +175 -0
- package/dist/mcp/index.js +9 -0
- package/dist/resolver-Cg62Av4_.d.ts +129 -0
- package/package.json +58 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { D as DefinitionProvider, R as ReferencesProvider, H as HierarchyProvider, a as DiagnosticsProvider, O as OutlineProvider, G as GlobalFindProvider, b as GraphProvider, F as FrontmatterProvider, g as EditResult, j as FrontmatterMatch, m as GlobalFindMatch, L as Link } from '../capabilities-CQHv2shL.js';
|
|
4
|
+
import { F as FileAccessProvider, E as EditProvider, R as ResolverConfig } from '../resolver-Cg62Av4_.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Zod schemas for MCP tool inputs and outputs.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
declare const FuzzyPositionSchema: z.ZodObject<{
|
|
12
|
+
uri: z.ZodString;
|
|
13
|
+
symbol_name: z.ZodString;
|
|
14
|
+
line_hint: z.ZodNumber;
|
|
15
|
+
order_hint: z.ZodDefault<z.ZodNumber>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
declare const ApplyEditSchema: z.ZodObject<{
|
|
18
|
+
uri: z.ZodString;
|
|
19
|
+
start_hash: z.ZodString;
|
|
20
|
+
end_hash: z.ZodOptional<z.ZodString>;
|
|
21
|
+
replace_text: z.ZodString;
|
|
22
|
+
description: z.ZodString;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
declare const CallHierarchySchema: z.ZodObject<{
|
|
25
|
+
uri: z.ZodString;
|
|
26
|
+
symbol_name: z.ZodString;
|
|
27
|
+
line_hint: z.ZodNumber;
|
|
28
|
+
order_hint: z.ZodDefault<z.ZodNumber>;
|
|
29
|
+
direction: z.ZodEnum<{
|
|
30
|
+
incoming: "incoming";
|
|
31
|
+
outgoing: "outgoing";
|
|
32
|
+
}>;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
declare const GlobalFindSchema: z.ZodObject<{
|
|
35
|
+
query: z.ZodString;
|
|
36
|
+
case_sensitive: z.ZodDefault<z.ZodBoolean>;
|
|
37
|
+
exact_match: z.ZodDefault<z.ZodBoolean>;
|
|
38
|
+
regex_mode: z.ZodDefault<z.ZodBoolean>;
|
|
39
|
+
}, z.core.$strip>;
|
|
40
|
+
declare const AddLinkSchema: z.ZodObject<{
|
|
41
|
+
path: z.ZodString;
|
|
42
|
+
pattern: z.ZodString;
|
|
43
|
+
link_to: z.ZodString;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
declare const GetFrontmatterStructureSchema: z.ZodObject<{
|
|
46
|
+
property: z.ZodString;
|
|
47
|
+
path: z.ZodOptional<z.ZodString>;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
declare const SetFrontmatterSchema: z.ZodObject<{
|
|
50
|
+
path: z.ZodString;
|
|
51
|
+
property: z.ZodString;
|
|
52
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>, z.ZodNumber, z.ZodArray<z.ZodNumber>, z.ZodBoolean, z.ZodArray<z.ZodBoolean>, z.ZodNull]>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* MCP entry point for installing individual providers onto an MCP server.
|
|
57
|
+
*
|
|
58
|
+
* @packageDocumentation
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Union of all provider types that can be installed on an MCP server.
|
|
63
|
+
*/
|
|
64
|
+
type AnyProvider = FileAccessProvider | EditProvider | DefinitionProvider | ReferencesProvider | HierarchyProvider | DiagnosticsProvider | OutlineProvider | GlobalFindProvider | GraphProvider | FrontmatterProvider;
|
|
65
|
+
/**
|
|
66
|
+
* Options for installing a provider that needs file access or symbol resolution.
|
|
67
|
+
*/
|
|
68
|
+
interface InstallOptions {
|
|
69
|
+
/** Required by providers that need symbol resolution (definition, references, hierarchy) or file reading (edit) */
|
|
70
|
+
fileAccess?: FileAccessProvider;
|
|
71
|
+
/** Configuration for the symbol resolver */
|
|
72
|
+
resolverConfig?: ResolverConfig;
|
|
73
|
+
}
|
|
74
|
+
type SnippetItem = {
|
|
75
|
+
uri: string;
|
|
76
|
+
startLine: number;
|
|
77
|
+
endLine: number;
|
|
78
|
+
content: string;
|
|
79
|
+
};
|
|
80
|
+
type SnippetsOutput = {
|
|
81
|
+
snippets: SnippetItem[];
|
|
82
|
+
};
|
|
83
|
+
/** Options for installing an {@link EditProvider}. */
|
|
84
|
+
interface EditInstallOptions extends InstallOptions {
|
|
85
|
+
/** Called with the raw tool input before the edit is applied. */
|
|
86
|
+
onEditInput?: (input: z.infer<typeof ApplyEditSchema>) => void;
|
|
87
|
+
/** Called with the result after a successful edit (approved or rejected by user). */
|
|
88
|
+
onEditOutput?: (output: EditResult) => void;
|
|
89
|
+
}
|
|
90
|
+
/** Options for installing a {@link DefinitionProvider}. */
|
|
91
|
+
interface DefinitionInstallOptions extends InstallOptions {
|
|
92
|
+
/** Called with the raw tool input for `goto_definition`. */
|
|
93
|
+
onDefinitionInput?: (input: z.infer<typeof FuzzyPositionSchema>) => void;
|
|
94
|
+
/** Called with the result of `goto_definition`. */
|
|
95
|
+
onDefinitionOutput?: (output: SnippetsOutput) => void;
|
|
96
|
+
/** Called with the raw tool input for `goto_type_definition`. */
|
|
97
|
+
onTypeDefinitionInput?: (input: z.infer<typeof FuzzyPositionSchema>) => void;
|
|
98
|
+
/** Called with the result of `goto_type_definition`. */
|
|
99
|
+
onTypeDefinitionOutput?: (output: SnippetsOutput) => void;
|
|
100
|
+
}
|
|
101
|
+
/** Options for installing a {@link ReferencesProvider}. */
|
|
102
|
+
interface ReferencesInstallOptions extends InstallOptions {
|
|
103
|
+
/** Called with the raw tool input for `find_references`. */
|
|
104
|
+
onReferencesInput?: (input: z.infer<typeof FuzzyPositionSchema>) => void;
|
|
105
|
+
/** Called with the result of `find_references`. */
|
|
106
|
+
onReferencesOutput?: (output: SnippetsOutput) => void;
|
|
107
|
+
}
|
|
108
|
+
/** Options for installing a {@link HierarchyProvider}. */
|
|
109
|
+
interface HierarchyInstallOptions extends InstallOptions {
|
|
110
|
+
/** Called with the raw tool input for `call_hierarchy`. */
|
|
111
|
+
onCallHierarchyInput?: (input: z.infer<typeof CallHierarchySchema>) => void;
|
|
112
|
+
/** Called with the result of `call_hierarchy`. */
|
|
113
|
+
onCallHierarchyOutput?: (output: SnippetsOutput) => void;
|
|
114
|
+
}
|
|
115
|
+
/** Options for installing a {@link GlobalFindProvider}. */
|
|
116
|
+
interface GlobalFindInstallOptions extends InstallOptions {
|
|
117
|
+
/** Called with the raw tool input for `global_find`. */
|
|
118
|
+
onGlobalFindInput?: (input: z.infer<typeof GlobalFindSchema>) => void;
|
|
119
|
+
/** Called with the result of `global_find`. */
|
|
120
|
+
onGlobalFindOutput?: (output: {
|
|
121
|
+
matches: GlobalFindMatch[];
|
|
122
|
+
count: number;
|
|
123
|
+
}) => void;
|
|
124
|
+
}
|
|
125
|
+
/** Options for installing a {@link GraphProvider}. */
|
|
126
|
+
interface GraphInstallOptions extends InstallOptions {
|
|
127
|
+
/** Called with the result of `get_link_structure`. */
|
|
128
|
+
onLinkStructureOutput?: (output: {
|
|
129
|
+
links: Link[];
|
|
130
|
+
}) => void;
|
|
131
|
+
/** Called with the raw tool input for `add_link`. */
|
|
132
|
+
onAddLinkInput?: (input: z.infer<typeof AddLinkSchema>) => void;
|
|
133
|
+
/** Called with the result of `add_link`. */
|
|
134
|
+
onAddLinkOutput?: (output: {
|
|
135
|
+
success: boolean;
|
|
136
|
+
message: string;
|
|
137
|
+
}) => void;
|
|
138
|
+
}
|
|
139
|
+
/** Options for installing a {@link FrontmatterProvider}. */
|
|
140
|
+
interface FrontmatterInstallOptions extends InstallOptions {
|
|
141
|
+
/** Called with the raw tool input for `get_frontmatter_structure`. */
|
|
142
|
+
onFrontmatterStructureInput?: (input: z.infer<typeof GetFrontmatterStructureSchema>) => void;
|
|
143
|
+
/** Called with the result of `get_frontmatter_structure`. */
|
|
144
|
+
onFrontmatterStructureOutput?: (output: {
|
|
145
|
+
matches: FrontmatterMatch[];
|
|
146
|
+
}) => void;
|
|
147
|
+
/** Called with the raw tool input for `set_frontmatter`. */
|
|
148
|
+
onSetFrontmatterInput?: (input: z.infer<typeof SetFrontmatterSchema>) => void;
|
|
149
|
+
/** Called with the result of `set_frontmatter`. */
|
|
150
|
+
onSetFrontmatterOutput?: (output: {
|
|
151
|
+
success: boolean;
|
|
152
|
+
message: string;
|
|
153
|
+
}) => void;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Install a provider's tools and resources onto an MCP server.
|
|
157
|
+
*
|
|
158
|
+
* The provider type is detected via duck-typing (checking for characteristic methods).
|
|
159
|
+
* Some providers require `fileAccess` in options:
|
|
160
|
+
* - `DefinitionProvider`, `ReferencesProvider`, `HierarchyProvider` need it for symbol resolution
|
|
161
|
+
* - `EditProvider` needs it for file hash verification
|
|
162
|
+
* - `DiagnosticsProvider`, `OutlineProvider`, `GraphProvider`, `FrontmatterProvider` use it optionally for auto-complete
|
|
163
|
+
*/
|
|
164
|
+
declare function install(server: McpServer, provider: FileAccessProvider, options?: InstallOptions): void;
|
|
165
|
+
declare function install(server: McpServer, provider: EditProvider, options?: EditInstallOptions): void;
|
|
166
|
+
declare function install(server: McpServer, provider: DefinitionProvider, options?: DefinitionInstallOptions): void;
|
|
167
|
+
declare function install(server: McpServer, provider: ReferencesProvider, options?: ReferencesInstallOptions): void;
|
|
168
|
+
declare function install(server: McpServer, provider: HierarchyProvider, options?: HierarchyInstallOptions): void;
|
|
169
|
+
declare function install(server: McpServer, provider: DiagnosticsProvider, options?: InstallOptions): void;
|
|
170
|
+
declare function install(server: McpServer, provider: OutlineProvider, options?: InstallOptions): void;
|
|
171
|
+
declare function install(server: McpServer, provider: GlobalFindProvider, options?: GlobalFindInstallOptions): void;
|
|
172
|
+
declare function install(server: McpServer, provider: GraphProvider, options?: GraphInstallOptions): void;
|
|
173
|
+
declare function install(server: McpServer, provider: FrontmatterProvider, options?: FrontmatterInstallOptions): void;
|
|
174
|
+
|
|
175
|
+
export { type AnyProvider, type DefinitionInstallOptions, type EditInstallOptions, type FrontmatterInstallOptions, type GlobalFindInstallOptions, type GraphInstallOptions, type HierarchyInstallOptions, type InstallOptions, type ReferencesInstallOptions, install };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {ResourceTemplate}from'@modelcontextprotocol/sdk/server/mcp.js';import {z}from'zod';var g=class extends Error{constructor(i,a,o){super(`Could not find symbol '${i}' at or near line ${a}. ${o}`);this.symbolName=i;this.lineHint=a;this.reason=o;this.name="SymbolResolutionError";}},w=class{constructor(e,i){this.fs=e;this.lineSearchRadius=i?.lineSearchRadius??2;}lineSearchRadius;async resolvePosition(e,i){let o=(await this.fs.readFile(e)).split(/\r?\n/),n=i.lineHint-1,t=i.orderHint??0,s=this.findSymbolInLine(o[n],i.symbolName,t);if(s!==null)return {line:n,character:s};for(let c=1;c<=this.lineSearchRadius;c++){let l=n-c;if(l>=0){let h=this.findSymbolInLine(o[l],i.symbolName,t);if(h!==null)return {line:l,character:h}}let d=n+c;if(d<o.length){let h=this.findSymbolInLine(o[d],i.symbolName,t);if(h!==null)return {line:d,character:h}}}throw new g(i.symbolName,i.lineHint,`Please verify the file content and try again. Searched lines ${Math.max(1,i.lineHint-this.lineSearchRadius)} to ${Math.min(o.length,i.lineHint+this.lineSearchRadius)}.`)}findSymbolInLine(e,i,a){if(e===void 0||e.length===0)return null;let o=0,n=0;for(;o<e.length;){let t=e.indexOf(i,o);if(t===-1)break;if(n===a)return t;n++,o=t+1;}return null}};var f=r=>({content:[{type:"text",text:JSON.stringify(r??"")}],structuredContent:r});function m(r){if(r.includes(".."))throw new Error('URI could not include ".." operator');return r.replace(/\\/g,"/")}function G(){return `edit-${Date.now()}`}function $(r){return r.length===0?"No diagnostics found.":r.map(e=>{let i=e.range.start.line+1,a=e.severity.toUpperCase(),o=e.source?` [${e.source}]`:"",n=e.code!==void 0?` (${e.code})`:"";return `- **${a}**${o}${n} at line ${i}: ${e.message}`}).join(`
|
|
2
|
+
`)}function D(r,e=0){if(r.length===0&&e===0)return "No symbols found.";let i=" ".repeat(e);return r.map(a=>{let o=a.range.start.line+1,n=a.range.end.line+1,t=o===n?`line ${o}`:`lines ${o}-${n}`,s=a.detail?` - ${a.detail}`:"",c=`${i}- **${a.kind}** \`${a.name}\`${s} (${t})`;return a.children&&a.children.length>0?`${c}
|
|
3
|
+
${D(a.children,e+1)}`:c}).join(`
|
|
4
|
+
`)}var H=new Uint16Array(256);for(let r=0;r<256;r++){let e=r<<8;for(let i=0;i<8;i++)e=e<<1^(e&32768?4129:0);H[r]=e&65535;}function I(r){let e=65535;for(let i=0;i<r.length;i++)e=e<<8&65535^H[(e>>8^r.charCodeAt(i))&255];return (e&255).toString(16).padStart(2,"0")}function C(r){return r.split(/\r?\n/).map((e,i)=>({num:i+1,text:e}))}function M(r){return r.map(e=>`${e.num}:${I(e.text)}|${e.text}`).join(`
|
|
5
|
+
`)}function _(r){let e=r.match(/^(\d+):([0-9a-f]{2})$/);if(!e||!e[1]||!e[2])throw new Error(`Invalid hashline reference "${r}". Expected format: "<line>:<hash>" (e.g., "3:a1")`);return {line:parseInt(e[1],10),hash:e[2]}}var j=z.string().describe("The relative file path"),N=z.string().describe("The text of the symbol to find"),U=z.number().int().positive().describe("Approximate 1-based line number where the symbol is expected"),W=z.number().int().min(0).default(0).describe("0-based index of which occurrence to target if symbol appears multiple times"),E=z.object({uri:j,symbol_name:N,line_hint:U,order_hint:W}),v=z.object({uri:j,start_hash:z.string().describe('Start line reference from hashline output, format "<line>:<hash>" (e.g., "3:a1"). Copy this exactly from the files:// resource output.'),end_hash:z.string().optional().describe('End line reference for multi-line edits (e.g., "5:0e"). The range is inclusive. Omit for single-line edits (defaults to start_hash).'),replace_text:z.string().describe("The new text to replace the entire line range with"),description:z.string().describe("Rationale for the edit")}),J=z.object({uri:z.string().describe("The file URI or path"),symbol_name:N,line_hint:U,order_hint:W,direction:z.enum(["incoming","outgoing"]).describe("Direction of the call hierarchy")}),me=z.string().describe("The search query"),he=z.boolean().default(false).describe("Whether the search is case-sensitive"),fe=z.boolean().default(false).describe("Whether to match exact words only"),ge=z.boolean().default(false).describe("Whether the query is a regular expression"),q=z.object({query:me,case_sensitive:he,exact_match:fe,regex_mode:ge}),B=z.object({path:z.string().describe("The path to the document to modify"),pattern:z.string().describe("The text pattern to find and replace with a link"),link_to:z.string().describe("The target URI the link should point to")}),Q=z.object({property:z.string().describe("The frontmatter property name to search for"),path:z.string().optional().describe("Optional path to limit the search to a specific document. If not provided, searches all documents.")}),K=z.object({path:z.string().describe("The path to the document to modify"),property:z.string().describe("The frontmatter property name to set"),value:z.union([z.string(),z.array(z.string()),z.number(),z.array(z.number()),z.boolean(),z.array(z.boolean()),z.null()]).describe("The value to set. Can be a string, number, boolean, array of these types, or null to remove.")});function ye(r){if(!r)return null;let e=r.match(/^L(\d+)(?:-L(\d+))?$/);if(!e||!e[1])return null;let i=parseInt(e[1],10),a=e[2]?parseInt(e[2],10):i;return i<1||a<i?null:{start:i,end:a}}function V(r){let e=r.indexOf("?");return e===-1?{path:r,params:new URLSearchParams}:{path:r.slice(0,e),params:new URLSearchParams(r.slice(e+1))}}function A(r){return async e=>{let i=e.lastIndexOf("/"),a=i>=0?e.slice(0,i):"",o=i>=0?e.slice(i+1):e;try{let n=await r(a||"."),t=o.toLowerCase();return n.filter(s=>s.toLowerCase().startsWith(t)).map(s=>a?`${a}/${s}`:s)}catch{return []}}}function X(r,e,i,a){r.registerTool("goto_definition",{description:"Navigate to the definition of a symbol.",inputSchema:E,outputSchema:{snippets:z.array(z.object({uri:z.string(),startLine:z.number(),endLine:z.number(),content:z.string()}))}},async o=>{a?.onInput?.(o);try{let n=m(o.uri),t={symbolName:o.symbol_name,lineHint:o.line_hint,orderHint:o.order_hint},s=await i.resolvePosition(n,t),c=(await e.provideDefinition(n,s)).map(l=>({uri:l.uri,startLine:l.range.start.line+1,endLine:l.range.end.line+1,content:l.content}));return a?.onOutput?.({snippets:c}),f({snippets:c})}catch(n){let t=n instanceof g?n.message:`Error: ${n instanceof Error?n.message:String(n)}`;return {content:[{type:"text",text:t}],structuredContent:{error:t},isError:true}}});}function Y(r,e,i,a){let o=e.provideTypeDefinition;o&&r.registerTool("goto_type_definition",{description:"Navigate to the type definition of a symbol.",inputSchema:E,outputSchema:{snippets:z.array(z.object({uri:z.string(),startLine:z.number(),endLine:z.number(),content:z.string()}))}},async n=>{a?.onInput?.(n);try{let t=m(n.uri),s={symbolName:n.symbol_name,lineHint:n.line_hint,orderHint:n.order_hint},c=await i.resolvePosition(t,s),l=(await o(t,c)).map(d=>({uri:d.uri,startLine:d.range.start.line+1,endLine:d.range.end.line+1,content:d.content}));return a?.onOutput?.({snippets:l}),f({snippets:l})}catch(t){let s=t instanceof g?t.message:`Error: ${t instanceof Error?t.message:String(t)}`;return {content:[{type:"text",text:s}],structuredContent:{error:s},isError:true}}});}function Z(r,e,i,a){r.registerTool("find_references",{description:"Find all references to a symbol. Returns a list of locations where the symbol is used.",inputSchema:E,outputSchema:{snippets:z.array(z.object({uri:z.string(),startLine:z.number(),endLine:z.number(),content:z.string()}))}},async o=>{a?.onInput?.(o);try{let n=m(o.uri),t={symbolName:o.symbol_name,lineHint:o.line_hint,orderHint:o.order_hint},s=await i.resolvePosition(n,t),c=(await e.provideReferences(n,s)).map(l=>({uri:l.uri,startLine:l.range.start.line+1,endLine:l.range.end.line+1,content:l.content}));return a?.onOutput?.({snippets:c}),f({snippets:c})}catch(n){let t=n instanceof g?n.message:`Error: ${n instanceof Error?n.message:String(n)}`;return {content:[{type:"text",text:t}],structuredContent:{error:t},isError:true}}});}function ee(r,e,i,a){r.registerTool("call_hierarchy",{description:"Get call hierarchy for a function or method. Shows incoming or outgoing calls.",inputSchema:J,outputSchema:{snippets:z.array(z.object({uri:z.string(),startLine:z.number(),endLine:z.number(),content:z.string()}))}},async o=>{a?.onInput?.(o);try{let n=m(o.uri),t={symbolName:o.symbol_name,lineHint:o.line_hint,orderHint:o.order_hint},s=await i.resolvePosition(n,t),c=(await e.provideCallHierarchy(n,s,o.direction)).map(l=>({uri:l.uri,startLine:l.range.start.line+1,endLine:l.range.end.line+1,content:l.content}));return a?.onOutput?.({snippets:c}),f({snippets:c})}catch(n){let t=n instanceof g?n.message:`Error: ${n instanceof Error?n.message:String(n)}`;return {content:[{type:"text",text:t}],structuredContent:{error:t},isError:true}}});}function te(r,e,i){let a=new ResourceTemplate("diagnostics://{+path}",{list:void 0,complete:i?{path:i}:void 0});if(r.registerResource("diagnostics",a,{description:"Diagnostics (errors, warnings, hints) for a specific file. Use the file path after diagnostics://",mimeType:"text/markdown"},async(o,n)=>{try{let t=n.path,s=m(t),c=await e.provideDiagnostics(s),l=$(c);return {contents:[{uri:`diagnostics://${t}`,mimeType:"text/markdown",text:l}]}}catch(t){let s=`Error: ${t instanceof Error?t.message:String(t)}`;return {contents:[{uri:`diagnostics://${n.path}`,mimeType:"text/markdown",text:s}]}}}),e.getWorkspaceDiagnostics){let o=e.getWorkspaceDiagnostics.bind(e);r.registerResource("workspace-diagnostics","diagnostics://workspace",{description:"All diagnostics (errors, warnings, hints) across the entire workspace",mimeType:"text/markdown"},async()=>{try{let n=await o(),t=new Map;for(let c of n){let l=t.get(c.uri)??[];l.push(c),t.set(c.uri,l);}if(t.size===0)return {contents:[{uri:"diagnostics://workspace",mimeType:"text/markdown",text:"No diagnostics found in workspace."}]};let s=[];for(let[c,l]of t)s.push(`## ${c}
|
|
6
|
+
${$(l)}`);return {contents:[{uri:"diagnostics://workspace",mimeType:"text/markdown",text:s.join(`
|
|
7
|
+
|
|
8
|
+
`)}]}}catch(n){return {contents:[{uri:"diagnostics://workspace",mimeType:"text/markdown",text:`Error: ${n instanceof Error?n.message:String(n)}`}]}}});}e.onDiagnosticsChanged&&e.onDiagnosticsChanged(o=>{let n=m(o);r.server.sendResourceUpdated({uri:`diagnostics://${n}`}),e.getWorkspaceDiagnostics&&r.server.sendResourceUpdated({uri:"diagnostics://workspace"});});}function ne(r,e,i){let a=new ResourceTemplate("outline://{+path}",{list:void 0,complete:i?{path:i}:void 0});r.registerResource("outline",a,{description:"Document outline (symbols like classes, functions, variables) for a specific file. Use the file path after outline://",mimeType:"text/markdown"},async(o,n)=>{try{let t=n.path,s=m(t),c=await e.provideDocumentSymbols(s),l=D(c);return {contents:[{uri:`outline://${t}`,mimeType:"text/markdown",text:l}]}}catch(t){let s=`Error: ${t instanceof Error?t.message:String(t)}`;return {contents:[{uri:`outline://${n.path}`,mimeType:"text/markdown",text:s}]}}});}function re(r,e,i,a){let o=e.previewAndApplyEdits?.bind(e)??e.applyEdits?.bind(e);o&&r.registerTool("apply_edit",{description:'Apply a text edit to a file. WORKFLOW: First read the file via the files:// resource to get hashline-formatted content (e.g., "3:a1| return x"). Then reference lines by their "line:hash" to specify the edit range. The hash verifies the file has not changed since your read \u2014 if it has, the edit is rejected and you must re-read the file. For single-line edits, only start_hash is needed. For multi-line edits, provide both start_hash and end_hash. The edit replaces the entire line range (inclusive) with replace_text. The edit must be approved by the user before being applied.',inputSchema:{uri:v.shape.uri,start_hash:v.shape.start_hash,end_hash:v.shape.end_hash,replace_text:v.shape.replace_text,description:v.shape.description},outputSchema:{success:z.boolean(),message:z.string()}},async n=>{a?.onInput?.(n);try{let t=m(n.uri),s=_(n.start_hash),c=n.end_hash?_(n.end_hash):s,d=(await i(t)).split(/\r?\n/);if(s.line<1||s.line>d.length)throw new Error(`Start line ${s.line} is out of range (file has ${d.length} lines)`);if(c.line<s.line||c.line>d.length)throw new Error(`End line ${c.line} is out of range (file has ${d.length} lines)`);let h=d[s.line-1],b=I(h);if(b!==s.hash)throw new Error(`Hash mismatch at line ${s.line}: expected "${s.hash}", got "${b}". File has changed since last read.`);let P=d[c.line-1],F=I(P);if(F!==c.hash)throw new Error(`Hash mismatch at line ${c.line}: expected "${c.hash}", got "${F}". File has changed since last read.`);let k={start:{line:s.line-1,character:0},end:{line:c.line-1,character:P.length}},T={id:G(),uri:t,edits:[{range:k,newText:n.replace_text}],description:n.description},y=await o(T)?{success:!0,message:"Edit successfully applied and saved."}:{success:!1,message:"Edit rejected by user."};return a?.onOutput?.(y),f(y)}catch(t){let s=`Error: ${t instanceof Error?t.message:String(t)}`;return {content:[{type:"text",text:s}],structuredContent:{success:false,message:s},isError:true}}});}function ie(r,e){let{readFile:i,readDirectory:a}=e,o=new ResourceTemplate("files://{+path}",{list:void 0,complete:{path:A(a)}});r.registerResource("filesystem",o,{description:'Access filesystem resources. For directories: returns children as JSON (git-ignored files excluded). For files: returns content in hashline format where each line is prefixed with "<lineNumber>:<hash>|" (e.g., "1:a3|function hello() {"). The hash is a 2-char hex CRC16 digest of the line content. Use these line:hash references with the apply_edit tool to make edits. Supports line ranges with #L23 or #L23-L30 fragment. Supports regex filtering with ?pattern=<regex> query parameter (matches raw line text, not the hash prefix). Line numbers in the output are always the original file line numbers, even when filtering.'},async(n,t)=>{let s=n.toString();try{let c=t.path,l,d=c,h=c.indexOf("#");h!==-1&&(l=c.slice(h+1),d=c.slice(0,h));let{path:b,params:P}=V(d),{path:F,params:k}=V(l??"");l=l?F:void 0;let T=new URLSearchParams([...P.entries(),...k.entries()]),R=m(b),y=ye(l),z=T.get("pattern");try{let L=await i(R),S=C(L);if(y&&(S=S.filter(O=>O.num>=y.start&&O.num<=y.end)),z){let O=new RegExp(z);S=S.filter(de=>O.test(de.text));}return {contents:[{uri:s,mimeType:"text/plain",text:M(S)}]}}catch{let L=await a(R);return {contents:[{uri:s,mimeType:"application/json",text:JSON.stringify(L)}]}}}catch(c){let l=`Error: ${c instanceof Error?c.message:String(c)}`;return {contents:[{uri:s,mimeType:"text/plain",text:l}]}}}),e.onFileChanged&&e.onFileChanged(n=>{let t=m(n);r.server.sendResourceUpdated({uri:`files://${t}`});});}function oe(r,e,i){r.registerTool("global_find",{description:"Search for text across the entire workspace.",inputSchema:q,outputSchema:{matches:z.array(z.object({uri:z.string(),line:z.number(),column:z.number(),matchText:z.string(),context:z.string()})),count:z.number()}},async a=>{i?.onInput?.(a);try{let o=a.case_sensitive??!1,n=a.exact_match??!1,t=a.regex_mode??!1,s=await e.globalFind(a.query,{caseSensitive:o,exactMatch:n,regexMode:t});return i?.onOutput?.({count:s.length,matches:s}),f({count:s.length,matches:s})}catch(o){let n=`Error: ${o instanceof Error?o.message:String(o)}`;return {content:[{type:"text",text:n}],structuredContent:{error:n},isError:true}}});}function se(r,e,i){let a=new ResourceTemplate("outlinks://{+path}",{list:void 0,complete:i?{path:i}:void 0});r.registerResource("outlinks",a,{description:"Outgoing links from a specific file. Use the file path after outlinks://",mimeType:"application/json"},async(n,t)=>{try{let s=t.path,c=m(s),l=await e.resolveOutlinks(c);return {contents:[{uri:`outlinks://${s}`,mimeType:"application/json",text:JSON.stringify(l,null,2)}]}}catch(s){let c=`Error: ${s instanceof Error?s.message:String(s)}`;return {contents:[{uri:`outlinks://${t.path}`,mimeType:"application/json",text:JSON.stringify({error:c})}]}}});let o=new ResourceTemplate("backlinks://{+path}",{list:void 0,complete:i?{path:i}:void 0});r.registerResource("backlinks",o,{description:"Incoming links (backlinks) to a specific file. Use the file path after backlinks://",mimeType:"application/json"},async(n,t)=>{try{let s=t.path,c=m(s),l=await e.resolveBacklinks(c);return {contents:[{uri:`backlinks://${s}`,mimeType:"application/json",text:JSON.stringify(l,null,2)}]}}catch(s){let c=`Error: ${s instanceof Error?s.message:String(s)}`;return {contents:[{uri:`backlinks://${t.path}`,mimeType:"application/json",text:JSON.stringify({error:c})}]}}});}function ae(r,e,i){r.registerTool("get_link_structure",{description:"Get all links in the workspace, showing relationships between documents.",inputSchema:{},outputSchema:{links:z.array(z.object({sourceUri:z.string(),targetUri:z.string(),subpath:z.string().optional(),displayText:z.string().optional(),resolved:z.boolean(),line:z.number(),column:z.number()}))}},async()=>{try{let a=await e.getLinkStructure();return i?.onOutput?.({links:a}),f({links:a})}catch(a){let o=`Error: ${a instanceof Error?a.message:String(a)}`;return {content:[{type:"text",text:o}],structuredContent:{error:o},isError:true}}});}function ce(r,e,i){r.registerTool("add_link",{description:"Add a link to a document by finding a text pattern and replacing it with a link to the target.",inputSchema:B,outputSchema:{success:z.boolean(),message:z.string().optional()}},async a=>{i?.onInput?.(a);try{let o=m(a.path),n=m(a.link_to);await e.addLink(o,a.pattern,n);let t={success:!0,message:"Link added successfully."};return i?.onOutput?.(t),f(t)}catch(o){let n=`Error: ${o instanceof Error?o.message:String(o)}`;return {content:[{type:"text",text:n}],structuredContent:{success:false,message:n},isError:true}}});}function ue(r,e,i){r.registerTool("get_frontmatter_structure",{description:"Get frontmatter property values across documents. If path is provided, searches only that document. Otherwise, searches all documents.",inputSchema:Q,outputSchema:{matches:z.array(z.object({path:z.string(),value:z.unknown()}))}},async a=>{i?.onInput?.(a);try{let o=a.path?m(a.path):void 0,n=await e.getFrontmatterStructure(a.property,o);return i?.onOutput?.({matches:n}),f({matches:n})}catch(o){let n=`Error: ${o instanceof Error?o.message:String(o)}`;return {content:[{type:"text",text:n}],structuredContent:{error:n},isError:true}}});}function le(r,e,i){r.registerTool("set_frontmatter",{description:"Set a frontmatter property on a document. Use null to remove the property.",inputSchema:K,outputSchema:{success:z.boolean(),message:z.string().optional()}},async a=>{i?.onInput?.(a);try{let o=m(a.path),n=a.value===null?void 0:a.value;await e.setFrontmatter(o,a.property,n);let t={success:!0,message:"Frontmatter updated successfully."};return i?.onOutput?.(t),f(t)}catch(o){let n=`Error: ${o instanceof Error?o.message:String(o)}`;return {content:[{type:"text",text:n}],structuredContent:{success:false,message:n},isError:true}}});}function pe(r,e,i){let a=new ResourceTemplate("frontmatter://{+path}",{list:void 0,complete:i?{path:i}:void 0});r.registerResource("frontmatter",a,{description:"Frontmatter metadata for a specific file. Use the file path after frontmatter://",mimeType:"application/json"},async(o,n)=>{try{let t=n.path,s=m(t),c=await e.getFrontmatter(s);return {contents:[{uri:`frontmatter://${t}`,mimeType:"application/json",text:JSON.stringify(c,null,2)}]}}catch(t){let s=`Error: ${t instanceof Error?t.message:String(t)}`;return {contents:[{uri:`frontmatter://${n.path}`,mimeType:"application/json",text:JSON.stringify({error:s})}]}}});}function $e(r,e,i){let a=i?.fileAccess,o=a?A(a.readDirectory):void 0,n=()=>{if(!a)throw new Error("fileAccess is required in options for providers that need symbol resolution");return new w(a,i?.resolverConfig)};if("readFile"in e&&"readDirectory"in e){ie(r,e);return}if("applyEdits"in e||"previewAndApplyEdits"in e){if(!a)throw new Error("fileAccess is required in options when installing an EditProvider");let t=i;re(r,e,a.readFile,{onInput:t?.onEditInput,onOutput:t?.onEditOutput});return}if("provideDefinition"in e){let t=e,s=i,c=n();X(r,t,c,{onInput:s?.onDefinitionInput,onOutput:s?.onDefinitionOutput}),t.provideTypeDefinition&&Y(r,t,c,{onInput:s?.onTypeDefinitionInput,onOutput:s?.onTypeDefinitionOutput});return}if("provideReferences"in e){let t=i;Z(r,e,n(),{onInput:t?.onReferencesInput,onOutput:t?.onReferencesOutput});return}if("provideCallHierarchy"in e){let t=i;ee(r,e,n(),{onInput:t?.onCallHierarchyInput,onOutput:t?.onCallHierarchyOutput});return}if("provideDiagnostics"in e){te(r,e,o);return}if("provideDocumentSymbols"in e){ne(r,e,o);return}if("globalFind"in e){let t=i;oe(r,e,{onInput:t?.onGlobalFindInput,onOutput:t?.onGlobalFindOutput});return}if("getLinkStructure"in e){let t=e,s=i;ae(r,t,{onOutput:s?.onLinkStructureOutput}),ce(r,t,{onInput:s?.onAddLinkInput,onOutput:s?.onAddLinkOutput}),se(r,t,o);return}if("getFrontmatterStructure"in e){let t=e,s=i;ue(r,t,{onInput:s?.onFrontmatterStructureInput,onOutput:s?.onFrontmatterStructureOutput}),le(r,t,{onInput:s?.onSetFrontmatterInput,onOutput:s?.onSetFrontmatterOutput}),pe(r,t,o);return}}
|
|
9
|
+
export{$e as install};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { P as PendingEditOperation, U as UnifiedUri, l as FuzzyPosition, h as ExactPosition } from './capabilities-CQHv2shL.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Infrastructure Interfaces for MCP LSP Driver SDK
|
|
5
|
+
*
|
|
6
|
+
* The SDK consumer (Plugin Developer) must implement these interfaces
|
|
7
|
+
* to bridge the SDK to the specific IDE.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Provides access to the file system for reading files.
|
|
12
|
+
* Since the SDK is responsible for resolving FuzzyPosition to ExactPosition,
|
|
13
|
+
* it needs direct access to read files from the disk.
|
|
14
|
+
*/
|
|
15
|
+
interface FileAccessProvider {
|
|
16
|
+
/**
|
|
17
|
+
* Reads the content of a file from the disk (ignoring unsaved IDE buffers).
|
|
18
|
+
* Used for symbol resolution and context retrieval.
|
|
19
|
+
*
|
|
20
|
+
* @param uri - The URI of the file to read
|
|
21
|
+
* @returns The content of the file as a string
|
|
22
|
+
* @throws Error if the file cannot be read
|
|
23
|
+
*/
|
|
24
|
+
readFile(uri: UnifiedUri): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Read children in a directory, exluding git-ignored files, similar to Unix `ls` command
|
|
27
|
+
* @param relativePath - The path to the folder to read
|
|
28
|
+
* @returns Array of file/folder paths in the directory
|
|
29
|
+
*/
|
|
30
|
+
readDirectory(relativePath: UnifiedUri): Promise<string[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Called by the driver to register a callback for file changes.
|
|
33
|
+
* When provided, the files:// resources become subscribable.
|
|
34
|
+
* The plugin should call the registered callback whenever a file changes.
|
|
35
|
+
*
|
|
36
|
+
* @param callback - The callback to invoke when a file changes
|
|
37
|
+
*/
|
|
38
|
+
onFileChanged?(callback: (uri: UnifiedUri) => void): void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Provides edit capabilities for applying changes to files.
|
|
42
|
+
* At least one of the methods must be implemented.
|
|
43
|
+
*/
|
|
44
|
+
interface EditProvider {
|
|
45
|
+
/**
|
|
46
|
+
* Applies edits directly without user interaction.
|
|
47
|
+
* Use this when user approval is handled elsewhere or not needed.
|
|
48
|
+
*
|
|
49
|
+
* @param operation - The pending edit operation to apply
|
|
50
|
+
* @returns true if applied successfully, false if failed
|
|
51
|
+
*/
|
|
52
|
+
applyEdits?(operation: PendingEditOperation): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* Displays a diff view or a confirmation dialog in the IDE.
|
|
55
|
+
* The user decides whether to apply the edits or discard them.
|
|
56
|
+
*
|
|
57
|
+
* @param operation - The pending edit operation to preview
|
|
58
|
+
* @returns true if applied, false if rejected/cancelled
|
|
59
|
+
*/
|
|
60
|
+
previewAndApplyEdits?(operation: PendingEditOperation): Promise<boolean>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Smart Resolver Logic for MCP LSP Driver SDK
|
|
65
|
+
*
|
|
66
|
+
* This is the internal engine of the SDK. It translates the LLM's imprecise
|
|
67
|
+
* instructions into precise coordinates.
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Configuration options for the SymbolResolver.
|
|
72
|
+
*/
|
|
73
|
+
interface ResolverConfig {
|
|
74
|
+
/**
|
|
75
|
+
* Number of lines to scan above and below the lineHint if the symbol
|
|
76
|
+
* is not found at the exact line. Defaults to 2.
|
|
77
|
+
*/
|
|
78
|
+
lineSearchRadius?: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Error thrown when a symbol cannot be resolved.
|
|
82
|
+
*/
|
|
83
|
+
declare class SymbolResolutionError extends Error {
|
|
84
|
+
readonly symbolName: string;
|
|
85
|
+
readonly lineHint: number;
|
|
86
|
+
readonly reason: string;
|
|
87
|
+
constructor(symbolName: string, lineHint: number, reason: string);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The SymbolResolver translates fuzzy positions (as provided by an LLM)
|
|
91
|
+
* into exact positions that can be used by the IDE.
|
|
92
|
+
*
|
|
93
|
+
* Algorithm:
|
|
94
|
+
* 1. Read file content via FileAccessProvider.
|
|
95
|
+
* 2. Split content into lines.
|
|
96
|
+
* 3. Target the lineHint (converting 1-based to 0-based).
|
|
97
|
+
* 4. Search for symbolName in that line.
|
|
98
|
+
* - If orderHint is 0, find first occurrence.
|
|
99
|
+
* - If orderHint is N, find Nth occurrence.
|
|
100
|
+
* 5. Robustness Fallback:
|
|
101
|
+
* - If the line is empty or symbol not found at lineHint,
|
|
102
|
+
* scan +/- lineSearchRadius lines to handle minor line shifts.
|
|
103
|
+
* 6. Return ExactPosition (line, character start index).
|
|
104
|
+
*/
|
|
105
|
+
declare class SymbolResolver {
|
|
106
|
+
private readonly fs;
|
|
107
|
+
private readonly lineSearchRadius;
|
|
108
|
+
constructor(fs: FileAccessProvider, config?: ResolverConfig);
|
|
109
|
+
/**
|
|
110
|
+
* Resolves a fuzzy position to an exact position.
|
|
111
|
+
*
|
|
112
|
+
* @param uri - The URI of the file
|
|
113
|
+
* @param fuzzy - The fuzzy position provided by the LLM
|
|
114
|
+
* @returns The exact position in the file
|
|
115
|
+
* @throws SymbolResolutionError if the symbol cannot be found
|
|
116
|
+
*/
|
|
117
|
+
resolvePosition(uri: UnifiedUri, fuzzy: FuzzyPosition): Promise<ExactPosition>;
|
|
118
|
+
/**
|
|
119
|
+
* Finds the Nth occurrence of a symbol in a line.
|
|
120
|
+
*
|
|
121
|
+
* @param line - The line to search in (may be undefined if out of bounds)
|
|
122
|
+
* @param symbolName - The symbol to find
|
|
123
|
+
* @param orderHint - Which occurrence to find (0-based)
|
|
124
|
+
* @returns The character offset of the symbol, or null if not found
|
|
125
|
+
*/
|
|
126
|
+
private findSymbolInLine;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { type EditProvider as E, type FileAccessProvider as F, type ResolverConfig as R, SymbolResolutionError as S, SymbolResolver as a };
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opticlm/connector",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./lsp": {
|
|
12
|
+
"types": "./dist/lsp/index.d.ts",
|
|
13
|
+
"default": "./dist/lsp/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./mcp": {
|
|
16
|
+
"types": "./dist/mcp/index.d.ts",
|
|
17
|
+
"default": "./dist/mcp/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": ["dist/**/*.js", "dist/**/*.d.ts", "LICENSE"],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"test:coverage": "vitest run --coverage",
|
|
26
|
+
"lint": "biome check .",
|
|
27
|
+
"lint:fix": "biome check --write .",
|
|
28
|
+
"format": "biome format --write .",
|
|
29
|
+
"prepublishOnly": "npm run build"
|
|
30
|
+
},
|
|
31
|
+
"keywords": ["mcp", "lsp", "language-server", "ide", "vscode", "jetbrains"],
|
|
32
|
+
"author": "EFLKumo",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/OpticLM/connector"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
40
|
+
"vscode-languageserver-protocol": "^3.17.5",
|
|
41
|
+
"zod": "^4.3.6"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"vscode-languageserver-protocol": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@biomejs/biome": "2.4.6",
|
|
50
|
+
"@types/node": "^25",
|
|
51
|
+
"tsup": "^8.5.1",
|
|
52
|
+
"typescript": "^5.9",
|
|
53
|
+
"vitest": "^4"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|