Koskamo

My TV Channel MCP

An official MCP server provided by My TV Channel that allows AI agents to create, manage, and run 24/7 linear TV channels. It supports importing videos by URL, transcoding to HLS, scheduling content, and monitoring channel status, forwarding requests to the vendor's REST API.

Endpoint and access

No sign-in to connect and list tools. Individual tools may still need a vendor account, credentials, or permissions; check the documented prerequisites.

My TV Channel MCP logo, by My TV ChannelVendor evidenceStreamable HTTPNo sign-inEndpoint checked 2026-11-07Operated by My TV Channel
You need:a human creates it at https://my-tv-channel.com/agent-keys/ or in the app under Profile → Agent AccessAn active subscription (any tier) is required
MCP endpoint (Streamable HTTP)
https://my-tv-channel.com/api/mytvchannel/mcp
Vendor documentation

What its tools do

19 tools, grouped by the hints the server advertises. Hints are self-declared, not verified behavior.

19 of 19 have a description · 19 of 19 declare behavior hints · 2 of 17 with parameters document them all

Checks and signals

Each line is separate evidence, not a trust score.

  • Passed Endpoint answered the live check ·
  • Passed Official MCP Registry namespace is tied to my-tv-channel.com ·
  • Passed Endpoint is on the registry namespace domain ·
  • Availability 1 of 1 checks passed in 30 days

What you can do with it

  • Create and configure a new linear TV channel
  • Import video content via public URL for transcoding
  • Schedule and manage video playback weights
  • Monitor channel status and quota limits

Source and maintenance

Vendor evidenceRegistry 1.1.0

Protocol and capabilities

Server
my-tv-channel 1.1.0
MCP protocol negotiated in check
2025-11-25
Tools
19 advertised
Resources
Not advertised
Resource templates
Not advertised
Prompts
Not advertised
Server-advertised capabilities
{
  "tools": {
    "listChanged": false
  }
}

Server-provided instructions

Untrusted server output shown for inspection. It is not Koskamo guidance and is excluded from knowledge search.

Create and run 24/7 linear TV channels. Typical flow: get_account_status to learn the tier and quota, create_channel, import_video_from_url for each video (setting its weight as you go), poll get_transcode_status until meetsLaunchRequirements is true, then get_channel until scheduleStatus is 'active' and report {handle}.my-tv-channel.com to the owner. A channel needs 4+ ready videos and 15+ minutes before it can go live. If you change weights on an already-live channel, call regenerate_schedule or the change will not air.

Tools

19 advertised

get_account_status — Get account status
Advertised by the server during the last successful anonymous check.
Advertised read-only

Start here. Returns who the account is, its subscription tier and limits, how much weekly upload/stream quota is used and remaining, and every channel the owner has with its ready-asset count and what it still needs to go live (launchGaps). Call this before creating anything so you know the real limits for THIS account rather than assuming.

