diff --git a/examples/Calc.hs b/examples/Calc.hs
--- a/examples/Calc.hs
+++ b/examples/Calc.hs
@@ -16,7 +16,7 @@
 import qualified Data.Text as T
 
 import           IHaskell.IPython.Kernel
-import           IHaskell.IPython.EasyKernel (installProfile, easyKernel, KernelConfig(..))
+import           IHaskell.IPython.EasyKernel (installKernelspec, easyKernel, KernelConfig(..))
 
 import           System.Environment (getArgs)
 import           System.FilePath ((</>))
@@ -106,12 +106,15 @@
 parse :: String -> Either ParseError Razor
 parse = runParser expr () "(input)"
 
--- -------------------- Language operations -------------------- | Completion
-langCompletion :: T.Text -> T.Text -> Int -> Maybe ([T.Text], T.Text, T.Text)
-langCompletion _code line col =
-  let (before, _) = T.splitAt col line
-  in fmap (\word -> (map T.pack . matchesFor $ T.unpack word, word, word))
-       (lastMaybe (T.words before))
+-------------------- Language operations -------------------- 
+--
+-- | Completion
+langCompletion :: Monad m => T.Text -> Int -> m (T.Text, [T.Text])
+langCompletion code pos = return $
+  let (before, _) = T.splitAt pos code
+  in case lastMaybe (T.words before) of
+    Nothing   -> ("", [])
+    Just word -> (word, map T.pack . matchesFor $ T.unpack word)
   where
     lastMaybe :: [a] -> Maybe a
     lastMaybe [] = Nothing
@@ -122,8 +125,8 @@
     available = ["sleep", "then", "end", "count"] ++ map show [(-1000 :: Int) .. 1000]
 
 -- | Documentation lookup
-langInfo :: T.Text -> Maybe (T.Text, T.Text, T.Text)
-langInfo obj =
+langInfo :: Monad m => T.Text -> Int -> m (Maybe [DisplayData])
+langInfo code pos = return $ toDisplay $
   if | any (T.isPrefixOf obj) ["sleep", "then", "end"] -> Just (obj, sleepDocs, sleepType)
      | T.isPrefixOf obj "count" -> Just (obj, countDocs, countType)
      | obj == "+" -> Just (obj, plusDocs, plusType)
@@ -133,6 +136,12 @@
       , T.all isDigit y -> Just (obj, floatDocs obj, floatType)
      | otherwise -> Nothing
   where
+    (before, _) = T.splitAt pos code
+    obj = last $ T.words before
+
+    toDisplay Nothing = Nothing
+    toDisplay (Just (x, y, z)) = Just [DisplayData PlainText $ T.unlines [x, y, z]]
+
     sleepDocs = "sleep DURATION then VALUE end: sleep DURATION seconds, then eval VALUE"
     sleepType = "sleep FLOAT then INT end"
     plusDocs = "Perform addition"
@@ -207,9 +216,17 @@
 mkConfig :: MVar Integer -- ^ The internal state of the execution
          -> KernelConfig IO [IntermediateEvalRes] (Either ParseError Integer)
 mkConfig var = KernelConfig
