Skip to content

feat(mcp): add connections get tool - #7066

Open
marcindobry wants to merge 13 commits into
masterfrom
marcin/NAN-6306/mcp-tool-connections_get
Open

feat(mcp): add connections get tool#7066
marcindobry wants to merge 13 commits into
masterfrom
marcin/NAN-6306/mcp-tool-connections_get

Conversation

@marcindobry

@marcindobry marcindobry commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Adds the connections_get Management MCP tool

NAN-6306: https://linear.app/nango/issue/NAN-6306/mcp-tool-connections-get

@linear-code

linear-code Bot commented Aug 10, 2026

Copy link
Copy Markdown

NAN-6306

@mintlify

mintlify Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
nango 🟢 Ready View Preview Aug 10, 2026, 11:31 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 16 files

Confidence score: 4/5

  • In packages/server/lib/controllers/mcp/connections/list.ts, very large page inputs can produce unbounded SQL offsets, which risks expensive table scans or offset errors under user-supplied pagination—cap page similarly to the v1 connections endpoint to bound query cost and avoid invalid offsets.
  • In packages/server/lib/controllers/mcp/management.integration.test.ts, the expected error substring does not match the actual formatArgumentsError output, so the test can fail for the wrong reason and stop validating the real tool-error contract—update the assertion text (or standardize the emitted message) so the test checks the intended behavior.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/server/lib/controllers/mcp/connections/list.ts">

<violation number="1" location="packages/server/lib/controllers/mcp/connections/list.ts:22">
P2: Large valid `page` values turn into unbounded SQL offsets; cap page like the v1 connections endpoint to prevent costly scans and invalid offset errors.

(Based on your team's feedback about avoiding unnecessary heavy reads.)</violation>
</file>

<file name="packages/server/lib/controllers/mcp/management.integration.test.ts">

<violation number="1" location="packages/server/lib/controllers/mcp/management.integration.test.ts:368">
P2: This assertion checks for the substring 'Invalid arguments for tool connections_get', but the tool error path produces 'Invalid connections_get arguments: ...'. formatArgumentsError (managementTool.ts) builds `Invalid ${toolName} arguments: ${details}`, confirmed by managementTool.unit.test.ts expecting 'Invalid test_tool arguments:' and connections/get.unit.test.ts expecting 'Invalid connections_get arguments:'. The checked substring never appears, so this test fails.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/server/lib/services/connectionCredentials.ts Outdated
end_user_organization_id: z.string().min(1).max(255).optional(),
tags: connectionTagsSchema.optional(),
limit: z.number().int().min(1).max(10_000).optional(),
page: z.number().int().min(0).optional()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Large valid page values turn into unbounded SQL offsets; cap page like the v1 connections endpoint to prevent costly scans and invalid offset errors.

(Based on your team's feedback about avoiding unnecessary heavy reads.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/server/lib/controllers/mcp/connections/list.ts, line 22:

<comment>Large valid `page` values turn into unbounded SQL offsets; cap page like the v1 connections endpoint to prevent costly scans and invalid offset errors.

(Based on your team's feedback about avoiding unnecessary heavy reads.) </comment>

<file context>
@@ -0,0 +1,51 @@
+        end_user_organization_id: z.string().min(1).max(255).optional(),
+        tags: connectionTagsSchema.optional(),
+        limit: z.number().int().min(1).max(10_000).optional(),
+        page: z.number().int().min(0).optional()
+    })
+    .strict();
</file context>

}
});
expect(invalid.json.result).toMatchObject({ isError: true });
expect(invalid.json.result.content[0].text).toContain('Invalid arguments for tool connections_get');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This assertion checks for the substring 'Invalid arguments for tool connections_get', but the tool error path produces 'Invalid connections_get arguments: ...'. formatArgumentsError (managementTool.ts) builds Invalid ${toolName} arguments: ${details}, confirmed by managementTool.unit.test.ts expecting 'Invalid test_tool arguments:' and connections/get.unit.test.ts expecting 'Invalid connections_get arguments:'. The checked substring never appears, so this test fails.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/server/lib/controllers/mcp/management.integration.test.ts, line 368:

<comment>This assertion checks for the substring 'Invalid arguments for tool connections_get', but the tool error path produces 'Invalid connections_get arguments: ...'. formatArgumentsError (managementTool.ts) builds `Invalid ${toolName} arguments: ${details}`, confirmed by managementTool.unit.test.ts expecting 'Invalid test_tool arguments:' and connections/get.unit.test.ts expecting 'Invalid connections_get arguments:'. The checked substring never appears, so this test fails.</comment>

