@datacules/agent-identity-mcp-client 0.10.0 → 0.11.1
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 +109 -0
- package/dist/cjs/caller.js +101 -0
- package/dist/cjs/caller.js.map +1 -0
- package/dist/cjs/index.js +52 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/store.js +152 -0
- package/dist/cjs/store.js.map +1 -0
- package/dist/esm/caller.js +97 -0
- package/dist/esm/caller.js.map +1 -0
- package/dist/esm/index.js +47 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/store.js +148 -0
- package/dist/esm/store.js.map +1 -0
- package/dist/types/caller.d.ts +58 -0
- package/dist/types/caller.d.ts.map +1 -0
- package/{src/index.ts → dist/types/index.d.ts} +1 -1
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/store.d.ts +89 -0
- package/dist/types/store.d.ts.map +1 -0
- package/package.json +22 -3
- package/src/caller.ts +0 -150
- package/src/mcp-client.test.ts +0 -201
- package/src/store.ts +0 -217
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -10
package/src/mcp-client.test.ts
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// Mock MCP SDK modules — the lazy-connect pattern means these are only imported
|
|
4
|
-
// at the module level but never called at runtime if we inject mock clients directly.
|
|
5
|
-
vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
|
|
6
|
-
Client: vi.fn(() => ({ callTool: vi.fn(), close: vi.fn(), connect: vi.fn() })),
|
|
7
|
-
}));
|
|
8
|
-
vi.mock('@modelcontextprotocol/sdk/client/sse.js', () => ({
|
|
9
|
-
SSEClientTransport: vi.fn(),
|
|
10
|
-
}));
|
|
11
|
-
vi.mock('@modelcontextprotocol/sdk/client/stdio.js', () => ({
|
|
12
|
-
StdioClientTransport: vi.fn(),
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
import { McpCredentialStore } from './store.js';
|
|
16
|
-
import { McpToolCaller } from './caller.js';
|
|
17
|
-
import type { Credential } from '@datacules/agent-identity';
|
|
18
|
-
|
|
19
|
-
const makeCred = (overrides: Partial<Credential> = {}): Credential => ({
|
|
20
|
-
id: 'cred-openai',
|
|
21
|
-
kind: 'fixed',
|
|
22
|
-
name: 'OpenAI Key',
|
|
23
|
-
scope: 'global',
|
|
24
|
-
status: 'active',
|
|
25
|
-
provider: 'openai',
|
|
26
|
-
ref: 'openai-prod-slot',
|
|
27
|
-
...overrides,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
/** Build the content array shape that McpCredentialStore / McpToolCaller expect from callTool() */
|
|
31
|
-
const makeToolResult = (data: unknown) => ({
|
|
32
|
-
content: [{ type: 'text', text: JSON.stringify(data) }],
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// ─── McpCredentialStore ──────────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
describe('McpCredentialStore', () => {
|
|
38
|
-
let store: McpCredentialStore;
|
|
39
|
-
let mockCallTool: ReturnType<typeof vi.fn>;
|
|
40
|
-
let mockClose: ReturnType<typeof vi.fn>;
|
|
41
|
-
|
|
42
|
-
beforeEach(() => {
|
|
43
|
-
vi.clearAllMocks();
|
|
44
|
-
mockCallTool = vi.fn();
|
|
45
|
-
mockClose = vi.fn();
|
|
46
|
-
store = new McpCredentialStore({ transport: 'http', serverUrl: 'http://localhost:3002' });
|
|
47
|
-
// Inject a mock client — ensureConnected() checks `this.client` first, so _connect() is never called.
|
|
48
|
-
(store as any).client = { callTool: mockCallTool, close: mockClose };
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('listActive()', () => {
|
|
52
|
-
it('returns only active credentials from the MCP server list_credentials response', async () => {
|
|
53
|
-
const active = makeCred({ id: 'cred-active', ref: 'active-slot' });
|
|
54
|
-
const pending = makeCred({ id: 'cred-pending', ref: 'pending-slot', status: 'pending' });
|
|
55
|
-
mockCallTool.mockResolvedValue(makeToolResult({ credentials: [active, pending] }));
|
|
56
|
-
const results = await store.listActive();
|
|
57
|
-
expect(results).toHaveLength(1);
|
|
58
|
-
expect(results[0]).toEqual(active);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('caches results — calls list_credentials tool only once for two listActive() calls', async () => {
|
|
62
|
-
const cred = makeCred();
|
|
63
|
-
mockCallTool.mockResolvedValue(makeToolResult({ credentials: [cred] }));
|
|
64
|
-
await store.listActive();
|
|
65
|
-
await store.listActive();
|
|
66
|
-
// Second call hits cache; callTool should be called exactly once.
|
|
67
|
-
expect(mockCallTool).toHaveBeenCalledTimes(1);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('invalidateCache() forces a fresh fetch on the next listActive() call', async () => {
|
|
71
|
-
const cred = makeCred();
|
|
72
|
-
mockCallTool.mockResolvedValue(makeToolResult({ credentials: [cred] }));
|
|
73
|
-
await store.listActive();
|
|
74
|
-
store.invalidateCache();
|
|
75
|
-
await store.listActive();
|
|
76
|
-
// Cache was cleared; callTool should have been called twice.
|
|
77
|
-
expect(mockCallTool).toHaveBeenCalledTimes(2);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('throws with a non-JSON message when the server returns unparseable text', async () => {
|
|
81
|
-
mockCallTool.mockResolvedValue({ content: [{ type: 'text', text: 'not-json-at-all' }] });
|
|
82
|
-
await expect(store.listActive()).rejects.toThrow('non-JSON response');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('throws with a missing-credentials-array message when the response has no credentials field', async () => {
|
|
86
|
-
mockCallTool.mockResolvedValue(makeToolResult({ data: [] }));
|
|
87
|
-
await expect(store.listActive()).rejects.toThrow('missing credentials array');
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe('findByRef()', () => {
|
|
92
|
-
it('returns the matching active credential by ref', async () => {
|
|
93
|
-
const cred = makeCred({ ref: 'openai-prod-slot' });
|
|
94
|
-
mockCallTool.mockResolvedValue(makeToolResult({ credentials: [cred] }));
|
|
95
|
-
expect(await store.findByRef('openai-prod-slot')).toEqual(cred);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('returns null when the ref is not present in the server credential list', async () => {
|
|
99
|
-
const cred = makeCred({ ref: 'other-slot' });
|
|
100
|
-
mockCallTool.mockResolvedValue(makeToolResult({ credentials: [cred] }));
|
|
101
|
-
expect(await store.findByRef('missing-ref')).toBeNull();
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe('listByKind()', () => {
|
|
106
|
-
it('returns only credentials matching the requested kind', async () => {
|
|
107
|
-
const fixed = makeCred({ kind: 'fixed', id: 'cred-f', ref: 'fixed-slot' });
|
|
108
|
-
const delegated = makeCred({ kind: 'user-delegated', id: 'cred-u', ref: 'user-slot' });
|
|
109
|
-
mockCallTool.mockResolvedValue(makeToolResult({ credentials: [fixed, delegated] }));
|
|
110
|
-
const result = await store.listByKind('fixed');
|
|
111
|
-
expect(result).toHaveLength(1);
|
|
112
|
-
expect(result[0].kind).toBe('fixed');
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe('disconnect()', () => {
|
|
117
|
-
it('calls close() on the injected client and sets this.client to null', async () => {
|
|
118
|
-
await store.disconnect();
|
|
119
|
-
expect(mockClose).toHaveBeenCalled();
|
|
120
|
-
expect((store as any).client).toBeNull();
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// ─── McpToolCaller ────────────────────────────────────────────────────────────
|
|
126
|
-
|
|
127
|
-
describe('McpToolCaller', () => {
|
|
128
|
-
let caller: McpToolCaller;
|
|
129
|
-
let mockCallTool: ReturnType<typeof vi.fn>;
|
|
130
|
-
let mockClose: ReturnType<typeof vi.fn>;
|
|
131
|
-
|
|
132
|
-
beforeEach(() => {
|
|
133
|
-
vi.clearAllMocks();
|
|
134
|
-
mockCallTool = vi.fn();
|
|
135
|
-
mockClose = vi.fn();
|
|
136
|
-
caller = new McpToolCaller({ transport: 'http', serverUrl: 'http://localhost:3002' });
|
|
137
|
-
// Inject a mock client — ensureConnected() short-circuits on this.client
|
|
138
|
-
(caller as any).client = { callTool: mockCallTool, close: mockClose };
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('resolveCredential() calls the resolve_credential tool with forwarded args and returns parsed result', async () => {
|
|
142
|
-
const expected = { ok: true, credentialId: 'cred-1', kind: 'fixed', resolvedFor: 'service' };
|
|
143
|
-
mockCallTool.mockResolvedValue(makeToolResult(expected));
|
|
144
|
-
const result = await caller.resolveCredential({ userId: 'u1', provider: 'openai' });
|
|
145
|
-
expect(result).toEqual(expected);
|
|
146
|
-
expect(mockCallTool).toHaveBeenCalledWith({
|
|
147
|
-
name: 'resolve_credential',
|
|
148
|
-
arguments: { userId: 'u1', provider: 'openai' },
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('resolveMigrationCredential() calls the resolve_migration_credential tool and returns pair', async () => {
|
|
153
|
-
const expected = {
|
|
154
|
-
ok: true,
|
|
155
|
-
migrationId: 'mig-1',
|
|
156
|
-
source: { credentialId: 'src' },
|
|
157
|
-
target: { credentialId: 'tgt' },
|
|
158
|
-
expiresAt: null,
|
|
159
|
-
};
|
|
160
|
-
mockCallTool.mockResolvedValue(makeToolResult(expected));
|
|
161
|
-
const result = await caller.resolveMigrationCredential({ migrationId: 'mig-1' });
|
|
162
|
-
expect(result.migrationId).toBe('mig-1');
|
|
163
|
-
expect(mockCallTool).toHaveBeenCalledWith(
|
|
164
|
-
expect.objectContaining({ name: 'resolve_migration_credential' })
|
|
165
|
-
);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('health() calls the health tool and returns the status object', async () => {
|
|
169
|
-
const expected = {
|
|
170
|
-
status: 'ok',
|
|
171
|
-
credentialsLoaded: 5,
|
|
172
|
-
rulesLoaded: 3,
|
|
173
|
-
timestamp: new Date().toISOString(),
|
|
174
|
-
};
|
|
175
|
-
mockCallTool.mockResolvedValue(makeToolResult(expected));
|
|
176
|
-
const result = await caller.health();
|
|
177
|
-
expect(result.status).toBe('ok');
|
|
178
|
-
expect(mockCallTool).toHaveBeenCalledWith({ name: 'health', arguments: {} });
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('callTool() generic escape hatch returns the parsed result for any tool', async () => {
|
|
182
|
-
mockCallTool.mockResolvedValue(makeToolResult({ foo: 'bar', count: 42 }));
|
|
183
|
-
const result = await caller.callTool('my_custom_tool', { arg: 1 });
|
|
184
|
-
expect(result).toEqual({ foo: 'bar', count: 42 });
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('throws with a non-JSON error when the tool returns unparseable text', async () => {
|
|
188
|
-
mockCallTool.mockResolvedValue({ content: [{ type: 'text', text: 'NOT JSON!' }] });
|
|
189
|
-
await expect(caller.callTool('my_tool', {})).rejects.toThrow('non-JSON');
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('throws with the tool error message when the parsed result contains an error field', async () => {
|
|
193
|
-
mockCallTool.mockResolvedValue(makeToolResult({ error: 'credential not found' }));
|
|
194
|
-
await expect(caller.callTool('resolve_credential', {})).rejects.toThrow('credential not found');
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it('disconnect() calls close() on the injected client', async () => {
|
|
198
|
-
await caller.disconnect();
|
|
199
|
-
expect(mockClose).toHaveBeenCalled();
|
|
200
|
-
});
|
|
201
|
-
});
|
package/src/store.ts
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* McpCredentialStore — CredentialStore implementation that fetches
|
|
3
|
-
* credentials from an external MCP server.
|
|
4
|
-
*
|
|
5
|
-
* Implements the full CredentialStore interface from @datacules/agent-identity
|
|
6
|
-
* so it can be dropped into any CredentialRouter without any other changes:
|
|
7
|
-
*
|
|
8
|
-
* import { McpCredentialStore } from '@datacules/agent-identity-mcp-client';
|
|
9
|
-
* import { createRouterFromStore } from '@datacules/agent-identity';
|
|
10
|
-
*
|
|
11
|
-
* const store = new McpCredentialStore({
|
|
12
|
-
* serverUrl: 'http://localhost:3002',
|
|
13
|
-
* authToken: process.env.MCP_AUTH_TOKEN,
|
|
14
|
-
* });
|
|
15
|
-
* const router = createRouterFromStore(store, rules, logger);
|
|
16
|
-
*
|
|
17
|
-
* The MCP server this client connects to MUST expose a `list_credentials`
|
|
18
|
-
* tool (i.e. another @datacules/agent-identity-mcp instance, a Vault MCP
|
|
19
|
-
* server, a 1Password MCP server, or any custom server following the same
|
|
20
|
-
* tool contract).
|
|
21
|
-
*
|
|
22
|
-
* Transport:
|
|
23
|
-
* - For a remote HTTP+SSE server: provide serverUrl
|
|
24
|
-
* - For an in-process stdio server (test / monorepo): provide serverProcess
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
28
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
29
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
30
|
-
import type { Credential, CredentialStore } from '@datacules/agent-identity';
|
|
31
|
-
|
|
32
|
-
// ─── Options ───────────────────────────────────────────────────────────────────
|
|
33
|
-
|
|
34
|
-
export interface McpCredentialStoreHttpOptions {
|
|
35
|
-
transport: 'http';
|
|
36
|
-
/**
|
|
37
|
-
* Base URL of the remote agent-identity MCP server.
|
|
38
|
-
* The SSE endpoint is expected at GET <serverUrl>/sse
|
|
39
|
-
* and messages at POST <serverUrl>/messages.
|
|
40
|
-
*/
|
|
41
|
-
serverUrl: string;
|
|
42
|
-
/** Bearer token if the remote server requires auth */
|
|
43
|
-
authToken?: string;
|
|
44
|
-
/** Client name sent during MCP handshake (default: 'agent-identity-mcp-client') */
|
|
45
|
-
clientName?: string;
|
|
46
|
-
/** Client version (default: '0.1.0') */
|
|
47
|
-
clientVersion?: string;
|
|
48
|
-
/** TTL of the in-memory credential cache in ms (default: 60_000) */
|
|
49
|
-
cacheTtlMs?: number;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface McpCredentialStoreStdioOptions {
|
|
53
|
-
transport: 'stdio';
|
|
54
|
-
/** Command to spawn the MCP server process */
|
|
55
|
-
command: string;
|
|
56
|
-
/** Arguments passed to the spawned process */
|
|
57
|
-
args?: string[];
|
|
58
|
-
/** Environment variables for the spawned process */
|
|
59
|
-
env?: Record<string, string>;
|
|
60
|
-
clientName?: string;
|
|
61
|
-
clientVersion?: string;
|
|
62
|
-
cacheTtlMs?: number;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export type McpCredentialStoreOptions =
|
|
66
|
-
| McpCredentialStoreHttpOptions
|
|
67
|
-
| McpCredentialStoreStdioOptions;
|
|
68
|
-
|
|
69
|
-
// ─── Cache entry ────────────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
interface CacheEntry {
|
|
72
|
-
credentials: Credential[];
|
|
73
|
-
expiresAt: number;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// ─── McpCredentialStore ─────────────────────────────────────────────────────────
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* CredentialStore implementation that pulls credentials from an external
|
|
80
|
-
* MCP server by calling its `list_credentials` tool.
|
|
81
|
-
*
|
|
82
|
-
* Credentials are cached in-memory for `cacheTtlMs` (default 60s) to avoid
|
|
83
|
-
* a round-trip on every router.resolve() call. Call invalidateCache() to
|
|
84
|
-
* force a fresh fetch on the next operation.
|
|
85
|
-
*
|
|
86
|
-
* The MCP client connection is lazy — the first store operation connects
|
|
87
|
-
* and caches the connection. Call disconnect() when the store is no longer
|
|
88
|
-
* needed (e.g. on process shutdown).
|
|
89
|
-
*/
|
|
90
|
-
export class McpCredentialStore implements CredentialStore {
|
|
91
|
-
private client: Client | null = null;
|
|
92
|
-
private cache: CacheEntry | null = null;
|
|
93
|
-
private readonly cacheTtlMs: number;
|
|
94
|
-
private readonly options: McpCredentialStoreOptions;
|
|
95
|
-
private connectPromise: Promise<void> | null = null;
|
|
96
|
-
|
|
97
|
-
constructor(options: McpCredentialStoreOptions) {
|
|
98
|
-
this.options = options;
|
|
99
|
-
this.cacheTtlMs = options.cacheTtlMs ?? 60_000;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ── Public CredentialStore interface ────────────────────────────────────────
|
|
103
|
-
|
|
104
|
-
async findByRef(ref: string): Promise<Credential | null> {
|
|
105
|
-
const all = await this.listActive();
|
|
106
|
-
return all.find((c) => c.ref === ref && c.status === 'active') ?? null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async listActive(): Promise<Credential[]> {
|
|
110
|
-
const cached = this.getFromCache();
|
|
111
|
-
if (cached) return cached.filter((c) => c.status === 'active');
|
|
112
|
-
const fresh = await this.fetchFromServer();
|
|
113
|
-
return fresh.filter((c) => c.status === 'active');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async listByKind(kind: Credential['kind']): Promise<Credential[]> {
|
|
117
|
-
const all = await this.listActive();
|
|
118
|
-
return all.filter((c) => c.kind === kind);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ── Cache management ──────────────────────────────────────────────────────
|
|
122
|
-
|
|
123
|
-
/** Force the next store operation to fetch fresh credentials from the server */
|
|
124
|
-
invalidateCache(): void {
|
|
125
|
-
this.cache = null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private getFromCache(): Credential[] | null {
|
|
129
|
-
if (!this.cache) return null;
|
|
130
|
-
if (Date.now() > this.cache.expiresAt) { this.cache = null; return null; }
|
|
131
|
-
return this.cache.credentials;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private setCache(credentials: Credential[]): void {
|
|
135
|
-
this.cache = { credentials, expiresAt: Date.now() + this.cacheTtlMs };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── MCP client lifecycle ─────────────────────────────────────────────────
|
|
139
|
-
|
|
140
|
-
private async ensureConnected(): Promise<void> {
|
|
141
|
-
if (this.client) return;
|
|
142
|
-
// Serialize concurrent callers so we only connect once
|
|
143
|
-
if (!this.connectPromise) this.connectPromise = this._connect();
|
|
144
|
-
await this.connectPromise;
|
|
145
|
-
this.connectPromise = null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private async _connect(): Promise<void> {
|
|
149
|
-
const opts = this.options;
|
|
150
|
-
const clientName = opts.clientName ?? 'agent-identity-mcp-client';
|
|
151
|
-
const clientVersion = opts.clientVersion ?? '0.1.0';
|
|
152
|
-
|
|
153
|
-
this.client = new Client(
|
|
154
|
-
{ name: clientName, version: clientVersion },
|
|
155
|
-
{ capabilities: {} }
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
if (opts.transport === 'http') {
|
|
159
|
-
const sseUrl = new URL('/sse', opts.serverUrl);
|
|
160
|
-
const headers: Record<string, string> = {};
|
|
161
|
-
if (opts.authToken) headers['Authorization'] = `Bearer ${opts.authToken}`;
|
|
162
|
-
|
|
163
|
-
const transport = new SSEClientTransport(sseUrl, { headers });
|
|
164
|
-
await this.client.connect(transport);
|
|
165
|
-
} else {
|
|
166
|
-
const transport = new StdioClientTransport({
|
|
167
|
-
command: opts.command,
|
|
168
|
-
args: opts.args ?? [],
|
|
169
|
-
env: opts.env,
|
|
170
|
-
});
|
|
171
|
-
await this.client.connect(transport);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** Disconnect the MCP client and clear the cache. Call on process shutdown. */
|
|
176
|
-
async disconnect(): Promise<void> {
|
|
177
|
-
this.invalidateCache();
|
|
178
|
-
if (this.client) {
|
|
179
|
-
await this.client.close();
|
|
180
|
-
this.client = null;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ── Remote fetch ───────────────────────────────────────────────────────────
|
|
185
|
-
|
|
186
|
-
private async fetchFromServer(): Promise<Credential[]> {
|
|
187
|
-
await this.ensureConnected();
|
|
188
|
-
|
|
189
|
-
const result = await this.client!.callTool({
|
|
190
|
-
name: 'list_credentials',
|
|
191
|
-
arguments: {},
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
const text = (result.content as Array<{ type: string; text: string }>)
|
|
195
|
-
.filter((c) => c.type === 'text')
|
|
196
|
-
.map((c) => c.text)
|
|
197
|
-
.join('');
|
|
198
|
-
|
|
199
|
-
let parsed: { credentials?: Credential[] };
|
|
200
|
-
try {
|
|
201
|
-
parsed = JSON.parse(text);
|
|
202
|
-
} catch {
|
|
203
|
-
throw new Error(
|
|
204
|
-
`[McpCredentialStore] MCP server returned non-JSON response from list_credentials: ${text.slice(0, 200)}`
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (!Array.isArray(parsed.credentials)) {
|
|
209
|
-
throw new Error(
|
|
210
|
-
'[McpCredentialStore] MCP server list_credentials response missing credentials array'
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
this.setCache(parsed.credentials);
|
|
215
|
-
return parsed.credentials;
|
|
216
|
-
}
|
|
217
|
-
}
|
package/tsconfig.build.json
DELETED