-  { languageName = "expanded_huttons_razor"
-  , languageVersion = [0, 1, 0]
-  , profileSource = Just . (</> "calc_profile.tar") <$> Paths.getDataDir
+  { kernelLanguageInfo = LanguageInfo
+    { languageName = "expanded_huttons_razor"
+    , languageVersion = "1.0.0"
+    , languageFileExtension = ".txt"
+    , languageCodeMirrorMode = "null"
+    }
+  , writeKernelspec = const $ return $ KernelSpec
+    { kernelDisplayName = "Hutton's Razor"
+    , kernelLanguage = "hutton"
+    , kernelCommand = ["simple-calc-example", "kernel", "{connection_file}"]
+    }
   , displayResult = displayRes
   , displayOutput = displayOut
   , completion = langCompletion
@@ -242,11 +259,10 @@
   case args of
     ["kernel", profileFile] ->
       easyKernel profileFile (mkConfig val)
-    ["setup"] -> do
-      putStrLn "Installing profile..."
-      installProfile (mkConfig val)
+    ["install"] -> do
+      putStrLn "Installing kernelspec..."
+      installKernelspec (mkConfig val) False Nothing
     _ -> do
       putStrLn "Usage:"
-      putStrLn "simple-calc-example setup        -- set up the profile"
-      putStrLn
-        "simple-calc-example kernel FILE  -- run a kernel with FILE for communication with the frontend"
+      putStrLn "simple-calc-example install      -- set up the kernelspec"
+      putStrLn "simple-calc-example kernel FILE  -- run a kernel with FILE for communication with the frontend"
diff --git a/examples/Simple.hs b/examples/Simple.hs
new file mode 100644
--- /dev/null
+++ b/examples/Simple.hs
@@ -0,0 +1,148 @@
+module Main where
+
+import qualified Data.Text as T
+
+import System.Environment (getArgs)
+
+import Text.Parsec
+import Text.Parsec.String
+
+import IHaskell.IPython.EasyKernel (easyKernel, installKernelspec, KernelConfig(..))
+import IHaskell.IPython.Types
+
+-- Define the actual language!
+data Expr = Plus Expr Expr
+          | Minus Expr Expr
+          | Times Expr Expr
+          | Div Expr Expr
+          | Exp Expr Expr
+          | Val Int
+  deriving (Show, Eq)
+
+eval :: Expr -> Int
+eval (Val i) = i
+eval (Plus x y) = eval x + eval y
+eval (Minus x y) = eval x - eval y
+eval (Times x y) = eval x * eval y
+eval (Div x y) = eval x `div` eval y
+eval (Exp x y) = eval x ^ eval y
+
+parseExpr :: String -> Either String Expr
+parseExpr str =
+  case parse expr "interactive" (filter (/= ' ') str) of
+    Left err -> Left (show err)
+    Right e  -> Right e
+  where
+    expr :: Parser Expr
+    expr = val <|> op
+
+    val :: Parser Expr
+    val = Val <$> read <$> many1 (oneOf "0123456789")
+
+    op :: Parser Expr
+    op = do
+      func <- choice $ map string $ ["plus", "minus", "times", "div", "exp"]
+      char '('
+      x <- expr
+      char ','
+      y <- expr
+      char ')'
+      return $ case func of
+        "plus"  -> Plus x y
+        "minus" -> Minus x y
+        "times" -> Times x y
+        "div"   -> Div x y
+        "exp"   -> Exp x y
+
+languageConfig :: LanguageInfo
+languageConfig = LanguageInfo
+  { languageName = "funcalc"
+  , languageVersion = "1.0.0"
+  , languageFileExtension = ".txt"
+  , languageCodeMirrorMode = "null"
+  }
+
+languageKernelspec :: KernelSpec
+languageKernelspec = KernelSpec
+  { kernelDisplayName = "Calculator"
+  , kernelLanguage = "calc"
+  , kernelCommand = ["fun-calc-example", "kernel", "{connection_file}"]
+  }
+
+displayString :: String -> [DisplayData]
+displayString str = [DisplayData PlainText (T.pack str)]
+
+languageCompletion :: Monad m => T.Text -> Int -> m (T.Text, [T.Text])
+languageCompletion code pos = return $ 
+  let (before, _) = T.splitAt pos code
+      word = last $ T.words $ T.map replace before
+  in (word, map T.pack $ matches $ T.unpack word)
+
+  where
+    matches :: String -> [String]
+    matches word =
+      case head word of
+        'p' -> ["plus"]
+        'm' -> ["minus"]
+        'e' -> ["exp"]
+        'd' -> ["div"]
+        't' -> ["times"]
+
+    replace :: Char -> Char
+    replace '(' = ' '
+    replace ')' = ' '
+    replace ',' = ' '
+    replace  x = x
+
+languageInspect :: Monad m => T.Text -> Int -> m (Maybe [DisplayData])
+languageInspect _ _ = return $
+  Just
+    [ DisplayData PlainText $ T.pack $
+      unlines
+        [ "We support five arithmetic functions:"
+        , "   - plus  +"
+        , "   - minus -"
+        , "   - div   /"
+        , "   - times *"
+        , "   - exp   ^"
+        , "Expressions are written as f(exp, exp)"
+        ]
+    ]
+
+languageRun :: T.Text -> IO () -> (String -> IO ()) -> IO (String, ExecuteReplyStatus, String)
+languageRun code init intermediate = do
+  init
+  let expr = parseExpr $ T.unpack code
+  intermediate (show expr)
+
+  return
+    (case expr of
+       Left err   -> err
+       Right expr -> show (eval expr), IHaskell.IPython.Types.Ok, "")
+
+
+simpleConfig :: KernelConfig IO String String
+simpleConfig = KernelConfig
+  { kernelLanguageInfo = languageConfig
+  , writeKernelspec = const $ return languageKernelspec
+  , displayOutput = displayString
+  , displayResult = displayString
+  , completion = languageCompletion
+  , inspectInfo = languageInspect
+  , run = languageRun
+  , debug = False
+  }
+
+main :: IO ()
+main = do
+  args <- getArgs
+  case args of
+    ["kernel", profileFile] ->
+      easyKernel profileFile simpleConfig
+    ["install"] -> do
+      putStrLn "Installing kernelspec..."
+      installKernelspec simpleConfig False Nothing
+    _ -> do
+      putStrLn "Usage:"
+      putStrLn "fun-calc-example install      -- set up the kernelspec"
+      putStrLn "fun-calc-example kernel FILE  -- run a kernel with FILE for communication with the frontend"
diff --git a/ipython-kernel.cabal b/ipython-kernel.cabal
--- a/ipython-kernel.cabal
+++ b/ipython-kernel.cabal
@@ -1,5 +1,5 @@
 name:                ipython-kernel
-version:             0.7.0.0
+version:             0.8.0.0
 synopsis:            A library for creating kernels for IPython frontends
 
 description:         ipython-kernel is a library for communicating with frontends for the interactive IPython framework. It is used extensively in IHaskell, the interactive Haskell environment.
@@ -40,9 +40,10 @@
                        cereal          >=0.3,
                        containers      >=0.5,
                        directory       >=1.1,
+                       temporary       >=1.2,
                        filepath        >=1.2,
+                       process         >=1.1,
                        mtl             >=2.1,
-                       tar             >=0.4.0.1,
                        text            >=0.11,
                        transformers    >=0.3,
                        uuid            >=1.3,
@@ -53,6 +54,20 @@
 executable simple-calc-example
   hs-source-dirs: examples
   main-is:        Calc.hs
+  build-depends:  ipython-kernel,
+                  base           >=4.6 && <4.9,
+                  filepath       >=1.2,
+                  mtl            >=2.1,
+                  parsec         >=3.1,
+                  text           >=0.11,
+                  transformers   >=0.3
+
+  if !flag(examples)
+    buildable: False
+
+executable fun-calc-example
+  hs-source-dirs: examples
+  main-is:        Simple.hs
   build-depends:  ipython-kernel,
                   base           >=4.6 && <4.9,
                   filepath       >=1.2,
diff --git a/src/IHaskell/IPython/EasyKernel.hs b/src/IHaskell/IPython/EasyKernel.hs
--- a/src/IHaskell/IPython/EasyKernel.hs
+++ b/src/IHaskell/IPython/EasyKernel.hs
@@ -9,46 +9,31 @@
 -- a simple language that nevertheless has side effects, global state, and timing effects is
 -- included in the examples directory.
 --
--- = Profiles To run your kernel, you will need an IPython profile that causes the frontend to run
--- it. To generate a fresh profile, run the command
---
--- > ipython profile create NAME
---
--- This will create a fresh IPython profile in @~\/.ipython\/profile_NAME@. This profile must be
--- modified in two ways:
---
--- 1. It needs to run your kernel instead of the default ipython 2. It must have message signing
--- turned off, because 'easyKernel' doesn't support it
---
--- == Setting the executable To set the executable, modify the configuration object's
--- @KernelManager.kernel_cmd@ property. For example:
---
--- > c.KernelManager.kernel_cmd = ['my_kernel', '{connection_file}']
+-- = Kernel Specs
 --
--- Your own main should arrange to parse command line arguments such
--- that the connection file is passed to easyKernel.
+-- To run your kernel, you will need to install the kernelspec into the Jupyter namespace.
+-- If your kernel name is `kernel`, you will need to run the command:
 --
--- == Message signing
--- To turn off message signing, use the following snippet:
+-- > kernel install
 --
--- > c.Session.key = b''
--- > c.Session.keyfile = b''
+-- This will inform Jupyter of the kernel so that it may be used.
 --
 -- == Further profile improvements
 -- Consult the IPython documentation along with the generated profile
 -- source code for further configuration of the frontend, including
 -- syntax highlighting, logos, help text, and so forth.
-module IHaskell.IPython.EasyKernel (easyKernel, installProfile, KernelConfig(..)) where
+module IHaskell.IPython.EasyKernel (easyKernel, installKernelspec, KernelConfig(..)) where
 
-import           Data.Aeson (decode)
+import           Data.Aeson (decode, encode)
 
 import qualified Data.ByteString.Lazy as BL
 
-import qualified Codec.Archive.Tar as Tar
+import           System.IO.Temp (withTempDirectory)
+import           System.Process (rawSystem)
 
 import           Control.Concurrent (MVar, readChan, writeChan, newMVar, readMVar, modifyMVar_)
 import           Control.Monad.IO.Class (MonadIO(..))
-import           Control.Monad (forever, when, unless)
+import           Control.Monad (forever, when, unless, void)
 
 import qualified Data.Map as Map
 import           Data.Maybe (fromMaybe)
@@ -59,7 +44,7 @@
 import           IHaskell.IPython.Types
 
 import           System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist,
-                                   getHomeDirectory)
+                                   getHomeDirectory, getTemporaryDirectory)
 import           System.FilePath ((</>))
 import           System.Exit (exitSuccess)
 import           System.IO (openFile, IOMode(ReadMode))
