Mcp Connections logo

Mcp Connections

Organization
vstorm-co
mcp-connections

Work with MCP servers — connect one, add an entry to the server catalog, debug "the agent cannot see my MCP tools" or a failing OAuth flow, or change how connections are probed, prefixed or filtered. Use when the ask involves an external SaaS tool (GitHub, Linear, Notion, Slack, Stripe, Postgres…) and before writing a capability that would just be an API client for one.

Overview

Publishervstorm-co
Repositoryagenticos
Skill namemcp-connections
Stars
137
Forks
27
Bundled files
Instructions only
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by vstorm-co on GitHub. Read the source before you install it.

Installation

Install the Mcp Connections AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/vstorm-co/agenticos/tree/main/.claude/skills/mcp-connections
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/vstorm-co/agenticos.git /tmp/agenticos
mkdir -p .claude/skills
cp -r /tmp/agenticos/.claude/skills/mcp-connections .claude/skills/mcp-connections
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Mcp Connections in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Mcp Connections on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Mcp Connections is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

MCP connections

Read docs/mcp.md for the model and docs/howto/add-mcp-server.md for adding a catalog entry. Code: app/agents/mcp.py, app/agents/mcp_oauth.py, app/services/mcp_connection.py, app/services/mcp_catalog.py.

Reach for this before writing a capability

If the ask is "let the agent read our Linear issues", the answer is usually a connection, not code. A capability is right for something the platform must guarantee — a budget guard, a sandbox, retrieval that cites sources. MCP is right for the forty SaaS products a company happens to use.

Adding a catalog entry is one object in app/core/catalog/mcp_servers.json. No code. And nothing needs to be in the catalog for a URL to work — the Custom server entry introspects any reachable server.

Personal vs organization

Two kinds, differing in exactly two places and both are the point:

PersonalOrganization
Reached byIts owner, through a binding to each person's own accountAny agent whose spec names it
GateOwner onlymcp:manage
Credential sealed toThe memberThe organization
A spec may name itNo - a spec names the service (account: personal, catalog_key) and the owner's connection is found at run timeYes, account: organization, connection_id

A spec binds a service one of two ways (McpServerRef in app/agents/spec.py). OrgMcpServerRef is the organization's connection, answering for everybody. PersonalMcpServerRef names a catalog key: build_toolsets_for_agent looks up the sender's own connection by that key on every turn - the message's author, never the thread's - and where there is none (an API key, the widget, a schedule, an unlinked chat sender) reports the gap instead of skipping the server, and _with_personal_service_gaps in the runner tells the model what is missing and where the person connects it. A member's connection is never named by id: a published agent that reached different tools depending on who built it could not be reviewed.

A personal connection is sealed to the member rather than an organization because it has none, and its owner may belong to several: binding it to whichever was active would make the token unreadable the moment they switched.

The four behaviours that surprise people

A dead server is skipped, not raised. Each server gets a 3-second tools/list probe before the turn; failures log a warning and the turn proceeds without those tools. Pydantic AI enters every toolset when a run starts, so raising would let one expired token abort every agent that names the connection. The trade is real: a skipped server means the model answers without the tools. It is briefed, though: UnavailableUnreachableServer for a failed probe, and AnsweredRecently for a server that answered within two minutes and fails on connect, both through unreachable_briefing in app/agents/mcp.py. /test and last_status are how you find out.

Tools are prefixed with the connection name. github-work → github_work_*. Two servers exposing the same tool name make Pydantic AI raise on duplicates, which aborts the turn.

Two connections reducing to the same prefix are deduplicated, first one wins, with a warning naming the loser. Deployment-managed servers are ordered first.

An allowlist filters before prefixing, so it compares unprefixed names.

OAuth

Five steps in mcp_oauth.py: discover (RFC 9728 → RFC 8414) → dynamic client registration (RFC 7591) → consent URL (PKCE + state + RFC 8707 resource indicator) → exchange → refresh. Split across two HTTP requests because this is a web app, not a CLI.