get_account_status input schema
{
  "type": "object",
  "required": [],
  "properties": {}
}
get_account_status annotations
{
  "title": "Get account status",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
list_channels — List channels
Advertised by the server during the last successful anonymous check.
Advertised read-only

List the channels owned by this account.

list_channels input schema
{
  "type": "object",
  "required": [],
  "properties": {}
}
list_channels annotations
{
  "title": "List channels",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
get_channel — Get channel
Advertised by the server during the last successful anonymous check.
Advertised read-only

Get one channel by id. Includes scheduleStatus (inactive | pending | active | error) — poll this to confirm a channel has gone live — plus watchUrl, launchedAt and isArchived.

get_channel input schema
{
  "type": "object",
  "required": [
    "channel_id"
  ],
  "properties": {
    "channel_id": {
      "type": "integer",
      "description": "Channel id"
    }
  }
}
get_channel annotations
{
  "title": "Get channel",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
check_handle_available — Check handle availability
Advertised by the server during the last successful anonymous check.
Advertised read-only

Check whether a channel handle is free before trying to create it. The handle becomes the subdomain: {handle}.my-tv-channel.com. 3-63 chars, lowercase a-z, 0-9 and hyphens.

check_handle_available input schema
{
  "type": "object",
  "required": [
    "handle"
  ],
  "properties": {
    "handle": {
      "type": "string",
      "description": "Desired handle, e.g. \"retro-arcade\""
    }
  }
}
check_handle_available annotations
{
  "title": "Check handle availability",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
create_channel — Create channel
Advertised by the server during the last successful anonymous check.

Create a 24/7 linear TV channel. It will NOT be live yet — a channel goes live automatically once it has at least 4 ready assets AND at least 15 minutes of total content. is_public lists it in Discover and is available on every subscription tier, including Starter.

create_channel input schema
{
  "type": "object",
  "required": [
    "handle",
    "name",
    "channel_type"
  ],
  "properties": {
    "name": {
      "type": "string",
      "description": "Display name"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "handle": {
      "type": "string",
      "description": "3-63 chars, lowercase a-z 0-9 and hyphens. Becomes {handle}.my-tv-channel.com"
    },
    "category": {
      "type": "string"
    },
    "location": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "latitude": {
          "type": "number"
        },
        "longitude": {
          "type": "number"
        },
        "radius_km": {
          "type": "number"
        }
      },
      "description": "Required only when channel_type is \"location\""
    },
    "is_public": {
      "type": "boolean",
      "description": "List in Discover. Available on all tiers."
    },
    "description": {
      "type": "string"
    },
    "orientation": {
      "enum": [
        "horizontal",
        "vertical"
      ],
      "type": "string",
      "description": "vertical = 9:16"
    },
    "channel_type": {
      "enum": [
        "thematic",
        "location"
      ],
      "type": "string",
      "description": "Use \"thematic\" unless the channel is tied to a place"
    },
    "primary_language": {
      "type": "string",
      "description": "ISO 639-1, e.g. \"en\", \"fr\""
    }
  }
}
create_channel annotations
{
  "title": "Create channel",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": false,
  "destructiveHint": false
}
update_channel — Update channel
Advertised by the server during the last successful anonymous check.

Update an existing channel: name, description, visibility, category, language, age rating, or the community chat link. Only send the fields you are changing.

update_channel input schema
{
  "type": "object",
  "required": [
    "channel_id"
  ],
  "properties": {
    "name": {
      "type": "string"
    },
    "category": {
      "type": "string"
    },
    "keywords": {
      "type": "string",
      "description": "Multilingual search keywords"
    },
    "is_public": {
      "type": "boolean",
      "description": "List in Discover. Allowed on every tier."
    },
    "channel_id": {
      "type": "integer"
    },
    "description": {
      "type": "string"
    },
    "chat_platform": {
      "enum": [
        "discord",
        "guilded",
        "matrix",
        "revolt",
        "kick",
        "restream"
      ],
      "type": "string",
      "description": "Community chat platform shown as an overlay on the player. Discord is the fully supported one; the others are accepted but only Discord has a working embed today."
    },
    "show_schedule": {
      "type": "boolean"
    },
    "chat_server_id": {
      "type": "string",
      "description": "Discord server (guild) id — the owner must supply this; you cannot discover it"
    },
    "chat_channel_id": {
      "type": "string",
      "description": "Discord channel id within that server"
    },
    "primary_language": {
      "type": "string",
      "description": "ISO 639-1, e.g. \"en\""
    },
    "age_classification": {
      "enum": [
        "4+",
        "9+",
        "13+",
        "16+",
        "18+"
      ],
      "type": "string"
    },
    "allow_offline_download": {
      "type": "boolean"
    }
  }
}
update_channel annotations
{
  "title": "Update channel",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
archive_channel — Archive channel
Advertised by the server during the last successful anonymous check.
Advertised destructive

Take a channel off the air and out of every listing. This ARCHIVES rather than destroys: the handle stays reserved and assets and schedule history are retained, so it can be restored. Refuses with 409 while transcodes are still running. Idempotent. This is the reversible way to take a channel down; delete_asset removes individual videos instead.

archive_channel input schema
{
  "type": "object",
  "required": [
    "channel_id"
  ],
  "properties": {
    "channel_id": {
      "type": "integer"
    }
  }
}
archive_channel annotations
{
  "title": "Archive channel",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": true
}
import_video_from_url — Import video from URL
Advertised by the server during the last successful anonymous check.
Open-world

Add a video to a channel from a direct media URL. THIS IS THE MAIN WAY TO ADD CONTENT over MCP — you cannot upload local file bytes through this interface, so if the owner has local files, ask them to host them somewhere reachable or upload via the app. The URL must be a direct, public, unauthenticated link to mp4/mov/m4v/mpeg/mpg/avi, or a FINITE .m3u8 (VOD, not a live stream) — not a web page, not behind a login, not a YouTube/Vimeo watch page. The server downloads it. Returns immediately; the asset then goes downloading -> pending -> transcoding -> ready. Duration counts against the weekly upload quota. Re-submitting the same URL to the same channel returns the original asset instead of importing twice.

import_video_from_url input schema
{
  "type": "object",
  "required": [
    "channel_id",
    "url"
  ],
  "properties": {
    "url": {
      "type": "string",
      "description": "Direct, public media URL"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "title": {
      "type": "string"
    },
    "weight": {
      "type": "integer",
      "maximum": 10,
      "minimum": 0,
      "description": "How often it plays, 0-10 (default 5). Set it here rather than after — see set_asset_weight."
    },
    "channel_id": {
      "type": "integer"
    }
  }
}
import_video_from_url annotations
{
  "title": "Import video from URL",
  "readOnlyHint": false,
  "openWorldHint": true,
  "idempotentHint": false,
  "destructiveHint": false
}
import_youtube_video — Import YouTube video
Advertised by the server during the last successful anonymous check.
Open-world

Import a YouTube video that the OWNER UPLOADED THEMSELVES. This will not work for anyone else's video, by design — a channel built from other people's videos is a copyright problem for the owner. Ownership is proved by a tag only the uploader can add: 1. The owner's tag looks like 'MyTVChannelApp#<device-id>'. When the import fails for a missing tag, the 403 error message contains that account's exact tag string. 2. They paste that exact string into the video's description or tags on YouTube. 3. Retry the import. You cannot do step 2 for them and you cannot bypass it. Public finished videos only: no live streams, no private or age-restricted videos, no playlists or channel URLs. Capped at 10 per day. Counts against the same weekly upload quota as everything else.

import_youtube_video input schema
{
  "type": "object",
  "required": [
    "channel_id",
    "url"
  ],
  "properties": {
    "url": {
      "type": "string",
      "description": "A single-video YouTube URL (watch, shorts or youtu.be)"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "title": {
      "type": "string",
      "description": "Overrides the YouTube title"
    },
    "weight": {
      "type": "integer",
      "maximum": 10,
      "minimum": 0
    },
    "channel_id": {
      "type": "integer"
    }
  }
}
import_youtube_video annotations
{
  "title": "Import YouTube video",
  "readOnlyHint": false,
  "openWorldHint": true,
  "idempotentHint": false,
  "destructiveHint": false
}
list_assets — List assets
Advertised by the server during the last successful anonymous check.
Advertised read-only

List the videos on a channel.

list_assets input schema
{
  "type": "object",
  "required": [
    "channel_id"
  ],
  "properties": {
    "channel_id": {
      "type": "integer"
    }
  }
}
list_assets annotations
{
  "title": "List assets",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
get_transcode_status — Get transcode status
Advertised by the server during the last successful anonymous check.
Advertised read-only

Poll transcode progress. Give either channel_id (all its videos) or asset_id (one). Each asset reports uploadStatus (downloading -> pending -> transcoding -> ready) and the summary tells you when meetsLaunchRequirements becomes true: 4+ ready assets AND 900+ seconds. Poll every 30-60s; transcoding takes roughly real-time/4.

get_transcode_status input schema
{
  "type": "object",
  "required": [],
  "properties": {
    "asset_id": {
      "type": "integer"
    },
    "channel_id": {
      "type": "integer"
    }
  }
}
get_transcode_status annotations
{
  "title": "Get transcode status",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
set_asset_weight — Set asset weight
Advertised by the server during the last successful anonymous check.

Change how often a video plays (weight 0-10, default 5; 0 removes it from rotation entirely), and optionally its title or tags. IMPORTANT ORDERING: weights only affect what actually airs when the schedule is (re)generated. A channel builds its schedule the instant it crosses the launch threshold, so weights set AFTER that are stored but do not air. Either set the weight when you import each video, or call regenerate_schedule afterwards. Weights are relative: identical weights across every video produce a uniform rotation, and the spread between them is what shapes airtime.

set_asset_weight input schema
{
  "type": "object",
  "required": [
    "asset_id"
  ],
  "properties": {
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Tag videos seqXX01, seqXX02 … (XX = any two letters) to force them to play in order as a mini-series."
    },
    "title": {
      "type": "string"
    },
    "weight": {
      "type": "integer",
      "maximum": 10,
      "minimum": 0
    },
    "asset_id": {
      "type": "integer"
    }
  }
}
set_asset_weight annotations
{
  "title": "Set asset weight",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
delete_asset — Delete asset
Advertised by the server during the last successful anonymous check.
Advertised destructive

Remove a video from a channel. Soft delete — it stops being scheduled immediately and the schedule regenerates automatically. Idempotent. The asset is retained and stops being scheduled; it is not erased. Affects one video — archive_channel takes a whole channel off the air.

delete_asset input schema
{
  "type": "object",
  "required": [
    "asset_id"
  ],
  "properties": {
    "asset_id": {
      "type": "integer"
    }
  }
}
delete_asset annotations
{
  "title": "Delete asset",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": true
}
retry_transcode — Retry transcode
Advertised by the server during the last successful anonymous check.

Re-queue a video whose transcode failed. Only works on assets in a failed state.

retry_transcode input schema
{
  "type": "object",
  "required": [
    "asset_id"
  ],
  "properties": {
    "asset_id": {
      "type": "integer"
    }
  }
}
retry_transcode annotations
{
  "title": "Retry transcode",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": false,
  "destructiveHint": false
}
regenerate_schedule — Regenerate schedule
Advertised by the server during the last successful anonymous check.
Advertised destructive

Rebuild the channel's schedule now using the current weights. Call this after changing weights on a channel that is ALREADY live, otherwise the change will not air until the hourly cron. Throttled to one call per channel per 5 minutes; exceeding that returns 429 with a Retry-After header giving the seconds remaining. Requires the channel to meet launch requirements.

regenerate_schedule input schema
{
  "type": "object",
  "required": [
    "channel_id"
  ],
  "properties": {
    "channel_id": {
      "type": "integer"
    }
  }
}
regenerate_schedule annotations
{
  "title": "Regenerate schedule",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": false,
  "destructiveHint": true
}
get_schedule — Get schedule
Advertised by the server during the last successful anonymous check.
Advertised read-only

Read the channel's EPG — what is playing and what is coming up. Note this one returns a plain array of entries (unlike other tools, which return an object).

get_schedule input schema
{
  "type": "object",
  "required": [
    "channel_id"
  ],
  "properties": {
    "channel_id": {
      "type": "integer"
    }
  }
}
get_schedule annotations
{
  "title": "Get schedule",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
get_schedule_rules — Get schedule rules
Advertised by the server during the last successful anonymous check.
Advertised read-only

Read the channel's declarative schedule rules (fixed time slots and dayparts), plus its scheduling mode and timezone.

get_schedule_rules input schema
{
  "type": "object",
  "required": [
    "channel_id"
  ],
  "properties": {
    "channel_id": {
      "type": "integer"
    }
  }
}
get_schedule_rules annotations
{
  "title": "Get schedule rules",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
validate_schedule_rules — Validate schedule rules
Advertised by the server during the last successful anonymous check.
Advertised read-only

DRY-RUN a ruleset. Saves nothing. Returns {valid, errors, warnings, preview} where preview is a simulated 24h grid of what would actually air. Intended as the dry run for set_schedule_rules: rules are wall-clock, slots start on-or-after their time, and dayparts reweight the random fill, so the airing result is hard to predict from the rules alone. Unlike set_schedule_rules this is not tier-gated and works on any tier. Same payload as set_schedule_rules.

validate_schedule_rules input schema
{
  "type": "object",
  "required": [
    "channel_id",
    "rules"
  ],
  "properties": {
    "hours": {
      "type": "integer",
      "description": "How many hours to simulate (default 24, max 168)"
    },
    "rules": {
      "type": "array",
      "items": {
        "type": "object"
      },
      "description": "Same shape as set_schedule_rules"
    },
    "timezone": {
      "type": "string",
      "description": "IANA name, e.g. \"Europe/Paris\""
    },
    "channel_id": {
      "type": "integer"
    }
  }
}
validate_schedule_rules annotations
{
  "title": "Validate schedule rules",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
set_schedule_rules — Set schedule rules
Advertised by the server during the last successful anonymous check.
Advertised destructive

Give the channel a durable schedule: fixed slots ("news at 18:00") and dayparts ("no kids content after 22:00"). The scheduler re-honours these on EVERY rebuild, so you set them once instead of babysitting the grid. **Pro tier or higher.** This REPLACES the entire ruleset — send every rule you want, not just new ones. Sending an empty array removes all rules and returns the channel to plain weighted-random. Validate first with validate_schedule_rules. Rule shapes: time_slot {rule_type:'time_slot', start_time:'18:00', end_time?:'21:00', days_mask:127, selector_type:'asset'|'tag'|'playlist', selector_value:<id|tag|[ids]>, priority?:0} daypart_weight {rule_type:'daypart_weight', start_time:'22:00', end_time:'06:00', days_mask:127, selector_type:'asset'|'tag', selector_value:..., weight_multiplier:0-10} days_mask is a bitmask: bit0=Mon … bit6=Sun. 127=daily, 31=Mon-Fri, 96=weekend. end_time on a time_slot makes it a BLOCK that keeps drawing from the selector until that time — that is how you get 'cartoons 06:00-09:00' rather than one cartoon. selector_type 'playlist' with an array of asset ids plays them IN ORDER. weight_multiplier 0 removes matching assets from that window entirely. Set `timezone` (IANA) or the times mean UTC, which is almost never what the owner meant. Slots start ON-OR-AFTER their time: the programme running into a slot is never cut short, so an 18:00 slot may begin at 18:02. That is intended.

set_schedule_rules input schema
{
  "type": "object",
  "required": [
    "channel_id",
    "rules"
  ],
  "properties": {
    "rules": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "rule_type",
          "start_time",
          "selector_type",
          "selector_value"
        ],
        "properties": {
          "end_time": {
            "type": "string",
            "description": "Required for daypart_weight; on a time_slot makes it a block"
          },
          "priority": {
            "type": "integer",
            "description": "Higher wins when slots overlap"
          },
          "days_mask": {
            "type": "integer",
            "maximum": 127,
            "minimum": 0
          },
          "rule_type": {
            "enum": [
              "time_slot",
              "daypart_weight"
            ],
            "type": "string"
          },
          "start_time": {
            "type": "string",
            "description": "HH:MM or HH:MM:SS, wall clock in the channel timezone"
          },
          "selector_type": {
            "enum": [
              "asset",
              "tag",
              "playlist"
            ],
            "type": "string"
          },
          "selector_value": {
            "description": "Asset id, tag name, or array of asset ids"
          },
          "weight_multiplier": {
            "type": "number",
            "maximum": 10,
            "minimum": 0
          }
        }
      },
      "description": "The COMPLETE ruleset. Empty array clears all rules."
    },
    "timezone": {
      "type": "string",
      "description": "IANA name, e.g. \"Europe/Paris\". Strongly recommended."
    },
    "channel_id": {
      "type": "integer"
    }
  }
}
set_schedule_rules annotations
{
  "title": "Set schedule rules",
  "readOnlyHint": false,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": true
}

Prompts

Not advertised

No prompt definitions available.

Resources

Not advertised

No resource definitions available.

90-day uptime

A lightweight connection check about every six hours, sampled on hosts with many listings. It shows the server answered, not that its tools work.

Too few checks for a percentage yet (1 of 1 answered).

  1. 2026-11-07: 1 of 1 checks answered

30-day availability

Aggregated from read-only remote checks; this is not a tool-execution or security guarantee.

Successful checks
1 of 1 (100%)
Average latency
593 ms
95th percentile latency
593 ms

Maintenance history

Public check and source-refresh events. Editorial and ownership records remain private.

  • · probe · passed · 593 ms · metadata changed · 19 tool(s)

Connect from your client

Each snippet registers the same endpoint; pick the client you use.

This server is operated by the named third party. Listing and endpoint checks are not an endorsement or a security audit. Review the operator's permissions, terms, and data handling before connecting. How listings are checked and corrected.

Claude Code

Run this once in a terminal. It registers the remote server. Use --scope project to write .mcp.json for the repo.
Claude Code configuration
claude mcp add --transport http my-tv-channel-mcp https://my-tv-channel.com/api/mytvchannel/mcp

Codex

Run this once. Codex stores MCP servers in ~/.codex/config.toml (or .codex/config.toml in a trusted project).
Codex configuration
codex mcp add my-tv-channel-mcp --url https://my-tv-channel.com/api/mytvchannel/mcp

Cursor

Merge this server into .cursor/mcp.json in your project, then enable my-tv-channel-mcp in your MCP settings.
Cursor configuration
{
  "mcpServers": {
    "my-tv-channel-mcp": {
      "url": "https://my-tv-channel.com/api/mytvchannel/mcp"
    }
  }
}

Cline

Merge this into cline_mcp_settings.json (Cline → MCP Servers → Configure).
Cline configuration
{
  "mcpServers": {
    "my-tv-channel-mcp": {
      "type": "streamableHttp",
      "url": "https://my-tv-channel.com/api/mytvchannel/mcp",
      "disabled": false
    }
  }
}

VS Code

Merge this server into .vscode/mcp.json. Use MCP: List Servers to start my-tv-channel-mcp and inspect its tools.
VS Code configuration
{
  "servers": {
    "my-tv-channel-mcp": {
      "type": "http",
      "url": "https://my-tv-channel.com/api/mytvchannel/mcp"
    }
  }
}

Gemini CLI

Run this once. It writes .gemini/settings.json in the current project; pass --scope user for ~/.gemini/settings.json.
Gemini CLI configuration
gemini mcp add --transport http my-tv-channel-mcp https://my-tv-channel.com/api/mytvchannel/mcp

Claude Desktop

Open Claude Settings > Connectors > Add custom connector, enter this server URL and enable it for the conversation. This is a manual remote-connector handoff, not a desktop JSON configuration.
Claude Desktop configuration
https://my-tv-channel.com/api/mytvchannel/mcp

Windsurf

Merge this into ~/.codeium/windsurf/mcp_config.json, then refresh MCP in Cascade. Windsurf remote servers use serverUrl, not url.
Windsurf configuration
{
  "mcpServers": {
    "my-tv-channel-mcp": {
      "serverUrl": "https://my-tv-channel.com/api/mytvchannel/mcp"
    }
  }
}

Let your agent find its tools

Find servers like My TV Channel MCP from your agent. Add Koskamo once to search the index for endpoints, access requirements, and client setup. Public discovery needs no key.

Do you run My TV Channel MCP?

Prove ownership with a DNS record, a well-known file, or your Official MCP Registry entry, and your edits publish immediately. Proofs are re-checked weekly; admins can still hide or remove a listing.

Claim this listing