@@ -72,22 +57,20 @@
          { 
          -- | Info on the language of the kernel.
          kernelLanguageInfo :: LanguageInfo
-         -- | Determine the source of a profile to install using 'installProfile'. The source should be a
-         -- tarball whose contents will be unpacked directly into the profile directory. For example, the
-         -- file whose name is @ipython_config.py@ in the tar file for a language named @lang@ will end up in
-         -- @~/.ipython/profile_lang/ipython_config.py@.
-         , profileSource :: IO (Maybe FilePath)
+         -- | Write all the files into the kernel directory, including `kernel.js`, `logo-64x64.png`, and any
+         -- other required files. The directory to write to will be passed to this function, and the return
+         -- value should be the kernelspec to be written to `kernel.json`.
+         , writeKernelspec :: FilePath -> IO KernelSpec
          -- | How to render intermediate output
          , displayOutput :: output -> [DisplayData]
          -- | How to render final cell results
          , displayResult :: result -> [DisplayData]
-         -- | Perform completion. The returned tuple consists of the matches, the matched text, and the
-         -- completion text. The arguments are the code in the cell, the current line as text, and the column
-         -- at which the cursor is placed.
-         , completion :: T.Text -> T.Text -> Int -> Maybe ([T.Text], T.Text, T.Text)
-         -- | Return the information or documentation for its argument. The returned tuple consists of the
-         -- name, the documentation, and the type, respectively.
-         , inspectInfo :: T.Text -> Maybe (T.Text, T.Text, T.Text)
+         -- | Perform completion. The returned tuple consists of the matched text and completions. The
+         -- arguments are the code in the cell and the position of the cursor in the cell.
+         , completion :: T.Text -> Int -> m (T.Text, [T.Text])
+         -- | Return the information or documentation for its argument, described by the cell contents and
+         -- cursor position. The returned value is simply the data to display.
+         , inspectInfo :: T.Text -> Int -> m (Maybe [DisplayData])
          -- | Execute a cell. The arguments are the contents of the cell, an IO action that will clear the
          -- current intermediate output, and an IO action that will add a new item to the intermediate
          -- output. The result consists of the actual result, the status to be sent to IPython, and the
@@ -97,34 +80,30 @@
          , debug :: Bool -- ^ Whether to print extra debugging information to
          }
 