<file context>
@@ -256,6 +267,174 @@ describe('POST /mcp management server', () => {
+            }
+        });
+        expect(invalid.json.result).toMatchObject({ isError: true });
+        expect(invalid.json.result.content[0].text).toContain('Invalid arguments for tool connections_get');
+
+        const missing = await mcpPost({
</file context>

Comment thread packages/server/lib/controllers/mcp/connections/get.ts Outdated
@marcindobry marcindobry changed the title feat(mcp): add connection management tools feat(mcp): add connections get tool Aug 10, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 10 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/server/lib/services/connectionCredentials.ts Outdated
Comment thread packages/server/lib/services/connectionCredentials.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 9 files (changes from recent commits).

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/shared/lib/services/connection-credentials.service.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Confidence score: 4/5

  • In packages/persist/lib/server.integration.test.ts, removing clearDb() can leave seeded integration data behind in a long-lived shared DB, which risks cross-test contamination and intermittent failures as later suites read unexpected rows — reintroduce cleanup or isolate each run with a fresh/schema-scoped test database.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/persist/lib/server.integration.test.ts">

<violation number="1" location="packages/persist/lib/server.integration.test.ts:74">
P3: Removing `clearDb()` leaves all seeded rows (account, env, plan, provider config, connection, sync, sync_job, and records) in the shared integration DB after the suite. When the integration DB is long-lived rather than recreated per run (e.g. local runs against a persistent DATABASE_URL), this data accumulates on every run since each `seeders.createAccount()` creates a fresh uuid account; consider deleting just the created rows in afterAll instead of dropping the whole schema.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic


afterAll(async () => {
await clearDb();
vi.restoreAllMocks();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Removing clearDb() leaves all seeded rows (account, env, plan, provider config, connection, sync, sync_job, and records) in the shared integration DB after the suite. When the integration DB is long-lived rather than recreated per run (e.g. local runs against a persistent DATABASE_URL), this data accumulates on every run since each seeders.createAccount() creates a fresh uuid account; consider deleting just the created rows in afterAll instead of dropping the whole schema.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/persist/lib/server.integration.test.ts, line 74:

<comment>Removing `clearDb()` leaves all seeded rows (account, env, plan, provider config, connection, sync, sync_job, and records) in the shared integration DB after the suite. When the integration DB is long-lived rather than recreated per run (e.g. local runs against a persistent DATABASE_URL), this data accumulates on every run since each `seeders.createAccount()` creates a fresh uuid account; consider deleting just the created rows in afterAll instead of dropping the whole schema.</comment>

<file context>
@@ -66,7 +71,10 @@ describe('Persist API', () => {
 
     afterAll(async () => {
-        await clearDb();
+        vi.restoreAllMocks();
+        if (httpServer) {
+            await new Promise<void>((resolve) => httpServer?.close(() => resolve()));
</file context>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 14 files (changes from recent commits).

Confidence score: 4/5

  • In packages/shared/lib/services/connection.service.unit.test.ts, the test currently enforces collapsing all credential refresh failures into invalid_credentials, which can mask distinct error codes from refreshOrTestCredentials and reduce debugging/API handling fidelity; update the expectation to preserve specific upstream error codes (or explicitly map only intended cases).
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/shared/lib/services/connection.service.unit.test.ts">

<violation number="1" location="packages/shared/lib/services/connection.service.unit.test.ts:140">
P2: This test codifies the behavior that every credential refresh failure is collapsed to a generic `invalid_credentials` code, discarding the specific error code that `refreshOrTestCredentials` (`connections/credentials/refresh.ts`) produces (e.g. `connection_refresh_exhausted`, `connection_refresh_backoff`, `invalid_crypted_connection`, `refresh_lock_timeout`). The implementation keeps only `status` and `payload` and drops the original code, so callers can no longer distinguish an exhausted/back-off credential or a lock-contention timeout from genuinely invalid credentials. The team expects lock contention to be preserved as `refresh_lock_timeout` rather than mapped to `invalid_credentials`. Add/extend a unit test asserting the specific refresh error code is preserved in the returned `GetConnectionError` (and fix `getConnectionWithCredentials` at connection.service.ts:650 accordingly, since this test locks in the lossy mapping).</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/shared/lib/services/connection.service.ts Outdated
connection
});
if (result.isErr()) {
expect(result.error).toMatchObject({ code: 'invalid_credentials', status: 424, payload: { reason: 'exhausted' } });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This test codifies the behavior that every credential refresh failure is collapsed to a generic invalid_credentials code, discarding the specific error code that refreshOrTestCredentials (connections/credentials/refresh.ts) produces (e.g. connection_refresh_exhausted, connection_refresh_backoff, invalid_crypted_connection, refresh_lock_timeout). The implementation keeps only status and payload and drops the original code, so callers can no longer distinguish an exhausted/back-off credential or a lock-contention timeout from genuinely invalid credentials. The team expects lock contention to be preserved as refresh_lock_timeout rather than mapped to invalid_credentials. Add/extend a unit test asserting the specific refresh error code is preserved in the returned GetConnectionError (and fix getConnectionWithCredentials at connection.service.ts:650 accordingly, since this test locks in the lossy mapping).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/shared/lib/services/connection.service.unit.test.ts, line 140:

<comment>This test codifies the behavior that every credential refresh failure is collapsed to a generic `invalid_credentials` code, discarding the specific error code that `refreshOrTestCredentials` (`connections/credentials/refresh.ts`) produces (e.g. `connection_refresh_exhausted`, `connection_refresh_backoff`, `invalid_crypted_connection`, `refresh_lock_timeout`). The implementation keeps only `status` and `payload` and drops the original code, so callers can no longer distinguish an exhausted/back-off credential or a lock-contention timeout from genuinely invalid credentials. The team expects lock contention to be preserved as `refresh_lock_timeout` rather than mapped to `invalid_credentials`. Add/extend a unit test asserting the specific refresh error code is preserved in the returned `GetConnectionError` (and fix `getConnectionWithCredentials` at connection.service.ts:650 accordingly, since this test locks in the lossy mapping).</comment>

<file context>
@@ -1,15 +1,163 @@
+            connection
+        });
+        if (result.isErr()) {
+            expect(result.error).toMatchObject({ code: 'invalid_credentials', status: 424, payload: { reason: 'exhausted' } });
+            expect(result.error.connection).not.toHaveProperty('credentials');
+        }
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how the existing endpoint works

Comment thread packages/server/lib/controllers/mcp/connections/get.ts Outdated
Comment thread packages/server/lib/controllers/mcp/managementServer.unit.test.ts Outdated
Comment thread packages/server/lib/controllers/connection/connectionId/getConnection.ts Outdated
Comment thread packages/server/lib/controllers/mcp/managementTool.ts Outdated
@marcindobry
marcindobry force-pushed the marcin/NAN-6306/mcp-tool-connections_get branch from 5ad8fc4 to def6c5d Compare August 14, 2026 10:24
@marcindobry
marcindobry marked this pull request as ready for review August 14, 2026 11:50
@marcindobry
marcindobry requested a review from a team August 14, 2026 11:50
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/shared/lib/services/connection.service.ts Centralizes detailed connection retrieval, credential refresh, refresh-token filtering, and primary-database enrichment.
packages/server/lib/controllers/mcp/connections/get.ts Implements the scoped connections_get MCP handler and prevents credential operations for read-only keys.
packages/server/lib/controllers/connection/connectionId/getConnection.ts Refactors the public endpoint to use the shared connection retrieval service while preserving response and error handling.
packages/server/lib/controllers/mcp/connections/formatter.ts Formats retrieved connections and recursively serializes credential dates for MCP output.
packages/server/lib/controllers/mcp/connections/schema.ts Defines the structured output contract for full MCP connection retrieval.

Reviews (4): Last reviewed commit: "refactor(api): narrow connection formatt..." | Re-trigger Greptile

Comment thread packages/shared/lib/services/connection.service.ts Outdated
Comment thread packages/shared/lib/services/connection.service.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/server/lib/formatters/connection.ts
resolvedConnection = connectionResult.response;
}

const result = await db.knex

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a part extracted out of listConnections method in this service that is relevant to callers of this method. Old code that can be found in the public API getConnection handler called listConnections, took the first connection, and took out the data that this queried. This is a dedicated query to do that instead.

@marcindobry
marcindobry requested a review from agusayerza August 14, 2026 14:21
};

const credentialResponse = await refreshOrTestCredentials({
const result = await connectionService.getConnectionWithCredentials({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'm not a huge fan of getting the whole connection, decrypting the credentials and dropping them if they are not required or the scopes don't allow it.

Comment on lines +14 to +24
import type { GetConnectionOutput } from './schema.js';

const getConnectionArgumentsSchema = z
.object({
connection_id: connectionIdSchema.min(1),
integration_id: providerConfigKeySchema.min(1),
refresh_token: z.boolean().optional(),
force_refresh: z.boolean().optional(),
refresh_github_app_jwt_token: z.boolean().optional()
})
.strict();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Saw that we are doing this as a convention, (input schema on the tool file and output on the schema file). Any particular reason to it?


function withoutDirectAndRawRefreshToken(credentials: OAuth2Credentials): OAuth2Credentials;
function withoutDirectAndRawRefreshToken(credentials: TwoStepCredentials): TwoStepCredentials;
function withoutDirectAndRawRefreshToken(credentials: OAuth2Credentials | TwoStepCredentials): OAuth2Credentials | TwoStepCredentials {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are provider-specific refresh token shapes that would leak with this. For example, [workday-refresh-token](

token_response:
token: access_token
token_expires_in_ms: 3600000
proxy:
base_url: https://${connectionConfig.tokenDomain}/ccx/api
docs: https://nango.dev/docs/api-integrations/workday-refresh-token
docs_connect: https://nango.dev/docs/api-integrations/workday-refresh-token/connect
connection_config:
tokenDomain:
type: string
title: Token Domain
description: The domain used to obtain the token for your Workday account
format: hostname
example: wd3-impl-services1.workday.com
prefix: https://
order: 1
doc_section: '#step-1-finding-your-token-domain'
tenant:
) comes to mind, as it stores it as credentials.refreshToken. Some TWO_STEP providers also retain it in raw under provider-specific names such as raw.RefreshToken or raw.refreshToken.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants