@opticlm/connector 2.0.1 → 2.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.
- package/README.md +7 -2
- package/dist/mcp/index.d.ts +14 -10
- package/dist/mcp/index.js +7 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,6 @@ Provides an abstract interface that allows LLMs to connect to fact sources such
|
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
7
|
-
- [Core Philosophy](#core-philosophy)
|
|
8
7
|
- [Installation](#installation)
|
|
9
8
|
- [Quick Start](#quick-start)
|
|
10
9
|
- [MCP Tools](#mcp-tools)
|
|
@@ -28,7 +27,9 @@ pnpm add @opticlm/connector
|
|
|
28
27
|
|
|
29
28
|
## Quick Start
|
|
30
29
|
|
|
31
|
-
Providers are installed
|
|
30
|
+
Providers are installed onto an MCP server using `install()` from `@opticlm/connector/mcp`. Each call registers the tools and resources for that specific provider. Providers that depend on file access (definition, references, hierarchy, edit) receive a `fileAccess` option.
|
|
31
|
+
|
|
32
|
+
You can pass a single provider or an array of providers of the same type. When an array is given, their results are merged automatically — array-returning methods (e.g. `provideDefinition`) are concatenated, void methods are called on all providers in parallel, and callback registrars (e.g. `onDiagnosticsChanged`) are registered on every provider in the array.
|
|
32
33
|
|
|
33
34
|
```typescript
|
|
34
35
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
@@ -91,6 +92,10 @@ install(server, definition, { fileAccess })
|
|
|
91
92
|
install(server, diagnostics, { fileAccess })
|
|
92
93
|
install(server, outline, { fileAccess })
|
|
93
94
|
|
|
95
|
+
// You can also pass an array to merge multiple providers of the same type:
|
|
96
|
+
// install(server, [definition, anotherDefinition], { fileAccess })
|
|
97
|
+
// install(server, [diagnostics, anotherDiagnostics], { fileAccess })
|
|
98
|
+
|
|
94
99
|
// 6. Connect to transport (you control the server lifecycle)
|
|
95
100
|
const transport = new StdioServerTransport()
|
|
96
101
|
await server.connect(transport)
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -155,21 +155,25 @@ interface FrontmatterInstallOptions extends InstallOptions {
|
|
|
155
155
|
/**
|
|
156
156
|
* Install a provider's tools and resources onto an MCP server.
|
|
157
157
|
*
|
|
158
|
+
* Accepts a single provider or an array of providers of the same type. When an array is
|
|
159
|
+
* passed, their results are merged: array-returning methods are concatenated, void methods
|
|
160
|
+
* are called on all providers in parallel, and callback registrars are registered on all.
|
|
161
|
+
*
|
|
158
162
|
* The provider type is detected via duck-typing (checking for characteristic methods).
|
|
159
163
|
* Some providers require `fileAccess` in options:
|
|
160
164
|
* - `DefinitionProvider`, `ReferencesProvider`, `HierarchyProvider` need it for symbol resolution
|
|
161
165
|
* - `EditProvider` needs it for file hash verification
|
|
162
166
|
* - `DiagnosticsProvider`, `OutlineProvider`, `GraphProvider`, `FrontmatterProvider` use it optionally for auto-complete
|
|
163
167
|
*/
|
|
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;
|
|
168
|
+
declare function install(server: McpServer, provider: FileAccessProvider | FileAccessProvider[], options?: InstallOptions): void;
|
|
169
|
+
declare function install(server: McpServer, provider: EditProvider | EditProvider[], options?: EditInstallOptions): void;
|
|
170
|
+
declare function install(server: McpServer, provider: DefinitionProvider | DefinitionProvider[], options?: DefinitionInstallOptions): void;
|
|
171
|
+
declare function install(server: McpServer, provider: ReferencesProvider | ReferencesProvider[], options?: ReferencesInstallOptions): void;
|
|
172
|
+
declare function install(server: McpServer, provider: HierarchyProvider | HierarchyProvider[], options?: HierarchyInstallOptions): void;
|
|
173
|
+
declare function install(server: McpServer, provider: DiagnosticsProvider | DiagnosticsProvider[], options?: InstallOptions): void;
|
|
174
|
+
declare function install(server: McpServer, provider: OutlineProvider | OutlineProvider[], options?: InstallOptions): void;
|
|
175
|
+
declare function install(server: McpServer, provider: GlobalFindProvider | GlobalFindProvider[], options?: GlobalFindInstallOptions): void;
|
|
176
|
+
declare function install(server: McpServer, provider: GraphProvider | GraphProvider[], options?: GraphInstallOptions): void;
|
|
177
|
+
declare function install(server: McpServer, provider: FrontmatterProvider | FrontmatterProvider[], options?: FrontmatterInstallOptions): void;
|
|
174
178
|
|
|
175
179
|
export { type AnyProvider, type DefinitionInstallOptions, type EditInstallOptions, type FrontmatterInstallOptions, type GlobalFindInstallOptions, type GraphInstallOptions, type HierarchyInstallOptions, type InstallOptions, type ReferencesInstallOptions, install };
|
package/dist/mcp/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {ResourceTemplate}from'@modelcontextprotocol/sdk/server/mcp.js';import {z}from'zod';var g=class extends Error{constructor(i,
|
|
2
|
-
`)}function
|
|
3
|
-
${
|
|
4
|
-
`)}var
|
|
5
|
-
`)}function
|
|
6
|
-
${
|
|
1
|
+
import {ResourceTemplate}from'@modelcontextprotocol/sdk/server/mcp.js';import {z as z$1}from'zod';var g=class extends Error{constructor(i,e,t){super(`Could not find symbol '${i}' at or near line ${e}. ${t}`);this.symbolName=i;this.lineHint=e;this.reason=t;this.name="SymbolResolutionError";}},O=class{constructor(n,i){this.fs=n;this.lineSearchRadius=i?.lineSearchRadius??2;}lineSearchRadius;async resolvePosition(n,i){let t=(await this.fs.readFile(n)).split(/\r?\n/),r=i.lineHint-1,o=i.orderHint??0,a=this.findSymbolInLine(t[r],i.symbolName,o);if(a!==null)return {line:r,character:a};for(let c=1;c<=this.lineSearchRadius;c++){let l=r-c;if(l>=0){let f=this.findSymbolInLine(t[l],i.symbolName,o);if(f!==null)return {line:l,character:f}}let d=r+c;if(d<t.length){let f=this.findSymbolInLine(t[d],i.symbolName,o);if(f!==null)return {line:d,character:f}}}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(t.length,i.lineHint+this.lineSearchRadius)}.`)}findSymbolInLine(n,i,e){if(n===void 0||n.length===0)return null;let t=0,r=0;for(;t<n.length;){let o=n.indexOf(i,t);if(o===-1)break;if(r===e)return o;r++,t=o+1;}return null}};var h=s=>({content:[{type:"text",text:JSON.stringify(s??"")}],structuredContent:s});function m(s){if(s.includes(".."))throw new Error('URI could not include ".." operator');return s.replace(/\\/g,"/")}function z(){return `edit-${Date.now()}`}function L(s){return s.length===0?"No diagnostics found.":s.map(n=>{let i=n.range.start.line+1,e=n.severity.toUpperCase(),t=n.source?` [${n.source}]`:"",r=n.code!==void 0?` (${n.code})`:"";return `- **${e}**${t}${r} at line ${i}: ${n.message}`}).join(`
|
|
2
|
+
`)}function $(s,n=0){if(s.length===0&&n===0)return "No symbols found.";let i=" ".repeat(n);return s.map(e=>{let t=e.range.start.line+1,r=e.range.end.line+1,o=t===r?`line ${t}`:`lines ${t}-${r}`,a=e.detail?` - ${e.detail}`:"",c=`${i}- **${e.kind}** \`${e.name}\`${a} (${o})`;return e.children&&e.children.length>0?`${c}
|
|
3
|
+
${$(e.children,n+1)}`:c}).join(`
|
|
4
|
+
`)}var C=new Uint16Array(256);for(let s=0;s<256;s++){let n=s<<8;for(let i=0;i<8;i++)n=n<<1^(n&32768?4129:0);C[s]=n&65535;}function k(s){let n=65535;for(let i=0;i<s.length;i++)n=n<<8&65535^C[(n>>8^s.charCodeAt(i))&255];return (n&255).toString(16).padStart(2,"0")}function H(s){return s.split(/\r?\n/).map((n,i)=>({num:i+1,text:n}))}function M(s){return s.map(n=>`${n.num}:${k(n.text)}|${n.text}`).join(`
|
|
5
|
+
`)}function A(s){let n=s.match(/^(\d+):([0-9a-f]{2})$/);if(!n||!n[1]||!n[2])throw new Error(`Invalid hashline reference "${s}". Expected format: "<line>:<hash>" (e.g., "3:a1")`);return {line:parseInt(n[1],10),hash:n[2]}}var j=z$1.string().describe("The relative file path"),N=z$1.string().describe("The text of the symbol to find"),U=z$1.number().int().positive().describe("Approximate 1-based line number where the symbol is expected"),W=z$1.number().int().min(0).default(0).describe("0-based index of which occurrence to target if symbol appears multiple times"),E=z$1.object({uri:j,symbol_name:N,line_hint:U,order_hint:W}),v=z$1.object({uri:j,start_hash:z$1.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$1.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$1.string().describe("The new text to replace the entire line range with"),description:z$1.string().describe("Rationale for the edit")}),J=z$1.object({uri:z$1.string().describe("The file URI or path"),symbol_name:N,line_hint:U,order_hint:W,direction:z$1.enum(["incoming","outgoing"]).describe("Direction of the call hierarchy")}),me=z$1.string().describe("The search query"),fe=z$1.boolean().default(false).describe("Whether the search is case-sensitive"),he=z$1.boolean().default(false).describe("Whether to match exact words only"),ge=z$1.boolean().default(false).describe("Whether the query is a regular expression"),q=z$1.object({query:me,case_sensitive:fe,exact_match:he,regex_mode:ge}),B=z$1.object({path:z$1.string().describe("The path to the document to modify"),pattern:z$1.string().describe("The text pattern to find and replace with a link"),link_to:z$1.string().describe("The target URI the link should point to")}),Q=z$1.object({property:z$1.string().describe("The frontmatter property name to search for"),path:z$1.string().optional().describe("Optional path to limit the search to a specific document. If not provided, searches all documents.")}),K=z$1.object({path:z$1.string().describe("The path to the document to modify"),property:z$1.string().describe("The frontmatter property name to set"),value:z$1.union([z$1.string(),z$1.array(z$1.string()),z$1.number(),z$1.array(z$1.number()),z$1.boolean(),z$1.array(z$1.boolean()),z$1.null()]).describe("The value to set. Can be a string, number, boolean, array of these types, or null to remove.")});function ye(s){if(!s)return null;let n=s.match(/^L(\d+)(?:-L(\d+))?$/);if(!n||!n[1])return null;let i=parseInt(n[1],10),e=n[2]?parseInt(n[2],10):i;return i<1||e<i?null:{start:i,end:e}}function V(s){let n=s.indexOf("?");return n===-1?{path:s,params:new URLSearchParams}:{path:s.slice(0,n),params:new URLSearchParams(s.slice(n+1))}}function _(s){return async n=>{let i=n.lastIndexOf("/"),e=i>=0?n.slice(0,i):"",t=i>=0?n.slice(i+1):n;try{let r=await s(e||"."),o=t.toLowerCase();return r.filter(a=>a.toLowerCase().startsWith(o)).map(a=>e?`${e}/${a}`:a)}catch{return []}}}function X(s,n,i,e){s.registerTool("goto_definition",{description:"Navigate to the definition of a symbol.",inputSchema:E,outputSchema:{snippets:z$1.array(z$1.object({uri:z$1.string(),startLine:z$1.number(),endLine:z$1.number(),content:z$1.string()}))}},async t=>{e?.onInput?.(t);try{let r=m(t.uri),o={symbolName:t.symbol_name,lineHint:t.line_hint,orderHint:t.order_hint},a=await i.resolvePosition(r,o),c=(await n.provideDefinition(r,a)).map(l=>({uri:l.uri,startLine:l.range.start.line+1,endLine:l.range.end.line+1,content:l.content}));return e?.onOutput?.({snippets:c}),h({snippets:c})}catch(r){let o=r instanceof g?r.message:`Error: ${r instanceof Error?r.message:String(r)}`;return {content:[{type:"text",text:o}],structuredContent:{error:o},isError:true}}});}function Y(s,n,i,e){let t=n.provideTypeDefinition;t&&s.registerTool("goto_type_definition",{description:"Navigate to the type definition of a symbol.",inputSchema:E,outputSchema:{snippets:z$1.array(z$1.object({uri:z$1.string(),startLine:z$1.number(),endLine:z$1.number(),content:z$1.string()}))}},async r=>{e?.onInput?.(r);try{let o=m(r.uri),a={symbolName:r.symbol_name,lineHint:r.line_hint,orderHint:r.order_hint},c=await i.resolvePosition(o,a),l=(await t(o,c)).map(d=>({uri:d.uri,startLine:d.range.start.line+1,endLine:d.range.end.line+1,content:d.content}));return e?.onOutput?.({snippets:l}),h({snippets:l})}catch(o){let a=o instanceof g?o.message:`Error: ${o instanceof Error?o.message:String(o)}`;return {content:[{type:"text",text:a}],structuredContent:{error:a},isError:true}}});}function Z(s,n,i,e){s.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$1.array(z$1.object({uri:z$1.string(),startLine:z$1.number(),endLine:z$1.number(),content:z$1.string()}))}},async t=>{e?.onInput?.(t);try{let r=m(t.uri),o={symbolName:t.symbol_name,lineHint:t.line_hint,orderHint:t.order_hint},a=await i.resolvePosition(r,o),c=(await n.provideReferences(r,a)).map(l=>({uri:l.uri,startLine:l.range.start.line+1,endLine:l.range.end.line+1,content:l.content}));return e?.onOutput?.({snippets:c}),h({snippets:c})}catch(r){let o=r instanceof g?r.message:`Error: ${r instanceof Error?r.message:String(r)}`;return {content:[{type:"text",text:o}],structuredContent:{error:o},isError:true}}});}function ee(s,n,i,e){s.registerTool("call_hierarchy",{description:"Get call hierarchy for a function or method. Shows incoming or outgoing calls.",inputSchema:J,outputSchema:{snippets:z$1.array(z$1.object({uri:z$1.string(),startLine:z$1.number(),endLine:z$1.number(),content:z$1.string()}))}},async t=>{e?.onInput?.(t);try{let r=m(t.uri),o={symbolName:t.symbol_name,lineHint:t.line_hint,orderHint:t.order_hint},a=await i.resolvePosition(r,o),c=(await n.provideCallHierarchy(r,a,t.direction)).map(l=>({uri:l.uri,startLine:l.range.start.line+1,endLine:l.range.end.line+1,content:l.content}));return e?.onOutput?.({snippets:c}),h({snippets:c})}catch(r){let o=r instanceof g?r.message:`Error: ${r instanceof Error?r.message:String(r)}`;return {content:[{type:"text",text:o}],structuredContent:{error:o},isError:true}}});}function te(s,n,i){let e=new ResourceTemplate("diagnostics://{+path}",{list:void 0,complete:i?{path:i}:void 0});if(s.registerResource("diagnostics",e,{description:"Diagnostics (errors, warnings, hints) for a specific file. Use the file path after diagnostics://",mimeType:"text/markdown"},async(t,r)=>{try{let o=r.path,a=m(o),c=await n.provideDiagnostics(a),l=L(c);return {contents:[{uri:`diagnostics://${o}`,mimeType:"text/markdown",text:l}]}}catch(o){let a=`Error: ${o instanceof Error?o.message:String(o)}`;return {contents:[{uri:`diagnostics://${r.path}`,mimeType:"text/markdown",text:a}]}}}),n.getWorkspaceDiagnostics){let t=n.getWorkspaceDiagnostics.bind(n);s.registerResource("workspace-diagnostics","diagnostics://workspace",{description:"All diagnostics (errors, warnings, hints) across the entire workspace",mimeType:"text/markdown"},async()=>{try{let r=await t(),o=new Map;for(let c of r){let l=o.get(c.uri)??[];l.push(c),o.set(c.uri,l);}if(o.size===0)return {contents:[{uri:"diagnostics://workspace",mimeType:"text/markdown",text:"No diagnostics found in workspace."}]};let a=[];for(let[c,l]of o)a.push(`## ${c}
|
|
6
|
+
${L(l)}`);return {contents:[{uri:"diagnostics://workspace",mimeType:"text/markdown",text:a.join(`
|
|
7
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}}
|
|
8
|
+
`)}]}}catch(r){return {contents:[{uri:"diagnostics://workspace",mimeType:"text/markdown",text:`Error: ${r instanceof Error?r.message:String(r)}`}]}}});}n.onDiagnosticsChanged&&n.onDiagnosticsChanged(t=>{let r=m(t);s.server.sendResourceUpdated({uri:`diagnostics://${r}`}),n.getWorkspaceDiagnostics&&s.server.sendResourceUpdated({uri:"diagnostics://workspace"});});}function re(s,n,i){let e=new ResourceTemplate("outline://{+path}",{list:void 0,complete:i?{path:i}:void 0});s.registerResource("outline",e,{description:"Document outline (symbols like classes, functions, variables) for a specific file. Use the file path after outline://",mimeType:"text/markdown"},async(t,r)=>{try{let o=r.path,a=m(o),c=await n.provideDocumentSymbols(a),l=$(c);return {contents:[{uri:`outline://${o}`,mimeType:"text/markdown",text:l}]}}catch(o){let a=`Error: ${o instanceof Error?o.message:String(o)}`;return {contents:[{uri:`outline://${r.path}`,mimeType:"text/markdown",text:a}]}}});}function ne(s,n,i,e){let t=n.previewAndApplyEdits?.bind(n)??n.applyEdits?.bind(n);t&&s.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$1.boolean(),message:z$1.string()}},async r=>{e?.onInput?.(r);try{let o=m(r.uri),a=A(r.start_hash),c=r.end_hash?A(r.end_hash):a,d=(await i(o)).split(/\r?\n/);if(a.line<1||a.line>d.length)throw new Error(`Start line ${a.line} is out of range (file has ${d.length} lines)`);if(c.line<a.line||c.line>d.length)throw new Error(`End line ${c.line} is out of range (file has ${d.length} lines)`);let f=d[a.line-1],x=k(f);if(x!==a.hash)throw new Error(`Hash mismatch at line ${a.line}: expected "${a.hash}", got "${x}". File has changed since last read.`);let P=d[c.line-1],F=k(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 I={start:{line:a.line-1,character:0},end:{line:c.line-1,character:P.length}},T={id:z(),uri:o,edits:[{range:I,newText:r.replace_text}],description:r.description},y=await t(T)?{success:!0,message:"Edit successfully applied and saved."}:{success:!1,message:"Edit rejected by user."};return e?.onOutput?.(y),h(y)}catch(o){let a=`Error: ${o instanceof Error?o.message:String(o)}`;return {content:[{type:"text",text:a}],structuredContent:{success:false,message:a},isError:true}}});}function ie(s,n){let{readFile:i,readDirectory:e}=n,t=new ResourceTemplate("files://{+path}",{list:void 0,complete:{path:_(e)}});s.registerResource("filesystem",t,{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(r,o)=>{let a=r.toString();try{let c=o.path,l,d=c,f=c.indexOf("#");f!==-1&&(l=c.slice(f+1),d=c.slice(0,f));let{path:x,params:P}=V(d),{path:F,params:I}=V(l??"");l=l?F:void 0;let T=new URLSearchParams([...P.entries(),...I.entries()]),R=m(x),y=ye(l),G=T.get("pattern");try{let D=await i(R),b=H(D);if(y&&(b=b.filter(w=>w.num>=y.start&&w.num<=y.end)),G){let w=new RegExp(G);b=b.filter(de=>w.test(de.text));}return {contents:[{uri:a,mimeType:"text/plain",text:M(b)}]}}catch{let D=await e(R);return {contents:[{uri:a,mimeType:"application/json",text:JSON.stringify(D)}]}}}catch(c){let l=`Error: ${c instanceof Error?c.message:String(c)}`;return {contents:[{uri:a,mimeType:"text/plain",text:l}]}}}),n.onFileChanged&&n.onFileChanged(r=>{let o=m(r);s.server.sendResourceUpdated({uri:`files://${o}`});});}function oe(s,n,i){s.registerTool("global_find",{description:"Search for text across the entire workspace.",inputSchema:q,outputSchema:{matches:z$1.array(z$1.object({uri:z$1.string(),line:z$1.number(),column:z$1.number(),matchText:z$1.string(),context:z$1.string()})),count:z$1.number()}},async e=>{i?.onInput?.(e);try{let t=e.case_sensitive??!1,r=e.exact_match??!1,o=e.regex_mode??!1,a=await n.globalFind(e.query,{caseSensitive:t,exactMatch:r,regexMode:o});return i?.onOutput?.({count:a.length,matches:a}),h({count:a.length,matches:a})}catch(t){let r=`Error: ${t instanceof Error?t.message:String(t)}`;return {content:[{type:"text",text:r}],structuredContent:{error:r},isError:true}}});}function se(s,n,i){let e=new ResourceTemplate("outlinks://{+path}",{list:void 0,complete:i?{path:i}:void 0});s.registerResource("outlinks",e,{description:"Outgoing links from a specific file. Use the file path after outlinks://",mimeType:"application/json"},async(r,o)=>{try{let a=o.path,c=m(a),l=await n.resolveOutlinks(c);return {contents:[{uri:`outlinks://${a}`,mimeType:"application/json",text:JSON.stringify(l,null,2)}]}}catch(a){let c=`Error: ${a instanceof Error?a.message:String(a)}`;return {contents:[{uri:`outlinks://${o.path}`,mimeType:"application/json",text:JSON.stringify({error:c})}]}}});let t=new ResourceTemplate("backlinks://{+path}",{list:void 0,complete:i?{path:i}:void 0});s.registerResource("backlinks",t,{description:"Incoming links (backlinks) to a specific file. Use the file path after backlinks://",mimeType:"application/json"},async(r,o)=>{try{let a=o.path,c=m(a),l=await n.resolveBacklinks(c);return {contents:[{uri:`backlinks://${a}`,mimeType:"application/json",text:JSON.stringify(l,null,2)}]}}catch(a){let c=`Error: ${a instanceof Error?a.message:String(a)}`;return {contents:[{uri:`backlinks://${o.path}`,mimeType:"application/json",text:JSON.stringify({error:c})}]}}});}function ae(s,n,i){s.registerTool("get_link_structure",{description:"Get all links in the workspace, showing relationships between documents.",inputSchema:{},outputSchema:{links:z$1.array(z$1.object({sourceUri:z$1.string(),targetUri:z$1.string(),subpath:z$1.string().optional(),displayText:z$1.string().optional(),resolved:z$1.boolean(),line:z$1.number(),column:z$1.number()}))}},async()=>{try{let e=await n.getLinkStructure();return i?.onOutput?.({links:e}),h({links:e})}catch(e){let t=`Error: ${e instanceof Error?e.message:String(e)}`;return {content:[{type:"text",text:t}],structuredContent:{error:t},isError:true}}});}function ce(s,n,i){s.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$1.boolean(),message:z$1.string().optional()}},async e=>{i?.onInput?.(e);try{let t=m(e.path),r=m(e.link_to);await n.addLink(t,e.pattern,r);let o={success:!0,message:"Link added successfully."};return i?.onOutput?.(o),h(o)}catch(t){let r=`Error: ${t instanceof Error?t.message:String(t)}`;return {content:[{type:"text",text:r}],structuredContent:{success:false,message:r},isError:true}}});}function ue(s,n,i){s.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$1.array(z$1.object({path:z$1.string(),value:z$1.unknown()}))}},async e=>{i?.onInput?.(e);try{let t=e.path?m(e.path):void 0,r=await n.getFrontmatterStructure(e.property,t);return i?.onOutput?.({matches:r}),h({matches:r})}catch(t){let r=`Error: ${t instanceof Error?t.message:String(t)}`;return {content:[{type:"text",text:r}],structuredContent:{error:r},isError:true}}});}function le(s,n,i){s.registerTool("set_frontmatter",{description:"Set a frontmatter property on a document. Use null to remove the property.",inputSchema:K,outputSchema:{success:z$1.boolean(),message:z$1.string().optional()}},async e=>{i?.onInput?.(e);try{let t=m(e.path),r=e.value===null?void 0:e.value;await n.setFrontmatter(t,e.property,r);let o={success:!0,message:"Frontmatter updated successfully."};return i?.onOutput?.(o),h(o)}catch(t){let r=`Error: ${t instanceof Error?t.message:String(t)}`;return {content:[{type:"text",text:r}],structuredContent:{success:false,message:r},isError:true}}});}function pe(s,n,i){let e=new ResourceTemplate("frontmatter://{+path}",{list:void 0,complete:i?{path:i}:void 0});s.registerResource("frontmatter",e,{description:"Frontmatter metadata for a specific file. Use the file path after frontmatter://",mimeType:"application/json"},async(t,r)=>{try{let o=r.path,a=m(o),c=await n.getFrontmatter(a);return {contents:[{uri:`frontmatter://${o}`,mimeType:"application/json",text:JSON.stringify(c,null,2)}]}}catch(o){let a=`Error: ${o instanceof Error?o.message:String(o)}`;return {contents:[{uri:`frontmatter://${r.path}`,mimeType:"application/json",text:JSON.stringify({error:a})}]}}});}function ve(s){if(s.length<=0)throw new Error("providers array must not be empty");if(s.length===1&&s[0])return s[0];let n=s[0];if(!n)throw new Error("The first item of providers array must be defined");if("readFile"in n&&"readDirectory"in n){let i=s,e={readFile:t=>n.readFile(t),readDirectory:t=>n.readDirectory(t)};return i.some(t=>t.onFileChanged)&&(e.onFileChanged=t=>{for(let r of i)r.onFileChanged?.(t);}),e}if("applyEdits"in n||"previewAndApplyEdits"in n)return n;if("provideDefinition"in n){let i=s,e={async provideDefinition(t,r){return (await Promise.all(i.map(a=>a.provideDefinition(t,r)))).flat()}};return i.some(t=>t.provideTypeDefinition)&&(e.provideTypeDefinition=async(t,r)=>{let o=i.filter(c=>c.provideTypeDefinition);return (await Promise.all(o.map(c=>c.provideTypeDefinition(t,r)))).flat()}),e}if("provideReferences"in n){let i=s;return {async provideReferences(e,t){return (await Promise.all(i.map(o=>o.provideReferences(e,t)))).flat()}}}if("provideCallHierarchy"in n){let i=s;return {async provideCallHierarchy(e,t,r){return (await Promise.all(i.map(a=>a.provideCallHierarchy(e,t,r)))).flat()}}}if("provideDiagnostics"in n){let i=s,e={async provideDiagnostics(t){return (await Promise.all(i.map(o=>o.provideDiagnostics(t)))).flat()}};return i.some(t=>t.getWorkspaceDiagnostics)&&(e.getWorkspaceDiagnostics=async()=>{let t=i.filter(o=>o.getWorkspaceDiagnostics);return (await Promise.all(t.map(o=>o.getWorkspaceDiagnostics()))).flat()}),i.some(t=>t.onDiagnosticsChanged)&&(e.onDiagnosticsChanged=t=>{for(let r of i)r.onDiagnosticsChanged?.(t);}),e}if("provideDocumentSymbols"in n){let i=s;return {async provideDocumentSymbols(e){return (await Promise.all(i.map(r=>r.provideDocumentSymbols(e)))).flat()}}}if("globalFind"in n){let i=s;return {async globalFind(e,t){return (await Promise.all(i.map(o=>o.globalFind(e,t)))).flat()}}}if("getLinkStructure"in n){let i=s;return {async getLinkStructure(){return (await Promise.all(i.map(t=>t.getLinkStructure()))).flat()},async resolveOutlinks(e){return (await Promise.all(i.map(r=>r.resolveOutlinks(e)))).flat()},async resolveBacklinks(e){return (await Promise.all(i.map(r=>r.resolveBacklinks(e)))).flat()},async addLink(e,t,r){await Promise.all(i.map(o=>o.addLink(e,t,r)));}}}if("getFrontmatterStructure"in n){let i=s;return {async getFrontmatterStructure(e,t){return (await Promise.all(i.map(o=>o.getFrontmatterStructure(e,t)))).flat()},async getFrontmatter(e){let t=await Promise.all(i.map(r=>r.getFrontmatter(e)));return Object.assign({},...t)},async setFrontmatter(e,t,r){await Promise.all(i.map(o=>o.setFrontmatter(e,t,r)));}}}return n}function $e(s,n,i){let e=Array.isArray(n)?ve(n):n,t=i?.fileAccess,r=t?_(t.readDirectory):void 0,o=()=>{if(!t)throw new Error("fileAccess is required in options for providers that need symbol resolution");return new O(t,i?.resolverConfig)};if("readFile"in e&&"readDirectory"in e){ie(s,e);return}if("applyEdits"in e||"previewAndApplyEdits"in e){if(!t)throw new Error("fileAccess is required in options when installing an EditProvider");let a=i;ne(s,e,t.readFile,{onInput:a?.onEditInput,onOutput:a?.onEditOutput});return}if("provideDefinition"in e){let a=e,c=i,l=o();X(s,a,l,{onInput:c?.onDefinitionInput,onOutput:c?.onDefinitionOutput}),a.provideTypeDefinition&&Y(s,a,l,{onInput:c?.onTypeDefinitionInput,onOutput:c?.onTypeDefinitionOutput});return}if("provideReferences"in e){let a=i;Z(s,e,o(),{onInput:a?.onReferencesInput,onOutput:a?.onReferencesOutput});return}if("provideCallHierarchy"in e){let a=i;ee(s,e,o(),{onInput:a?.onCallHierarchyInput,onOutput:a?.onCallHierarchyOutput});return}if("provideDiagnostics"in e){te(s,e,r);return}if("provideDocumentSymbols"in e){re(s,e,r);return}if("globalFind"in e){let a=i;oe(s,e,{onInput:a?.onGlobalFindInput,onOutput:a?.onGlobalFindOutput});return}if("getLinkStructure"in e){let a=e,c=i;ae(s,a,{onOutput:c?.onLinkStructureOutput}),ce(s,a,{onInput:c?.onAddLinkInput,onOutput:c?.onAddLinkOutput}),se(s,a,r);return}if("getFrontmatterStructure"in e){let a=e,c=i;ue(s,a,{onInput:c?.onFrontmatterStructureInput,onOutput:c?.onFrontmatterStructureOutput}),le(s,a,{onInput:c?.onSetFrontmatterInput,onOutput:c?.onSetFrontmatterOutput}),pe(s,a,r);return}}
|
|
9
9
|
export{$e as install};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opticlm/connector",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Provides an abstract interface that allows LLMs to connect to fact sources such as LSPs, code diagnostics, symbol definitions/references, links, and frontmatter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/index.d.ts",
|