@ziggs-ai/ziggs-mcp 0.9.11 → 0.9.12

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.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ZIG-1362 — call a brokered MCP connection from the ziggs-mcp surface.
3
+ *
4
+ * Same door the SDK's `mcp_tool_call` / `mcp_tools_list` use
5
+ * (`POST /connections/:id/mcp`). `ziggs_connection_proxy` cannot: it resolves
6
+ * a named REST connector from `connection.provider`, and MCP rows are
7
+ * `provider: 'mcp'`.
8
+ */
9
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
10
+ import { type Creds } from '@ziggs-ai/api-client';
11
+ export declare function resolveMcpGatewayTarget(opts: {
12
+ connectionId: string;
13
+ grantId: string;
14
+ operatorKey: string;
15
+ agentId: string;
16
+ baseUrl?: string;
17
+ }): {
18
+ url: URL;
19
+ headers: Record<string, string>;
20
+ };
21
+ export declare function resolveMcpConnectionTarget(creds: Creds, connectionId?: string, grantId?: string): Promise<{
22
+ connectionId: string;
23
+ grantId: string;
24
+ }>;
25
+ /** Test seam — production always uses the StreamableHTTP client below. */
26
+ export type McpGatewayRunner = <T>(target: {
27
+ url: URL;
28
+ headers: Record<string, string>;
29
+ }, run: (client: Client) => Promise<T>) => Promise<T>;
30
+ /** @internal tests only */
31
+ export declare function setMcpGatewayRunnerForTests(runner: McpGatewayRunner | null): void;
32
+ export declare function withMcpGatewayClient<T>(creds: Creds, connectionId: string, grantId: string, run: (client: Client) => Promise<T>): Promise<T>;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * ZIG-1362 — call a brokered MCP connection from the ziggs-mcp surface.
3
+ *
4
+ * Same door the SDK's `mcp_tool_call` / `mcp_tools_list` use
5
+ * (`POST /connections/:id/mcp`). `ziggs_connection_proxy` cannot: it resolves
6
+ * a named REST connector from `connection.provider`, and MCP rows are
7
+ * `provider: 'mcp'`.
8
+ */
9
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
10
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
11
+ import { ConnectionsClient, getBackendUrl, } from '@ziggs-ai/api-client';
12
+ export function resolveMcpGatewayTarget(opts) {
13
+ if (!opts.connectionId)
14
+ throw new Error('connectionId is required');
15
+ if (!opts.grantId)
16
+ throw new Error('grantId is required');
17
+ if (!opts.operatorKey)
18
+ throw new Error('operatorKey is required');
19
+ if (!opts.agentId)
20
+ throw new Error('agentId is required');
21
+ const baseUrl = (opts.baseUrl || getBackendUrl()).replace(/\/$/, '');
22
+ return {
23
+ url: new URL(`${baseUrl}/connections/${encodeURIComponent(opts.connectionId)}/mcp`),
24
+ headers: {
25
+ authorization: `Bearer ${opts.operatorKey}`,
26
+ 'x-agent-id': opts.agentId,
27
+ 'x-grant-id': opts.grantId,
28
+ },
29
+ };
30
+ }
31
+ export async function resolveMcpConnectionTarget(creds, connectionId, grantId) {
32
+ if (connectionId && grantId)
33
+ return { connectionId, grantId };
34
+ if (!creds.operatorKey)
35
+ throw new Error('operatorKey is required');
36
+ if (!creds.agentId)
37
+ throw new Error('agentId is required');
38
+ const groups = await new ConnectionsClient(creds.operatorKey, creds.agentId).listForHolder();
39
+ const live = groups.filter((g) => (g.grants ?? []).length > 0);
40
+ if (live.length === 0) {
41
+ throw new Error('No live connection grant. Ask the connection owner to issue one for this agent, then retry.');
42
+ }
43
+ const chosen = connectionId
44
+ ? live.find((g) => g.connectionId === connectionId)
45
+ : live.length === 1
46
+ ? live[0]
47
+ : undefined;
48
+ if (!chosen) {
49
+ throw new Error(`Name the connection to use — grants exist on ${live
50
+ .map((g) => g.connectionId)
51
+ .join(', ')}`);
52
+ }
53
+ const grant = grantId
54
+ ? (chosen.grants ?? []).find((g) => g.grantId === grantId)
55
+ : (chosen.grants ?? [])[0];
56
+ if (!grant) {
57
+ throw new Error(`No live grant on ${chosen.connectionId} for this agent`);
58
+ }
59
+ return { connectionId: chosen.connectionId, grantId: grant.grantId };
60
+ }
61
+ const defaultRunner = async (target, run) => {
62
+ const transport = new StreamableHTTPClientTransport(target.url, {
63
+ requestInit: { headers: target.headers },
64
+ });
65
+ const client = new Client({ name: 'ziggs-mcp', version: '1.0.0' });
66
+ try {
67
+ await client.connect(transport);
68
+ return await run(client);
69
+ }
70
+ finally {
71
+ await client.close().catch(() => undefined);
72
+ }
73
+ };
74
+ let gatewayRunner = defaultRunner;
75
+ /** @internal tests only */
76
+ export function setMcpGatewayRunnerForTests(runner) {
77
+ gatewayRunner = runner ?? defaultRunner;
78
+ }
79
+ export async function withMcpGatewayClient(creds, connectionId, grantId, run) {
80
+ if (!creds.operatorKey || !creds.agentId) {
81
+ throw new Error('operatorKey and agentId are required');
82
+ }
83
+ const target = resolveMcpGatewayTarget({
84
+ connectionId,
85
+ grantId,
86
+ operatorKey: creds.operatorKey,
87
+ agentId: creds.agentId,
88
+ });
89
+ return gatewayRunner(target, run);
90
+ }
package/dist/tools.js CHANGED
@@ -7,6 +7,7 @@ import { registerPaymentTools } from './paymentTools.js';
7
7
  import { formatInboxToolResult, buildReadContextReadPlan, } from './inboxToolResult.js';