--- the console | Attempt to install the IPython profile from the .tar file indicated by the
--- 'profileSource' field of the configuration, if it is not already installed.
-installProfile :: MonadIO m => KernelConfig m output result -> m ()
-installProfile config = do
-  installed <- isInstalled
-  unless installed $ do
-    profSrc <- liftIO $ profileSource config
-    case profSrc of
-      Nothing -> liftIO (putStrLn "No IPython profile is installed or specified")
-      Just tar -> do
-        profExists <- liftIO $ doesFileExist tar
-        profTgt <- profDir
-        if profExists
-          then do
-            liftIO $ createDirectoryIfMissing True profTgt
-            liftIO $ Tar.extract profTgt tar
-          else liftIO . putStrLn $
-            "The supplied profile source '" ++ tar ++ "' does not exist"
+-- Install the kernelspec, using the `writeKernelspec` field of the kernel configuration.
+installKernelspec :: MonadIO m
+                  => KernelConfig m output result -- ^ Kernel configuration to install
+                  -> Bool                         -- ^ Whether to use Jupyter `--replace`
+                  -> Maybe FilePath               -- ^ (Optional) prefix to install into for Jupyter `--prefix`
+                  -> m ()
+installKernelspec config replace installPrefixMay =
+  liftIO $ withTmpDir $ \tmp -> do
+    let kernelDir = tmp </> languageName (kernelLanguageInfo config)
+    createDirectoryIfMissing True kernelDir
+    kernelSpec <- writeKernelspec config kernelDir
 
