appduct 0.9.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 +274 -0
- package/bin.js +2 -0
- package/dist/artifact-inspect.d.ts +110 -0
- package/dist/artifact-inspect.d.ts.map +1 -0
- package/dist/artifact-inspect.js +315 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/cli/command-options.d.ts +44 -0
- package/dist/cli/command-options.d.ts.map +1 -0
- package/dist/cli/command-options.js +95 -0
- package/dist/cli/create-cli.d.ts +2 -0
- package/dist/cli/create-cli.d.ts.map +1 -0
- package/dist/cli/create-cli.js +56 -0
- package/dist/cli/dispatch.d.ts +3 -0
- package/dist/cli/dispatch.d.ts.map +1 -0
- package/dist/cli/dispatch.js +275 -0
- package/dist/cli/open-target.d.ts +112 -0
- package/dist/cli/open-target.d.ts.map +1 -0
- package/dist/cli/open-target.js +438 -0
- package/dist/cli/result-types.d.ts +194 -0
- package/dist/cli/result-types.d.ts.map +1 -0
- package/dist/cli/result-types.js +6 -0
- package/dist/cli/runner.d.ts +35 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +111 -0
- package/dist/cli/types.d.ts +20 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +10 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/client/app-client.d.ts +126 -0
- package/dist/client/app-client.d.ts.map +1 -0
- package/dist/client/app-client.js +223 -0
- package/dist/client/bootstrap.d.ts +48 -0
- package/dist/client/bootstrap.d.ts.map +1 -0
- package/dist/client/bootstrap.js +108 -0
- package/dist/client/connect.d.ts +17 -0
- package/dist/client/connect.d.ts.map +1 -0
- package/dist/client/connect.js +38 -0
- package/dist/client/errors.d.ts +26 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +40 -0
- package/dist/client/index.d.ts +19 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +16 -0
- package/dist/commands/daemon.d.ts +22 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +128 -0
- package/dist/commands/doctor.d.ts +23 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +42 -0
- package/dist/commands/events.d.ts +37 -0
- package/dist/commands/events.d.ts.map +1 -0
- package/dist/commands/events.js +76 -0
- package/dist/commands/init.d.ts +51 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +231 -0
- package/dist/commands/invoke.d.ts +22 -0
- package/dist/commands/invoke.d.ts.map +1 -0
- package/dist/commands/invoke.js +54 -0
- package/dist/commands/keygen.d.ts +23 -0
- package/dist/commands/keygen.d.ts.map +1 -0
- package/dist/commands/keygen.js +46 -0
- package/dist/commands/link.d.ts +30 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +43 -0
- package/dist/commands/ls.d.ts +9 -0
- package/dist/commands/ls.d.ts.map +1 -0
- package/dist/commands/ls.js +7 -0
- package/dist/commands/mcp.d.ts +49 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +98 -0
- package/dist/commands/revoke.d.ts +12 -0
- package/dist/commands/revoke.d.ts.map +1 -0
- package/dist/commands/revoke.js +7 -0
- package/dist/commands/tools.d.ts +23 -0
- package/dist/commands/tools.d.ts.map +1 -0
- package/dist/commands/tools.js +52 -0
- package/dist/daemon/address.d.ts +30 -0
- package/dist/daemon/address.d.ts.map +1 -0
- package/dist/daemon/address.js +71 -0
- package/dist/daemon/audit.d.ts +107 -0
- package/dist/daemon/audit.d.ts.map +1 -0
- package/dist/daemon/audit.js +237 -0
- package/dist/daemon/calls.d.ts +84 -0
- package/dist/daemon/calls.d.ts.map +1 -0
- package/dist/daemon/calls.js +158 -0
- package/dist/daemon/config.d.ts +79 -0
- package/dist/daemon/config.d.ts.map +1 -0
- package/dist/daemon/config.js +170 -0
- package/dist/daemon/daemon.d.ts +43 -0
- package/dist/daemon/daemon.d.ts.map +1 -0
- package/dist/daemon/daemon.js +560 -0
- package/dist/daemon/event-bus.d.ts +49 -0
- package/dist/daemon/event-bus.d.ts.map +1 -0
- package/dist/daemon/event-bus.js +111 -0
- package/dist/daemon/links.d.ts +54 -0
- package/dist/daemon/links.d.ts.map +1 -0
- package/dist/daemon/links.js +118 -0
- package/dist/daemon/listener.d.ts +34 -0
- package/dist/daemon/listener.d.ts.map +1 -0
- package/dist/daemon/listener.js +132 -0
- package/dist/daemon/log-rotation.d.ts +82 -0
- package/dist/daemon/log-rotation.d.ts.map +1 -0
- package/dist/daemon/log-rotation.js +136 -0
- package/dist/daemon/pidfile.d.ts +38 -0
- package/dist/daemon/pidfile.d.ts.map +1 -0
- package/dist/daemon/pidfile.js +90 -0
- package/dist/daemon/policy.d.ts +22 -0
- package/dist/daemon/policy.d.ts.map +1 -0
- package/dist/daemon/policy.js +24 -0
- package/dist/daemon/registry.d.ts +19 -0
- package/dist/daemon/registry.d.ts.map +1 -0
- package/dist/daemon/registry.js +31 -0
- package/dist/daemon/rpc-server.d.ts +54 -0
- package/dist/daemon/rpc-server.d.ts.map +1 -0
- package/dist/daemon/rpc-server.js +203 -0
- package/dist/daemon/sessions.d.ts +86 -0
- package/dist/daemon/sessions.d.ts.map +1 -0
- package/dist/daemon/sessions.js +429 -0
- package/dist/daemon/socket-probe.d.ts +16 -0
- package/dist/daemon/socket-probe.d.ts.map +1 -0
- package/dist/daemon/socket-probe.js +28 -0
- package/dist/daemon/state-dir.d.ts +31 -0
- package/dist/daemon/state-dir.d.ts.map +1 -0
- package/dist/daemon/state-dir.js +43 -0
- package/dist/daemon/timers.d.ts +16 -0
- package/dist/daemon/timers.d.ts.map +1 -0
- package/dist/daemon/timers.js +12 -0
- package/dist/daemon/tls.d.ts +58 -0
- package/dist/daemon/tls.d.ts.map +1 -0
- package/dist/daemon/tls.js +79 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +162 -0
- package/dist/host-certificate.d.ts +8 -0
- package/dist/host-certificate.d.ts.map +1 -0
- package/dist/host-certificate.js +111 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/key-material.d.ts +14 -0
- package/dist/key-material.d.ts.map +1 -0
- package/dist/key-material.js +42 -0
- package/dist/link.d.ts +76 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +117 -0
- package/dist/mcp/connect-tool.d.ts +115 -0
- package/dist/mcp/connect-tool.d.ts.map +1 -0
- package/dist/mcp/connect-tool.js +396 -0
- package/dist/mcp/daemon-tools.d.ts +13 -0
- package/dist/mcp/daemon-tools.d.ts.map +1 -0
- package/dist/mcp/daemon-tools.js +29 -0
- package/dist/mcp/events-tool.d.ts +111 -0
- package/dist/mcp/events-tool.d.ts.map +1 -0
- package/dist/mcp/events-tool.js +322 -0
- package/dist/mcp/server.d.ts +58 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +538 -0
- package/dist/mcp/tool-mapping.d.ts +58 -0
- package/dist/mcp/tool-mapping.d.ts.map +1 -0
- package/dist/mcp/tool-mapping.js +143 -0
- package/dist/mcp/tool-namespace.d.ts +40 -0
- package/dist/mcp/tool-namespace.d.ts.map +1 -0
- package/dist/mcp/tool-namespace.js +65 -0
- package/dist/native-scheme.d.ts +59 -0
- package/dist/native-scheme.d.ts.map +1 -0
- package/dist/native-scheme.js +465 -0
- package/dist/output.d.ts +28 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +342 -0
- package/dist/package-version.d.ts +30 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +47 -0
- package/dist/qr-terminal.d.ts +13 -0
- package/dist/qr-terminal.d.ts.map +1 -0
- package/dist/qr-terminal.js +45 -0
- package/dist/rpc/client.d.ts +137 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/client.js +819 -0
- package/dist/scheme.d.ts +181 -0
- package/dist/scheme.d.ts.map +1 -0
- package/dist/scheme.js +321 -0
- package/dist/spki-pin.d.ts +3 -0
- package/dist/spki-pin.d.ts.map +1 -0
- package/dist/spki-pin.js +14 -0
- package/package.json +65 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps a `NamespacedTool` (daemon `ToolDescriptor` + resolved external name) onto the MCP SDK's
|
|
3
|
+
* `Tool` wire shape (ARCHITECTURE.md §9/§7): `description` maps directly and `annotations` map
|
|
4
|
+
* verbatim, while `input_schema`/`output_schema` are emitted only if the SDK's own `ToolSchema`
|
|
5
|
+
* accepts them (issue #26) — a rejected `input_schema` is replaced with an empty, permissive
|
|
6
|
+
* object schema and a rejected `output_schema` is dropped entirely. Either way the tool stays
|
|
7
|
+
* listed and callable: one app-side schema MCP cannot represent must never take down the whole
|
|
8
|
+
* `tools/list`.
|
|
9
|
+
*
|
|
10
|
+
* The gate is the SDK schema itself rather than a hand-rolled `type === "object"` check, because
|
|
11
|
+
* MCP constrains more than the root type — `properties` must be a record of *object* subschemas
|
|
12
|
+
* (the JSON Schema shorthand `properties: { a: true }` is rejected) and `required` must be an
|
|
13
|
+
* array. Any of those makes a client reject the entire `tools/list` result, so the only safe
|
|
14
|
+
* predicate is the one the client will actually apply.
|
|
15
|
+
*
|
|
16
|
+
* A tool whose effective policy is `"prompt"` (ARCHITECTURE.md §12) gets
|
|
17
|
+
* `_meta["anthropic/requiresUserInteraction"] = true` — but only when the caller says to
|
|
18
|
+
* (`emitRequiresUserInteractionFlag`), which `mcp/server.ts` decides is true only when the
|
|
19
|
+
* connected client is known to enforce the flag (issue #14) *and* it hasn't already preferred the
|
|
20
|
+
* elicitation channel instead (issue #10). Emitting the flag for a client that ignores it would
|
|
21
|
+
* create a false sense of security; emitting it *and* using elicitation for the same tool would
|
|
22
|
+
* arm two consent prompts for one call — so this function never decides that itself, only renders
|
|
23
|
+
* the decision it's handed.
|
|
24
|
+
*/
|
|
25
|
+
import { ToolSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
26
|
+
/** MCP requires `inputSchema.type === "object"`; an app that registered a tool without a schema
|
|
27
|
+
* gets the most permissive possible one rather than an MCP-invalid `{}`. */
|
|
28
|
+
const EMPTY_OBJECT_SCHEMA = { type: "object", additionalProperties: true };
|
|
29
|
+
/**
|
|
30
|
+
* Why the SDK's `ToolSchema` would reject `schema` in this slot, or `undefined` when it accepts
|
|
31
|
+
* it. The probe wraps the schema in an otherwise-minimal valid `Tool` so the only thing under test
|
|
32
|
+
* is the schema; the returned reason is the first issue, with the leading `inputSchema`/
|
|
33
|
+
* `outputSchema` path segment stripped so the message reads from the app author's point of view.
|
|
34
|
+
*/
|
|
35
|
+
const mcpSchemaRejection = (schema, slot) => {
|
|
36
|
+
const probe = slot === "input"
|
|
37
|
+
? { name: "probe", inputSchema: schema }
|
|
38
|
+
: { name: "probe", inputSchema: EMPTY_OBJECT_SCHEMA, outputSchema: schema };
|
|
39
|
+
const parsed = ToolSchema.safeParse(probe);
|
|
40
|
+
if (parsed.success) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const issue = parsed.error.issues[0];
|
|
44
|
+
if (!issue) {
|
|
45
|
+
return "it does not match MCP's tool schema";
|
|
46
|
+
}
|
|
47
|
+
const path = issue.path.slice(1).join(".");
|
|
48
|
+
return path.length > 0 ? `${path}: ${issue.message}` : issue.message;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Whether `tools/list` will carry an `outputSchema` for this descriptor. `server.ts`'s success
|
|
52
|
+
* path shares this predicate so the "did we advertise a schema?" and "must this result carry
|
|
53
|
+
* `structuredContent`?" decisions can never disagree — a client demands the latter exactly when
|
|
54
|
+
* it saw the former.
|
|
55
|
+
*/
|
|
56
|
+
export const emitsMcpOutputSchema = (schema) => {
|
|
57
|
+
return schema !== undefined && mcpSchemaRejection(schema, "output") === undefined;
|
|
58
|
+
};
|
|
59
|
+
/** stderr only — stdout carries the MCP transport's protocol frames (ARCHITECTURE.md §9). */
|
|
60
|
+
const defaultWarn = (message) => {
|
|
61
|
+
console.error(`appduct mcp: ${message}`);
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Upper bound on remembered notices. A key embeds the offending schema, and an app is free to
|
|
65
|
+
* build a schema from live data (a `z.enum` over rows it just fetched), so an unbounded set would
|
|
66
|
+
* grow with every distinct broken schema for the life of the process. Past the cap the oldest key
|
|
67
|
+
* is evicted, which at worst re-prints a notice the operator has already seen.
|
|
68
|
+
*/
|
|
69
|
+
const MAX_REMEMBERED_NOTICES = 256;
|
|
70
|
+
/**
|
|
71
|
+
* Builds a mapper with its own degradation-notice dedup, so nothing here is module state and no
|
|
72
|
+
* reset hook has to be exported for tests: a server owns one mapper for its lifetime, and a test
|
|
73
|
+
* owns one per case. `warn` defaults to stderr and exists so a test can capture notices without
|
|
74
|
+
* spying on the console.
|
|
75
|
+
*
|
|
76
|
+
* Notices dedupe on session + tool name + the offending schema, because `tools/list` is
|
|
77
|
+
* re-answered on every client request and on every `notifications/tools/list_changed` refresh. Two
|
|
78
|
+
* devices exposing the same broken tool each get their own notice, and re-registering a tool with
|
|
79
|
+
* a *differently* broken schema warns again; only the same problem on the same tool of the same
|
|
80
|
+
* session stays quiet. The session's `selector` is the key rather than the namespaced `mcpName` so
|
|
81
|
+
* the single↔multi session flip, which rewrites every `mcpName`, does not re-warn about tools
|
|
82
|
+
* nothing changed about.
|
|
83
|
+
*/
|
|
84
|
+
export const createMcpToolMapper = (warn = defaultWarn) => {
|
|
85
|
+
const warned = new Set();
|
|
86
|
+
const warnOnce = (tool, slot, schema, message) => {
|
|
87
|
+
const key = `${tool.selector}\u0000${tool.descriptor.name}\u0000${slot}\u0000${JSON.stringify(schema)}`;
|
|
88
|
+
if (warned.has(key)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// A `Set` iterates in insertion order, so the first key is the oldest.
|
|
92
|
+
if (warned.size >= MAX_REMEMBERED_NOTICES) {
|
|
93
|
+
const oldest = warned.values().next();
|
|
94
|
+
if (!oldest.done) {
|
|
95
|
+
warned.delete(oldest.value);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
warned.add(key);
|
|
99
|
+
warn(message);
|
|
100
|
+
};
|
|
101
|
+
const mapInputSchema = (tool) => {
|
|
102
|
+
const schema = tool.descriptor.input_schema;
|
|
103
|
+
if (schema === undefined) {
|
|
104
|
+
return EMPTY_OBJECT_SCHEMA;
|
|
105
|
+
}
|
|
106
|
+
const rejection = mcpSchemaRejection(schema, "input");
|
|
107
|
+
if (rejection === undefined) {
|
|
108
|
+
return schema;
|
|
109
|
+
}
|
|
110
|
+
warnOnce(tool, "input", schema, `Tool "${tool.mcpName}" declares an input schema MCP cannot accept (${rejection}); MCP tool ` +
|
|
111
|
+
"arguments are always an object, so the schema was replaced with a permissive empty object " +
|
|
112
|
+
"schema and agents cannot see the tool's real arguments. Wrap the input in an object schema.");
|
|
113
|
+
return EMPTY_OBJECT_SCHEMA;
|
|
114
|
+
};
|
|
115
|
+
const mapOutputSchema = (tool) => {
|
|
116
|
+
const schema = tool.descriptor.output_schema;
|
|
117
|
+
if (schema === undefined) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const rejection = mcpSchemaRejection(schema, "output");
|
|
121
|
+
if (rejection === undefined) {
|
|
122
|
+
return schema;
|
|
123
|
+
}
|
|
124
|
+
warnOnce(tool, "output", schema, `Tool "${tool.mcpName}" declares an output schema MCP cannot accept (${rejection}); it was ` +
|
|
125
|
+
"dropped from tools/list, so agents get the result without a schema to validate it against. " +
|
|
126
|
+
"Wrap the output in an object schema to give agents a described, structured result.");
|
|
127
|
+
return undefined;
|
|
128
|
+
};
|
|
129
|
+
return (tool, emitRequiresUserInteractionFlag) => {
|
|
130
|
+
const { descriptor } = tool;
|
|
131
|
+
const requiresUserInteraction = tool.policy === "prompt" && emitRequiresUserInteractionFlag;
|
|
132
|
+
const outputSchema = mapOutputSchema(tool);
|
|
133
|
+
return {
|
|
134
|
+
name: tool.mcpName,
|
|
135
|
+
description: descriptor.description,
|
|
136
|
+
inputSchema: mapInputSchema(tool),
|
|
137
|
+
...(outputSchema ? { outputSchema } : {}),
|
|
138
|
+
...(descriptor.annotations ? { annotations: { ...descriptor.annotations } } : {}),
|
|
139
|
+
// Must be the JSON boolean `true` literal — any other value is ignored by the client.
|
|
140
|
+
...(requiresUserInteraction ? { _meta: { "anthropic/requiresUserInteraction": true } } : {}),
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the effective MCP tool list from live daemon sessions and their tool registries
|
|
3
|
+
* (ARCHITECTURE.md §9): exactly one live session exposes tools under their
|
|
4
|
+
* own names; two or more namespace every tool as `<alias>__<name>` so names never collide across
|
|
5
|
+
* devices.
|
|
6
|
+
*
|
|
7
|
+
* The `__` separator is unambiguous to split back apart even though a tool's own name
|
|
8
|
+
* (`[a-zA-Z0-9_-]{1,64}`, `daemon/registry.ts`) may itself contain underscores: aliases
|
|
9
|
+
* (`daemon/sessions.ts`'s `slugifyDeviceModel`/`dedupeAlias`) only ever contain lowercase
|
|
10
|
+
* alphanumerics and hyphens, so the alias half of a namespaced name never itself contains `__` —
|
|
11
|
+
* the first occurrence of `__` in a namespaced name is always the alias/tool-name boundary.
|
|
12
|
+
*/
|
|
13
|
+
import type { EffectivePolicyDecision, SessionSummary, ToolDescriptor, ToolsListEntry } from "@appduct/shared";
|
|
14
|
+
export declare const TOOL_NAMESPACE_SEPARATOR = "__";
|
|
15
|
+
export type NamespacedTool = {
|
|
16
|
+
/** The name exposed to MCP clients: `descriptor.name` verbatim when exactly one session is live,
|
|
17
|
+
* else `<alias>__<name>`. */
|
|
18
|
+
mcpName: string;
|
|
19
|
+
/** The session alias `tools.call`'s `selector` should target for this tool. */
|
|
20
|
+
selector: string;
|
|
21
|
+
descriptor: ToolDescriptor;
|
|
22
|
+
/** The effective policy decision for this tool right now (ARCHITECTURE.md §12), as resolved by
|
|
23
|
+
* the daemon's `tools.list` — carried through so the MCP server can emit
|
|
24
|
+
* `_meta["anthropic/requiresUserInteraction"]` and set `consent` on `tools.call` without a
|
|
25
|
+
* second round trip (issue #14). */
|
|
26
|
+
policy: EffectivePolicyDecision;
|
|
27
|
+
};
|
|
28
|
+
/** Builds the namespaced (or not) tool list for the current set of live sessions. `toolsByAlias`
|
|
29
|
+
* is looked up by `session.alias` (not `sessionId`) to match `tools.list`'s `selector` semantics. */
|
|
30
|
+
export declare const buildNamespacedTools: (sessions: readonly SessionSummary[], toolsByAlias: ReadonlyMap<string, readonly ToolsListEntry[]>) => NamespacedTool[];
|
|
31
|
+
export declare const findNamespacedTool: (tools: readonly NamespacedTool[], mcpName: string) => NamespacedTool | undefined;
|
|
32
|
+
/** A stable, order-independent key used to detect whether the effective tool list actually
|
|
33
|
+
* changed (name, target session, and the descriptor fields a client can see) — used to decide
|
|
34
|
+
* whether to fire `notifications/tools/list_changed` rather than on every qualifying daemon event.
|
|
35
|
+
*
|
|
36
|
+
* `timeout_ms` is deliberately excluded: it never reaches the MCP `Tool` JSON (`tool-mapping.ts`),
|
|
37
|
+
* so an app re-registering a tool with only a different deadline changes nothing a client could
|
|
38
|
+
* observe, and telling it the list changed would just make it re-fetch an identical list. */
|
|
39
|
+
export declare const namespacedToolsSnapshotKey: (tools: readonly NamespacedTool[]) => string;
|
|
40
|
+
//# sourceMappingURL=tool-namespace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-namespace.d.ts","sourceRoot":"","sources":["../../src/mcp/tool-namespace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE/G,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C,MAAM,MAAM,cAAc,GAAG;IAC3B;iCAC6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,cAAc,CAAC;IAC3B;;;wCAGoC;IACpC,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC;AAEF;qGACqG;AACrG,eAAO,MAAM,oBAAoB,GAC/B,UAAU,SAAS,cAAc,EAAE,EACnC,cAAc,WAAW,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC,KAC3D,cAAc,EA4BhB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,OAAO,SAAS,cAAc,EAAE,EAChC,SAAS,MAAM,KACd,cAAc,GAAG,SAEnB,CAAC;AAEF;;;;;;6FAM6F;AAC7F,eAAO,MAAM,0BAA0B,GAAI,OAAO,SAAS,cAAc,EAAE,KAAG,MAe7E,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the effective MCP tool list from live daemon sessions and their tool registries
|
|
3
|
+
* (ARCHITECTURE.md §9): exactly one live session exposes tools under their
|
|
4
|
+
* own names; two or more namespace every tool as `<alias>__<name>` so names never collide across
|
|
5
|
+
* devices.
|
|
6
|
+
*
|
|
7
|
+
* The `__` separator is unambiguous to split back apart even though a tool's own name
|
|
8
|
+
* (`[a-zA-Z0-9_-]{1,64}`, `daemon/registry.ts`) may itself contain underscores: aliases
|
|
9
|
+
* (`daemon/sessions.ts`'s `slugifyDeviceModel`/`dedupeAlias`) only ever contain lowercase
|
|
10
|
+
* alphanumerics and hyphens, so the alias half of a namespaced name never itself contains `__` —
|
|
11
|
+
* the first occurrence of `__` in a namespaced name is always the alias/tool-name boundary.
|
|
12
|
+
*/
|
|
13
|
+
export const TOOL_NAMESPACE_SEPARATOR = "__";
|
|
14
|
+
/** Builds the namespaced (or not) tool list for the current set of live sessions. `toolsByAlias`
|
|
15
|
+
* is looked up by `session.alias` (not `sessionId`) to match `tools.list`'s `selector` semantics. */
|
|
16
|
+
export const buildNamespacedTools = (sessions, toolsByAlias) => {
|
|
17
|
+
const namespaced = sessions.length > 1;
|
|
18
|
+
const tools = [];
|
|
19
|
+
for (const session of sessions) {
|
|
20
|
+
for (const entry of toolsByAlias.get(session.alias) ?? []) {
|
|
21
|
+
// Explicit pick (not a `{ policy, ...descriptor }` rest-spread) so a future non-descriptor
|
|
22
|
+
// field added to `ToolsListEntry` can't silently leak into `descriptor` and from there into
|
|
23
|
+
// the MCP tool surface and the snapshot key below.
|
|
24
|
+
const descriptor = {
|
|
25
|
+
name: entry.name,
|
|
26
|
+
description: entry.description,
|
|
27
|
+
input_schema: entry.input_schema,
|
|
28
|
+
output_schema: entry.output_schema,
|
|
29
|
+
annotations: entry.annotations,
|
|
30
|
+
timeout_ms: entry.timeout_ms,
|
|
31
|
+
};
|
|
32
|
+
tools.push({
|
|
33
|
+
mcpName: namespaced ? `${session.alias}${TOOL_NAMESPACE_SEPARATOR}${descriptor.name}` : descriptor.name,
|
|
34
|
+
selector: session.alias,
|
|
35
|
+
descriptor,
|
|
36
|
+
policy: entry.policy,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return tools;
|
|
41
|
+
};
|
|
42
|
+
export const findNamespacedTool = (tools, mcpName) => {
|
|
43
|
+
return tools.find((tool) => tool.mcpName === mcpName);
|
|
44
|
+
};
|
|
45
|
+
/** A stable, order-independent key used to detect whether the effective tool list actually
|
|
46
|
+
* changed (name, target session, and the descriptor fields a client can see) — used to decide
|
|
47
|
+
* whether to fire `notifications/tools/list_changed` rather than on every qualifying daemon event.
|
|
48
|
+
*
|
|
49
|
+
* `timeout_ms` is deliberately excluded: it never reaches the MCP `Tool` JSON (`tool-mapping.ts`),
|
|
50
|
+
* so an app re-registering a tool with only a different deadline changes nothing a client could
|
|
51
|
+
* observe, and telling it the list changed would just make it re-fetch an identical list. */
|
|
52
|
+
export const namespacedToolsSnapshotKey = (tools) => {
|
|
53
|
+
const sorted = tools
|
|
54
|
+
.map((tool) => {
|
|
55
|
+
const { timeout_ms: _timeoutMs, ...clientVisibleDescriptor } = tool.descriptor;
|
|
56
|
+
return {
|
|
57
|
+
name: tool.mcpName,
|
|
58
|
+
selector: tool.selector,
|
|
59
|
+
descriptor: clientVisibleDescriptor,
|
|
60
|
+
policy: tool.policy,
|
|
61
|
+
};
|
|
62
|
+
})
|
|
63
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
64
|
+
return JSON.stringify(sorted);
|
|
65
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static-file scheme discovery for plain iOS (Xcode) and Android (Gradle) apps
|
|
3
|
+
* (docs/tasks/20-cli-native-scheme-discovery.md, phase 3 of issue #48).
|
|
4
|
+
*
|
|
5
|
+
* `scheme.ts`'s `resolveScheme` treats this module as one more thing to try after `<cwd>/app.json`
|
|
6
|
+
* — it never walks up (an app root is where these commands run, same rule as `app.json`), and,
|
|
7
|
+
* like every other source in that file, **nothing here executes project code**: no `plutil`, no
|
|
8
|
+
* `xcodebuild`, no Gradle evaluation. Every probe is a plain read of a well-known project file,
|
|
9
|
+
* parsed defensively:
|
|
10
|
+
*
|
|
11
|
+
* - Android: `app/build.gradle.kts` / `app/build.gradle` for a `manifestPlaceholders["appductScheme"]`
|
|
12
|
+
* (or `.appductScheme =`) assignment, then `app/src/main/AndroidManifest.xml` for the first
|
|
13
|
+
* `<data android:scheme>` inside an intent filter that also declares
|
|
14
|
+
* `android.intent.action.VIEW`.
|
|
15
|
+
* - iOS: any `Info.plist` up to two levels below the app root (excluding `Pods`, `build`,
|
|
16
|
+
* `node_modules` and `DerivedData` — vendored/build-output trees that are not the project's own
|
|
17
|
+
* config, and can be enormous) for the first `CFBundleURLSchemes` entry, then `project.yml`
|
|
18
|
+
* (xcodegen) for the same key under `info.properties.CFBundleURLTypes`.
|
|
19
|
+
*
|
|
20
|
+
* A binary-encoded `Info.plist` (Xcode can write either encoding) is reported as unreadable rather
|
|
21
|
+
* than decoded — `plutil -convert xml1` would work, but that is exactly the "run a tool against
|
|
22
|
+
* project state" this module exists to avoid, so a binary plist just falls through to `--scheme`
|
|
23
|
+
* like any other file this can't make sense of.
|
|
24
|
+
*
|
|
25
|
+
* **Disagreement is not guessed away.** If more than one probe below resolves to a *different*
|
|
26
|
+
* scheme (e.g. a monorepo whose `android/` and `ios/` declare different values), discovery throws
|
|
27
|
+
* a usage error naming every conflicting source instead of picking one — silently choosing would
|
|
28
|
+
* mean `appduct link` opens the wrong app on whichever platform lost.
|
|
29
|
+
*/
|
|
30
|
+
export type NativeSchemeSource = "android-gradle" | "android-manifest" | "ios-info-plist" | "ios-project-yml";
|
|
31
|
+
export type NativeSchemeProbeResult = {
|
|
32
|
+
source: NativeSchemeSource;
|
|
33
|
+
scheme: string;
|
|
34
|
+
/** Human-readable path (plus what was read from it), for disagreement errors and `init`'s hint. */
|
|
35
|
+
origin: string;
|
|
36
|
+
};
|
|
37
|
+
export type NativeSchemeDiscovery = {
|
|
38
|
+
/** `undefined` when no probe resolved a scheme. */
|
|
39
|
+
result?: NativeSchemeProbeResult;
|
|
40
|
+
/** Every probe's human-readable description, in order, for {@link describeMissingScheme}-style
|
|
41
|
+
* reporting — always as many entries as there are probes, whether or not any of them hit. */
|
|
42
|
+
tried: string[];
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Runs every static-file probe against `root` (never walking up — same rule as `app.json`) and
|
|
46
|
+
* decides the result:
|
|
47
|
+
*
|
|
48
|
+
* - No probe resolves a scheme: `result` is `undefined`.
|
|
49
|
+
* - Every probe that resolves one agrees: that scheme wins, attributed to whichever probe ran
|
|
50
|
+
* first (the order in {@link PROBES}).
|
|
51
|
+
* - Two probes resolve *different* schemes: throws a usage error naming both sources and their
|
|
52
|
+
* values, since guessing which one is right would silently target the wrong app on one
|
|
53
|
+
* platform.
|
|
54
|
+
*
|
|
55
|
+
* `tried` always has one entry per probe, hit or miss, so a "no scheme anywhere" error can list
|
|
56
|
+
* every location this looked without the caller needing to know which probes exist.
|
|
57
|
+
*/
|
|
58
|
+
export declare const discoverNativeScheme: (root: string) => Promise<NativeSchemeDiscovery>;
|
|
59
|
+
//# sourceMappingURL=native-scheme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-scheme.d.ts","sourceRoot":"","sources":["../src/native-scheme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAQH,MAAM,MAAM,kBAAkB,GAC1B,gBAAgB,GAChB,kBAAkB,GAClB,gBAAgB,GAChB,iBAAiB,CAAC;AAEtB,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,mDAAmD;IACnD,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC;iGAC6F;IAC7F,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAihBF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,qBAAqB,CA4BtF,CAAC"}
|