8
8
  import { agreementAppUrl, filterTasksForDelegate, formatPendingDecisionsPayload, resolveWebAppOrigin, buildPendingNextActions, } from './pendingDecisions.js';
9
9
  import { PROTOCOL } from './protocol/delegateProtocol.js';
10
+ import { resolveMcpConnectionTarget, withMcpGatewayClient, } from './mcpConnectionTools.js';
10
11
  const RELAY_COORDINATOR_AGENT_ID = 'relay-coordinator';
11
12
  function buildRelayCoordinatorTaskBody(opts) {
12
13
  const title = opts.title?.trim() || 'Relay coordinator job';
@@ -188,9 +189,10 @@ function registerMarketplaceTools(server, creds) {
188
189
  }
189
190
  function registerConnectionTools(server, creds) {
190
191
  registerCapability(server, connectionProxyCapability, creds);
191
- registerStrictTool(server, 'ziggs_connection_list', 'Discover the third-party connections (credentials like GitHub/Jira, NOT agent-to-agent Links see ziggs_link_list for that) you hold grants for (e.g. "is GitHub connected?") without the owner sharing connectionId/grantId out of band. ' +
192
+ registerStrictTool(server, 'ziggs_connection_list', 'Discover the third-party connections (credentials like GitHub/Jira, and remote MCP servers — NOT agent-to-agent Links; see ziggs_link_list for that) you hold grants for, without the owner sharing connectionId/grantId out of band. ' +
192
193
  'Returns, per connection: connectionId, provider, and the grant(s) you hold — each as the canonical grant shape (grantId, scope, caveats, and grant health active/expired/revoked). ' +
193
- 'Read-only — never returns credential material. Feed the connectionId + a grantId with health "active" into ziggs_connection_proxy to actually use it.', {}, READ_ONLY, async () => {
194
+ 'Read-only — never returns credential material. ' +
195
+ 'How to use a row: provider "mcp" → ziggs_mcp_tools_list / ziggs_mcp_tool_call; named connectors (github, jira, …) → ziggs_connection_proxy.', {}, READ_ONLY, async () => {
194
196
  try {
195
197
  const connections = await new ConnectionsClient(creds.operatorKey, creds.agentId).listForHolder();
196
198
  return textResult({ connections });
@@ -199,6 +201,75 @@ function registerConnectionTools(server, creds) {
199
201
  return toolError(e);
200
202
  }
201
203
  });
204
+ // ZIG-1362 — brokered MCP connections (provider: mcp). Not connection_proxy.
205
+ registerStrictTool(server, 'ziggs_mcp_tools_list', "List the tools a connected MCP server exposes, through the Ziggs gateway — the owner's credential stays server-side. " +
206
+ 'The grant may allow only some of them; a call outside the grant is refused by the gateway. ' +
207
+ 'Omit connectionId/grantId when you hold a grant on exactly one connection.', {
208
+ connectionId: z
209
+ .string()
210
+ .optional()
211
+ .describe('Connection to inspect. Omit when you hold a grant on exactly one.'),
212
+ grantId: z
213
+ .string()
214
+ .optional()
215
+ .describe('Grant to use. Omit to use the live grant on that connection.'),
216
+ }, READ_ONLY, async ({ connectionId, grantId }) => {
217
+ try {
218
+ const t = await resolveMcpConnectionTarget(creds, connectionId, grantId);
219
+ const tools = await withMcpGatewayClient(creds, t.connectionId, t.grantId, async (c) => {
220
+ const res = await c.listTools();
221
+ return (res.tools ?? []).map((tool) => ({
222
+ name: tool.name,
223
+ description: tool.description ?? null,
224
+ }));
225
+ });
226
+ return textResult({
227
+ connectionId: t.connectionId,
228
+ grantId: t.grantId,
229
+ tools,
230
+ });
231
+ }
232
+ catch (e) {
233
+ return toolError(e);
234
+ }
235
+ });
236
+ registerStrictTool(server, 'ziggs_mcp_tool_call', 'Call one tool on a connected MCP server through the Ziggs gateway. ' +
237
+ 'Use this when the connection provider is a remote MCP server — ziggs_connection_proxy is for named connectors (github, jira) and refuses MCP with "Unknown provider: mcp". ' +
238
+ "The gateway injects the owner's token and checks the tool name against your grant. Discover names with ziggs_mcp_tools_list.", {
239
+ tool: z
240
+ .string()
241
+ .describe('Tool name on the connected MCP server, e.g. list_issues.'),
242
+ args: z
243
+ .record(z.unknown())
244
+ .optional()
245
+ .describe("Arguments for that tool, as the server's schema defines them."),
246
+ connectionId: z
247
+ .string()
248
+ .optional()
249
+ .describe('Connection to call through. Omit when you hold a grant on exactly one.'),
250
+ grantId: z
251
+ .string()
252
+ .optional()
253
+ .describe('Grant to use. Omit to use the live grant on that connection.'),
254
+ }, WRITE, async ({ tool, args, connectionId, grantId }) => {
255
+ try {
256
+ if (!tool)
257
+ throw new Error('tool is required');
258
+ const t = await resolveMcpConnectionTarget(creds, connectionId, grantId);
259
+ const result = await withMcpGatewayClient(creds, t.connectionId, t.grantId, (c) => c.callTool({
260
+ name: tool,
261
+ arguments: args ?? {},
262
+ }));
263
+ return textResult({
264
+ connectionId: t.connectionId,
265
+ tool,
266
+ result,
267
+ });
268
+ }
269
+ catch (e) {
270
+ return toolError(e);
271
+ }
272
+ });
202
273
  registerCapability(server, requestConnectionCapability, creds);
203
274
  }
204
275
  export function registerZiggsTools(server, creds, cfg) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/ziggs-mcp",
3
- "version": "0.9.11",
3
+ "version": "0.9.12",
4
4
  "description": "MCP server for Claude Code, Cursor, and other MCP hosts \u2014 act as your Ziggs delegate agent",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@modelcontextprotocol/sdk": "^1.29.0",
41
- "@ziggs-ai/api-client": "0.9.11",
41
+ "@ziggs-ai/api-client": "0.9.12",
42
42
  "dotenv": "^16.6.1",
43
43
  "zod": "^3.24.2"
44
44
  },