+    let filename = kernelDir </> "kernel.json"
+    BL.writeFile filename $ encode $ toJSON kernelSpec
+
+    let replaceFlag = ["--replace" | replace]
+        installPrefixFlag = maybe ["--user"] (\prefix -> ["--prefix", prefix]) installPrefixMay
+        cmd = concat [["kernelspec", "install"], installPrefixFlag, [kernelDir], replaceFlag]
+    void $ rawSystem "ipython" cmd
+
   where
-    profDir = do
-      home <- liftIO getHomeDirectory
-      return $ home </> ".ipython" </> ("profile_" ++ languageName (kernelLanguageInfo config))
-    isInstalled = do
-      prof <- profDir
-      dirThere <- liftIO $ doesDirectoryExist prof
-      isProf <- liftIO . doesFileExist $ prof </> "ipython_config.py"
-      return $ dirThere && isProf
+    withTmpDir act = do
+      tmp <- getTemporaryDirectory
+      withTempDirectory tmp "easyKernel" act
 
 getProfile :: FilePath -> IO Profile
 getProfile fn = do
@@ -204,10 +183,12 @@
                                       sendOutput x =
                                                       send $ PublishDisplayData
                                                                outputHeader
-                                                               (languageName $ kernelLanguageInfo config)
+                                                               (languageName $ kernelLanguageInfo
+                                                                                 config)
                                                                (displayOutput config x)
                                   in run config code clearOutput sendOutput
-  liftIO . send $ PublishDisplayData outputHeader (languageName $ kernelLanguageInfo config) (displayResult config res)
+  liftIO . send $ PublishDisplayData outputHeader (languageName $ kernelLanguageInfo config)
+                    (displayResult config res)
 
 
   idleHeader <- dupHeader replyHeader StatusMessage
@@ -224,12 +205,27 @@
       , status = replyStatus
       }
 
-replyTo config _ _ req@CompleteRequest{} replyHeader =
-  -- TODO: FIX
-  error "Completion: Unimplemented for IPython 3.0"
+replyTo config _ _ req@CompleteRequest{} replyHeader = do
+  let code = getCode req
+      pos = getCursorPos req
+  (matchedText, completions) <- completion config code pos
 
-replyTo _ _ _ InspectRequest{} _ =
-  error "Inspection: Unimplemented for IPython 3.0"
+  let start = pos - T.length matchedText
+      end = pos
+      reply = CompleteReply replyHeader completions start end Map.empty True
+  return reply
+
+replyTo config _ _ req@InspectRequest{} replyHeader = do
+  result <- inspectInfo config (inspectCode req) (inspectCursorPos req)
+  let reply =
+        case result of
+          Just datas -> InspectReply
+            { header = replyHeader
+            , inspectStatus = True
+            , inspectData = datas
+            }
+          _ -> InspectReply { header = replyHeader, inspectStatus = False, inspectData = [] }
+  return reply
 
 replyTo _ _ _ msg _ = do
   liftIO $ putStrLn "Unknown message: "
