@lanes-sh/link 0.1.0 → 0.1.2
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 +3 -3
- package/instructions/skills/lanes-link/SKILL.md +1 -1
- package/package.json +3 -3
- package/src/cli/commands/operate/outputs.ts +5 -38
- package/src/cli/commands/operate/tools.ts +343 -0
- package/src/cli/commands/operate.ts +1 -0
- package/src/cli/endpoint-url.ts +46 -1
- package/src/cli/lanes.ts +1 -1
- package/src/cli/main.ts +8 -0
- package/src/cli/publish.ts +46 -3
- package/src/cli/usage.ts +2 -1
- package/src/deployments/deploy.ts +28 -1
- package/src/server/endpoint.ts +11 -1
- package/src/server/generation.ts +217 -0
- package/src/server/generations.ts +71 -213
- package/src/server/harness.ts +5 -0
- package/src/server/index.ts +2 -1
- package/src/server/mcp/build.ts +56 -2
- package/src/server/mcp/index.ts +1 -0
- package/src/server/mcp/visibility.ts +22 -0
package/src/server/mcp/build.ts
CHANGED
|
@@ -38,14 +38,49 @@ export function buildMcpServer(options: BuildServerOptions): McpServer {
|
|
|
38
38
|
// rather than trusting a copy to stay one. It is the whole endpoint being
|
|
39
39
|
// described, not this connection, so it does not name the profiles the
|
|
40
40
|
// way `title` does.
|
|
41
|
-
description: 'A self-
|
|
41
|
+
description: 'A self-hostable MCP gateway for all your connections, memory, skills, and secrets',
|
|
42
42
|
websiteUrl: 'https://github.com/lanes-sh/link',
|
|
43
43
|
icons: SERVER_ICONS,
|
|
44
44
|
},
|
|
45
45
|
// Second argument, not the first: `instructions` is a `ServerOptions` field,
|
|
46
46
|
// and `Implementation` would take it as an unknown extra and drop it from
|
|
47
47
|
// `initialize` without complaining.
|
|
48
|
-
{
|
|
48
|
+
{
|
|
49
|
+
instructions: serverInstructions(names, merged),
|
|
50
|
+
// Declared `false` because it is false, and the SDK defaults it to `true`.
|
|
51
|
+
//
|
|
52
|
+
// `listChanged` is a promise to send `notifications/tools/list_changed`
|
|
53
|
+
// when the surface changes. This endpoint cannot keep it: it is stateless
|
|
54
|
+
// streamable HTTP, so there is no stream to deliver a notification on and
|
|
55
|
+
// this very server instance is discarded once the response is written.
|
|
56
|
+
// Nothing in `src/` sends one, and nothing can.
|
|
57
|
+
//
|
|
58
|
+
// Leaving the default on is not a harmless inaccuracy. A client that
|
|
59
|
+
// believes it will be told has no reason to ask again, so it keeps the
|
|
60
|
+
// list it captured when it first connected — and the surface here grows
|
|
61
|
+
// every time an account is connected (ADR-029). That combination cost a
|
|
62
|
+
// connector registered before `connect` ran its whole tool list: it held
|
|
63
|
+
// the two setup tools for as long as it lived, and no refresh replaced
|
|
64
|
+
// them, because the refresh was an `initialize` that never went on to ask
|
|
65
|
+
// for `tools/list`.
|
|
66
|
+
//
|
|
67
|
+
// Saying `false` costs a re-list per session and buys a surface that is
|
|
68
|
+
// never stale.
|
|
69
|
+
//
|
|
70
|
+
// `tools` unconditionally, because the argument above is about tools and
|
|
71
|
+
// an endpoint that serves none should still answer "none right now"
|
|
72
|
+
// rather than "this server does not do tools". Resources and prompts only
|
|
73
|
+
// when the profile has some: declaring a capability installs its handler
|
|
74
|
+
// set, so a client that gates its list calls on what is advertised would
|
|
75
|
+
// otherwise issue `resources/list`, `resources/templates/list` and
|
|
76
|
+
// `prompts/list` — three stateless POSTs, each rebuilding the whole
|
|
77
|
+
// server — to be told nothing is there.
|
|
78
|
+
capabilities: {
|
|
79
|
+
tools: { listChanged: false },
|
|
80
|
+
...(offers(merged, isResource) ? { resources: { listChanged: false } } : {}),
|
|
81
|
+
...(offers(merged, isPrompt) ? { prompts: { listChanged: false } } : {}),
|
|
82
|
+
},
|
|
83
|
+
},
|
|
49
84
|
);
|
|
50
85
|
|
|
51
86
|
for (const [id, entry] of merged) {
|
|
@@ -66,3 +101,22 @@ export function buildMcpServer(options: BuildServerOptions): McpServer {
|
|
|
66
101
|
|
|
67
102
|
return server;
|
|
68
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Whether any reachable capability registers as this kind.
|
|
107
|
+
*
|
|
108
|
+
* Reads the same `merged` map the registration loop below consumes and asks it
|
|
109
|
+
* the same question `isResource`/`isPrompt` answer there, so what is advertised
|
|
110
|
+
* and what is registered cannot disagree. A discovered capability is always a
|
|
111
|
+
* tool, which is why it is not consulted here.
|
|
112
|
+
*/
|
|
113
|
+
function offers(
|
|
114
|
+
merged: ReturnType<typeof mergeCapabilities>,
|
|
115
|
+
kind: typeof isResource | typeof isPrompt,
|
|
116
|
+
): boolean {
|
|
117
|
+
for (const entry of merged.values()) {
|
|
118
|
+
if (!entry.discovered && entry.capability && kind(entry.capability)) return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
}
|
package/src/server/mcp/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isTool } from '#connectivity';
|
|
1
2
|
import type { Principal } from '#auth';
|
|
2
3
|
import type { Config } from '#profile';
|
|
3
4
|
import type { ProviderRegistry } from '#registry';
|
|
@@ -114,6 +115,27 @@ export function visibleCapabilities(options: BuildServerOptions): string[] {
|
|
|
114
115
|
return [...mergeCapabilities(options).keys()];
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
/**
|
|
119
|
+
* How many of those are tools, as `tools/list` would count them.
|
|
120
|
+
*
|
|
121
|
+
* Not the same number as `visibleCapabilities().length`, and the difference is
|
|
122
|
+
* the kind of thing that only shows up when someone reads it: a reachable
|
|
123
|
+
* capability may register as a resource or a prompt instead, so counting ids
|
|
124
|
+
* and calling the answer "tools" overstates the list by however many of those
|
|
125
|
+
* a profile has. The kind is decided here exactly as `buildMcpServer` decides
|
|
126
|
+
* it — a discovered capability is always a tool, an authored one is asked.
|
|
127
|
+
*/
|
|
128
|
+
export function visibleToolCount(options: BuildServerOptions): number {
|
|
129
|
+
let count = 0;
|
|
130
|
+
|
|
131
|
+
for (const entry of mergeCapabilities(options).values()) {
|
|
132
|
+
if (entry.discovered) count += 1;
|
|
133
|
+
else if (entry.capability && isTool(entry.capability)) count += 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return count;
|
|
137
|
+
}
|
|
138
|
+
|
|
117
139
|
/**
|
|
118
140
|
* Say which accounts are reachable, grouped by profile.
|
|
119
141
|
*
|