A server with no registration endpoint (HubSpot) refuses at step two, and the only way past is a client the operator registered at the provider by hand. McpOAuthStart takes client_id / client_secret for that (#1620); the connect dialog collects them in McpOAuthClientFields, with the redirect URL the provider has to hold - {FRONTEND_URL}/api/me/mcp-connections/oauth/callback, exactly.

Every URL in that flow is SSRF-checked and pinned, not just the one somebody typed — discovery means the remote server picks most of the addresses we call. The flow's only client is mcp_oauth._client(), a PinnedAsyncClient (app/core/pinned_http.py): it checks each request's URL with sanitize.resolve_pinned_url and then dials that address, with the original host in Host and in TLS SNI. Nothing resolves the name twice, so there is no window to rebind (#860). Do not add a request path that builds a plain httpx.AsyncClient — _send takes a PinnedAsyncClient so that mistake does not type-check.

A refused hop crosses httpx out of the transport as a UrlRefusedError, and _send answers it with a fixed OAuthError sentence. Catch the narrow type, never ValueError: only the narrow one is a refusal written here, and every other ValueError reported as "this server pointed us at a blocked address" is a confident lie about whose fault a failure was (#861).

Behind HTTP_PROXY/HTTPS_PROXY the proxy does the connecting, so there the pinned address is what the proxy is asked to reach. Honoured deliberately — a proxy-only deployment would otherwise lose OAuth entirely, and the proxy is its own egress control. Do not "fix" that by naming a transport on the client: an explicit transport turns httpx's environment-proxy mounting off, which is how it was broken once already.

Two things the pin does not cover, and neither is an oversight. The consent URL is checked with validate_mcp_url and then handed to a browser that resolves it itself. The connection's own URL is checked at save and resolved again at run time — a point-in-time check (#840), bearable only because an operator typed it. A URL a model chose belongs in neither; it needs safe_download.

MCP_ALLOWED_HOSTS exempts a listed host from the address half of the check on a connection's own URL: _checked_url (save, update, the sign-in probe and the OAuth start) always passes honour_allowlist=True. The OAuth requests after it get mcp.trusted_hosts_for(server_url), which is every listed host when the connection's own host is listed and none otherwise. resolve_pinned_url still resolves and pins a trusted host and refuses loopback, link-local and metadata answers for it. The catalog audit stays strict. Never compute the trusted set from a URL a remote server named: that is how a public server would aim its token endpoint at a listed host.

An organization's OAuth connection is still the consenting person's grant at the provider. Revoking their access there breaks the organization's server.

MCP approvals

approval_required_tools covers capability tools; MCP tools are marked separately by ApprovalMarked and enforced by the same ApprovalGate. A binding's writes policy trusts only readOnlyHint: true; missing or false hints require approval. all also gates reads, and none waives the binding's gate. The platform server always enforces at least writes, with deliberate exceptions in HELD and UNGATED in services/platform_mcp/_tools.py.

Channel runs, including delegates and resumptions, enforce at least writes on all MCP bindings. Read-only calls no longer acquire an extra approval merely by running in a chat app for a linked member. Unlinked room participants still require approval for every MCP call, including organization reads. Personal connections resolve for each message's linked sender, never the publisher or a different participant.

Cost is the same story: what a server does on its own side is outside this platform's budget. Only model tokens are counted.

Routes

GET  /api/v1/me/mcp-connections            personal
GET  /api/v1/mcp-connections               organization  (mcp:manage)
POST /api/v1/mcp-connections/{id}/test     probe, list tools, persist last_status
POST /api/v1/mcp-connections/oauth/start   declared above /{id} — that route parses
                                           its segment as a UUID and would 422

PATCH with auth_token: "" clears a stored credential.

Test

frontend/e2e/mcp-servers.spec.ts covers the UI. For the backend, the interesting assertions are the refusals: a personal connection is not bindable by a spec, a connection from another organization is unreachable, and a prefix collision drops the second server rather than aborting the turn.

Frequently asked questions

What does the Mcp Connections AI skill do?

Work with MCP servers — connect one, add an entry to the server catalog, debug "the agent cannot see my MCP tools" or a failing OAuth flow, or change how connections are probed, prefixed or filtered. Use when the ask involves an external SaaS tool (GitHub, Linear, Notion, Slack, Stripe, Postgres…) and before writing a capability that would just be an API client for one.

Why use Mcp Connections on TypingMind?

Because you install it once and use it with any model. Mcp Connections is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Mcp Connections in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/vstorm-co/agenticos/tree/main/.claude/skills/mcp-connections. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Mcp Connections?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Mcp Connections?

As many as you like. As long as a model supports skills, you can use Mcp Connections with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Mcp Connections AI skill free?

Yes. It is published on GitHub by vstorm-co under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